ServiceProxy.java revision c28bf353190eb576072a8fd2f98821424144876e
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
18package com.android.emailcommon.service;
19
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.os.Debug;
25import android.os.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
29/**
30 * The EmailServiceProxy class provides a simple interface for the UI to call into the various
31 * EmailService classes (e.g. ExchangeService for EAS).  It wraps the service connect/disconnect
32 * process so that the caller need not be concerned with it.
33 *
34 * Use the class like this:
35 *   new EmailServiceClass(context, class).loadAttachment(attachmentId, callback)
36 *
37 * Methods without a return value return immediately (i.e. are asynchronous); methods with a
38 * return value wait for a result from the Service (i.e. they should not be called from the UI
39 * thread) with a default timeout of 30 seconds (settable)
40 *
41 * An EmailServiceProxy object cannot be reused (trying to do so generates a RemoteException)
42 */
43
44public abstract class ServiceProxy {
45    private static final boolean DEBUG_PROXY = true; // STOPSHIP DO NOT CHECK THIS IN SET TO TRUE
46    private final String mTag;
47
48    private final Context mContext;
49    protected final Intent mIntent;
50    private Runnable mRunnable = new ProxyRunnable();
51    private ProxyTask mTask;
52    private String mName = " unnamed";
53    private final ServiceConnection mConnection = new ProxyConnection();
54    // Service call timeout (in seconds)
55    private int mTimeout = 45;
56    private long mStartTime;
57    private boolean mDead = false;
58
59    public abstract void onConnected(IBinder binder);
60
61    public ServiceProxy(Context _context, Intent _intent) {
62        mContext = _context;
63        mIntent = _intent;
64        mTag = getClass().getSimpleName();
65        if (Debug.isDebuggerConnected()) {
66            mTimeout <<= 2;
67        }
68    }
69
70    private class ProxyConnection implements ServiceConnection {
71        public void onServiceConnected(ComponentName name, IBinder binder) {
72            onConnected(binder);
73            if (DEBUG_PROXY) {
74                Log.v(mTag, "Connected: " + name.getShortClassName());
75            }
76            // Run our task on a new thread
77            new Thread(new Runnable() {
78                public void run() {
79                    runTask();
80                }}).start();
81        }
82
83        public void onServiceDisconnected(ComponentName name) {
84            if (DEBUG_PROXY) {
85                Log.v(mTag, "Disconnected: " + name.getShortClassName());
86            }
87        }
88    }
89
90    public interface ProxyTask {
91        public void run() throws RemoteException;
92    }
93
94    private class ProxyRunnable implements Runnable {
95        @Override
96        public void run() {
97            try {
98                mTask.run();
99            } catch (RemoteException e) {
100            }
101        }
102    }
103
104    public ServiceProxy setTimeout(int secs) {
105        mTimeout = secs;
106        return this;
107    }
108
109    public int getTimeout() {
110        return mTimeout;
111    }
112
113    public void endTask() {
114        try {
115            mContext.unbindService(mConnection);
116        } catch (IllegalArgumentException e) {
117            // This can happen if the user ended the activity that was using the service
118            // This is harmless, but we've got to catch it
119        }
120
121        mDead = true;
122        synchronized(mConnection) {
123            if (DEBUG_PROXY) {
124                Log.v(mTag, "Task " + mName + " completed; disconnecting");
125            }
126            mConnection.notify();
127        }
128    }
129
130    private void runTask() {
131        Thread thread = new Thread(mRunnable);
132        thread.start();
133        try {
134            thread.join();
135        } catch (InterruptedException e) {
136        }
137        endTask();
138    }
139
140    public boolean setTask(ProxyTask task, String name) {
141        mName = name;
142        return setTask(task);
143    }
144
145    public boolean setTask(ProxyTask task) throws IllegalStateException {
146        if (mDead) {
147            throw new IllegalStateException();
148        }
149        mTask = task;
150        mStartTime = System.currentTimeMillis();
151        if (DEBUG_PROXY) {
152            Log.v(mTag, "Bind requested for task " + mName);
153        }
154        return mContext.bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE);
155    }
156
157    public void waitForCompletion() {
158        synchronized (mConnection) {
159            long time = System.currentTimeMillis();
160            try {
161                if (DEBUG_PROXY) {
162                    Log.v(mTag, "Waiting for task " + mName + " to complete...");
163                }
164                mConnection.wait(mTimeout * 1000L);
165            } catch (InterruptedException e) {
166                // Can be ignored safely
167            }
168            if (DEBUG_PROXY) {
169                Log.v(mTag, "Wait for " + mName + " finished in " +
170                        (System.currentTimeMillis() - time) + "ms");
171            }
172        }
173    }
174
175    public void close() throws RemoteException {
176        if (mDead) {
177            throw new RemoteException();
178        }
179        endTask();
180    }
181
182    /**
183     * Connection test; return indicates whether the remote service can be connected to
184     * @return the result of trying to connect to the remote service
185     */
186    public boolean test() {
187        try {
188            return setTask(new ProxyTask() {
189                public void run() throws RemoteException {
190                    if (DEBUG_PROXY) {
191                        Log.v(mTag, "Connection test succeeded in " +
192                                (System.currentTimeMillis() - mStartTime) + "ms");
193                    }
194                }
195            }, "test");
196        } catch (Exception e) {
197            // For any failure, return false.
198            return false;
199        }
200    }
201}
202