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.Debug; 25 import android.os.IBinder; 26 import android.os.RemoteException; 27 import 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 44 public 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 onConnected(IBinder binder)59 public abstract void onConnected(IBinder binder); 60 ServiceProxy(Context _context, Intent _intent)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 { onServiceConnected(ComponentName name, IBinder binder)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 onServiceDisconnected(ComponentName name)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 { run()95 public void run() throws RemoteException; 96 } 97 98 private class ProxyRunnable implements Runnable { 99 @Override run()100 public void run() { 101 try { 102 mTask.run(); 103 } catch (RemoteException e) { 104 } 105 } 106 } 107 setTimeout(int secs)108 public ServiceProxy setTimeout(int secs) { 109 mTimeout = secs; 110 return this; 111 } 112 getTimeout()113 public int getTimeout() { 114 return mTimeout; 115 } 116 endTask()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 runTask()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 setTask(ProxyTask task, String name)143 public boolean setTask(ProxyTask task, String name) { 144 mName = name; 145 return setTask(task); 146 } 147 setTask(ProxyTask task)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 waitForCompletion()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 close()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 */ test()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