Flesh out EasService some, and start using it.
- Fix some of the TODOs.
- Add it to the manifest.
- Route some operations from EmailSyncAdapterService to it
during testing/development.
Note that this last bit is a bit Frankenstein right now, so
it's guarded by a constant (which should stay false when checked
in) and in no way will an account function "normally" during the
transition.
Change-Id: I52e5500c7eac1999963712f3bd96d114f9afc3ed
(cherry picked from commit c3ad5a3c532b60f73ff7337f0962211a6f14b919)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0f9392c..73430b9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -79,6 +79,17 @@
<receiver
android:name="com.android.emailsync.MailboxAlarmReceiver"/>
+ <service
+ android:name="com.android.exchange.service.EasService"
+ android:exported="true">
+<!-- TODO: Switch this from EmailSyncAdapterService
+ <intent-filter>
+ <action
+ android:name="com.android.email.EXCHANGE_INTENT" />
+ </intent-filter>
+-->
+ </service>
+
<!--Required stanza to register the EAS EmailSyncAdapterService with SyncManager -->
<service
android:name="com.android.exchange.service.EmailSyncAdapterService"
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index bb1f4da..fc97507 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -21,10 +21,12 @@
import android.content.Intent;
import android.content.SyncResult;
import android.database.Cursor;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
+import android.text.TextUtils;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
@@ -33,6 +35,7 @@
import com.android.emailcommon.service.IEmailService;
import com.android.emailcommon.service.IEmailServiceCallback;
import com.android.emailcommon.service.SearchParams;
+import com.android.emailcommon.service.ServiceProxy;
import com.android.exchange.Eas;
import com.android.exchange.eas.EasFolderSync;
import com.android.exchange.eas.EasOperation;
@@ -57,8 +60,14 @@
*/
private static String[] AUTHORITIES_TO_SYNC;
+ /** Bookkeeping for ping tasks & sync threads management. */
private final PingSyncSynchronizer mSynchronizer;
+ /**
+ * Implementation of the IEmailService interface.
+ * For the most part these calls should consist of creating the correct {@link EasOperation}
+ * class and calling {@link #doOperation} with it.
+ */
private final IEmailService.Stub mBinder = new IEmailService.Stub() {
@Override
public void sendMail(final long accountId) {}
@@ -111,6 +120,49 @@
}
};
+ /**
+ * Content selection string for getting all accounts that are configured for push.
+ * TODO: Add protocol check so that we don't get e.g. IMAP accounts here.
+ * (Not currently necessary but eventually will be.)
+ */
+ private static final String PUSH_ACCOUNTS_SELECTION =
+ EmailContent.AccountColumns.SYNC_INTERVAL +
+ "=" + Integer.toString(Account.CHECK_INTERVAL_PUSH);
+
+ /** {@link AsyncTask} to restart pings for all accounts that need it. */
+ private class RestartPingsTask extends AsyncTask<Void, Void, Void> {
+ private boolean mHasRestartedPing = false;
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ final Cursor c = EasService.this.getContentResolver().query(Account.CONTENT_URI,
+ Account.CONTENT_PROJECTION, PUSH_ACCOUNTS_SELECTION, null, null);
+ if (c != null) {
+ try {
+ while (c.moveToNext()) {
+ final Account account = new Account();
+ account.restore(c);
+ if (EasService.this.pingNeededForAccount(account)) {
+ mHasRestartedPing = true;
+ EasService.this.mSynchronizer.pushModify(account.mId);
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (!mHasRestartedPing) {
+ LogUtils.d(TAG, "RestartPingsTask did not start any pings.");
+ EasService.this.mSynchronizer.stopServiceIfIdle();
+ }
+ }
+ }
+
public EasService() {
super();
mSynchronizer = new PingSyncSynchronizer(this);
@@ -125,12 +177,17 @@
CalendarContract.AUTHORITY,
ContactsContract.AUTHORITY
};
- // TODO: Restart all pings that are needed.
+
+ // Restart push for all accounts that need it. Because this requires DB loads, we do it in
+ // an AsyncTask, and we startService to ensure that we stick around long enough for the
+ // task to complete. The task will stop the service if necessary after it's done.
+ startService(new Intent(this, EasService.class));
+ new RestartPingsTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onDestroy() {
- // TODO: Stop all running pings.
+ mSynchronizer.stopAllPings();
}
@Override
@@ -138,6 +195,23 @@
return mBinder;
}
+ @Override
+ public int onStartCommand(final Intent intent, final int flags, final int startId) {
+ if (intent != null &&
+ TextUtils.equals(Eas.EXCHANGE_SERVICE_INTENT_ACTION, intent.getAction())) {
+ if (intent.getBooleanExtra(ServiceProxy.EXTRA_FORCE_SHUTDOWN, false)) {
+ // We've been asked to forcibly shutdown. This happens if email accounts are
+ // deleted, otherwise we can get errors if services are still running for
+ // accounts that are now gone.
+ // TODO: This is kind of a hack, it would be nicer if we could handle it correctly
+ // if accounts disappear out from under us.
+ LogUtils.d(TAG, "Forced shutdown, killing process");
+ System.exit(-1);
+ }
+ }
+ return START_STICKY;
+ }
+
public int doOperation(final EasOperation operation, final SyncResult syncResult,
final String loggingName) {
final long accountId = operation.getAccountId();
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 834d84b..96eb1a6 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -22,17 +22,20 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.AbstractThreadedSyncAdapter;
+import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SyncResult;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.CalendarContract;
import android.provider.ContactsContract;
@@ -80,6 +83,16 @@
private static final String TAG = Eas.LOG_TAG;
+ /**
+ * Temporary while converting to EasService. Do not check in set to true.
+ * When true, delegates various operations to {@link EasService}, for use while developing the
+ * new service.
+ * The two following fields are used to support what happens when this is true.
+ */
+ private static final boolean DELEGATE_TO_EAS_SERVICE = false;
+ private IEmailService mEasService;
+ private ServiceConnection mConnection;
+
private static final String EXTRA_START_PING = "START_PING";
private static final String EXTRA_PING_ACCOUNT = "PING_ACCOUNT";
private static final long SYNC_ERROR_BACKOFF_MILLIS = 5 * DateUtils.MINUTE_IN_MILLIS;
@@ -362,6 +375,13 @@
@Override
public Bundle validate(final HostAuth hostAuth) {
LogUtils.d(TAG, "IEmailService.validate");
+ if (mEasService != null) {
+ try {
+ return mEasService.validate(hostAuth);
+ } catch (final RemoteException re) {
+ LogUtils.e(TAG, re, "While asking EasService to handle validate");
+ }
+ }
return new EasFolderSync(EmailSyncAdapterService.this, hostAuth).doValidate();
}
@@ -375,6 +395,14 @@
@Override
public void updateFolderList(final long accountId) {
LogUtils.d(TAG, "IEmailService.updateFolderList: %d", accountId);
+ if (mEasService != null) {
+ try {
+ mEasService.updateFolderList(accountId);
+ return;
+ } catch (final RemoteException re) {
+ LogUtils.e(TAG, re, "While asking EasService to updateFolderList");
+ }
+ }
final String emailAddress = getEmailAddressForAccount(accountId);
if (emailAddress != null) {
final Bundle extras = new Bundle(1);
@@ -498,6 +526,21 @@
// Restart push for all accounts that need it.
new RestartPingsTask(getContentResolver(), mSyncHandlerMap).executeOnExecutor(
AsyncTask.THREAD_POOL_EXECUTOR);
+ if (DELEGATE_TO_EAS_SERVICE) {
+ // TODO: This block is temporary to support the transition to EasService.
+ mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ mEasService = IEmailService.Stub.asInterface(binder);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mEasService = null;
+ }
+ };
+ bindService(new Intent(this, EasService.class), mConnection, Context.BIND_AUTO_CREATE);
+ }
}
@Override
@@ -509,6 +552,10 @@
task.stop();
}
}
+ if (DELEGATE_TO_EAS_SERVICE) {
+ // TODO: This block is temporary to support the transition to EasService.
+ unbindService(mConnection);
+ }
}
@Override
diff --git a/src/com/android/exchange/service/PingSyncSynchronizer.java b/src/com/android/exchange/service/PingSyncSynchronizer.java
index 54c34a9..7871f0f 100644
--- a/src/com/android/exchange/service/PingSyncSynchronizer.java
+++ b/src/com/android/exchange/service/PingSyncSynchronizer.java
@@ -177,7 +177,7 @@
// No ping, no running syncs -- start a new ping.
// TODO: Fix this.
//mPingTask = new PingTask();
- mPingTask.start();
+ //mPingTask.start();
} else {
// Ping is already running, so tell it to restart to pick up any new params.
mPingTask.restart();
@@ -327,4 +327,34 @@
mLock.unlock();
}
}
+
+ /**
+ * Stops our service if our map contains no active accounts.
+ */
+ public void stopServiceIfIdle() {
+ mLock.lock();
+ try {
+ LogUtils.d(TAG, "PSS stopIfIdle");
+ if (mAccountStateMap.size() == 0) {
+ LogUtils.i(TAG, "PSS has no active accounts; stopping service.");
+ mService.stopSelf();
+ }
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * Tells all running ping tasks to stop.
+ */
+ public void stopAllPings() {
+ mLock.lock();
+ try {
+ for (int i = 0; i < mAccountStateMap.size(); ++i) {
+ mAccountStateMap.valueAt(i).pushStop();
+ }
+ } finally {
+ mLock.unlock();
+ }
+ }
}