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 = false; // 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                    try {
80                        runTask();
81                    } finally {
82                        endTask();
83                    }
84                }}).start();
85        }
86
87        public void onServiceDisconnected(ComponentName name) {
88            if (DEBUG_PROXY) {
89                Log.v(mTag, "Disconnected: " + name.getShortClassName());
90            }
91        }
92    }
93
94    public interface ProxyTask {
95        public void run() throws RemoteException;
96    }
97
98    private class ProxyRunnable implements Runnable {
99        @Override
100        public void run() {
101            try {
102                mTask.run();
103            } catch (RemoteException e) {
104            }
105        }
106    }
107
108    public ServiceProxy setTimeout(int secs) {
109        mTimeout = secs;
110        return this;
111    }
112
113    public int getTimeout() {
114        return mTimeout;
115    }
116
117    public void endTask() {
118        try {
119            mContext.unbindService(mConnection);
120        } catch (IllegalArgumentException e) {
121            // This can happen if the user ended the activity that was using the service
122            // This is harmless, but we've got to catch it
123        }
124
125        mDead = true;
126        synchronized(mConnection) {
127            if (DEBUG_PROXY) {
128                Log.v(mTag, "Task " + mName + " completed; disconnecting");
129            }
130            mConnection.notify();
131        }
132    }
133
134    private void runTask() {
135        Thread thread = new Thread(mRunnable);
136        thread.start();
137        try {
138            thread.join();
139        } catch (InterruptedException e) {
140        }
141    }
142
143    public boolean setTask(ProxyTask task, String name) {
144        mName = name;
145        return setTask(task);
146    }
147
148    public boolean setTask(ProxyTask task) throws IllegalStateException {
149        if (mDead) {
150            throw new IllegalStateException();
151        }
152        mTask = task;
153        mStartTime = System.currentTimeMillis();
154        if (DEBUG_PROXY) {
155            Log.v(mTag, "Bind requested for task " + mName);
156        }
157        return mContext.bindService(mIntent, mConnection, Context.BIND_AUTO_CREATE);
158    }
159
160    public void waitForCompletion() {
161        synchronized (mConnection) {
162            long time = System.currentTimeMillis();
163            try {
164                if (DEBUG_PROXY) {
165                    Log.v(mTag, "Waiting for task " + mName + " to complete...");
166                }
167                mConnection.wait(mTimeout * 1000L);
168            } catch (InterruptedException e) {
169                // Can be ignored safely
170            }
171            if (DEBUG_PROXY) {
172                Log.v(mTag, "Wait for " + mName + " finished in " +
173                        (System.currentTimeMillis() - time) + "ms");
174            }
175        }
176    }
177
178    public void close() throws RemoteException {
179        if (mDead) {
180            throw new RemoteException();
181        }
182        endTask();
183    }
184
185    /**
186     * Connection test; return indicates whether the remote service can be connected to
187     * @return the result of trying to connect to the remote service
188     */
189    public boolean test() {
190        try {
191            return setTask(new ProxyTask() {
192                public void run() throws RemoteException {
193                    if (DEBUG_PROXY) {
194                        Log.v(mTag, "Connection test succeeded in " +
195                                (System.currentTimeMillis() - mStartTime) + "ms");
196                    }
197                }
198            }, "test");
199        } catch (Exception e) {
200            // For any failure, return false.
201            return false;
202        }
203    }
204}
205