• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  /*
3  * Copyright (C) 2011 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.emailcommon.service;
19 
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.os.AsyncTask;
25 import android.os.Debug;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.RemoteException;
29 
30 import com.android.mail.utils.LogUtils;
31 
32 /**
33  * ServiceProxy is a superclass for proxy objects which make a single call to a service. It handles
34  * connecting to the service, running a task supplied by the subclass when the connection is ready,
35  * and disconnecting from the service afterwards. ServiceProxy objects cannot be reused (trying to
36  * do so generates an {@link IllegalStateException}).
37  *
38  * Subclasses must override {@link #onConnected} to store the binder. Then, when the subclass wants
39  * to make a service call, it should call {@link #setTask}, supplying the {@link ProxyTask} that
40  * should run when the connection is ready. {@link ProxyTask#run} should implement the necessary
41  * logic to make the call on the service.
42  */
43 
44 public abstract class ServiceProxy {
45     public static final String EXTRA_FORCE_SHUTDOWN = "ServiceProxy.FORCE_SHUTDOWN";
46 
47     private static final boolean DEBUG_PROXY = false; // DO NOT CHECK THIS IN SET TO TRUE
48     private final String mTag;
49 
50     private final Context mContext;
51     protected final Intent mIntent;
52     private ProxyTask mTask;
53     private String mName = " unnamed";
54     private final ServiceConnection mConnection = new ProxyConnection();
55     // Service call timeout (in seconds)
56     private int mTimeout = 45;
57     private long mStartTime;
58     private boolean mTaskSet = false;
59     private boolean mTaskCompleted = false;
60 
getIntentForEmailPackage(Context context, String actionName)61     public static Intent getIntentForEmailPackage(Context context, String actionName) {
62         return new Intent(getIntentStringForEmailPackage(context, actionName));
63     }
64 
65     /**
66      * Create Intent action based on the Email package name
67      * Package com.android.email + ACTION -> com.android.email.ACTION
68      * Package com.google.android.email + ACTION -> com.google.android.email.ACTION
69      * Package com.android.exchange + ACTION -> com.android.email.ACTION
70      * Package com.google.exchange + ACTION -> com.google.android.email.ACTION
71      *
72      * @param context the caller's context
73      * @param actionName the Intent action
74      * @return an Intent action based on the package name
75      */
getIntentStringForEmailPackage(Context context, String actionName)76     public static String getIntentStringForEmailPackage(Context context, String actionName) {
77         String packageName = context.getPackageName();
78         int lastDot = packageName.lastIndexOf('.');
79         return packageName.substring(0, lastDot + 1) + "email." + actionName;
80     }
81 
82     /**
83      * This function is called after the proxy connects to the service but before it runs its task.
84      * Subclasses must override this to store the binder correctly.
85      * @param binder The service IBinder.
86      */
onConnected(IBinder binder)87     public abstract void onConnected(IBinder binder);
88 
ServiceProxy(Context _context, Intent _intent)89     public ServiceProxy(Context _context, Intent _intent) {
90         mContext = _context;
91         mIntent = _intent;
92         mTag = getClass().getSimpleName();
93         if (Debug.isDebuggerConnected()) {
94             mTimeout <<= 2;
95         }
96     }
97 
98     private class ProxyConnection implements ServiceConnection {
99         @Override
onServiceConnected(ComponentName name, IBinder binder)100         public void onServiceConnected(ComponentName name, IBinder binder) {
101             if (DEBUG_PROXY) {
102                 LogUtils.v(mTag, "Connected: " + name.getShortClassName() + " at " +
103                         (System.currentTimeMillis() - mStartTime) + "ms");
104             }
105 
106             // Let subclasses handle the binder.
107             onConnected(binder);
108 
109             // Do our work in another thread.
110             new AsyncTask<Void, Void, Void>() {
111                 @Override
112                 protected Void doInBackground(Void... params) {
113                     try {
114                         mTask.run();
115                     } catch (RemoteException e) {
116                     }
117                     try {
118                         // Each ServiceProxy handles just one task, so we unbind after we're
119                         // done with our work.
120                         mContext.unbindService(mConnection);
121                     } catch (IllegalArgumentException e) {
122                         // This can happen if the user ended the activity that was using the
123                         // service. This is harmless, but we've got to catch it.
124                     }
125                     mTaskCompleted = true;
126                     synchronized(mConnection) {
127                         if (DEBUG_PROXY) {
128                             LogUtils.v(mTag, "Task " + mName + " completed; disconnecting");
129                         }
130                         mConnection.notify();
131                     }
132                     return null;
133                 }
134             }.execute();
135         }
136 
137         @Override
onServiceDisconnected(ComponentName name)138         public void onServiceDisconnected(ComponentName name) {
139             if (DEBUG_PROXY) {
140                 LogUtils.v(mTag, "Disconnected: " + name.getShortClassName() + " at " +
141                         (System.currentTimeMillis() - mStartTime) + "ms");
142             }
143         }
144     }
145 
146     protected interface ProxyTask {
run()147         public void run() throws RemoteException;
148     }
149 
setTimeout(int secs)150     public ServiceProxy setTimeout(int secs) {
151         mTimeout = secs;
152         return this;
153     }
154 
getTimeout()155     public int getTimeout() {
156         return mTimeout;
157     }
158 
setTask(ProxyTask task, String name)159     protected boolean setTask(ProxyTask task, String name) throws IllegalStateException {
160         if (mTaskSet) {
161             throw new IllegalStateException("Cannot call setTask twice on the same ServiceProxy.");
162         }
163         mTaskSet = true;
164         mName = name;
165         mTask = task;
166         mStartTime = System.currentTimeMillis();
167         if (DEBUG_PROXY) {
168             LogUtils.v(mTag, "Bind requested for task " + mName);
169         }
170         return mContext.bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE);
171     }
172 
173     /**
174      * Callers that want to wait on the {@link ProxyTask} should call this immediately after calling
175      * {@link #setTask}. This will wait until the task completes, up to the timeout (which can be
176      * set with {@link #setTimeout}).
177      */
waitForCompletion()178     protected void waitForCompletion() {
179         /*
180          * onServiceConnected() is always called on the main thread, and we block the current thread
181          * for up to 10 seconds as a timeout. If we're currently on the main thread,
182          * onServiceConnected() is not called until our timeout elapses (and the UI is frozen for
183          * the duration).
184          */
185         if (Looper.myLooper() == Looper.getMainLooper()) {
186             throw new IllegalStateException("This cannot be called on the main thread.");
187         }
188 
189         synchronized (mConnection) {
190             long time = System.currentTimeMillis();
191             try {
192                 if (DEBUG_PROXY) {
193                     LogUtils.v(mTag, "Waiting for task " + mName + " to complete...");
194                 }
195                 mConnection.wait(mTimeout * 1000L);
196             } catch (InterruptedException e) {
197                 // Can be ignored safely
198             }
199             if (DEBUG_PROXY) {
200                 LogUtils.v(mTag, "Wait for " + mName +
201                         (mTaskCompleted ? " finished in " : " timed out in ") +
202                         (System.currentTimeMillis() - time) + "ms");
203             }
204         }
205     }
206 
207     /**
208      * Connection test; return indicates whether the remote service can be connected to
209      * @return the result of trying to connect to the remote service
210      */
test()211     public boolean test() {
212         try {
213             return setTask(new ProxyTask() {
214                 @Override
215                 public void run() throws RemoteException {
216                     if (DEBUG_PROXY) {
217                         LogUtils.v(mTag, "Connection test succeeded in " +
218                                 (System.currentTimeMillis() - mStartTime) + "ms");
219                     }
220                 }
221             }, "test");
222         } catch (Exception e) {
223             // For any failure, return false.
224             return false;
225         }
226     }
227 }
228