package com.kuxhausen.huemore.net.dev; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import java.lang.ref.WeakReference; /** * Represents the device manager in bound Inter-Process communication with device driver(s). * <p/> * TODO increase fault tolerance and stress test, then bring up with the existing device types */ public class IpcMaster { // Debugging commands public static final int MSG_DEBUG_PING = 101; public static final int MSG_DEBUG_ACK = 102; // IPC control commands /** * Message from the device manager to a device driver. Command to register the manager. The * Message's replyTo field must be a Messenger of the manager where callbacks should be sent. The * device driver must respond with {@link #MSG_DRIVER_PID}. */ public static final int MSG_REGISTER_MANAGER = 201; /** * Message from a device driver to the device manager. Response to {@link #MSG_REGISTER_MANAGER} * containing the Process ID for this device driver. */ public static final int MSG_DRIVER_PID = 202; /** * Message from the device manager to a device driver. The device driver must respond within an * {@link #MSG_WATCHDOG_ACK}. If the device manager doesn't receive a response within 1 second, * the device driver may be process killed. */ public static final int MSG_WATCHDOG_POLL = 203; /** * Response to {@link #MSG_WATCHDOG_POLL}. */ public static final int MSG_WATCHDOG_ACK = 204; /** * Message from the device manager to a driver driver to stop sending messages and shut down. */ public static final int MSG_UNREGISTER_MANAGER = 204; // Device control commands public static final int MSG_TARGET_STATEMESSAGE = 301; public static final int MSG_OBSERVED_STATEMESSAGE = 302; public static final int MSG_CONNECTIONS_CONNECTIVITY = 303; public static final int MSG_BULBS_CONNECTIVITY = 304; public static final int MSG_TARGET_BULBNAME = 305; public static final int MSG_LAUNCH_CONFIGURATION = 306; Service mContext; // TODO generalize to connecting to different device services at the same time Messenger mSampleMessanger = null; Integer mSampleDevicePid = null; boolean mIsBound; public IpcMaster(Service s) { mContext = s; doBindService(); } public void onDestroy() { doUnbindService(); } /** * Target we publish for device services to send messages to our IncomingHandler. */ final Messenger mMessenger = new Messenger(new IncomingHandler(new WeakReference<>(this))); void doBindService() { DevLogger.debugLog("DeviceManagerSending: bind"); mContext.bindService(new Intent(mContext, SampleIpcMinion.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } void doUnbindService() { if (mIsBound) { // If we've received the service, and hence registered with it, now is the time to unregister if (mSampleMessanger != null) { try { Message msg = Message.obtain(null, IpcMaster.MSG_UNREGISTER_MANAGER); msg.replyTo = mMessenger; mSampleMessanger.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service has crashed } } // Detach our existing connection. DevLogger.debugLog("DeviceManagerSending: unbind"); mContext.unbindService(mConnection); mIsBound = false; } } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been established, giving us the // service object we can use to interact with the service. We are communicating with our // service through an IDL interface, so get a client-side representation of that from the // raw service object. mSampleMessanger = new Messenger(service); // We want to monitor the service for as long as we are connected to it try { Message msg = Message.obtain(null, IpcMaster.MSG_REGISTER_MANAGER); msg.replyTo = mMessenger; mSampleMessanger.send(msg); if (DevLogger.NET_DEBUG) { //Turn on some test communication new NetExerciser().execute(mSampleMessanger, mMessenger); } } catch (RemoteException e) { // The service has crashed before we could even do anything with it; we can count on soon // being disconnected (and then reconnected if it can be restarted) so there is no need to // do anything here. } DevLogger.debugLog("DeviceManagerNotified: onServiceConnected"); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been unexpectedly disconnected // That is, its process crashed. mSampleMessanger = null; DevLogger.debugLog("DeviceManagerNotified: onServiceDisconnected"); } }; /** * Handler of incoming messages from device services. */ private static class IncomingHandler extends Handler { WeakReference<IpcMaster> mManagerWeakReference; public IncomingHandler(WeakReference<IpcMaster> managerWeakReference) { mManagerWeakReference = managerWeakReference; } @Override public void handleMessage(Message msg) { switch (msg.what) { case IpcMaster.MSG_DRIVER_PID: mManagerWeakReference.get().mSampleDevicePid = msg.arg1; case IpcMaster.MSG_DEBUG_PING: DevLogger.debugLog("ExperimentalDeviceManager.PING " + msg.arg1); DevLogger.getLogger().accumulate("EDM.PING", msg.arg1); if (DevLogger.NET_DEBUG) { NetExerciser.simulateWork(5); } try { mManagerWeakReference.get().mSampleMessanger.send( Message.obtain(null, IpcMaster.MSG_DEBUG_ACK, msg.arg1, 0)); } catch (RemoteException e) { // The client is dead. Remove references to it; mManagerWeakReference.get().mSampleMessanger = null; } break; case IpcMaster.MSG_DEBUG_ACK: DevLogger.debugLog("ExperimentalDeviceManager.ACK " + msg.arg1); DevLogger.getLogger().accumulate("EDM.ACKBRI", msg.arg1); default: super.handleMessage(msg); } } } }