content: /**
   * Copyright (C) 2010 The Android Open Source Project
   *
   * 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 com.android.internal.util;
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.ServiceConnection;
  import android.os.Build;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Messenger;
  import android.os.RemoteException;
  import android.util.Slog;
  
  import java.util.Stack;
  
  /**
   * <p>An asynchronous channel between two handlers.</p>
   *
   * <p>The handlers maybe in the same process or in another process. There
   * are two protocol styles that can be used with an AysncChannel. The
   * first is a simple request/reply protocol where the server does
   * not need to know which client is issuing the request.</p>
   *
   * <p>In a simple request/reply protocol the client/source sends requests to the
   * server/destination. And the server uses the replyToMessage methods.
   * In this usage model there is no need for the destination to
   * use the connect methods. The typical sequence of operations is:</p>
   *<ol>
   *   <li>Client calls AsyncChannel#connectSync or Asynchronously:</li>
   *      <ol>For an asynchronous half connection client calls AsyncChannel#connect.</ol>
   *          <li>Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel</li>
   *      </ol>
   *   <li><code>comm-loop:</code></li>
   *   <li>Client calls AsyncChannel#sendMessage</li>
   *   <li>Server processes messages and optionally replies using AsyncChannel#replyToMessage
   *   <li>Loop to <code>comm-loop</code> until done</li>
   *   <li>When done Client calls {@link AsyncChannel#disconnect}</li>
   *   <li>Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel</li>
   *</ol>
   *<br/>
   * <p>A second usage model is where the server/destination needs to know
   * which client it's connected too. For example the server needs to
   * send unsolicited messages back to the client. Or the server keeps
   * different state for each client. In this model the server will also
   * use the connect methods. The typical sequence of operation is:</p>
   *<ol>
   *   <li>Client calls AsyncChannel#fullyConnectSync or Asynchronously:<li>
   *      <ol>For an asynchronous full connection it calls AsyncChannel#connect</li>
   *          <li>Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel</li>
   *          <li>Client calls AsyncChannel#sendMessage(CMD_CHANNEL_FULL_CONNECTION)</li>
   *      </ol>
   *   <li>Server receives CMD_CHANNEL_FULL_CONNECTION</li>
   *   <li>Server calls AsyncChannel#connected</li>
   *   <li>Server sends AsyncChannel#sendMessage(CMD_CHANNEL_FULLY_CONNECTED)</li>
   *   <li>Client receives CMD_CHANNEL_FULLY_CONNECTED</li>
   *   <li><code>comm-loop:</code></li>
   *   <li>Client/Server uses AsyncChannel#sendMessage/replyToMessage
   *       to communicate and perform work</li>
   *   <li>Loop to <code>comm-loop</code> until done</li>
   *   <li>When done Client/Server calls {@link AsyncChannel#disconnect}</li>
   *   <li>Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel</li>
   *</ol>
   *
   * TODO: Consider simplifying where we have connect and fullyConnect with only one response
   * message RSP_CHANNEL_CONNECT instead of two, CMD_CHANNEL_HALF_CONNECTED and
   * CMD_CHANNEL_FULLY_CONNECTED. We'd also change CMD_CHANNEL_FULL_CONNECTION to REQ_CHANNEL_CONNECT.
   */
  public class AsyncChannel {
      /** Log tag */
      private static final String TAG = "AsyncChannel";
  
      /** Enable to turn on debugging */
      private static final boolean DBG = false;
  
      private static final int BASE = Protocol.BASE_SYSTEM_ASYNC_CHANNEL;
  
      /**
       * Command sent when the channel is half connected. Half connected
       * means that the channel can be used to send commends to the destination
       * but the destination is unaware that the channel exists. The first
       * command sent to the destination is typically CMD_CHANNEL_FULL_CONNECTION if
       * it is desired to establish a long term connection, but any command maybe
       * sent.
       *
       * msg.arg1 == 0 : STATUS_SUCCESSFUL
       *             1 : STATUS_BINDING_UNSUCCESSFUL
       * msg.obj  == the AsyncChannel
       * msg.replyTo == dstMessenger if successful
       */
      @UnsupportedAppUsage
      public static final int CMD_CHANNEL_HALF_CONNECTED = BASE + 0;
  
      /**
       * Command typically sent when after receiving the CMD_CHANNEL_HALF_CONNECTED.
       * This is used to initiate a long term connection with the destination and
       * typically the destination will reply with CMD_CHANNEL_FULLY_CONNECTED.
       *
       * msg.replyTo = srcMessenger.
       */
      @UnsupportedAppUsage
      public static final int CMD_CHANNEL_FULL_CONNECTION = BASE + 1;
  
      /**
       * Command typically sent after the destination receives a CMD_CHANNEL_FULL_CONNECTION.
       * This signifies the acceptance or rejection of the channel by the sender.
       *
       * msg.arg1 == 0 : Accept connection
       *               : All other values signify the destination rejected the connection
       *                 and {@link AsyncChannel#disconnect} would typically be called.
       */
      public static final int CMD_CHANNEL_FULLY_CONNECTED = BASE + 2;
  
      /**
       * Command sent when one side or the other wishes to disconnect. The sender
       * may or may not be able to receive a reply depending upon the protocol and
       * the state of the connection. The receiver should call {@link AsyncChannel#disconnect}
       * to close its side of the channel and it will receive a CMD_CHANNEL_DISCONNECTED
       * when the channel is closed.
       *
       * msg.replyTo = messenger that is disconnecting
       */
      public static final int CMD_CHANNEL_DISCONNECT = BASE + 3;
  
      /**
       * Command sent when the channel becomes disconnected. This is sent when the
       * channel is forcibly disconnected by the system or as a reply to CMD_CHANNEL_DISCONNECT.
       *
       * msg.arg1 == 0 : STATUS_SUCCESSFUL
       *             1 : STATUS_BINDING_UNSUCCESSFUL
       *             2 : STATUS_SEND_UNSUCCESSFUL
       *               : All other values signify failure and the channel state is indeterminate
       * msg.obj  == the AsyncChannel
       * msg.replyTo = messenger disconnecting or null if it was never connected.
       */
      public static final int CMD_CHANNEL_DISCONNECTED = BASE + 4;
  
      private static final int CMD_TO_STRING_COUNT = CMD_CHANNEL_DISCONNECTED - BASE + 1;
      private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
      static {
          sCmdToString[CMD_CHANNEL_HALF_CONNECTED - BASE] = "CMD_CHANNEL_HALF_CONNECTED";
          sCmdToString[CMD_CHANNEL_FULL_CONNECTION - BASE] = "CMD_CHANNEL_FULL_CONNECTION";
          sCmdToString[CMD_CHANNEL_FULLY_CONNECTED - BASE] = "CMD_CHANNEL_FULLY_CONNECTED";
          sCmdToString[CMD_CHANNEL_DISCONNECT - BASE] = "CMD_CHANNEL_DISCONNECT";
          sCmdToString[CMD_CHANNEL_DISCONNECTED - BASE] = "CMD_CHANNEL_DISCONNECTED";
      }
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      protected static String cmdToString(int cmd) {
          cmd -= BASE;
          if ((cmd >= 0) && (cmd < sCmdToString.length)) {
              return sCmdToString[cmd];
          } else {
              return null;
          }
      }
  
      /** Successful status always 0, !0 is an unsuccessful status */
      @UnsupportedAppUsage
      public static final int STATUS_SUCCESSFUL = 0;
  
      /** Error attempting to bind on a connect */
      public static final int STATUS_BINDING_UNSUCCESSFUL = 1;
  
      /** Error attempting to send a message */
      public static final int STATUS_SEND_UNSUCCESSFUL = 2;
  
      /** CMD_FULLY_CONNECTED refused because a connection already exists*/
      public static final int STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED = 3;
  
      /** Error indicating abnormal termination of destination messenger */
      public static final int STATUS_REMOTE_DISCONNECTION = 4;
  
      /** Service connection */
      private AsyncChannelConnection mConnection;
  
      /** Context for source */
      private Context mSrcContext;
  
      /** Handler for source */
      private Handler mSrcHandler;
  
      /** Messenger for source */
      private Messenger mSrcMessenger;
  
      /** Messenger for destination */
      private Messenger mDstMessenger;
  
      /** Death Monitor for destination messenger */
      private DeathMonitor mDeathMonitor;
  
      /**
       * AsyncChannel constructor
       */
      @UnsupportedAppUsage
      public AsyncChannel() {
      }
  
      /**
       * Connect handler to named package/class synchronously.
       *
       * @param srcContext is the context of the source
       * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
       *            messages
       * @param dstPackageName is the destination package name
       * @param dstClassName is the fully qualified class name (i.e. contains
       *            package name)
       *
       * @return STATUS_SUCCESSFUL on success any other value is an error.
       */
      public int connectSrcHandlerToPackageSync(
              Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {
          if (DBG) log("connect srcHandler to dst Package & class E");
  
          mConnection = new AsyncChannelConnection();
  
          /* Initialize the source information */
          mSrcContext = srcContext;
          mSrcHandler = srcHandler;
          mSrcMessenger = new Messenger(srcHandler);
  
          /*
           * Initialize destination information to null they will
           * be initialized when the AsyncChannelConnection#onServiceConnected
           * is called
           */
          mDstMessenger = null;
  
          /* Send intent to create the connection */
          Intent intent = new Intent(Intent.ACTION_MAIN);
          intent.setClassName(dstPackageName, dstClassName);
          boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
          if (DBG) log("connect srcHandler to dst Package & class X result=" + result);
          return result ? STATUS_SUCCESSFUL : STATUS_BINDING_UNSUCCESSFUL;
      }
  
      /**
       * Connect a handler to Messenger synchronously.
       *
       * @param srcContext is the context of the source
       * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
       *            messages
       * @param dstMessenger is the hander to send messages to.
       *
       * @return STATUS_SUCCESSFUL on success any other value is an error.
       */
      @UnsupportedAppUsage
      public int connectSync(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
          if (DBG) log("halfConnectSync srcHandler to the dstMessenger  E");
  
          // We are connected
          connected(srcContext, srcHandler, dstMessenger);
  
          if (DBG) log("halfConnectSync srcHandler to the dstMessenger X");
          return STATUS_SUCCESSFUL;
      }
  
      /**
       * connect two local Handlers synchronously.
       *
       * @param srcContext is the context of the source
       * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
       *            messages
       * @param dstHandler is the hander to send messages to.
       *
       * @return STATUS_SUCCESSFUL on success any other value is an error.
       */
      public int connectSync(Context srcContext, Handler srcHandler, Handler dstHandler) {
          return connectSync(srcContext, srcHandler, new Messenger(dstHandler));
      }
  
      /**
       * Fully connect two local Handlers synchronously.
       *
       * @param srcContext is the context of the source
       * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
       *            messages
       * @param dstHandler is the hander to send messages to.
       *
       * @return STATUS_SUCCESSFUL on success any other value is an error.
       */
      public int fullyConnectSync(Context srcContext, Handler srcHandler, Handler dstHandler) {
          int status = connectSync(srcContext, srcHandler, dstHandler);
          if (status == STATUS_SUCCESSFUL) {
              Message response = sendMessageSynchronously(CMD_CHANNEL_FULL_CONNECTION);
              status = response.arg1;
          }
          return status;
      }
  
      /**
       * Connect handler to named package/class.
       *
       * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
       *      msg.arg1 = status
       *      msg.obj = the AsyncChannel
       *
       * @param srcContext is the context of the source
       * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
       *            messages
       * @param dstPackageName is the destination package name
       * @param dstClassName is the fully qualified class name (i.e. contains
       *            package name)
       */
      public void connect(Context srcContext, Handler srcHandler, String dstPackageName,
              String dstClassName) {
          if (DBG) log("connect srcHandler to dst Package & class E");
  
          final class ConnectAsync implements Runnable {
              Context mSrcCtx;
              Handler mSrcHdlr;
              String mDstPackageName;
              String mDstClassName;
  
              ConnectAsync(Context srcContext, Handler srcHandler, String dstPackageName,
                      String dstClassName) {
                  mSrcCtx = srcContext;
                  mSrcHdlr = srcHandler;
                  mDstPackageName = dstPackageName;
                  mDstClassName = dstClassName;
              }
  
              @Override
              public void run() {
                  int result = connectSrcHandlerToPackageSync(mSrcCtx, mSrcHdlr, mDstPackageName,
                          mDstClassName);
                  replyHalfConnected(result);
              }
          }
  
          ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);
          new Thread(ca).start();
  
          if (DBG) log("connect srcHandler to dst Package & class X");
      }
  
      /**
       * Connect handler to a class
       *
       * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
       *      msg.arg1 = status
       *      msg.obj = the AsyncChannel
       *
       * @param srcContext
       * @param srcHandler
       * @param klass is the class to send messages to.
       */
      public void connect(Context srcContext, Handler srcHandler, Class<?> klass) {
          connect(srcContext, srcHandler, klass.getPackage().getName(), klass.getName());
      }
  
      /**
       * Connect handler and messenger.
       *
       * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
       *      msg.arg1 = status
       *      msg.obj = the AsyncChannel
       *
       * @param srcContext
       * @param srcHandler
       * @param dstMessenger
       */
      @UnsupportedAppUsage
      public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
          if (DBG) log("connect srcHandler to the dstMessenger  E");
  
          // We are connected
          connected(srcContext, srcHandler, dstMessenger);
  
          // Tell source we are half connected
          replyHalfConnected(STATUS_SUCCESSFUL);
  
          if (DBG) log("connect srcHandler to the dstMessenger X");
      }
  
      /**
       * Connect handler to messenger. This method is typically called
       * when a server receives a CMD_CHANNEL_FULL_CONNECTION request
       * and initializes the internal instance variables to allow communication
       * with the dstMessenger.
       *
       * @param srcContext
       * @param srcHandler
       * @param dstMessenger
       */
      @UnsupportedAppUsage
      public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
          if (DBG) log("connected srcHandler to the dstMessenger  E");
  
          // Initialize source fields
          mSrcContext = srcContext;
          mSrcHandler = srcHandler;
          mSrcMessenger = new Messenger(mSrcHandler);
  
          // Initialize destination fields
          mDstMessenger = dstMessenger;
          if (DBG) log("connected srcHandler to the dstMessenger X");
      }
  
      /**
       * Connect two local Handlers.
       *
       * @param srcContext is the context of the source
       * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
       *            messages
       * @param dstHandler is the hander to send messages to.
       */
      public void connect(Context srcContext, Handler srcHandler, Handler dstHandler) {
          connect(srcContext, srcHandler, new Messenger(dstHandler));
      }
  
      /**
       * Connect service and messenger.
       *
       * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcAsyncService when complete.
       *      msg.arg1 = status
       *      msg.obj = the AsyncChannel
       *
       * @param srcAsyncService
       * @param dstMessenger
       */
      public void connect(AsyncService srcAsyncService, Messenger dstMessenger) {
          connect(srcAsyncService, srcAsyncService.getHandler(), dstMessenger);
      }
  
      /**
       * To close the connection call when handler receives CMD_CHANNEL_DISCONNECTED
       */
      public void disconnected() {
          mSrcContext = null;
          mSrcHandler = null;
          mSrcMessenger = null;
          mDstMessenger = null;
          mDeathMonitor = null;
          mConnection = null;
      }
  
      /**
       * Disconnect
       */
      @UnsupportedAppUsage
      public void disconnect() {
          if ((mConnection != null) && (mSrcContext != null)) {
              mSrcContext.unbindService(mConnection);
              mConnection = null;
          }
          try {
              // Send the DISCONNECTED, although it may not be received
              // but its the best we can do.
              Message msg = Message.obtain();
              msg.what = CMD_CHANNEL_DISCONNECTED;
              msg.replyTo = mSrcMessenger;
              mDstMessenger.send(msg);
          } catch(Exception e) {
          }
          // Tell source we're disconnected.
          replyDisconnected(STATUS_SUCCESSFUL);
          mSrcHandler = null;
          // Unlink only when bindService isn't used
          if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) {
              mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0);
              mDeathMonitor = null;
          }
      }
  
      /**
       * Send a message to the destination handler.
       *
       * @param msg
       */
      @UnsupportedAppUsage
      public void sendMessage(Message msg) {
          msg.replyTo = mSrcMessenger;
          try {
              mDstMessenger.send(msg);
          } catch (RemoteException e) {
              replyDisconnected(STATUS_SEND_UNSUCCESSFUL);
          }
      }
  
      /**
       * Send a message to the destination handler
       *
       * @param what
       */
      @UnsupportedAppUsage
      public void sendMessage(int what) {
          Message msg = Message.obtain();
          msg.what = what;
          sendMessage(msg);
      }
  
      /**
       * Send a message to the destination handler
       *
       * @param what
       * @param arg1
       */
      @UnsupportedAppUsage
      public void sendMessage(int what, int arg1) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          sendMessage(msg);
      }
  
      /**
       * Send a message to the destination handler
       *
       * @param what
       * @param arg1
       * @param arg2
       */
      @UnsupportedAppUsage
      public void sendMessage(int what, int arg1, int arg2) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          msg.arg2 = arg2;
          sendMessage(msg);
      }
  
      /**
       * Send a message to the destination handler
       *
       * @param what
       * @param arg1
       * @param arg2
       * @param obj
       */
      @UnsupportedAppUsage
      public void sendMessage(int what, int arg1, int arg2, Object obj) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          msg.arg2 = arg2;
          msg.obj = obj;
          sendMessage(msg);
      }
  
      /**
       * Send a message to the destination handler
       *
       * @param what
       * @param obj
       */
      public void sendMessage(int what, Object obj) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.obj = obj;
          sendMessage(msg);
      }
  
      /**
       * Reply to srcMsg sending dstMsg
       *
       * @param srcMsg
       * @param dstMsg
       */
      @UnsupportedAppUsage
      public void replyToMessage(Message srcMsg, Message dstMsg) {
          try {
              dstMsg.replyTo = mSrcMessenger;
              srcMsg.replyTo.send(dstMsg);
          } catch (RemoteException e) {
              log("TODO: handle replyToMessage RemoteException" + e);
              e.printStackTrace();
          }
      }
  
      /**
       * Reply to srcMsg
       *
       * @param srcMsg
       * @param what
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public void replyToMessage(Message srcMsg, int what) {
          Message msg = Message.obtain();
          msg.what = what;
          replyToMessage(srcMsg, msg);
      }
  
      /**
       * Reply to srcMsg
       *
       * @param srcMsg
       * @param what
       * @param arg1
       */
      @UnsupportedAppUsage
      public void replyToMessage(Message srcMsg, int what, int arg1) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          replyToMessage(srcMsg, msg);
      }
  
      /**
       * Reply to srcMsg
       *
       * @param srcMsg
       * @param what
       * @param arg1
       * @param arg2
       */
      public void replyToMessage(Message srcMsg, int what, int arg1, int arg2) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          msg.arg2 = arg2;
          replyToMessage(srcMsg, msg);
      }
  
      /**
       * Reply to srcMsg
       *
       * @param srcMsg
       * @param what
       * @param arg1
       * @param arg2
       * @param obj
       */
      @UnsupportedAppUsage
      public void replyToMessage(Message srcMsg, int what, int arg1, int arg2, Object obj) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          msg.arg2 = arg2;
          msg.obj = obj;
          replyToMessage(srcMsg, msg);
      }
  
      /**
       * Reply to srcMsg
       *
       * @param srcMsg
       * @param what
       * @param obj
       */
      @UnsupportedAppUsage
      public void replyToMessage(Message srcMsg, int what, Object obj) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.obj = obj;
          replyToMessage(srcMsg, msg);
      }
  
      /**
       * Send the Message synchronously.
       *
       * @param msg to send
       * @return reply message or null if an error.
       */
      @UnsupportedAppUsage
      public Message sendMessageSynchronously(Message msg) {
          Message resultMsg = SyncMessenger.sendMessageSynchronously(mDstMessenger, msg);
          return resultMsg;
      }
  
      /**
       * Send the Message synchronously.
       *
       * @param what
       * @return reply message or null if an error.
       */
      public Message sendMessageSynchronously(int what) {
          Message msg = Message.obtain();
          msg.what = what;
          Message resultMsg = sendMessageSynchronously(msg);
          return resultMsg;
      }
  
      /**
       * Send the Message synchronously.
       *
       * @param what
       * @param arg1
       * @return reply message or null if an error.
       */
      public Message sendMessageSynchronously(int what, int arg1) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          Message resultMsg = sendMessageSynchronously(msg);
          return resultMsg;
      }
  
      /**
       * Send the Message synchronously.
       *
       * @param what
       * @param arg1
       * @param arg2
       * @return reply message or null if an error.
       */
      @UnsupportedAppUsage
      public Message sendMessageSynchronously(int what, int arg1, int arg2) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          msg.arg2 = arg2;
          Message resultMsg = sendMessageSynchronously(msg);
          return resultMsg;
      }
  
      /**
       * Send the Message synchronously.
       *
       * @param what
       * @param arg1
       * @param arg2
       * @param obj
       * @return reply message or null if an error.
       */
      public Message sendMessageSynchronously(int what, int arg1, int arg2, Object obj) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg1 = arg1;
          msg.arg2 = arg2;
          msg.obj = obj;
          Message resultMsg = sendMessageSynchronously(msg);
          return resultMsg;
      }
  
      /**
       * Send the Message synchronously.
       *
       * @param what
       * @param obj
       * @return reply message or null if an error.
       */
      public Message sendMessageSynchronously(int what, Object obj) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.obj = obj;
          Message resultMsg = sendMessageSynchronously(msg);
          return resultMsg;
      }
  
      /**
       * Helper class to send messages synchronously
       */
      private static class SyncMessenger {
          /** A stack of SyncMessengers */
          private static Stack<SyncMessenger> sStack = new Stack<SyncMessenger>();
          /** A number of SyncMessengers created */
          private static int sCount = 0;
          /** The handler thread */
          private HandlerThread mHandlerThread;
          /** The handler that will receive the result */
          private SyncHandler mHandler;
          /** The messenger used to send the message */
          private Messenger mMessenger;
  
          /** private constructor */
          private SyncMessenger() {
          }
  
          /** Synchronous Handler class */
          private class SyncHandler extends Handler {
              /** The object used to wait/notify */
              private Object mLockObject = new Object();
              /** The resulting message */
              private Message mResultMsg;
  
              /** Constructor */
              private SyncHandler(Looper looper) {
                  super(looper);
              }
  
              /** Handle of the reply message */
              @Override
              public void handleMessage(Message msg) {
                  Message msgCopy = Message.obtain();
                  msgCopy.copyFrom(msg);
                  synchronized(mLockObject) {
                      mResultMsg = msgCopy;
                      mLockObject.notify();
                  }
              }
          }
  
          /**
           * @return the SyncMessenger
           */
          private static SyncMessenger obtain() {
              SyncMessenger sm;
              synchronized (sStack) {
                  if (sStack.isEmpty()) {
                      sm = new SyncMessenger();
                      sm.mHandlerThread = new HandlerThread("SyncHandler-" + sCount++);
                      sm.mHandlerThread.start();
                      sm.mHandler = sm.new SyncHandler(sm.mHandlerThread.getLooper());
                      sm.mMessenger = new Messenger(sm.mHandler);
                  } else {
                      sm = sStack.pop();
                  }
              }
              return sm;
          }
  
          /**
           * Recycle this object
           */
          private void recycle() {
              synchronized (sStack) {
                  sStack.push(this);
              }
          }
  
          /**
           * Send a message synchronously.
           *
           * @param msg to send
           * @return result message or null if an error occurs
           */
          private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
              SyncMessenger sm = SyncMessenger.obtain();
              Message resultMsg = null;
              try {
                  if (dstMessenger != null && msg != null) {
                      msg.replyTo = sm.mMessenger;
                      synchronized (sm.mHandler.mLockObject) {
                          if (sm.mHandler.mResultMsg != null) {
                              Slog.wtf(TAG, "mResultMsg should be null here");
                              sm.mHandler.mResultMsg = null;
                          }
                          dstMessenger.send(msg);
                          sm.mHandler.mLockObject.wait();
                          resultMsg = sm.mHandler.mResultMsg;
                          sm.mHandler.mResultMsg = null;
                      }
                  }
              } catch (InterruptedException e) {
                  Slog.e(TAG, "error in sendMessageSynchronously", e);
              } catch (RemoteException e) {
                  Slog.e(TAG, "error in sendMessageSynchronously", e);
              }
              sm.recycle();
              return resultMsg;
          }
      }
  
      /**
       * Reply to the src handler that we're half connected.
       * see: CMD_CHANNEL_HALF_CONNECTED for message contents
       *
       * @param status to be stored in msg.arg1
       */
      private void replyHalfConnected(int status) {
          Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
          msg.arg1 = status;
          msg.obj = this;
          msg.replyTo = mDstMessenger;
          if (!linkToDeathMonitor()) {
              // Override status to indicate failure
              msg.arg1 = STATUS_BINDING_UNSUCCESSFUL;
          }
  
          mSrcHandler.sendMessage(msg);
      }
  
      /**
       * Link to death monitor for destination messenger. Returns true if successfully binded to
       * destination messenger; false otherwise.
       */
      private boolean linkToDeathMonitor() {
          // Link to death only when bindService isn't used and not already linked.
          if (mConnection == null && mDeathMonitor == null) {
              mDeathMonitor = new DeathMonitor();
              try {
                  mDstMessenger.getBinder().linkToDeath(mDeathMonitor, 0);
              } catch (RemoteException e) {
                  mDeathMonitor = null;
                  return false;
              }
          }
          return true;
      }
  
      /**
       * Reply to the src handler that we are disconnected
       * see: CMD_CHANNEL_DISCONNECTED for message contents
       *
       * @param status to be stored in msg.arg1
       */
      private void replyDisconnected(int status) {
          // Can't reply if already disconnected. Avoid NullPointerException.
          if (mSrcHandler == null) return;
          Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
          msg.arg1 = status;
          msg.obj = this;
          msg.replyTo = mDstMessenger;
          mSrcHandler.sendMessage(msg);
      }
  
  
      /**
       * ServiceConnection to receive call backs.
       */
      class AsyncChannelConnection implements ServiceConnection {
          AsyncChannelConnection() {
          }
  
          @Override
          public void onServiceConnected(ComponentName className, IBinder service) {
              mDstMessenger = new Messenger(service);
              replyHalfConnected(STATUS_SUCCESSFUL);
          }
  
          @Override
          public void onServiceDisconnected(ComponentName className) {
              replyDisconnected(STATUS_SUCCESSFUL);
          }
      }
  
      /**
       * Log the string.
       *
       * @param s
       */
      private static void log(String s) {
          Slog.d(TAG, s);
      }
  
      private final class DeathMonitor implements IBinder.DeathRecipient {
  
          DeathMonitor() {
          }
  
          public void binderDied() {
              replyDisconnected(STATUS_REMOTE_DISCONNECTION);
          }
  
      }
  }
  