package org.xbmc.eventclient; import java.io.FileInputStream; import java.io.IOException; import java.net.InetAddress; import org.xbmc.api.data.IEventClient; import android.util.Log; /** * XBMC Event Client Class * * Implements an XBMC-Client. This class can be used to implement your own * application which should act as a Input device for XBMC. Also starts a * Ping-Thread, which tells the XBMC EventServer that the client is alive. * Therefore if you close your application you SHOULD call stopClient()! * * 03.09.2009 freezy changed class name and member variables * * @author Stefan Agner */ public class EventClient implements IEventClient { private final String mDeviceName; private boolean mHasIcon = false; private PingThread mPingThread; private byte mIconType = Packet.ICON_NONE; private byte[] mIconData; private InetAddress mHostAddress; private int mHostPort; private static final String TAG = "EventClient"; /** * Creates an empty instance but doesn't start it. */ public EventClient(String deviceName) { Log.i(TAG, "EventClient(" + deviceName + ")"); mDeviceName = deviceName; } /** * Starts a XBMC EventClient without an icon. * * @param hostAddress Address of the Host running XBMC * @param hostPort Port of the Host running XBMC (default 9777) * @param deviceName Name of the Device * @throws IOException */ public EventClient(InetAddress hostAddress, int hostPort, String deviceName) { Log.i(TAG, "EventClient(" + hostAddress + ", " + hostPort + ", " + deviceName + ")"); mDeviceName = deviceName; startClient(hostAddress, hostPort); } /** * Starts a XBMC EventClient. * * @param hostAddress Address of the Host running XBMC * @param hostPort Port of the Host running XBMC (default 9777) * @param deviceName Name of the Device * @param iconFile Path to the Iconfile (PNG, JPEG or GIF) * @throws IOException */ public EventClient(InetAddress hostAddress, int hostPort, String deviceName, String iconFile) { Log.i(TAG, "EventClient(" + hostAddress + ", " + hostPort + ", " + deviceName + ", " + iconFile + ")"); mDeviceName = deviceName; setIcon(iconFile); startClient(hostAddress, hostPort); } /** * Starts a XBMC EventClient. * * @param hostAddress Address of the Host running XBMC * @param hostPort Port of the Host running XBMC (default 9777) * @param deviceName Name of the Device * @param iconType Type of the icon file (see Packet.ICON_PNG, * Packet.ICON_JPEG or Packet.ICON_GIF) * @param iconData The icon itself as a Byte-Array * @throws IOException */ public EventClient(InetAddress hostAddress, int hostPort, String deviceName, byte iconType, byte[] iconData) { Log.i(TAG, "EventClient(" + hostAddress + ", " + hostPort + ", " + deviceName + ", " + iconType + ", " + iconData + ")"); mDeviceName = deviceName; setIcon(iconType, iconData); startClient(hostAddress, hostPort); } public void setHost(InetAddress addr, int port) { if (addr != null) { Log.e(TAG, "Setting mHostAddress = " + addr.getHostAddress()); if (mHostAddress == null) { startClient(addr, port); } else { mHostAddress = addr; mHostPort = port; mPingThread.setHost(addr, port); } } else { Log.e(TAG, "Setting null host!"); try { throw new Exception("Setting null host."); } catch (Exception e) { e.printStackTrace(); } mHostAddress = null; } } /** * Sets the icon using a path name to a image file (png, jpg or gif). * @param iconPath Path to icon */ public void setIcon(String iconPath) { byte iconType = Packet.ICON_PNG; // Assume png as icon type if (iconPath.toLowerCase().endsWith(".jpeg")) iconType = Packet.ICON_JPEG; if (iconPath.toLowerCase().endsWith(".jpg")) iconType = Packet.ICON_JPEG; if (iconPath.toLowerCase().endsWith(".gif")) iconType = Packet.ICON_GIF; // Read the icon file to the byte array... FileInputStream iconFileStream; byte[] iconData; try { iconFileStream = new FileInputStream(iconPath); iconData = new byte[iconFileStream.available()]; iconFileStream.read(iconData); } catch (IOException e) { mHasIcon = false; return; } mHasIcon = true; setIcon(iconType, iconData); } /** * Sets the icon from raw data * @param iconType Type of the icon file (see Packet.ICON_PNG, Packet.ICON_JPEG or Packet.ICON_GIF) * @param iconData The icon itself as a Byte-Array */ public void setIcon(byte iconType, byte[] iconData) { mIconType = iconType; mIconData = iconData; } /** * Starts a XBMC EventClient. * * @param hostAddress Address of the Host running XBMC * @param hostPort Port of the Host running XBMC (default 9777) * @throws IOException */ private void startClient(InetAddress hostAddress, int hostPort) { Log.i(TAG, "Starting new EventClient at " + hostAddress + ":" + hostPort); // Save host address and port mHostAddress = hostAddress; mHostPort = hostPort; // Send Hello Packet... PacketHELO p; if (mHasIcon) p = new PacketHELO(mDeviceName, mIconType, mIconData); else p = new PacketHELO(mDeviceName); p.send(hostAddress, hostPort); // Start Thread (for Ping packets...) mPingThread = new PingThread(hostAddress, hostPort, 20000); mPingThread.start(); } /** * Stops the XBMC EventClient (especially the Ping-Thread) * * @throws IOException */ public void stopClient() throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { // Stop Ping-Thread... mPingThread.giveup(); mPingThread.interrupt(); PacketBYE p = new PacketBYE(); p.send(mHostAddress, mHostPort); } } /** * Displays a notification window in XBMC. * * @param title Message title * @param message The actual message */ public void sendNotification(String title, String message) throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketNOTIFICATION p; if (mHasIcon) p = new PacketNOTIFICATION(title, message, mIconType, mIconData); else p = new PacketNOTIFICATION(title, message); p.send(addr, mHostPort); } } public void sendNotification(String title, String message, byte icontype, byte[] icondata) throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketNOTIFICATION p = new PacketNOTIFICATION(title, message, icontype, icondata); p.send(addr, mHostPort); } } /** * Sends a Button event * * @param code Raw button code (default: 0) * @param repeat This key press should repeat until released (default: 1) * Note that queued pressed cannot repeat. * @param down If this is 1, it implies a press event, 0 implies a * release event. (default: 1) * @param queue A queued key press means that the button event is executed * just once after which the next key press is processed. It * can be used for macros. Currently there is no support for * time delays between queued presses. (default: 0) * @param amount Unimplemented for now; in the future it will be used for * specifying magnitude of analog key press events * @param axis */ public void sendButton(short code, boolean repeat, boolean down, boolean queue, short amount, byte axis) throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketBUTTON p = new PacketBUTTON(code, repeat, down, queue, amount, axis); p.send(addr, mHostPort); } } /** * Sends a Button event * * @param map_name * A combination of map_name and button_name refers to a mapping * in the user's Keymap.xml or Lircmap.xml. map_name can be one * of the following: * <ul> * <li><code>KB</code> - Standard keyboard map (<code><keyboard></code> section)</li> * <li><code>XG</code> - Xbox gamepad map (<code><gamepad></code> section)</li> * <li><code>R1</code> - Xbox remote map (<code><remote></code> section)</li> * <li><code>R2</code> - Xbox universal remote map (<code><universalremote></code> * section)</li> * <li><code>LI:devicename</code> - LIRC remote map where <code>devicename</code> is * the actual device's name</li> * </ul> * @param button_name * A button name defined in the map specified in map_name. For * example, if map_name is "KB" refering to the <keyboard> * section in Keymap.xml then, valid button_names include * "printscreen", "minus", "x", etc. * @param repeat * This key press should repeat until released (default: 1) Note * that queued pressed cannot repeat. * @param down * If this is 1, it implies a press event, 0 implies a release * event. (default: 1) * @param queue * A queued key press means that the button event is executed * just once after which the next key press is processed. It can * be used for macros. Currently there is no support for time * delays between queued presses. (default: 0) * @param amount * Unimplemented for now; in the future it will be used for * specifying magnitude of analog key press events * @param axis */ public void sendButton(String map_name, String button_name, boolean repeat, boolean down, boolean queue, short amount, byte axis) { final InetAddress addr = mHostAddress; if (addr != null) { Log.i(TAG, "sendButton(" + map_name + ", \"" + button_name + "\", " + (repeat ? "rep, " : "nonrep, ") + (down ? "down)" : "up)")); PacketBUTTON p = new PacketBUTTON(map_name, button_name, repeat, down, queue, amount, axis); p.send(addr, mHostPort); } else { Log.e(TAG, "sendButton failed due to unset host address!"); } } /** * Sets the mouse position in XBMC * * @param x Horizontal position ranging from 0 to 65535 * @param y Vertical position ranging from 0 to 65535 */ public void sendMouse(int x, int y) throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketMOUSE p = new PacketMOUSE(x, y); p.send(addr, mHostPort); } } /** * Sends a ping to the XBMC EventServer * * @throws IOException */ public void ping() throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketPING p = new PacketPING(); p.send(addr, mHostPort); } } /** * Tells XBMC to log the message to xbmc.log with the loglevel as specified. * * @param loglevel * The log level, follows XBMC standard. * <ul> * <li>0 = DEBUG</li> * <li>1 = INFO</li> * <li>2 = NOTICE</li> * <li>3 = WARNING</li> * <li>4 = ERROR</li> * <li>5 = SEVERE</li> * </ul> * @param logmessage * The message to log */ public void sendLog(byte loglevel, String logmessage) throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketLOG p = new PacketLOG(loglevel, logmessage); p.send(addr, mHostPort); } } /** * Tells XBMC to do the action specified, based on the type it knows were it * needs to be sent. * * @param actionmessage Actionmessage (as in scripting/skinning) */ public void sendAction(String actionmessage) throws IOException { final InetAddress addr = mHostAddress; if (addr != null) { PacketACTION p = new PacketACTION(actionmessage); p.send(addr, mHostPort); } } /** * Implements a PingThread which tells XBMC EventServer that the Client is * alive (this should be done at least every 60 seconds! * * @author Stefan Agner */ private static class PingThread extends Thread { private InetAddress mHostAddress; private int mHostPort; private int mSleepTime; private boolean mGiveup = false; public PingThread(InetAddress hostAddress, int hostPort, int sleepTime) { super("XBMC EventClient Ping-Thread"); mHostAddress = hostAddress; mHostPort = hostPort; mSleepTime = sleepTime; } public void giveup() { mGiveup = true; } public void setHost(InetAddress addr, int port) { mHostAddress = addr; mHostPort = port; } public void run() { while (!mGiveup) { PacketPING p = new PacketPING(); p.send(mHostAddress, mHostPort); try { Thread.sleep(mSleepTime); } catch (InterruptedException e) { } } } } }