1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.email.service; 18 19 import com.android.email.mail.MessagingException; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.os.Bundle; 26 import android.os.Debug; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 /** 32 * The EmailServiceProxy class provides a simple interface for the UI to call into the various 33 * EmailService classes (e.g. SyncManager for EAS). It wraps the service connect/disconnect 34 * process so that the caller need not be concerned with it. 35 * 36 * Use the class like this: 37 * new EmailServiceClass(context, class).loadAttachment(attachmentId, callback) 38 * 39 * Methods without a return value return immediately (i.e. are asynchronous); methods with a 40 * return value wait for a result from the Service (i.e. they should not be called from the UI 41 * thread) with a default timeout of 30 seconds (settable) 42 * 43 * An EmailServiceProxy object cannot be reused (trying to do so generates a RemoteException) 44 */ 45 46 public class EmailServiceProxy implements IEmailService { 47 private static final boolean DEBUG_PROXY = false; // DO NOT CHECK THIS IN SET TO TRUE 48 private static final String TAG = "EmailServiceProxy"; 49 50 public static final String AUTO_DISCOVER_BUNDLE_ERROR_CODE = "autodiscover_error_code"; 51 public static final String AUTO_DISCOVER_BUNDLE_HOST_AUTH = "autodiscover_host_auth"; 52 53 private final Context mContext; 54 private final Class<?> mClass; 55 private final IEmailServiceCallback mCallback; 56 private Runnable mRunnable; 57 private final ServiceConnection mSyncManagerConnection = new EmailServiceConnection (); 58 private IEmailService mService = null; 59 private Object mReturn = null; 60 // Service call timeout (in seconds) 61 private int mTimeout = 45; 62 private boolean mDead = false; 63 64 public EmailServiceProxy(Context _context, Class<?> _class) { 65 this(_context, _class, null); 66 } 67 68 public EmailServiceProxy(Context _context, Class<?> _class, IEmailServiceCallback _callback) { 69 mContext = _context; 70 mClass = _class; 71 mCallback = _callback; 72 // Proxy calls have a timeout, and this can cause failures while debugging due to the 73 // far slower execution speed. In particular, validate calls fail regularly with ssl 74 // connections at the default timeout (30 seconds) 75 if (Debug.isDebuggerConnected()) { 76 mTimeout <<= 2; 77 } 78 } 79 80 class EmailServiceConnection implements ServiceConnection { 81 public void onServiceConnected(ComponentName name, IBinder binder) { 82 mService = IEmailService.Stub.asInterface(binder); 83 if (DEBUG_PROXY) { 84 Log.v(TAG, "Service " + mClass.getSimpleName() + " connected"); 85 } 86 // Run our task on a new thread 87 new Thread(new Runnable() { 88 public void run() { 89 runTask(); 90 }}).start(); 91 } 92 93 public void onServiceDisconnected(ComponentName name) { 94 if (DEBUG_PROXY) { 95 Log.v(TAG, "Service " + mClass.getSimpleName() + " disconnected"); 96 } 97 } 98 } 99 100 public EmailServiceProxy setTimeout(int secs) { 101 mTimeout = secs; 102 return this; 103 } 104 105 private void runTask() { 106 Thread thread = new Thread(mRunnable); 107 thread.start(); 108 try { 109 thread.join(); 110 } catch (InterruptedException e) { 111 } 112 113 try { 114 mContext.unbindService(mSyncManagerConnection); 115 } catch (IllegalArgumentException e) { 116 // This can happen if the user ended the activity that was using the service 117 // This is harmless, but we've got to catch it 118 } 119 120 mDead = true; 121 synchronized(mSyncManagerConnection) { 122 if (DEBUG_PROXY) { 123 Log.v(TAG, "Service task completed; disconnecting"); 124 } 125 mSyncManagerConnection.notify(); 126 } 127 } 128 129 private void setTask(Runnable runnable) throws RemoteException { 130 if (mDead) { 131 throw new RemoteException(); 132 } 133 mRunnable = runnable; 134 if (DEBUG_PROXY) { 135 Log.v(TAG, "Service " + mClass.getSimpleName() + " bind requested"); 136 } 137 mContext.bindService(new Intent(mContext, mClass), mSyncManagerConnection, 138 Context.BIND_AUTO_CREATE); 139 } 140 141 public void waitForCompletion() { 142 synchronized (mSyncManagerConnection) { 143 long time = System.currentTimeMillis(); 144 try { 145 if (DEBUG_PROXY) { 146 Log.v(TAG, "Waiting for task to complete..."); 147 } 148 mSyncManagerConnection.wait(mTimeout * 1000L); 149 } catch (InterruptedException e) { 150 // Can be ignored safely 151 } 152 if (DEBUG_PROXY) { 153 Log.v(TAG, "Wait finished in " + (System.currentTimeMillis() - time) + "ms"); 154 } 155 } 156 } 157 158 public void loadAttachment(final long attachmentId, final String destinationFile, 159 final String contentUriString) throws RemoteException { 160 setTask(new Runnable () { 161 public void run() { 162 try { 163 if (mCallback != null) mService.setCallback(mCallback); 164 mService.loadAttachment(attachmentId, destinationFile, contentUriString); 165 } catch (RemoteException e) { 166 } 167 } 168 }); 169 } 170 171 public void startSync(final long mailboxId) throws RemoteException { 172 setTask(new Runnable () { 173 public void run() { 174 try { 175 if (mCallback != null) mService.setCallback(mCallback); 176 mService.startSync(mailboxId); 177 } catch (RemoteException e) { 178 } 179 } 180 }); 181 } 182 183 public void stopSync(final long mailboxId) throws RemoteException { 184 setTask(new Runnable () { 185 public void run() { 186 try { 187 if (mCallback != null) mService.setCallback(mCallback); 188 mService.stopSync(mailboxId); 189 } catch (RemoteException e) { 190 } 191 } 192 }); 193 } 194 195 public int validate(final String protocol, final String host, final String userName, 196 final String password, final int port, final boolean ssl, 197 final boolean trustCertificates) throws RemoteException { 198 setTask(new Runnable () { 199 public void run() { 200 try { 201 if (mCallback != null) mService.setCallback(mCallback); 202 mReturn = mService.validate(protocol, host, userName, password, port, ssl, 203 trustCertificates); 204 } catch (RemoteException e) { 205 } 206 } 207 }); 208 waitForCompletion(); 209 if (mReturn == null) { 210 return MessagingException.UNSPECIFIED_EXCEPTION; 211 } else { 212 Log.v(TAG, "validate returns " + mReturn); 213 return (Integer)mReturn; 214 } 215 } 216 217 public Bundle autoDiscover(final String userName, final String password) 218 throws RemoteException { 219 setTask(new Runnable () { 220 public void run() { 221 try { 222 if (mCallback != null) mService.setCallback(mCallback); 223 mReturn = mService.autoDiscover(userName, password); 224 } catch (RemoteException e) { 225 } 226 } 227 }); 228 waitForCompletion(); 229 if (mReturn == null) { 230 return null; 231 } else { 232 Bundle bundle = (Bundle) mReturn; 233 Log.v(TAG, "autoDiscover returns " + bundle.getInt(AUTO_DISCOVER_BUNDLE_ERROR_CODE)); 234 return bundle; 235 } 236 } 237 238 public void updateFolderList(final long accountId) throws RemoteException { 239 setTask(new Runnable () { 240 public void run() { 241 try { 242 if (mCallback != null) mService.setCallback(mCallback); 243 mService.updateFolderList(accountId); 244 } catch (RemoteException e) { 245 } 246 } 247 }); 248 } 249 250 public void setLogging(final int on) throws RemoteException { 251 setTask(new Runnable () { 252 public void run() { 253 try { 254 if (mCallback != null) mService.setCallback(mCallback); 255 mService.setLogging(on); 256 } catch (RemoteException e) { 257 } 258 } 259 }); 260 } 261 262 public void setCallback(final IEmailServiceCallback cb) throws RemoteException { 263 setTask(new Runnable () { 264 public void run() { 265 try { 266 mService.setCallback(cb); 267 } catch (RemoteException e) { 268 } 269 } 270 }); 271 } 272 273 public void hostChanged(final long accountId) throws RemoteException { 274 setTask(new Runnable () { 275 public void run() { 276 try { 277 mService.hostChanged(accountId); 278 } catch (RemoteException e) { 279 } 280 } 281 }); 282 } 283 284 public void sendMeetingResponse(final long messageId, final int response) throws RemoteException { 285 setTask(new Runnable () { 286 public void run() { 287 try { 288 if (mCallback != null) mService.setCallback(mCallback); 289 mService.sendMeetingResponse(messageId, response); 290 } catch (RemoteException e) { 291 } 292 } 293 }); 294 } 295 296 public void loadMore(long messageId) throws RemoteException { 297 // TODO Auto-generated method stub 298 } 299 300 public boolean createFolder(long accountId, String name) throws RemoteException { 301 return false; 302 } 303 304 public boolean deleteFolder(long accountId, String name) throws RemoteException { 305 return false; 306 } 307 308 public boolean renameFolder(long accountId, String oldName, String newName) 309 throws RemoteException { 310 return false; 311 } 312 313 public IBinder asBinder() { 314 return null; 315 } 316 } 317