Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b35f942 authored by Wink Saville's avatar Wink Saville Committed by Android (Google) Code Review
Browse files

Merge "Add AsyncChannel and AsyncService."

parents fb3132e8 d20a5d6b
Loading
Loading
Loading
Loading
+778 −0
Original line number Diff line number Diff line
/**
 * 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.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
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;

/**
 * An asynchronous channel between two handlers.
 *
 * 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.
 *
 * 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:
 *
 *   1) Client calls AsyncChannel#connect
 *   2) Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel

 *   3) Client calls AsyncChannel#sendMessage(msgX)
 *   4) Server receives and processes msgX
 *   5) Server optionally calls AsyncChannel#replyToMessage(msgY)
 *      and if sent Client receives and processes msgY
 *   6) Loop to step 3 until done
 *
 *   7) When done Client calls {@link AsyncChannel#disconnect(int)}
 *   8) Client receives CMD_CHANNEL_DISCONNECTED from AsyncChannel
 *
 * 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:
 *
 *   1)  Client calls AsyncChannel#connect
 *   2)  Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel
 *   3)  Client calls AsyncChannel#sendMessage(CMD_CHANNEL_FULL_CONNECTION)
 *   4)  Server receives CMD_CHANNEL_FULL_CONNECTION
 *   5)  Server calls AsyncChannel#connect
 *   6)  Server receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel
 *   7)  Server sends AsyncChannel#sendMessage(CMD_CHANNEL_FULLY_CONNECTED)
 *   8)  Client receives CMD_CHANNEL_FULLY_CONNECTED
 *
 *   9)  Client/Server uses AsyncChannel#sendMessage/replyToMessage
 *       to communicate and perform work
 *   10) Loop to step 9 until done
 *
 *   11) When done Client/Server calls {@link AsyncChannel#disconnect(int)}
 *   12) Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel
 */
public class AsyncChannel {
    /** Log tag */
    private static final String TAG = "AsyncChannel";

    /** Enable to turn on debugging */
    private static final boolean DBG = false;

    /**
     * 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.arg2 == token parameter
     * msg.replyTo == dstMessenger if successful
     */
    public static final int CMD_CHANNEL_HALF_CONNECTED = -1;

    /**
     * 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.
     */
    public static final int CMD_CHANNEL_FULL_CONNECTION = -2;

    /**
     * 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(int)} would typically be called.
     */
    public static final int CMD_CHANNEL_FULLY_CONNECTED = -3;

    /**
     * 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(int)}
     * 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 = -4;

    /**
     * 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
     *               : All other values signify failure and the channel state is indeterminate
     * msg.arg2 == token parameter
     * msg.replyTo = messenger disconnecting or null if it was never connected.
     */
    public static final int CMD_CHANNEL_DISCONNECTED = -5;

    /** Successful status always 0, !0 is an unsuccessful status */
    public static final int STATUS_SUCCESSFUL = 0;

    /** Error attempting to bind on a connect */
    public static final int STATUS_BINDING_UNSUCCESSFUL = 1;

    /** 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;

    /**
     * AsyncChannel constructor
     */
    public AsyncChannel() {
    }

    /**
     * Connect handler to named package/class.
     *
     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
     *
     * @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)
     * @param token unique id for this connection
     */
    private void connectSrcHandlerToPackage(
            Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName,
            int token) {
        if (DBG) log("connect srcHandler to dst Package & class E");

        mConnection = new AsyncChannelConnection(token);

        /* 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 (!result) {
            replyHalfConnected(STATUS_BINDING_UNSUCCESSFUL, token);
        }

        if (DBG) log("connect srcHandler to dst Package & class X result=" + result);
    }

    /**
     * Connect handler to named package/class.
     *
     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
     *      msg.arg1 = status
     *      msg.arg2 = token
     *
     * @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)
     * @param token returned in msg.arg2
     */
    public void connect(Context srcContext, Handler srcHandler, String dstPackageName,
            String dstClassName, int token) {
        if (DBG) log("connect srcHandler to dst Package & class E");

        final class ConnectAsync implements Runnable {
            Context mSrcCtx;
            Handler mSrcHdlr;
            String mDstPackageName;
            String mDstClassName;
            int mToken;

            ConnectAsync(Context srcContext, Handler srcHandler, String dstPackageName,
                    String dstClassName, int token) {
                mSrcCtx = srcContext;
                mSrcHdlr = srcHandler;
                mDstPackageName = dstPackageName;
                mDstClassName = dstClassName;
                mToken = token;
            }

            @Override
            public void run() {
                connectSrcHandlerToPackage(mSrcCtx, mSrcHdlr, mDstPackageName, mDstClassName,
                        mToken);
            }
        }

        ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName,
                token);
        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.arg2 = token
     *
     * @param srcContext
     * @param srcHandler
     * @param klass is the class to send messages to.
     * @param token returned in msg.arg2
     */
    public void connect(Context srcContext, Handler srcHandler, Class<?> klass, int token) {
        connect(srcContext, srcHandler, klass.getPackage().getName(), klass.getName(), token);
    }

    /**
     * Connect handler and messenger.
     *
     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
     *      msg.arg1 = status
     *      msg.arg2 = token
     *
     * @param srcContext
     * @param srcHandler
     * @param dstMessenger
     * @param token returned in msg.arg2
     */
    public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger, int token) {
        if (DBG) log("connect srcHandler to the dstMessenger  E");

        // Initialize source fields
        mSrcContext = srcContext;
        mSrcHandler = srcHandler;
        mSrcMessenger = new Messenger(mSrcHandler);

        // Initialize destination fields
        mDstMessenger = dstMessenger;

        if (DBG) log("tell source we are half connected");

        // Tell source we are half connected
        replyHalfConnected(STATUS_SUCCESSFUL, token);

        if (DBG) log("connect srcHandler to the dstMessenger X");
    }

    /**
     * Connect two local Handlers.
     *
     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
     *      msg.arg1 = status
     *      msg.arg2 = token
     *
     * @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.
     * @param token returned in msg.arg2
     */
    public void connect(Context srcContext, Handler srcHandler, Handler dstHandler, int token) {
        connect(srcContext, srcHandler, new Messenger(dstHandler), token);
    }

    /**
     * Connect service and messenger.
     *
     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcAsyncService when complete.
     *      msg.arg1 = status
     *      msg.arg2 = token
     *
     * @param srcAsyncService
     * @param dstMessenger
     * @param token returned in msg.arg2
     */
    public void connect(AsyncService srcAsyncService, Messenger dstMessenger, int token) {
        connect(srcAsyncService, srcAsyncService.getHandler(), dstMessenger, token);
    }

    /**
     * To close the connection call when handler receives CMD_CHANNEL_DISCONNECTED
     */
    public void disconnected() {
        mSrcHandler = null;
        mSrcMessenger = null;
        mDstMessenger = null;
        mConnection = null;
    }

    /**
     * Disconnect
     */
    public void disconnect(int token) {
        if (mConnection != null) {
            mConnection.setToken(token);
            mSrcContext.unbindService(mConnection);
        }
        if (mSrcHandler != null) {
            Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
            msg.arg1 = STATUS_SUCCESSFUL;
            msg.arg2 = token;
            msg.replyTo = mDstMessenger;
            mSrcHandler.sendMessage(msg);
        }
    }

    /**
     * Send a message to the destination handler.
     *
     * @param msg
     */
    public void sendMessage(Message msg) {
        msg.replyTo = mSrcMessenger;
        try {
            mDstMessenger.send(msg);
        } catch (RemoteException e) {
            log("TODO: handle sendMessage RemoteException" + e);
        }
    }

    /**
     * Send a message to the destination handler
     *
     * @param what
     */
    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
     */
    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
     */
    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
     */
    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
     */
    public void replyToMessage(Message srcMsg, Message dstMsg) {
        try {
            srcMsg.replyTo.send(dstMsg);
        } catch (RemoteException e) {
            log("TODO: handle replyToMessage RemoteException" + e);
            e.printStackTrace();
        }
    }

    /**
     * Reply to srcMsg
     *
     * @param srcMsg
     * @param what
     */
    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
     */
    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
     */
    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
     */
    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.
     */
    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.
     */
    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) {
                mResultMsg = Message.obtain();
                mResultMsg.copyFrom(msg);
                synchronized(mLockObject) {
                    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();
            try {
                msg.replyTo = sm.mMessenger;
                dstMessenger.send(msg);
                synchronized (sm.mHandler.mLockObject) {
                    sm.mHandler.mLockObject.wait();
                }
            } catch (InterruptedException e) {
                sm.mHandler.mResultMsg = null;
            } catch (RemoteException e) {
                sm.mHandler.mResultMsg = null;
            }
            Message resultMsg = sm.mHandler.mResultMsg;
            sm.recycle();
            return resultMsg;
        }
    }

    /**
     * Reply to the src handler that we're half connected.
     *
     * @param status to be stored in msg.arg1
     */
    private void replyHalfConnected(int status, int token) {
        Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
        msg.arg1 = status;
        msg.arg2 = token;
        msg.replyTo = mDstMessenger;
        mSrcHandler.sendMessage(msg);
    }

    /**
     * ServiceConnection to receive call backs.
     */
    class AsyncChannelConnection implements ServiceConnection {
        private int mToken;

        AsyncChannelConnection(int token) {
            mToken = token;
        }

        /**
         * @param token
         */
        public void setToken(int token) {
            mToken = token;
        }

        public void onServiceConnected(ComponentName className, IBinder service) {
            mDstMessenger = new Messenger(service);
            replyHalfConnected(STATUS_SUCCESSFUL, mToken);
        }

        public void onServiceDisconnected(ComponentName className) {
            Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
            msg.arg1 = STATUS_SUCCESSFUL;
            msg.arg2 = mToken;
            msg.replyTo = mDstMessenger;
            mSrcHandler.sendMessage(msg);
        }
    }

    /**
     * Log the string.
     *
     * @param s
     */
    private static void log(String s) {
        Slog.d(TAG, s);
    }
}
+128 −0

File added.

Preview size limit exceeded, changes collapsed.

+38 −0

File added.

Preview size limit exceeded, changes collapsed.