AsyncChannel.java revision cfce303cbdd59a3883957e4bc96a0476ceeb86ac
1/**
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.util;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.Messenger;
29import android.os.RemoteException;
30import android.util.Slog;
31
32import java.util.Stack;
33
34/**
35 * <p>An asynchronous channel between two handlers.</p>
36 *
37 * <p>The handlers maybe in the same process or in another process. There
38 * are two protocol styles that can be used with an AysncChannel. The
39 * first is a simple request/reply protocol where the server does
40 * not need to know which client is issuing the request.</p>
41 *
42 * <p>In a simple request/reply protocol the client/source sends requests to the
43 * server/destination. And the server uses the replyToMessage methods.
44 * In this usage model there is no need for the destination to
45 * use the connect methods. The typical sequence of operations is:</p>
46 *<ol>
47 *   <li>Client calls AsyncChannel#connect</li>
48 *   <li>Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel</li>
49 *   <li><code>comm-loop:</code></li>
50 *   <li>Client calls AsyncChannel#sendMessage(msgX)</li>
51 *   <li>Server receives and processes msgX</li>
52 *   <li>Server optionally calls AsyncChannel#replyToMessage(msgY)
53 *       and if sent Client receives and processes msgY</li>
54 *   <li>Loop to <code>comm-loop</code> until done</li>
55 *   <li>When done Client calls {@link AsyncChannel#disconnect(int)}</li>
56 *   <li>Client receives CMD_CHANNEL_DISCONNECTED from AsyncChannel</li>
57 *</ol>
58 *<br/>
59 * <p>A second usage model is where the server/destination needs to know
60 * which client it's connected too. For example the server needs to
61 * send unsolicited messages back to the client. Or the server keeps
62 * different state for each client. In this model the server will also
63 * use the connect methods. The typical sequence of operation is:</p>
64 *<ol>
65 *   <li>Client calls AsyncChannel#connect</li>
66 *   <li>Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel</li>
67 *   <li>Client calls AsyncChannel#sendMessage(CMD_CHANNEL_FULL_CONNECTION)</li>
68 *   <li>Server receives CMD_CHANNEL_FULL_CONNECTION</li>
69 *   <li>Server calls AsyncChannel#connect</li>
70 *   <li>Server receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel</li>
71 *   <li>Server sends AsyncChannel#sendMessage(CMD_CHANNEL_FULLY_CONNECTED)</li>
72 *   <li>Client receives CMD_CHANNEL_FULLY_CONNECTED</li>
73 *   <li><code>comm-loop:</code></li>
74 *   <li>Client/Server uses AsyncChannel#sendMessage/replyToMessage
75 *       to communicate and perform work</li>
76 *   <li>Loop to <code>comm-loop</code> until done</li>
77 *   <li>When done Client/Server calls {@link AsyncChannel#disconnect(int)}</li>
78 *   <li>Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel</li>
79 *</ol>
80 */
81public class AsyncChannel {
82    /** Log tag */
83    private static final String TAG = "AsyncChannel";
84
85    /** Enable to turn on debugging */
86    private static final boolean DBG = false;
87
88    /**
89     * Command sent when the channel is half connected. Half connected
90     * means that the channel can be used to send commends to the destination
91     * but the destination is unaware that the channel exists. The first
92     * command sent to the destination is typically CMD_CHANNEL_FULL_CONNECTION if
93     * it is desired to establish a long term connection, but any command maybe
94     * sent.
95     *
96     * msg.arg1 == 0 : STATUS_SUCCESSFUL
97     *             1 : STATUS_BINDING_UNSUCCESSFUL
98     * msg.obj  == the AsyncChannel
99     * msg.replyTo == dstMessenger if successful
100     */
101    public static final int CMD_CHANNEL_HALF_CONNECTED = -1;
102
103    /**
104     * Command typically sent when after receiving the CMD_CHANNEL_HALF_CONNECTED.
105     * This is used to initiate a long term connection with the destination and
106     * typically the destination will reply with CMD_CHANNEL_FULLY_CONNECTED.
107     *
108     * msg.replyTo = srcMessenger.
109     */
110    public static final int CMD_CHANNEL_FULL_CONNECTION = -2;
111
112    /**
113     * Command typically sent after the destination receives a CMD_CHANNEL_FULL_CONNECTION.
114     * This signifies the acceptance or rejection of the channel by the sender.
115     *
116     * msg.arg1 == 0 : Accept connection
117     *               : All other values signify the destination rejected the connection
118     *                 and {@link AsyncChannel#disconnect(int)} would typically be called.
119     */
120    public static final int CMD_CHANNEL_FULLY_CONNECTED = -3;
121
122    /**
123     * Command sent when one side or the other wishes to disconnect. The sender
124     * may or may not be able to receive a reply depending upon the protocol and
125     * the state of the connection. The receiver should call {@link AsyncChannel#disconnect(int)}
126     * to close its side of the channel and it will receive a CMD_CHANNEL_DISCONNECTED
127     * when the channel is closed.
128     *
129     * msg.replyTo = messenger that is disconnecting
130     */
131    public static final int CMD_CHANNEL_DISCONNECT = -4;
132
133    /**
134     * Command sent when the channel becomes disconnected. This is sent when the
135     * channel is forcibly disconnected by the system or as a reply to CMD_CHANNEL_DISCONNECT.
136     *
137     * msg.arg1 == 0 : STATUS_SUCCESSFUL
138     *               : All other values signify failure and the channel state is indeterminate
139     * msg.obj  == the AsyncChannel
140     * msg.replyTo = messenger disconnecting or null if it was never connected.
141     */
142    public static final int CMD_CHANNEL_DISCONNECTED = -5;
143
144    /** Successful status always 0, !0 is an unsuccessful status */
145    public static final int STATUS_SUCCESSFUL = 0;
146
147    /** Error attempting to bind on a connect */
148    public static final int STATUS_BINDING_UNSUCCESSFUL = 1;
149
150    /** Service connection */
151    private AsyncChannelConnection mConnection;
152
153    /** Context for source */
154    private Context mSrcContext;
155
156    /** Handler for source */
157    private Handler mSrcHandler;
158
159    /** Messenger for source */
160    private Messenger mSrcMessenger;
161
162    /** Messenger for destination */
163    private Messenger mDstMessenger;
164
165    /**
166     * AsyncChannel constructor
167     */
168    public AsyncChannel() {
169    }
170
171    /**
172     * Connect handler to named package/class.
173     *
174     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
175     *
176     * @param srcContext is the context of the source
177     * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
178     *            messages
179     * @param dstPackageName is the destination package name
180     * @param dstClassName is the fully qualified class name (i.e. contains
181     *            package name)
182     */
183    private void connectSrcHandlerToPackage(
184            Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {
185        if (DBG) log("connect srcHandler to dst Package & class E");
186
187        mConnection = new AsyncChannelConnection();
188
189        /* Initialize the source information */
190        mSrcContext = srcContext;
191        mSrcHandler = srcHandler;
192        mSrcMessenger = new Messenger(srcHandler);
193
194        /*
195         * Initialize destination information to null they will
196         * be initialized when the AsyncChannelConnection#onServiceConnected
197         * is called
198         */
199        mDstMessenger = null;
200
201        /* Send intent to create the connection */
202        Intent intent = new Intent(Intent.ACTION_MAIN);
203        intent.setClassName(dstPackageName, dstClassName);
204        boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
205        if (!result) {
206            replyHalfConnected(STATUS_BINDING_UNSUCCESSFUL);
207        }
208
209        if (DBG) log("connect srcHandler to dst Package & class X result=" + result);
210    }
211
212    /**
213     * Connect handler to named package/class.
214     *
215     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
216     *      msg.arg1 = status
217     *      msg.obj = the AsyncChannel
218     *
219     * @param srcContext is the context of the source
220     * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
221     *            messages
222     * @param dstPackageName is the destination package name
223     * @param dstClassName is the fully qualified class name (i.e. contains
224     *            package name)
225     */
226    public void connect(Context srcContext, Handler srcHandler, String dstPackageName,
227            String dstClassName) {
228        if (DBG) log("connect srcHandler to dst Package & class E");
229
230        final class ConnectAsync implements Runnable {
231            Context mSrcCtx;
232            Handler mSrcHdlr;
233            String mDstPackageName;
234            String mDstClassName;
235
236            ConnectAsync(Context srcContext, Handler srcHandler, String dstPackageName,
237                    String dstClassName) {
238                mSrcCtx = srcContext;
239                mSrcHdlr = srcHandler;
240                mDstPackageName = dstPackageName;
241                mDstClassName = dstClassName;
242            }
243
244            public void run() {
245                connectSrcHandlerToPackage(mSrcCtx, mSrcHdlr, mDstPackageName, mDstClassName);
246            }
247        }
248
249        ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);
250        new Thread(ca).start();
251
252        if (DBG) log("connect srcHandler to dst Package & class X");
253    }
254
255    /**
256     * Connect handler to a class
257     *
258     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
259     *      msg.arg1 = status
260     *      msg.obj = the AsyncChannel
261     *
262     * @param srcContext
263     * @param srcHandler
264     * @param klass is the class to send messages to.
265     */
266    public void connect(Context srcContext, Handler srcHandler, Class<?> klass) {
267        connect(srcContext, srcHandler, klass.getPackage().getName(), klass.getName());
268    }
269
270    /**
271     * Connect handler and messenger.
272     *
273     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
274     *      msg.arg1 = status
275     *      msg.obj = the AsyncChannel
276     *
277     * @param srcContext
278     * @param srcHandler
279     * @param dstMessenger
280     */
281    public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
282        if (DBG) log("connect srcHandler to the dstMessenger  E");
283
284        // Initialize source fields
285        mSrcContext = srcContext;
286        mSrcHandler = srcHandler;
287        mSrcMessenger = new Messenger(mSrcHandler);
288
289        // Initialize destination fields
290        mDstMessenger = dstMessenger;
291
292        if (DBG) log("tell source we are half connected");
293
294        // Tell source we are half connected
295        replyHalfConnected(STATUS_SUCCESSFUL);
296
297        if (DBG) log("connect srcHandler to the dstMessenger X");
298    }
299
300    /**
301     * Connect two local Handlers.
302     *
303     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
304     *      msg.arg1 = status
305     *      msg.obj = the AsyncChannel
306     *
307     * @param srcContext is the context of the source
308     * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
309     *            messages
310     * @param dstHandler is the hander to send messages to.
311     */
312    public void connect(Context srcContext, Handler srcHandler, Handler dstHandler) {
313        connect(srcContext, srcHandler, new Messenger(dstHandler));
314    }
315
316    /**
317     * Connect service and messenger.
318     *
319     * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcAsyncService when complete.
320     *      msg.arg1 = status
321     *      msg.obj = the AsyncChannel
322     *
323     * @param srcAsyncService
324     * @param dstMessenger
325     */
326    public void connect(AsyncService srcAsyncService, Messenger dstMessenger) {
327        connect(srcAsyncService, srcAsyncService.getHandler(), dstMessenger);
328    }
329
330    /**
331     * To close the connection call when handler receives CMD_CHANNEL_DISCONNECTED
332     */
333    public void disconnected() {
334        mSrcHandler = null;
335        mSrcMessenger = null;
336        mDstMessenger = null;
337        mConnection = null;
338    }
339
340    /**
341     * Disconnect
342     */
343    public void disconnect() {
344        if (mConnection != null) {
345            mSrcContext.unbindService(mConnection);
346        }
347        if (mSrcHandler != null) {
348            Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
349            msg.arg1 = STATUS_SUCCESSFUL;
350            msg.obj = this;
351            msg.replyTo = mDstMessenger;
352            mSrcHandler.sendMessage(msg);
353        }
354    }
355
356    /**
357     * Send a message to the destination handler.
358     *
359     * @param msg
360     */
361    public void sendMessage(Message msg) {
362        msg.replyTo = mSrcMessenger;
363        try {
364            mDstMessenger.send(msg);
365        } catch (RemoteException e) {
366            log("TODO: handle sendMessage RemoteException" + e);
367        }
368    }
369
370    /**
371     * Send a message to the destination handler
372     *
373     * @param what
374     */
375    public void sendMessage(int what) {
376        Message msg = Message.obtain();
377        msg.what = what;
378        sendMessage(msg);
379    }
380
381    /**
382     * Send a message to the destination handler
383     *
384     * @param what
385     * @param arg1
386     */
387    public void sendMessage(int what, int arg1) {
388        Message msg = Message.obtain();
389        msg.what = what;
390        msg.arg1 = arg1;
391        sendMessage(msg);
392    }
393
394    /**
395     * Send a message to the destination handler
396     *
397     * @param what
398     * @param arg1
399     * @param arg2
400     */
401    public void sendMessage(int what, int arg1, int arg2) {
402        Message msg = Message.obtain();
403        msg.what = what;
404        msg.arg1 = arg1;
405        msg.arg2 = arg2;
406        sendMessage(msg);
407    }
408
409    /**
410     * Send a message to the destination handler
411     *
412     * @param what
413     * @param arg1
414     * @param arg2
415     * @param obj
416     */
417    public void sendMessage(int what, int arg1, int arg2, Object obj) {
418        Message msg = Message.obtain();
419        msg.what = what;
420        msg.arg1 = arg1;
421        msg.arg2 = arg2;
422        msg.obj = obj;
423        sendMessage(msg);
424    }
425
426    /**
427     * Send a message to the destination handler
428     *
429     * @param what
430     * @param obj
431     */
432    public void sendMessage(int what, Object obj) {
433        Message msg = Message.obtain();
434        msg.what = what;
435        msg.obj = obj;
436        sendMessage(msg);
437    }
438
439    /**
440     * Reply to srcMsg sending dstMsg
441     *
442     * @param srcMsg
443     * @param dstMsg
444     */
445    public void replyToMessage(Message srcMsg, Message dstMsg) {
446        try {
447            srcMsg.replyTo.send(dstMsg);
448        } catch (RemoteException e) {
449            log("TODO: handle replyToMessage RemoteException" + e);
450            e.printStackTrace();
451        }
452    }
453
454    /**
455     * Reply to srcMsg
456     *
457     * @param srcMsg
458     * @param what
459     */
460    public void replyToMessage(Message srcMsg, int what) {
461        Message msg = Message.obtain();
462        msg.what = what;
463        replyToMessage(srcMsg, msg);
464    }
465
466    /**
467     * Reply to srcMsg
468     *
469     * @param srcMsg
470     * @param what
471     * @param arg1
472     */
473    public void replyToMessage(Message srcMsg, int what, int arg1) {
474        Message msg = Message.obtain();
475        msg.what = what;
476        msg.arg1 = arg1;
477        replyToMessage(srcMsg, msg);
478    }
479
480    /**
481     * Reply to srcMsg
482     *
483     * @param srcMsg
484     * @param what
485     * @param arg1
486     * @param arg2
487     */
488    public void replyToMessage(Message srcMsg, int what, int arg1, int arg2) {
489        Message msg = Message.obtain();
490        msg.what = what;
491        msg.arg1 = arg1;
492        msg.arg2 = arg2;
493        replyToMessage(srcMsg, msg);
494    }
495
496    /**
497     * Reply to srcMsg
498     *
499     * @param srcMsg
500     * @param what
501     * @param arg1
502     * @param arg2
503     * @param obj
504     */
505    public void replyToMessage(Message srcMsg, int what, int arg1, int arg2, Object obj) {
506        Message msg = Message.obtain();
507        msg.what = what;
508        msg.arg1 = arg1;
509        msg.arg2 = arg2;
510        msg.obj = obj;
511        replyToMessage(srcMsg, msg);
512    }
513
514    /**
515     * Reply to srcMsg
516     *
517     * @param srcMsg
518     * @param what
519     * @param obj
520     */
521    public void replyToMessage(Message srcMsg, int what, Object obj) {
522        Message msg = Message.obtain();
523        msg.what = what;
524        msg.obj = obj;
525        replyToMessage(srcMsg, msg);
526    }
527
528    /**
529     * Send the Message synchronously.
530     *
531     * @param msg to send
532     * @return reply message or null if an error.
533     */
534    public Message sendMessageSynchronously(Message msg) {
535        Message resultMsg = SyncMessenger.sendMessageSynchronously(mDstMessenger, msg);
536        return resultMsg;
537    }
538
539    /**
540     * Send the Message synchronously.
541     *
542     * @param what
543     * @return reply message or null if an error.
544     */
545    public Message sendMessageSynchronously(int what) {
546        Message msg = Message.obtain();
547        msg.what = what;
548        Message resultMsg = sendMessageSynchronously(msg);
549        return resultMsg;
550    }
551
552    /**
553     * Send the Message synchronously.
554     *
555     * @param what
556     * @param arg1
557     * @return reply message or null if an error.
558     */
559    public Message sendMessageSynchronously(int what, int arg1) {
560        Message msg = Message.obtain();
561        msg.what = what;
562        msg.arg1 = arg1;
563        Message resultMsg = sendMessageSynchronously(msg);
564        return resultMsg;
565    }
566
567    /**
568     * Send the Message synchronously.
569     *
570     * @param what
571     * @param arg1
572     * @param arg2
573     * @return reply message or null if an error.
574     */
575    public Message sendMessageSynchronously(int what, int arg1, int arg2) {
576        Message msg = Message.obtain();
577        msg.what = what;
578        msg.arg1 = arg1;
579        msg.arg2 = arg2;
580        Message resultMsg = sendMessageSynchronously(msg);
581        return resultMsg;
582    }
583
584    /**
585     * Send the Message synchronously.
586     *
587     * @param what
588     * @param arg1
589     * @param arg2
590     * @param obj
591     * @return reply message or null if an error.
592     */
593    public Message sendMessageSynchronously(int what, int arg1, int arg2, Object obj) {
594        Message msg = Message.obtain();
595        msg.what = what;
596        msg.arg1 = arg1;
597        msg.arg2 = arg2;
598        msg.obj = obj;
599        Message resultMsg = sendMessageSynchronously(msg);
600        return resultMsg;
601    }
602
603    /**
604     * Send the Message synchronously.
605     *
606     * @param what
607     * @param obj
608     * @return reply message or null if an error.
609     */
610    public Message sendMessageSynchronously(int what, Object obj) {
611        Message msg = Message.obtain();
612        msg.what = what;
613        msg.obj = obj;
614        Message resultMsg = sendMessageSynchronously(msg);
615        return resultMsg;
616    }
617
618    /**
619     * Helper class to send messages synchronously
620     */
621    private static class SyncMessenger {
622        /** A stack of SyncMessengers */
623        private static Stack<SyncMessenger> sStack = new Stack<SyncMessenger>();
624        /** A number of SyncMessengers created */
625        private static int sCount = 0;
626        /** The handler thread */
627        private HandlerThread mHandlerThread;
628        /** The handler that will receive the result */
629        private SyncHandler mHandler;
630        /** The messenger used to send the message */
631        private Messenger mMessenger;
632
633        /** private constructor */
634        private SyncMessenger() {
635        }
636
637        /** Synchronous Handler class */
638        private class SyncHandler extends Handler {
639            /** The object used to wait/notify */
640            private Object mLockObject = new Object();
641            /** The resulting message */
642            private Message mResultMsg;
643
644            /** Constructor */
645            private SyncHandler(Looper looper) {
646                super(looper);
647            }
648
649            /** Handle of the reply message */
650            @Override
651            public void handleMessage(Message msg) {
652                mResultMsg = Message.obtain();
653                mResultMsg.copyFrom(msg);
654                synchronized(mLockObject) {
655                    mLockObject.notify();
656                }
657            }
658        }
659
660        /**
661         * @return the SyncMessenger
662         */
663        private static SyncMessenger obtain() {
664            SyncMessenger sm;
665            synchronized (sStack) {
666                if (sStack.isEmpty()) {
667                    sm = new SyncMessenger();
668                    sm.mHandlerThread = new HandlerThread("SyncHandler-" + sCount++);
669                    sm.mHandlerThread.start();
670                    sm.mHandler = sm.new SyncHandler(sm.mHandlerThread.getLooper());
671                    sm.mMessenger = new Messenger(sm.mHandler);
672                } else {
673                    sm = sStack.pop();
674                }
675            }
676            return sm;
677        }
678
679        /**
680         * Recycle this object
681         */
682        private void recycle() {
683            synchronized (sStack) {
684                sStack.push(this);
685            }
686        }
687
688        /**
689         * Send a message synchronously.
690         *
691         * @param msg to send
692         * @return result message or null if an error occurs
693         */
694        private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
695            SyncMessenger sm = SyncMessenger.obtain();
696            try {
697                msg.replyTo = sm.mMessenger;
698                dstMessenger.send(msg);
699                synchronized (sm.mHandler.mLockObject) {
700                    sm.mHandler.mLockObject.wait();
701                }
702            } catch (InterruptedException e) {
703                sm.mHandler.mResultMsg = null;
704            } catch (RemoteException e) {
705                sm.mHandler.mResultMsg = null;
706            }
707            Message resultMsg = sm.mHandler.mResultMsg;
708            sm.recycle();
709            return resultMsg;
710        }
711    }
712
713    /**
714     * Reply to the src handler that we're half connected.
715     *
716     * @param status to be stored in msg.arg1
717     */
718    private void replyHalfConnected(int status) {
719        Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
720        msg.arg1 = status;
721        msg.obj = this;
722        msg.replyTo = mDstMessenger;
723        mSrcHandler.sendMessage(msg);
724    }
725
726    /**
727     * ServiceConnection to receive call backs.
728     */
729    class AsyncChannelConnection implements ServiceConnection {
730        AsyncChannelConnection() {
731        }
732
733        public void onServiceConnected(ComponentName className, IBinder service) {
734            mDstMessenger = new Messenger(service);
735            replyHalfConnected(STATUS_SUCCESSFUL);
736        }
737
738        public void onServiceDisconnected(ComponentName className) {
739            Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
740            msg.arg1 = STATUS_SUCCESSFUL;
741            msg.obj = AsyncChannel.this;
742            msg.replyTo = mDstMessenger;
743            mSrcHandler.sendMessage(msg);
744        }
745    }
746
747    /**
748     * Log the string.
749     *
750     * @param s
751     */
752    private static void log(String s) {
753        Slog.d(TAG, s);
754    }
755}
756