am 7a9b65fc: merge from froyo-plus-aosp
Merge commit '7a9b65fc1306714db3eaac604b0b95960b3e783a'
* commit '7a9b65fc1306714db3eaac604b0b95960b3e783a':
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a21a23d..e982ec6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -216,26 +216,18 @@
</intent-filter>
</activity>
<!--EXCHANGE-REMOVE-SECTION-START-->
- <receiver android:name="com.android.exchange.EmailSyncAlarmReceiver"/>
- <receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
- <receiver android:name="com.android.exchange.BootReceiver" android:enabled="true">
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- </intent-filter>
- </receiver>
+ <receiver android:name="com.android.exchange.EmailSyncAlarmReceiver"/>
+ <receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
<!--EXCHANGE-REMOVE-SECTION-END-->
- <receiver android:name=".service.BootReceiver" android:enabled="true">
+ <receiver android:name=".service.EmailBroadcastReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
- </intent-filter>
- <intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
- </intent-filter>
- <intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_OK" />
</intent-filter>
</receiver>
+ <service android:name=".service.EmailBroadcastProcessorService" />
<!-- Support for DeviceAdmin / DevicePolicyManager. See SecurityPolicy class for impl. -->
<receiver
@@ -250,15 +242,6 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
-
- <receiver
- android:name=".OneTimeInitializer"
- android:enabled="true"
- >
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- </intent-filter>
- </receiver>
<service
android:name=".service.MailService"
diff --git a/proguard.flags b/proguard.flags
index bbffc8a..f3b7e57 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -23,9 +23,9 @@
-keep class * extends org.apache.james.mime4j.util.TempStorage
-
# Keep names that are used only by unit tests
+# Any methods whose name is '*ForTest' are preserved.
-keep class ** {
*** *ForTest(...);
}
@@ -172,3 +172,7 @@
-keep class org.apache.james.mime4j.message.Message {
*;
}
+
+-keepclasseswithmembers class org.apache.commons.io.IOUtils {
+ *** toByteArray(...);
+}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 95b4e9e..e209501 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -278,9 +278,8 @@
<string name="message_view_fetching_attachment_toast">Fetching attachment.</string>
<!-- Appears progress dialog for fetching attachment -->
<string name="message_view_fetching_attachment_progress">Fetching attachment <xliff:g id="filename">%s</xliff:g></string>
- <!-- Calendar invitation, link to calendar.
- Preserve the chevron (unicode ») untranslated -->
- <string name="message_view_invite_view">View in Calendar »</string>
+ <!-- Calendar invitation, label of the button to open in calendar -->
+ <string name="message_view_invite_view">View in Calendar</string>
<!-- String shown with a calendar invitation. -->
<string name="message_view_invite_title">Calendar Invite</string>
<!-- String shown with a calendar invitation. -->
@@ -445,6 +444,8 @@
<string name="account_setup_exchange_ssl_label">Use secure connection (SSL)</string>
<!-- On "Exchange" setup screen, the trust ssl certificates checkbox label -->
<string name="account_setup_exchange_trust_certificates_label">Accept all SSL certificates</string>
+ <!-- On "Exchange" setup screen, the exchange device-id label -->
+ <string name="account_setup_exchange_device_id_label">Mobile Device ID</string>
<!-- In Account setup options screen, Activity title -->
<string name="account_setup_options_title">Account options</string>
diff --git a/src/com/android/exchange/BootReceiver.java b/src/com/android/exchange/BootReceiver.java
deleted file mode 100644
index 1ebfa7b..0000000
--- a/src/com/android/exchange/BootReceiver.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.exchange;
-
-import com.android.email.ExchangeUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class BootReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d("Exchange", "BootReceiver onReceive");
- ExchangeUtils.startExchangeService(context);
- }
-}
diff --git a/src/com/android/exchange/Eas.java b/src/com/android/exchange/Eas.java
index 8e561e7..2fa162b 100644
--- a/src/com/android/exchange/Eas.java
+++ b/src/com/android/exchange/Eas.java
@@ -45,6 +45,8 @@
public static final double SUPPORTED_PROTOCOL_EX2003_DOUBLE = 2.5;
public static final String SUPPORTED_PROTOCOL_EX2007 = "12.0";
public static final double SUPPORTED_PROTOCOL_EX2007_DOUBLE = 12.0;
+ public static final String SUPPORTED_PROTOCOL_EX2007_SP1 = "12.1";
+ public static final double SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE = 12.1;
public static final String DEFAULT_PROTOCOL_VERSION = SUPPORTED_PROTOCOL_EX2003;
// From EAS spec
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 3eb48c6..62d7804 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -352,7 +352,8 @@
// Find the most recent version we support
for (String version: supportedVersionsArray) {
if (version.equals(Eas.SUPPORTED_PROTOCOL_EX2003) ||
- version.equals(Eas.SUPPORTED_PROTOCOL_EX2007)) {
+ version.equals(Eas.SUPPORTED_PROTOCOL_EX2007) ||
+ version.equals(Eas.SUPPORTED_PROTOCOL_EX2007_SP1)) {
ourVersion = version;
}
}
@@ -400,10 +401,17 @@
// Make sure we've got the right protocol version set up
setupProtocolVersion(svc, versions);
- // Run second test here for provisioning failures...
- Serializer s = new Serializer();
+ // Run second test here for provisioning failures using FolderSync
userLog("Try folder sync");
- s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text("0")
+ // Send "0" as the sync key for new accounts; otherwise we'll use the current key
+ String syncKey = "0";
+ Account existingAccount =
+ Utility.findExistingAccount(context, -1L, hostAddress, userName);
+ if (existingAccount != null && existingAccount.mSyncKey != null) {
+ syncKey = existingAccount.mSyncKey;
+ }
+ Serializer s = new Serializer();
+ s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
.end().end().done();
resp = svc.sendHttpClientPost("FolderSync", s.toByteArray());
code = resp.getStatusLine().getStatusCode();
@@ -1979,9 +1987,12 @@
userLog("sync, sending ", className, " syncKey: ", syncKey);
s.start(Tags.SYNC_SYNC)
.start(Tags.SYNC_COLLECTIONS)
- .start(Tags.SYNC_COLLECTION)
- .data(Tags.SYNC_CLASS, className)
- .data(Tags.SYNC_SYNC_KEY, syncKey)
+ .start(Tags.SYNC_COLLECTION);
+ // The "Class" element is removed in EAS 12.1 and later versions
+ if (mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
+ s.data(Tags.SYNC_CLASS, className);
+ }
+ s.data(Tags.SYNC_SYNC_KEY, syncKey)
.data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId);
// Start with the default timeout
diff --git a/src/com/android/exchange/MailboxAlarmReceiver.java b/src/com/android/exchange/MailboxAlarmReceiver.java
index f8551e4..565b158 100644
--- a/src/com/android/exchange/MailboxAlarmReceiver.java
+++ b/src/com/android/exchange/MailboxAlarmReceiver.java
@@ -29,9 +29,14 @@
public class MailboxAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- long mid = intent.getLongExtra("mailbox", -1);
- SyncManager.log("Alarm received for: " + SyncManager.alarmOwner(mid));
- SyncManager.alert(context, mid);
+ long mailboxId = intent.getLongExtra("mailbox", SyncManager.SYNC_MANAGER_ID);
+ // SYNC_MANAGER_SERVICE_ID tells us that the service is asking to be started
+ if (mailboxId == SyncManager.SYNC_MANAGER_SERVICE_ID) {
+ context.startService(new Intent(context, SyncManager.class));
+ } else {
+ SyncManager.log("Alarm received for: " + SyncManager.alarmOwner(mailboxId));
+ SyncManager.alert(context, mailboxId);
+ }
}
}
diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java
index 7a56cbb..651cf63 100644
--- a/src/com/android/exchange/SyncManager.java
+++ b/src/com/android/exchange/SyncManager.java
@@ -114,7 +114,8 @@
private static final String TAG = "EAS SyncManager";
// The SyncManager's mailbox "id"
- private static final int SYNC_MANAGER_ID = -1;
+ protected static final int SYNC_MANAGER_ID = -1;
+ protected static final int SYNC_MANAGER_SERVICE_ID = 0;
private static final int SECONDS = 1000;
private static final int MINUTES = 60*SECONDS;
@@ -182,8 +183,6 @@
// All threads can use this lock to wait for connectivity
public static final Object sConnectivityLock = new Object();
public static boolean sConnectivityHold = false;
- // Keep our cached list of active Accounts here
- public static final AccountList sAccountList = new AccountList();
// Keeps track of running services (by mailbox id)
private HashMap<Long, AbstractSyncService> mServiceMap =
@@ -196,6 +195,8 @@
private HashMap<Long, PendingIntent> mPendingIntents = new HashMap<Long, PendingIntent>();
// The actual WakeLock obtained by SyncManager
private WakeLock mWakeLock = null;
+ // Keep our cached list of active Accounts here
+ public final AccountList mAccountList = new AccountList();
// Observers that we use to look for changed mail-related data
private Handler mHandler = new Handler();
@@ -204,6 +205,7 @@
private SyncedMessageObserver mSyncedMessageObserver;
private MessageObserver mMessageObserver;
private EasSyncStatusObserver mSyncStatusObserver;
+ private Object mStatusChangeListener;
private EasAccountsUpdatedListener mAccountsUpdatedListener;
private HashMap<Long, CalendarObserver> mCalendarObservers =
@@ -221,7 +223,7 @@
// Count of ClientConnectionManager shutdowns
private static volatile int sClientConnectionManagerShutdownCount = 0;
- private boolean mStop = false;
+ private static volatile boolean sStop = false;
// The reason for SyncManager's next wakeup call
private String mNextWaitReason;
@@ -434,18 +436,18 @@
super(handler);
// At startup, we want to see what EAS accounts exist and cache them
Context context = getContext();
- synchronized (sAccountList) {
+ synchronized (mAccountList) {
Cursor c = getContentResolver().query(Account.CONTENT_URI,
Account.CONTENT_PROJECTION, null, null, null);
// Build the account list from the cursor
try {
- collectEasAccounts(c, sAccountList);
+ collectEasAccounts(c, mAccountList);
} finally {
c.close();
}
// Create an account mailbox for any account without one
- for (Account account : sAccountList) {
+ for (Account account : mAccountList) {
int cnt = Mailbox.count(context, Mailbox.CONTENT_URI, "accountKey="
+ account.mId, null);
if (cnt == 0) {
@@ -464,8 +466,8 @@
if (mSyncableEasMailboxSelector == null) {
StringBuilder sb = new StringBuilder(WHERE_NOT_INTERVAL_NEVER_AND_ACCOUNT_KEY_IN);
boolean first = true;
- synchronized (sAccountList) {
- for (Account account : sAccountList) {
+ synchronized (mAccountList) {
+ for (Account account : mAccountList) {
if (!first) {
sb.append(',');
} else {
@@ -489,8 +491,8 @@
if (mEasAccountSelector == null) {
StringBuilder sb = new StringBuilder(ACCOUNT_KEY_IN);
boolean first = true;
- synchronized (sAccountList) {
- for (Account account : sAccountList) {
+ synchronized (mAccountList) {
+ for (Account account : mAccountList) {
if (!first) {
sb.append(',');
} else {
@@ -520,8 +522,8 @@
Account.CONTENT_PROJECTION, null, null, null);
try {
collectEasAccounts(c, currentAccounts);
- synchronized (sAccountList) {
- for (Account account : sAccountList) {
+ synchronized (mAccountList) {
+ for (Account account : mAccountList) {
// Ignore accounts not fully created
if ((account.mFlags & Account.FLAGS_INCOMPLETE) != 0) {
log("Account observer noticed incomplete account; ignoring");
@@ -568,7 +570,7 @@
}
// Look for new accounts
for (Account account : currentAccounts) {
- if (!sAccountList.contains(account.mId)) {
+ if (!mAccountList.contains(account.mId)) {
// Don't forget to cache the HostAuth
HostAuth ha = HostAuth.restoreHostAuthWithId(getContext(),
account.mHostAuthKeyRecv);
@@ -577,14 +579,14 @@
// This is an addition; create our magic hidden mailbox...
log("Account observer found new account: " + account.mDisplayName);
addAccountMailbox(account.mId);
- sAccountList.add(account);
+ mAccountList.add(account);
mSyncableEasMailboxSelector = null;
mEasAccountSelector = null;
}
}
// Finally, make sure our account list is up to date
- sAccountList.clear();
- sAccountList.addAll(currentAccounts);
+ mAccountList.clear();
+ mAccountList.addAll(currentAccounts);
}
} finally {
c.close();
@@ -599,7 +601,7 @@
new Thread(new Runnable() {
public void run() {
onAccountChanged();
- }}).start();
+ }}, "Account Observer").start();
}
private void collectEasAccounts(Cursor c, ArrayList<Account> accounts) {
@@ -773,7 +775,7 @@
} finally {
c.close();
}
- }}).start();
+ }}, "Calendar Observer").start();
}
}
}
@@ -831,9 +833,14 @@
}
static public Account getAccountById(long accountId) {
- synchronized (sAccountList) {
- return sAccountList.getById(accountId);
+ SyncManager syncManager = INSTANCE;
+ if (syncManager != null) {
+ AccountList accountList = syncManager.mAccountList;
+ synchronized (accountList) {
+ return accountList.getById(accountId);
+ }
}
+ return null;
}
static public String getEasAccountSelector() {
@@ -963,13 +970,13 @@
public void run() {
android.accounts.Account[] accountMgrList = AccountManager.get(syncManager)
.getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
- synchronized (sAccountList) {
+ synchronized (mAccountList) {
// Make sure we have an up-to-date sAccountList. If not (for example, if the
// service has been destroyed), we would be reconciling against an empty account
// list, which would cause the deletion of all of our accounts
if (mAccountObserver != null) {
mAccountObserver.onAccountChanged();
- reconcileAccountsWithAccountManager(syncManager, sAccountList,
+ reconcileAccountsWithAccountManager(syncManager, mAccountList,
accountMgrList, false, mResolver);
}
}
@@ -1055,113 +1062,6 @@
return mBinder;
}
- /**
- * Note that there are two ways the EAS SyncManager service can be created:
- *
- * 1) as a background service instantiated via startService (which happens on boot, when the
- * first EAS account is created, etc), in which case the service thread is spun up, mailboxes
- * sync, etc. and
- * 2) to execute an RPC call from the UI, in which case the background service will already be
- * running most of the time (unless we're creating a first EAS account)
- *
- * If the running background service detects that there are no EAS accounts (on boot, if none
- * were created, or afterward if the last remaining EAS account is deleted), it will call
- * stopSelf() to terminate operation.
- *
- * The goal is to ensure that the background service is running at all times when there is at
- * least one EAS account in existence
- *
- * Because there are edge cases in which our process can crash (typically, this has been seen
- * in UI crashes, ANR's, etc.), it's possible for the UI to start up again without the
- * background service having been started. We explicitly try to start the service in Welcome
- * (to handle the case of the app having been reloaded). We also start the service on any
- * startSync call (if it isn't already running)
- */
- @Override
- public void onCreate() {
- alwaysLog("!!! EAS SyncManager, onCreate");
- if (INSTANCE == null) {
- INSTANCE = this;
- mResolver = getContentResolver();
- mAccountObserver = new AccountObserver(mHandler);
- mResolver.registerContentObserver(Account.CONTENT_URI, true, mAccountObserver);
- mMailboxObserver = new MailboxObserver(mHandler);
- mSyncedMessageObserver = new SyncedMessageObserver(mHandler);
- mMessageObserver = new MessageObserver(mHandler);
- mSyncStatusObserver = new EasSyncStatusObserver();
- } else {
- alwaysLog("!!! EAS SyncManager onCreated, but INSTANCE not null??");
- }
- if (sDeviceId == null) {
- try {
- getDeviceId(this);
- } catch (IOException e) {
- // We can't run in this situation
- throw new RuntimeException();
- }
- }
- // Run the reconciler and clean up any mismatched accounts - if we weren't running when
- // accounts were deleted, it won't have been called.
- runAccountReconciler();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- alwaysLog("!!! EAS SyncManager, onStartCommand");
-
- // Restore accounts, if it has not happened already
- AccountBackupRestore.restoreAccountsIfNeeded(this);
-
- maybeStartSyncManagerThread();
- if (sServiceThread == null) {
- alwaysLog("!!! EAS SyncManager, stopping self");
- stopSelf();
- }
- return Service.START_STICKY;
- }
-
- @Override
- public void onDestroy() {
- alwaysLog("!!! EAS SyncManager, onDestroy");
- if (INSTANCE != null) {
- INSTANCE = null;
- mResolver.unregisterContentObserver(mAccountObserver);
- unregisterCalendarObservers();
- mResolver = null;
- mAccountObserver = null;
- mMailboxObserver = null;
- mSyncedMessageObserver = null;
- mMessageObserver = null;
- mSyncStatusObserver = null;
- mAccountsUpdatedListener = null;
- }
- }
-
- void maybeStartSyncManagerThread() {
- // Start our thread...
- // See if there are any EAS accounts; otherwise, just go away
- if (EmailContent.count(this, HostAuth.CONTENT_URI, WHERE_PROTOCOL_EAS, null) > 0) {
- if (sServiceThread == null || !sServiceThread.isAlive()) {
- log(sServiceThread == null ? "Starting thread..." : "Restarting thread...");
- sServiceThread = new Thread(this, "SyncManager");
- sServiceThread.start();
- }
- }
- }
-
- static void checkSyncManagerServiceRunning() {
- // Get the service thread running if it isn't
- // This is a stopgap for cases in which SyncManager died (due to a crash somewhere in
- // com.android.email) and hasn't been restarted
- // See the comment for onCreate for details
- SyncManager syncManager = INSTANCE;
- if (syncManager == null) return;
- if (sServiceThread == null) {
- alwaysLog("!!! checkSyncManagerServiceRunning; starting service...");
- syncManager.startService(new Intent(syncManager, SyncManager.class));
- }
- }
-
static public ConnPerRoute sConnPerRoute = new ConnPerRoute() {
public int getMaxForRoute(HttpRoute route) {
return 8;
@@ -1453,6 +1353,10 @@
if (service != null) {
// Handle alerts in a background thread, as we are typically called from a
// broadcast receiver, and are therefore running in the UI thread
+ String threadName = "SyncManager Alert: ";
+ if (service.mMailbox != null) {
+ threadName += service.mMailbox.mDisplayName;
+ }
new Thread(new Runnable() {
public void run() {
Mailbox m = Mailbox.restoreMailboxWithId(syncManager, id);
@@ -1484,7 +1388,7 @@
SyncManager.shutdownConnectionManager();
}
}
- }}).start();
+ }}, threadName).start();
}
}
}
@@ -1541,8 +1445,8 @@
* Make our sync settings match those of AccountManager
*/
private void checkPIMSyncSettings() {
- synchronized (sAccountList) {
- for (Account account : sAccountList) {
+ synchronized (mAccountList) {
+ for (Account account : mAccountList) {
updatePIMSyncSettings(account, Mailbox.TYPE_CONTACTS, ContactsContract.AUTHORITY);
updatePIMSyncSettings(account, Mailbox.TYPE_CALENDAR, Calendar.AUTHORITY);
}
@@ -1668,8 +1572,8 @@
// Otherwise, stop all syncs
} else {
log("Background data off: stop all syncs");
- synchronized (sAccountList) {
- for (Account account : sAccountList)
+ synchronized (mAccountList) {
+ for (Account account : mAccountList)
SyncManager.stopAccountSyncs(account.mId);
}
}
@@ -1723,7 +1627,7 @@
private void requestSync(Mailbox m, int reason, Request req) {
// Don't sync if there's no connectivity
- if (sConnectivityHold || (m == null)) return;
+ if (sConnectivityHold || (m == null) || sStop) return;
synchronized (sSyncLock) {
Account acct = Account.restoreAccountWithId(this, m.mAccountKey);
if (acct != null) {
@@ -1770,7 +1674,7 @@
boolean waiting = false;
ConnectivityManager cm =
(ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
- while (!mStop) {
+ while (!sStop) {
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
// We're done if there's an active network
@@ -1807,9 +1711,112 @@
}
}
- public void run() {
- mStop = false;
+ /**
+ * Note that there are two ways the EAS SyncManager service can be created:
+ *
+ * 1) as a background service instantiated via startService (which happens on boot, when the
+ * first EAS account is created, etc), in which case the service thread is spun up, mailboxes
+ * sync, etc. and
+ * 2) to execute an RPC call from the UI, in which case the background service will already be
+ * running most of the time (unless we're creating a first EAS account)
+ *
+ * If the running background service detects that there are no EAS accounts (on boot, if none
+ * were created, or afterward if the last remaining EAS account is deleted), it will call
+ * stopSelf() to terminate operation.
+ *
+ * The goal is to ensure that the background service is running at all times when there is at
+ * least one EAS account in existence
+ *
+ * Because there are edge cases in which our process can crash (typically, this has been seen
+ * in UI crashes, ANR's, etc.), it's possible for the UI to start up again without the
+ * background service having been started. We explicitly try to start the service in Welcome
+ * (to handle the case of the app having been reloaded). We also start the service on any
+ * startSync call (if it isn't already running)
+ */
+ @Override
+ public void onCreate() {
+ synchronized (sSyncLock) {
+ alwaysLog("!!! EAS SyncManager, onCreate");
+ // If we're in the process of shutting down, try again in 5 seconds
+ if (sStop) {
+ return;
+ }
+ if (sDeviceId == null) {
+ try {
+ getDeviceId(this);
+ } catch (IOException e) {
+ // We can't run in this situation
+ throw new RuntimeException();
+ }
+ }
+ // Run the reconciler and clean up any mismatched accounts - if we weren't running when
+ // accounts were deleted, it won't have been called.
+ runAccountReconciler();
+ }
+ }
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ synchronized (sSyncLock) {
+ alwaysLog("!!! EAS SyncManager, onStartCommand");
+ // Restore accounts, if it has not happened already
+ AccountBackupRestore.restoreAccountsIfNeeded(this);
+ maybeStartSyncManagerThread();
+ if (sServiceThread == null) {
+ alwaysLog("!!! EAS SyncManager, stopping self");
+ stopSelf();
+ } else if (sStop) {
+ // If we were in the middle of trying to stop, attempt a restart in 5 seconds
+ setAlarm(SYNC_MANAGER_SERVICE_ID, 5*SECONDS);
+ }
+ return Service.START_STICKY;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ synchronized(sSyncLock) {
+ alwaysLog("!!! EAS SyncManager, onDestroy");
+ // Stop the sync manager thread and return
+ synchronized (sSyncLock) {
+ sStop = true;
+ if (sServiceThread != null) {
+ sServiceThread.interrupt();
+ }
+ }
+ }
+ }
+
+ void maybeStartSyncManagerThread() {
+ // Start our thread...
+ // See if there are any EAS accounts; otherwise, just go away
+ if (EmailContent.count(this, HostAuth.CONTENT_URI, WHERE_PROTOCOL_EAS, null) > 0) {
+ if (sServiceThread == null || !sServiceThread.isAlive()) {
+ log(sServiceThread == null ? "Starting thread..." : "Restarting thread...");
+ sServiceThread = new Thread(this, "SyncManager");
+ INSTANCE = this;
+ sServiceThread.start();
+ }
+ }
+ }
+
+ /**
+ * Start up the SyncManager service if it's not already running
+ * This is a stopgap for cases in which SyncManager died (due to a crash somewhere in
+ * com.android.email) and hasn't been restarted. See the comment for onCreate for details
+ */
+ static void checkSyncManagerServiceRunning() {
+ SyncManager syncManager = INSTANCE;
+ if (syncManager == null) return;
+ if (sServiceThread == null) {
+ alwaysLog("!!! checkSyncManagerServiceRunning; starting service...");
+ syncManager.startService(new Intent(syncManager, SyncManager.class));
+ }
+ }
+
+ public void run() {
+ sStop = false;
+ alwaysLog("!!! SyncManager thread running");
// If we're really debugging, turn on all logging
if (Eas.DEBUG) {
Eas.USER_LOG = true;
@@ -1822,41 +1829,56 @@
Debug.waitForDebugger();
}
- // Set up our observers; we need them to know when to start/stop various syncs based
- // on the insert/delete/update of mailboxes and accounts
- // We also observe synced messages to trigger upsyncs at the appropriate time
- mResolver.registerContentObserver(Mailbox.CONTENT_URI, false, mMailboxObserver);
- mResolver.registerContentObserver(Message.SYNCED_CONTENT_URI, true, mSyncedMessageObserver);
- mResolver.registerContentObserver(Message.CONTENT_URI, true, mMessageObserver);
- ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS,
- mSyncStatusObserver);
- mAccountsUpdatedListener = new EasAccountsUpdatedListener();
- // TODO Find and fix root cause of duplication
- try {
- AccountManager.get(getApplication())
- .addOnAccountsUpdatedListener(mAccountsUpdatedListener, mHandler, true);
- } catch (IllegalStateException e1) {
- // This exception is more of a warning; we shouldn't be in the state in which we
- // already have a listener.
+ // Synchronize here to prevent a shutdown from happening while we initialize our observers
+ // and receivers
+ synchronized (sSyncLock) {
+ if (INSTANCE != null) {
+ mResolver = getContentResolver();
+
+ // Set up our observers; we need them to know when to start/stop various syncs based
+ // on the insert/delete/update of mailboxes and accounts
+ // We also observe synced messages to trigger upsyncs at the appropriate time
+ mAccountObserver = new AccountObserver(mHandler);
+ mResolver.registerContentObserver(Account.CONTENT_URI, true, mAccountObserver);
+ mMailboxObserver = new MailboxObserver(mHandler);
+ mResolver.registerContentObserver(Mailbox.CONTENT_URI, false, mMailboxObserver);
+ mSyncedMessageObserver = new SyncedMessageObserver(mHandler);
+ mResolver.registerContentObserver(Message.SYNCED_CONTENT_URI, true,
+ mSyncedMessageObserver);
+ mMessageObserver = new MessageObserver(mHandler);
+ mResolver.registerContentObserver(Message.CONTENT_URI, true, mMessageObserver);
+ mSyncStatusObserver = new EasSyncStatusObserver();
+ mStatusChangeListener =
+ ContentResolver.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncStatusObserver);
+
+ // Set up our observer for AccountManager
+ mAccountsUpdatedListener = new EasAccountsUpdatedListener();
+ AccountManager.get(getApplication()).addOnAccountsUpdatedListener(
+ mAccountsUpdatedListener, mHandler, true);
+
+ // Set up receivers for connectivity and background data setting
+ mConnectivityReceiver = new ConnectivityReceiver();
+ registerReceiver(mConnectivityReceiver, new IntentFilter(
+ ConnectivityManager.CONNECTIVITY_ACTION));
+
+ mBackgroundDataSettingReceiver = new ConnectivityReceiver();
+ registerReceiver(mBackgroundDataSettingReceiver, new IntentFilter(
+ ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED));
+ // Save away the current background data setting; we'll keep track of it with the
+ // receiver we just registered
+ ConnectivityManager cm = (ConnectivityManager)getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mBackgroundData = cm.getBackgroundDataSetting();
+
+ // See if any settings have changed while we weren't running...
+ checkPIMSyncSettings();
+ }
}
- // Set up receivers for ConnectivityManager
- mConnectivityReceiver = new ConnectivityReceiver();
- registerReceiver(mConnectivityReceiver,
- new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
- mBackgroundDataSettingReceiver = new ConnectivityReceiver();
- registerReceiver(mBackgroundDataSettingReceiver,
- new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED));
- // Save away background data setting; we'll keep track of it with the receiver
- ConnectivityManager cm =
- (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
- mBackgroundData = cm.getBackgroundDataSetting();
-
- // See if any settings have changed while we weren't running...
- checkPIMSyncSettings();
-
try {
- while (!mStop) {
+ // Loop indefinitely until we're shut down
+ while (!sStop) {
runAwake(SYNC_MANAGER_ID);
waitForConnectivity();
mNextWaitReason = "Heartbeat";
@@ -1877,6 +1899,7 @@
}
} catch (InterruptedException e) {
// Needs to be caught, but causes no problem
+ log("SyncManager interrupted");
} finally {
synchronized (this) {
if (mKicked) {
@@ -1891,51 +1914,78 @@
Log.e(TAG, "RuntimeException in SyncManager", e);
throw e;
} finally {
- log("Finishing SyncManager");
- // Lots of cleanup here
- // Stop our running syncs
- stopServiceThreads();
-
- // Stop receivers and content observers
- if (mConnectivityReceiver != null) {
- unregisterReceiver(mConnectivityReceiver);
- }
- if (mBackgroundDataSettingReceiver != null) {
- unregisterReceiver(mBackgroundDataSettingReceiver);
- }
-
- if (INSTANCE != null) {
- ContentResolver resolver = getContentResolver();
- resolver.unregisterContentObserver(mAccountObserver);
- resolver.unregisterContentObserver(mMailboxObserver);
- resolver.unregisterContentObserver(mSyncedMessageObserver);
- resolver.unregisterContentObserver(mMessageObserver);
- unregisterCalendarObservers();
- }
- // Don't leak the Intent associated with this listener
- if (mAccountsUpdatedListener != null) {
- AccountManager.get(this).removeOnAccountsUpdatedListener(mAccountsUpdatedListener);
- mAccountsUpdatedListener = null;
- }
-
- // Clear pending alarms and associated Intents
- clearAlarms();
-
- // Release our wake lock, if we have one
- synchronized (mWakeLocks) {
- if (mWakeLock != null) {
- mWakeLock.release();
- mWakeLock = null;
- }
- }
-
- log("Goodbye");
+ shutdown();
}
+ }
- if (!mStop) {
- // If this wasn't intentional, try to restart the service
- throw new RuntimeException("EAS SyncManager crash; please restart me...");
- }
+ private void shutdown() {
+ synchronized (sSyncLock) {
+ // If INSTANCE is null, we've already been shut down
+ if (INSTANCE != null) {
+ log("SyncManager shutting down...");
+
+ // Stop our running syncs
+ stopServiceThreads();
+
+ // Stop receivers
+ if (mConnectivityReceiver != null) {
+ unregisterReceiver(mConnectivityReceiver);
+ }
+ if (mBackgroundDataSettingReceiver != null) {
+ unregisterReceiver(mBackgroundDataSettingReceiver);
+ }
+
+ // Unregister observers
+ ContentResolver resolver = getContentResolver();
+ if (mSyncedMessageObserver != null) {
+ resolver.unregisterContentObserver(mSyncedMessageObserver);
+ mSyncedMessageObserver = null;
+ }
+ if (mMessageObserver != null) {
+ resolver.unregisterContentObserver(mMessageObserver);
+ mMessageObserver = null;
+ }
+ if (mAccountObserver != null) {
+ resolver.unregisterContentObserver(mAccountObserver);
+ mAccountObserver = null;
+ }
+ if (mMailboxObserver != null) {
+ resolver.unregisterContentObserver(mMailboxObserver);
+ mMailboxObserver = null;
+ }
+ unregisterCalendarObservers();
+
+ // Remove account listener (registered with AccountManager)
+ if (mAccountsUpdatedListener != null) {
+ AccountManager.get(this).removeOnAccountsUpdatedListener(
+ mAccountsUpdatedListener);
+ mAccountsUpdatedListener = null;
+ }
+
+ // Remove the sync status change listener (and null out the observer)
+ if (mStatusChangeListener != null) {
+ ContentResolver.removeStatusChangeListener(mStatusChangeListener);
+ mStatusChangeListener = null;
+ mSyncStatusObserver = null;
+ }
+
+ // Clear pending alarms and associated Intents
+ clearAlarms();
+
+ // Release our wake lock, if we have one
+ synchronized (mWakeLocks) {
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+ }
+
+ INSTANCE = null;
+ sServiceThread = null;
+ sStop = false;
+ log("Goodbye");
+ }
+ }
}
private void releaseMailbox(long mailboxId) {
@@ -2112,7 +2162,7 @@
long requestTime = service.mRequestTime;
if (requestTime > 0) {
long timeToRequest = requestTime - now;
- if (service instanceof AbstractSyncService && timeToRequest <= 0) {
+ if (timeToRequest <= 0) {
service.mRequestTime = 0;
service.alarm();
} else if (requestTime > 0 && timeToRequest < nextWait) {
diff --git a/src/com/android/exchange/adapter/ContactsSyncAdapter.java b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
index 8b4adba..c05a820 100644
--- a/src/com/android/exchange/adapter/ContactsSyncAdapter.java
+++ b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
@@ -1071,8 +1071,8 @@
// If we've found an existing data row, we'll delete it. Any rows left at the
// end should be deleted...
- if (result != null) {
- list.remove(result);
+ for (NamedContentValues values : result) {
+ list.remove(values);
}
// Return the row found (or null)
diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java
index af1ecdd..5304639 100644
--- a/src/com/android/exchange/adapter/ProvisionParser.java
+++ b/src/com/android/exchange/adapter/ProvisionParser.java
@@ -56,7 +56,7 @@
return mRemoteWipe;
}
- public void parseProvisionDocWbxml() throws IOException {
+ private void parseProvisionDocWbxml() throws IOException {
int minPasswordLength = 0;
int passwordMode = PolicySet.PASSWORD_MODE_NONE;
int maxPasswordFails = 0;
@@ -64,6 +64,7 @@
boolean canSupport = true;
while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) {
+ boolean supported = true;
switch (tag) {
case Tags.PROVISION_DEVICE_PASSWORD_ENABLED:
if (getValueInt() == 1) {
@@ -92,10 +93,28 @@
// Hint: I haven't seen any that's more specific than "simple"
getValue();
break;
- // The following policy, if false, can't be supported at the moment
+ // The following policies, if false, can't be supported at the moment
case Tags.PROVISION_ATTACHMENTS_ENABLED:
+ case Tags.PROVISION_ALLOW_STORAGE_CARD:
+ case Tags.PROVISION_ALLOW_CAMERA:
+ case Tags.PROVISION_ALLOW_UNSIGNED_APPLICATIONS:
+ case Tags.PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES:
+ case Tags.PROVISION_ALLOW_WIFI:
+ case Tags.PROVISION_ALLOW_TEXT_MESSAGING:
+ case Tags.PROVISION_ALLOW_POP_IMAP_EMAIL:
+ case Tags.PROVISION_ALLOW_IRDA:
+ case Tags.PROVISION_ALLOW_HTML_EMAIL:
+ case Tags.PROVISION_ALLOW_BROWSER:
+ case Tags.PROVISION_ALLOW_CONSUMER_EMAIL:
+ case Tags.PROVISION_ALLOW_INTERNET_SHARING:
if (getValueInt() == 0) {
- canSupport = false;
+ supported = false;
+ }
+ break;
+ // Bluetooth: 0 = no bluetooth; 1 = only hands-free; 2 = allowed
+ case Tags.PROVISION_ALLOW_BLUETOOTH:
+ if (getValueInt() != 2) {
+ supported = false;
}
break;
// The following policies, if true, can't be supported at the moment
@@ -103,14 +122,68 @@
case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED:
case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION:
case Tags.PROVISION_DEVICE_PASSWORD_HISTORY:
- case Tags.PROVISION_MAX_ATTACHMENT_SIZE:
+ case Tags.PROVISION_REQUIRE_DEVICE_ENCRYPTION:
+ case Tags.PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES:
+ case Tags.PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES:
+ case Tags.PROVISION_REQUIRE_SIGNED_SMIME_ALGORITHM:
+ case Tags.PROVISION_REQUIRE_ENCRYPTION_SMIME_ALGORITHM:
+ case Tags.PROVISION_REQUIRE_MANUAL_SYNC_WHEN_ROAMING:
if (getValueInt() == 1) {
- canSupport = false;
+ supported = false;
+ }
+ break;
+ // The following, if greater than zero, can't be supported at the moment
+ case Tags.PROVISION_MAX_ATTACHMENT_SIZE:
+ if (getValueInt() > 0) {
+ supported = false;
+ }
+ break;
+ // Complex character setting is only used if we're in "strong" (alphanumeric) mode
+ case Tags.PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS:
+ if ((passwordMode == PolicySet.PASSWORD_MODE_STRONG) && (getValueInt() > 0)) {
+ supported = false;
+ }
+ break;
+ // The following policies are moot; they allow functionality that we don't support
+ case Tags.PROVISION_ALLOW_DESKTOP_SYNC:
+ case Tags.PROVISION_ALLOW_SMIME_ENCRYPTION_NEGOTIATION:
+ case Tags.PROVISION_ALLOW_SMIME_SOFT_CERTS:
+ case Tags.PROVISION_ALLOW_REMOTE_DESKTOP:
+ skipTag();
+ break;
+ // We don't handle approved/unapproved application lists
+ case Tags.PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST:
+ case Tags.PROVISION_APPROVED_APPLICATION_LIST:
+ // Parse and throw away the content
+ if (specifiesApplications(tag)) {
+ supported = false;
+ }
+ break;
+ // NOTE: We can support these entirely within the email application if we choose
+ case Tags.PROVISION_MAX_CALENDAR_AGE_FILTER:
+ case Tags.PROVISION_MAX_EMAIL_AGE_FILTER:
+ // 0 indicates no specified filter
+ if (getValueInt() != 0) {
+ supported = false;
+ }
+ break;
+ // NOTE: We can support these entirely within the email application if we choose
+ case Tags.PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE:
+ case Tags.PROVISION_MAX_EMAIL_HTML_BODY_TRUNCATION_SIZE:
+ String value = getValue();
+ // -1 indicates no required truncation
+ if (!value.equals("-1")) {
+ supported = false;
}
break;
default:
skipTag();
}
+
+ if (!supported) {
+ log("** Policy not supported");
+ canSupport = false;
+ }
}
if (canSupport) {
@@ -119,6 +192,27 @@
}
}
+ /**
+ * Return whether or not either of the application list tags specifies any applications
+ * @param endTag the tag whose children we're walking through
+ * @return whether any applications were specified (by name or by hash)
+ * @throws IOException
+ */
+ private boolean specifiesApplications(int endTag) throws IOException {
+ boolean specifiesApplications = false;
+ while (nextTag(endTag) != END) {
+ switch (tag) {
+ case Tags.PROVISION_APPLICATION_NAME:
+ case Tags.PROVISION_HASH:
+ specifiesApplications = true;
+ break;
+ default:
+ skipTag();
+ }
+ }
+ return specifiesApplications;
+ }
+
class ShadowPolicySet {
int mMinPasswordLength = 0;
int mPasswordMode = PolicySet.PASSWORD_MODE_NONE;
@@ -126,7 +220,7 @@
int mMaxScreenLockTime = 0;
}
- public void parseProvisionDocXml(String doc) throws IOException {
+ /*package*/ void parseProvisionDocXml(String doc) throws IOException {
ShadowPolicySet sps = new ShadowPolicySet();
try {
@@ -154,7 +248,7 @@
/**
* Return true if password is required; otherwise false.
*/
- boolean parseSecurityPolicy(XmlPullParser parser, ShadowPolicySet sps)
+ private boolean parseSecurityPolicy(XmlPullParser parser, ShadowPolicySet sps)
throws XmlPullParserException, IOException {
boolean passwordRequired = true;
while (true) {
@@ -177,7 +271,7 @@
return passwordRequired;
}
- void parseCharacteristic(XmlPullParser parser, ShadowPolicySet sps)
+ private void parseCharacteristic(XmlPullParser parser, ShadowPolicySet sps)
throws XmlPullParserException, IOException {
boolean enforceInactivityTimer = true;
while (true) {
@@ -219,7 +313,7 @@
}
}
- void parseRegistry(XmlPullParser parser, ShadowPolicySet sps)
+ private void parseRegistry(XmlPullParser parser, ShadowPolicySet sps)
throws XmlPullParserException, IOException {
while (true) {
int type = parser.nextTag();
@@ -234,7 +328,7 @@
}
}
- void parseWapProvisioningDoc(XmlPullParser parser, ShadowPolicySet sps)
+ private void parseWapProvisioningDoc(XmlPullParser parser, ShadowPolicySet sps)
throws XmlPullParserException, IOException {
while (true) {
int type = parser.nextTag();
@@ -258,7 +352,7 @@
}
}
- public void parseProvisionData() throws IOException {
+ private void parseProvisionData() throws IOException {
while (nextTag(Tags.PROVISION_DATA) != END) {
if (tag == Tags.PROVISION_EAS_PROVISION_DOC) {
parseProvisionDocWbxml();
@@ -268,7 +362,7 @@
}
}
- public void parsePolicy() throws IOException {
+ private void parsePolicy() throws IOException {
String policyType = null;
while (nextTag(Tags.PROVISION_POLICY) != END) {
switch (tag) {
@@ -297,7 +391,7 @@
}
}
- public void parsePolicies() throws IOException {
+ private void parsePolicies() throws IOException {
while (nextTag(Tags.PROVISION_POLICIES) != END) {
if (tag == Tags.PROVISION_POLICY) {
parsePolicy();
diff --git a/src/com/android/exchange/adapter/Tags.java b/src/com/android/exchange/adapter/Tags.java
index ef70981..2745e33 100644
--- a/src/com/android/exchange/adapter/Tags.java
+++ b/src/com/android/exchange/adapter/Tags.java
@@ -596,7 +596,7 @@
"MinDevicePasswordComplexCharacters", "AllowWiFi", "AllowTextMessaging",
"AllowPOPIMAPEmail", "AllowBluetooth", "AllowIrDA", "RequireManualSyncWhenRoaming",
"AllowDesktopSync",
- "MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilder",
+ "MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilter",
"MaxEmailBodyTruncationSize", "MaxEmailHTMLBodyTruncationSize",
"RequireSignedSMIMEMessages", "RequireEncryptedSMIMEMessages",
"RequireSignedSMIMEAlgorithm", "RequireEncryptionSMIMEAlgorithm",