D-Pad on the remote screen now uses EventServer by default.

Not finished: EventServer port is still hardcoded (9777) and there's no possibility of configuration.
This commit is contained in:
Synced Synapse 2015-06-17 22:30:40 +01:00
parent a9e0a07572
commit 8ce709c55a
3 changed files with 260 additions and 35 deletions

View File

@ -0,0 +1,153 @@
/*
* Copyright 2015 Synced Synapse. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.eventclient;
import android.annotation.SuppressLint;
import android.os.*;
import android.os.Process;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.Utils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Class that establishes and maintains a connection to Kodi's EventServer
* This class keeps pinging Kodi to keep the connection alive and contains
* auxiliary methods that allow the sending of packets to Kodi.
* Make sure to call quit() when done with it, so that it gracefully shuts down
*/
public class EventServerConnection {
private static final String TAG = LogUtils.makeLogTag(EventServerConnection.class);
private static final int PING_INTERVAL = 45000; // ms
private static final String DEVICE_NAME = "Kore Remote";
/**
* Host to connect too
*/
private final HostInfo hostInfo;
private final InetAddress hostInetAddress;
private boolean isConnected = false;
// Handler on which packets will be posted, to send them asynchronously
private Handler commHandler = null;
private HandlerThread handlerThread = null;
private PacketPING packetPING = new PacketPING();
private Runnable pingRunnable = new Runnable() {
@Override
public void run() {
LogUtils.LOGD(TAG, "Pinging EventServer");
try {
packetPING.send(hostInetAddress, hostInfo.getEventServerPort());
} catch (IOException exc) {
LogUtils.LOGD(TAG, "Got an IOException when sending a PING Packet to Kodi's EventServer");
}
commHandler.postDelayed(this, PING_INTERVAL);
}
};
/**
* Constructor. Starts the thread that keeps the connection alive. Make sure to call quit() when done.
* @param hostInfo Host to connect to
* @throws UnknownHostException
*/
public EventServerConnection(final HostInfo hostInfo) throws UnknownHostException{
this.hostInfo = hostInfo;
hostInetAddress = InetAddress.getByName(hostInfo.getAddress());
startEventClient();
}
/**
* Creates the HandlerThread that will be used to post packets, establishes a connection with EventServer
* (sends HELO packet) and starts the ping thread
*/
private void startEventClient() {
LogUtils.LOGD(TAG, "Starting EventServer Thread");
// Handler thread that will keep pinging and send the requests to Kodi
handlerThread = new HandlerThread("EventServerConnection", Process.THREAD_PRIORITY_DEFAULT);
handlerThread.start();
// Get the HandlerThread's Looper and use it for our Handler
commHandler = new Handler(handlerThread.getLooper());
// Send Hello Packet
// commHandler.post(new Runnable() {
// @Override
// public void run() {
// PacketHELO p;
// p = new PacketHELO(DEVICE_NAME);
// try {
// p.send(hostInetAddress, hostInfo.getEventServerPort());
// } catch (IOException exc) {
// // We are ignoring this one... Not sure if a good idea, but we're not on the UI thread
// LogUtils.LOGD(TAG, "Got an IOException when sending a HELO Packet to Kodi's EventServer");
// }
//
// // Start pinging
// commHandler.postDelayed(pingRunnable, PING_INTERVAL);
// }
// });
// Start pinging
commHandler.postDelayed(pingRunnable, PING_INTERVAL);
isConnected = true;
}
/**
* Stops the HandlerThread that is being used to send packets to Kodi
*/
@SuppressLint("NewApi")
public void quit() {
LogUtils.LOGD(TAG, "Quiting EventServer handler thread");
if (Utils.isJellybeanMR2OrLater()) {
handlerThread.quitSafely();
} else {
handlerThread.quit();
}
isConnected = false;
}
/**
* Sends a packet to Kodi's Event Server
* Only sends the packet if connected, i.e. if quit() has not been not called
* @param p Packet to send
*/
public void sendPacket(final Packet p) {
if (!isConnected) {
return;
}
LogUtils.LOGD(TAG, "Sending Packet");
commHandler.post(new Runnable() {
@Override
public void run() {
try {
p.send(hostInetAddress, hostInfo.getEventServerPort());
} catch (IOException exc) {
LogUtils.LOGD(TAG, "Got an IOException when sending a packet to Kodi's EventServer");
}
}
});
}
}

View File

@ -44,7 +44,12 @@ public class HostInfo {
*/
public static final int DEFAULT_WOL_PORT = 9;
/**
/**
* Default EventServer port for Kodi
*/
public static final int DEFAULT_EVENT_SERVER_PORT = 9777;
/**
* Internal id of the host
*/
private int id;
@ -60,6 +65,7 @@ public class HostInfo {
private String address;
private int httpPort;
private int tcpPort;
private int eventServerPort;
/**
* Authentication information
@ -108,6 +114,8 @@ public class HostInfo {
this.macAddress = macAddress;
this.wolPort = wolPort;
this.eventServerPort = DEFAULT_EVENT_SERVER_PORT;
// For performance reasons
this.auxImageHttpAddress = getHttpURL() + "/image/";
}
@ -177,7 +185,11 @@ public class HostInfo {
return protocol;
}
/**
public int getEventServerPort() {
return eventServerPort;
}
/**
* Overrides the protocol for this host info
* @param protocol Protocol
*/

View File

@ -20,13 +20,13 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@ -40,6 +40,10 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import org.xbmc.kore.R;
import org.xbmc.kore.eventclient.ButtonCodes;
import org.xbmc.kore.eventclient.EventServerConnection;
import org.xbmc.kore.eventclient.Packet;
import org.xbmc.kore.eventclient.PacketBUTTON;
import org.xbmc.kore.host.HostConnectionObserver;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
@ -56,6 +60,8 @@ import org.xbmc.kore.utils.RepeatListener;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.net.UnknownHostException;
import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;
@ -128,6 +134,9 @@ public class RemoteFragment extends Fragment
// Touch listener that provides touch feedbacl
private View.OnTouchListener feedbackTouckListener;
// EventServer connection
private EventServerConnection eventServerConnection = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -137,6 +146,8 @@ public class RemoteFragment extends Fragment
buttonInAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.button_in);
buttonOutAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.button_out);
createEventServerConnection();
feedbackTouckListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@ -160,19 +171,29 @@ public class RemoteFragment extends Fragment
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_remote, container, false);
ButterKnife.inject(this, root);
// Setup repeat buttons
setupRepeatButton(leftButton, new Input.Left());
setupRepeatButton(rightButton, new Input.Right());
setupRepeatButton(upButton, new Input.Up());
setupRepeatButton(downButton, new Input.Down());
if (eventServerConnection != null) {
// Setup d-pad to use EventServer
setupEventServerButton(leftButton, ButtonCodes.REMOTE_LEFT);
setupEventServerButton(rightButton, ButtonCodes.REMOTE_RIGHT);
setupEventServerButton(upButton, ButtonCodes.REMOTE_UP);
setupEventServerButton(downButton, ButtonCodes.REMOTE_DOWN);
setupEventServerButton(selectButton, ButtonCodes.REMOTE_SELECT);
} else {
// Otherwise, use json-rpc
setupRepeatButton(leftButton, new Input.Left());
setupRepeatButton(rightButton, new Input.Right());
setupRepeatButton(upButton, new Input.Up());
setupRepeatButton(downButton, new Input.Down());
setupDefaultButton(selectButton, new Input.Select(), null);
}
setupNoRepeatButton(selectButton, new Input.Select(), null);
setupNoRepeatButton(backButton, new Input.Back(), null);
setupNoRepeatButton(infoButton,
new Input.ExecuteAction(Input.ExecuteAction.INFO),
new Input.ExecuteAction(Input.ExecuteAction.CODECINFO));
setupNoRepeatButton(osdButton, new Input.ExecuteAction(Input.ExecuteAction.OSD), null);
setupNoRepeatButton(contextButton, new Input.ExecuteAction(Input.ExecuteAction.CONTEXTMENU), null);
// Other buttons
setupDefaultButton(backButton, new Input.Back(), null);
setupDefaultButton(infoButton,
new Input.ExecuteAction(Input.ExecuteAction.INFO),
new Input.ExecuteAction(Input.ExecuteAction.CODECINFO));
setupDefaultButton(osdButton, new Input.ExecuteAction(Input.ExecuteAction.OSD), null);
setupDefaultButton(contextButton, new Input.ExecuteAction(Input.ExecuteAction.CONTEXTMENU), null);
adjustRemoteButtons();
@ -185,17 +206,6 @@ public class RemoteFragment extends Fragment
return root;
}
// /**
// * Select button callback
// * @param v Clicked view
// */
// @OnClick(R.id.select)
// public void onSelectClicked(View v) {
// v.startAnimation(buttonInOutAnim);
// Input.Select action = new Input.Select();
// action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
// }
@TargetApi(21)
private void adjustRemoteButtons() {
Resources.Theme theme = getActivity().getTheme();
@ -244,27 +254,42 @@ public class RemoteFragment extends Fragment
public void onResume() {
super.onResume();
hostConnectionObserver.registerPlayerObserver(this, true);
if (eventServerConnection == null)
createEventServerConnection();
}
@Override
public void onPause() {
super.onPause();
hostConnectionObserver.unregisterPlayerObserver(this);
if (eventServerConnection != null) {
eventServerConnection.quit();
eventServerConnection = null;
}
}
private void createEventServerConnection() {
try {
eventServerConnection = new EventServerConnection(hostManager.getHostInfo());
} catch (UnknownHostException exc) {
LogUtils.LOGD(TAG, "Got an UnknownHostException, disabling EventServer");
eventServerConnection = null;
}
}
private void setupRepeatButton(View button, final ApiMethod<String> action) {
button.setOnTouchListener(new RepeatListener(UIUtils.initialButtonRepeatInterval, UIUtils.buttonRepeatInterval,
new View.OnClickListener() {
@Override
public void onClick(View v) {
action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}, buttonInAnim, buttonOutAnim));
new View.OnClickListener() {
@Override
public void onClick(View v) {
action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}, buttonInAnim, buttonOutAnim));
}
private void setupNoRepeatButton(View button,
final ApiMethod<String> clickAction,
final ApiMethod<String> longClickAction) {
private void setupDefaultButton(View button,
final ApiMethod<String> clickAction,
final ApiMethod<String> longClickAction) {
// Set animation
button.setOnTouchListener(feedbackTouckListener);
if (clickAction != null) {
@ -286,6 +311,41 @@ public class RemoteFragment extends Fragment
}
}
private void setupEventServerButton(View button, final String action) {
short amount = 0;
byte axis = 0;
final Packet packetDown =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, true, true, false, amount, axis);
final Packet packetUp =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, false, false, false, amount, axis);
// Set animation and packet
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
buttonInAnim.setFillAfter(true);
v.startAnimation(buttonInAnim);
eventServerConnection.sendPacket(packetDown);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
v.startAnimation(buttonOutAnim);
eventServerConnection.sendPacket(packetUp);
break;
}
return false;
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Nothing to do
}
});
}
/**
* Default callback for methods that don't return anything
*/