Home | History | Annotate | Download | only in service
      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 
     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      */
     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      */
     87     public abstract void onConnected(IBinder binder);
     88 
     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
    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
    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 {
    147         public void run() throws RemoteException;
    148     }
    149 
    150     public ServiceProxy setTimeout(int secs) {
    151         mTimeout = secs;
    152         return this;
    153     }
    154 
    155     public int getTimeout() {
    156         return mTimeout;
    157     }
    158 
    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      */
    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      */
    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