Merge "Import revised translations" into froyo
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 198c9dd..a21a23d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -170,6 +170,23 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
+
+ <!--
+ This activity catches shortcuts to account created on Android 1.6 and before,
+ and redirects to MessageList.
+ singleTask is necessary to make sure the activity is really launched.
+ Without it, the framework brings up the app to front, but doesn't necessarily
+ launch the activity.
+ -->
+ <activity
+ android:name=".activity.FolderMessageList"
+ android:launchMode="singleTask"
+ >
+ <intent-filter>
+ <!-- This action is only to allow an entry point for launcher shortcuts -->
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
<activity
android:name=".activity.MessageView"
diff --git a/src/com/android/exchange/CalendarSyncAdapterService.java b/src/com/android/exchange/CalendarSyncAdapterService.java
index d2f4ecf..357d67f 100644
--- a/src/com/android/exchange/CalendarSyncAdapterService.java
+++ b/src/com/android/exchange/CalendarSyncAdapterService.java
@@ -43,6 +43,8 @@
private static final String ACCOUNT_AND_TYPE_CALENDAR =
MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + '=' + Mailbox.TYPE_CALENDAR;
+ private static final String DIRTY_IN_ACCOUNT =
+ Events._SYNC_DIRTY + "=1 AND " + Events._SYNC_ACCOUNT + "=?";
public CalendarSyncAdapterService() {
super();
@@ -93,16 +95,13 @@
throws OperationCanceledException {
ContentResolver cr = context.getContentResolver();
boolean logging = Eas.USER_LOG;
- if (logging) {
- Log.d(TAG, "performSync");
- }
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
Cursor c = cr.query(Events.CONTENT_URI,
- new String[] {Events._ID}, Events._SYNC_DIRTY + "=1", null, null);
+ new String[] {Events._ID}, DIRTY_IN_ACCOUNT, new String[] {account.name}, null);
try {
if (!c.moveToFirst()) {
if (logging) {
- Log.d(TAG, "Upload sync; no changes");
+ Log.d(TAG, "No changes for " + account.name);
}
return;
}
@@ -125,7 +124,7 @@
try {
if (mailboxCursor.moveToFirst()) {
if (logging) {
- Log.d(TAG, "Calendar sync requested for " + account.name);
+ Log.d(TAG, "Upload sync requested for " + account.name);
}
// Ask for a sync from our sync manager
SyncManager.serviceRequest(mailboxCursor.getLong(0),
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 4aee013..0d6695f 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -86,7 +86,7 @@
import android.provider.Calendar.Events;
import android.util.Log;
import android.util.Xml;
-import android.util.base64.Base64;
+import android.util.Base64;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -696,7 +696,7 @@
*/
static public GalResult searchGal(Context context, long accountId, String filter)
{
- Account acct = SyncManager.getAccountList().getById(accountId);
+ Account acct = SyncManager.getAccountById(accountId);
if (acct != null) {
HostAuth ha = HostAuth.restoreHostAuthWithId(context, acct.mHostAuthKeyRecv);
EasSyncService svc = new EasSyncService("%GalLookupk%");
diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java
index 527f03b..14d839a 100644
--- a/src/com/android/exchange/SyncManager.java
+++ b/src/com/android/exchange/SyncManager.java
@@ -173,10 +173,12 @@
public static final int PING_STATUS_UNABLE = 3;
// We synchronize on this for all actions affecting the service and error maps
- private static Object sSyncToken = new Object();
+ private static final Object sSyncLock = new Object();
// All threads can use this lock to wait for connectivity
- public static Object sConnectivityLock = new Object();
+ 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 =
@@ -189,7 +191,6 @@
private HashMap<Long, PendingIntent> mPendingIntents = new HashMap<Long, PendingIntent>();
// The actual WakeLock obtained by SyncManager
private WakeLock mWakeLock = null;
- private static final AccountList EMPTY_ACCOUNT_LIST = new AccountList();
// Observers that we use to look for changed mail-related data
private Handler mHandler = new Handler();
@@ -268,8 +269,6 @@
IEmailServiceCallback cb = INSTANCE == null ? null: INSTANCE.mCallback;
if (cb != null) {
cb.syncMailboxStatus(mailboxId, statusCode, progress);
- } else if (INSTANCE != null) {
- log("orphan syncMailboxStatus, id=" + mailboxId + " status=" + statusCode);
}
}
};
@@ -337,7 +336,7 @@
public void hostChanged(long accountId) throws RemoteException {
SyncManager syncManager = INSTANCE;
if (syncManager == null) return;
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
HashMap<Long, SyncError> syncErrorMap = syncManager.mSyncErrorMap;
ArrayList<Long> deletedMailboxes = new ArrayList<Long>();
// Go through the various error mailboxes
@@ -402,7 +401,7 @@
private static final long serialVersionUID = 1L;
public boolean contains(long id) {
- for (Account account: this) {
+ for (Account account : this) {
if (account.mId == id) {
return true;
}
@@ -411,7 +410,7 @@
}
public Account getById(long id) {
- for (Account account: this) {
+ for (Account account : this) {
if (account.mId == id) {
return account;
}
@@ -421,29 +420,30 @@
}
class AccountObserver extends ContentObserver {
- // mAccounts keeps track of Accounts that we care about (EAS for now)
- AccountList mAccounts = new AccountList();
String mSyncableEasMailboxSelector = null;
String mEasAccountSelector = null;
public AccountObserver(Handler handler) {
super(handler);
// At startup, we want to see what EAS accounts exist and cache them
- Cursor c = getContentResolver().query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
- null, null, null);
- try {
- collectEasAccounts(c, mAccounts);
- } finally {
- c.close();
- }
-
- // Create the account mailbox for any account that doesn't have one
Context context = getContext();
- for (Account account: mAccounts) {
- int cnt = Mailbox.count(context, Mailbox.CONTENT_URI, "accountKey=" + account.mId,
- null);
- if (cnt == 0) {
- addAccountMailbox(account.mId);
+ synchronized (sAccountList) {
+ Cursor c = getContentResolver().query(Account.CONTENT_URI,
+ Account.CONTENT_PROJECTION, null, null, null);
+ // Build the account list from the cursor
+ try {
+ collectEasAccounts(c, sAccountList);
+ } finally {
+ c.close();
+ }
+
+ // Create an account mailbox for any account without one
+ for (Account account : sAccountList) {
+ int cnt = Mailbox.count(context, Mailbox.CONTENT_URI, "accountKey="
+ + account.mId, null);
+ if (cnt == 0) {
+ addAccountMailbox(account.mId);
+ }
}
}
}
@@ -457,13 +457,15 @@
if (mSyncableEasMailboxSelector == null) {
StringBuilder sb = new StringBuilder(WHERE_NOT_INTERVAL_NEVER_AND_ACCOUNT_KEY_IN);
boolean first = true;
- for (Account account: mAccounts) {
- if (!first) {
- sb.append(',');
- } else {
- first = false;
+ synchronized (sAccountList) {
+ for (Account account : sAccountList) {
+ if (!first) {
+ sb.append(',');
+ } else {
+ first = false;
+ }
+ sb.append(account.mId);
}
- sb.append(account.mId);
}
sb.append(')');
mSyncableEasMailboxSelector = sb.toString();
@@ -480,13 +482,15 @@
if (mEasAccountSelector == null) {
StringBuilder sb = new StringBuilder(ACCOUNT_KEY_IN);
boolean first = true;
- for (Account account: mAccounts) {
- if (!first) {
- sb.append(',');
- } else {
- first = false;
+ synchronized (sAccountList) {
+ for (Account account : sAccountList) {
+ if (!first) {
+ sb.append(',');
+ } else {
+ first = false;
+ }
+ sb.append(account.mId);
}
- sb.append(account.mId);
}
sb.append(')');
mEasAccountSelector = sb.toString();
@@ -498,91 +502,94 @@
return (account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0;
}
+ private void onAccountChanged() {
+ maybeStartSyncManagerThread();
+ Context context = getContext();
+
+ // A change to the list requires us to scan for deletions (stop running syncs)
+ // At startup, we want to see what accounts exist and cache them
+ AccountList currentAccounts = new AccountList();
+ Cursor c = getContentResolver().query(Account.CONTENT_URI,
+ Account.CONTENT_PROJECTION, null, null, null);
+ try {
+ collectEasAccounts(c, currentAccounts);
+ synchronized (sAccountList) {
+ for (Account account : sAccountList) {
+ // Ignore accounts not fully created
+ if ((account.mFlags & Account.FLAGS_INCOMPLETE) != 0) {
+ log("Account observer noticed incomplete account; ignoring");
+ continue;
+ } else if (!currentAccounts.contains(account.mId)) {
+ // This is a deletion; shut down any account-related syncs
+ stopAccountSyncs(account.mId, true);
+ // Delete this from AccountManager...
+ android.accounts.Account acct = new android.accounts.Account(
+ account.mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
+ AccountManager.get(SyncManager.this).removeAccount(acct, null, null);
+ mSyncableEasMailboxSelector = null;
+ mEasAccountSelector = null;
+ } else {
+ // An account has changed
+ Account updatedAccount = Account.restoreAccountWithId(context,
+ account.mId);
+ if (account.mSyncInterval != updatedAccount.mSyncInterval
+ || account.mSyncLookback != updatedAccount.mSyncLookback) {
+ // Set pushable boxes' interval to the interval of the Account
+ ContentValues cv = new ContentValues();
+ cv.put(MailboxColumns.SYNC_INTERVAL, updatedAccount.mSyncInterval);
+ getContentResolver().update(Mailbox.CONTENT_URI, cv,
+ WHERE_IN_ACCOUNT_AND_PUSHABLE, new String[] {
+ Long.toString(account.mId)
+ });
+ // Stop all current syncs; the appropriate ones will restart
+ log("Account " + account.mDisplayName + " changed; stop syncs");
+ stopAccountSyncs(account.mId, true);
+ }
+
+ // See if this account is no longer on security hold
+ if (onSecurityHold(account) && !onSecurityHold(updatedAccount)) {
+ releaseSyncHolds(SyncManager.this,
+ AbstractSyncService.EXIT_SECURITY_FAILURE, account);
+ }
+
+ // Put current values into our cached account
+ account.mSyncInterval = updatedAccount.mSyncInterval;
+ account.mSyncLookback = updatedAccount.mSyncLookback;
+ account.mFlags = updatedAccount.mFlags;
+ }
+ }
+ // Look for new accounts
+ for (Account account : currentAccounts) {
+ if (!sAccountList.contains(account.mId)) {
+ // This is an addition; create our magic hidden mailbox...
+ log("Account observer found new account: " + account.mDisplayName);
+ addAccountMailbox(account.mId);
+ // Don't forget to cache the HostAuth
+ HostAuth ha = HostAuth.restoreHostAuthWithId(getContext(),
+ account.mHostAuthKeyRecv);
+ account.mHostAuthRecv = ha;
+ sAccountList.add(account);
+ mSyncableEasMailboxSelector = null;
+ mEasAccountSelector = null;
+ }
+ }
+ // Finally, make sure our account list is up to date
+ sAccountList.clear();
+ sAccountList.addAll(currentAccounts);
+ }
+ } finally {
+ c.close();
+ }
+
+ // See if there's anything to do...
+ kick("account changed");
+ }
+
@Override
public void onChange(boolean selfChange) {
new Thread(new Runnable() {
public void run() {
- maybeStartSyncManagerThread();
- Context context = getContext();
-
- // A change to the list requires us to scan for deletions (stop running syncs)
- // At startup, we want to see what accounts exist and cache them
- AccountList currentAccounts = new AccountList();
- Cursor c = getContentResolver().query(Account.CONTENT_URI,
- Account.CONTENT_PROJECTION, null, null, null);
- try {
- collectEasAccounts(c, currentAccounts);
- for (Account account : mAccounts) {
- // Ignore accounts not fully created
- if ((account.mFlags & Account.FLAGS_INCOMPLETE) != 0) {
- log("Account observer noticed incomplete account; ignoring");
- continue;
- } else if (!currentAccounts.contains(account.mId)) {
- // This is a deletion; shut down any account-related syncs
- stopAccountSyncs(account.mId, true);
- // Delete this from AccountManager...
- android.accounts.Account acct =
- new android.accounts.Account(account.mEmailAddress,
- Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
- AccountManager.get(SyncManager.this).removeAccount(acct,
- null, null);
- mSyncableEasMailboxSelector = null;
- mEasAccountSelector = null;
- } else {
- // An account has changed
- Account updatedAccount =
- Account.restoreAccountWithId(context, account.mId);
- if (account.mSyncInterval != updatedAccount.mSyncInterval ||
- account.mSyncLookback != updatedAccount.mSyncLookback) {
- // Set pushable boxes' interval to the interval of the Account
- ContentValues cv = new ContentValues();
- cv.put(MailboxColumns.SYNC_INTERVAL,
- updatedAccount.mSyncInterval);
- getContentResolver().update(Mailbox.CONTENT_URI, cv,
- WHERE_IN_ACCOUNT_AND_PUSHABLE,
- new String[] {Long.toString(account.mId)});
- // Stop all current syncs; the appropriate ones will restart
- log("Account " + account.mDisplayName + " changed; stop syncs");
- stopAccountSyncs(account.mId, true);
- }
-
- // See if this account is no longer on security hold
- if (onSecurityHold(account) && !onSecurityHold(updatedAccount)) {
- releaseSyncHolds(SyncManager.this,
- AbstractSyncService.EXIT_SECURITY_FAILURE, account);
- }
-
- // Put current values into our cached account
- account.mSyncInterval = updatedAccount.mSyncInterval;
- account.mSyncLookback = updatedAccount.mSyncLookback;
- account.mFlags = updatedAccount.mFlags;
- }
- }
-
- // Look for new accounts
- for (Account account: currentAccounts) {
- if (!mAccounts.contains(account.mId)) {
- // This is an addition; create our magic hidden mailbox...
- log("Account observer found new account: " + account.mDisplayName);
- addAccountMailbox(account.mId);
- // Don't forget to cache the HostAuth
- HostAuth ha = HostAuth.restoreHostAuthWithId(getContext(),
- account.mHostAuthKeyRecv);
- account.mHostAuthRecv = ha;
- mAccounts.add(account);
- mSyncableEasMailboxSelector = null;
- mEasAccountSelector = null;
- }
- }
-
- // Finally, make sure mAccounts is up to date
- mAccounts = currentAccounts;
- } finally {
- c.close();
- }
-
- // See if there's anything to do...
- kick("account changed");
+ onAccountChanged();
}}).start();
}
@@ -799,10 +806,8 @@
return sCallbackProxy;
}
- static public AccountList getAccountList() {
- SyncManager syncManager = INSTANCE;
- if (syncManager == null) return EMPTY_ACCOUNT_LIST;
- return syncManager.mAccountObserver.mAccounts;
+ static public Account getAccountById(long accountId) {
+ return sAccountList.getById(accountId);
}
static public String getEasAccountSelector() {
@@ -811,10 +816,6 @@
return syncManager.mAccountObserver.getAccountKeyWhere();
}
- private Account getAccountById(long accountId) {
- return mAccountObserver.mAccounts.getById(accountId);
- }
-
public class SyncStatus {
static public final int NOT_RUNNING = 0;
static public final int DIED = 1;
@@ -844,6 +845,28 @@
}
}
+ private void logSyncHolds() {
+ if (Eas.USER_LOG && !mSyncErrorMap.isEmpty()) {
+ log("Sync holds:");
+ long time = System.currentTimeMillis();
+ synchronized (sSyncLock) {
+ for (long mailboxId : mSyncErrorMap.keySet()) {
+ Mailbox m = Mailbox.restoreMailboxWithId(this, mailboxId);
+ if (m == null) {
+ log("Mailbox " + mailboxId + " no longer exists");
+ } else {
+ SyncError error = mSyncErrorMap.get(mailboxId);
+ log("Mailbox " + m.mDisplayName + ", error = " + error.reason
+ + ", fatal = " + error.fatal);
+ if (error.holdEndTime > 0) {
+ log("Hold ends in " + ((error.holdEndTime - time) / 1000) + "s");
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Release a specific type of hold (the reason) for the specified Account; if the account
* is null, mailboxes from all accounts with the specified hold will be released
@@ -856,7 +879,7 @@
}
private void releaseSyncHoldsImpl(Context context, int reason, Account account) {
- synchronized(sSyncToken) {
+ synchronized(sSyncLock) {
ArrayList<Long> releaseList = new ArrayList<Long>();
for (long mailboxId: mSyncErrorMap.keySet()) {
if (account != null) {
@@ -901,9 +924,10 @@
if (syncManager != null) {
android.accounts.Account[] accountMgrList = AccountManager.get(syncManager)
.getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
- AccountList providerList = getAccountList();
- reconcileAccountsWithAccountManager(syncManager,
- providerList, accountMgrList, false, mResolver);
+ synchronized (sAccountList) {
+ reconcileAccountsWithAccountManager(syncManager, sAccountList,
+ accountMgrList, false, mResolver);
+ }
}
}
}.start();
@@ -1117,7 +1141,7 @@
}
private void stopAccountSyncs(long acctId, boolean includeAccountMailbox) {
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
List<Long> deletedBoxes = new ArrayList<Long>();
for (Long mid : mServiceMap.keySet()) {
Mailbox box = Mailbox.restoreMailboxWithId(this, mid);
@@ -1159,7 +1183,7 @@
Long.toString(Mailbox.TYPE_EAS_ACCOUNT_MAILBOX)}, null);
try {
if (c.moveToFirst()) {
- synchronized(sSyncToken) {
+ synchronized(sSyncLock) {
Mailbox m = new Mailbox().restore(c);
Account acct = Account.restoreAccountWithId(context, accountId);
if (acct == null) {
@@ -1421,10 +1445,11 @@
* Make our sync settings match those of AccountManager
*/
private void checkPIMSyncSettings() {
- List<Account> easAccounts = getAccountList();
- for (Account easAccount: easAccounts) {
- updatePIMSyncSettings(easAccount, Mailbox.TYPE_CONTACTS, ContactsContract.AUTHORITY);
- updatePIMSyncSettings(easAccount, Mailbox.TYPE_CALENDAR, Calendar.AUTHORITY);
+ synchronized (sAccountList) {
+ for (Account account : sAccountList) {
+ updatePIMSyncSettings(account, Mailbox.TYPE_CONTACTS, ContactsContract.AUTHORITY);
+ updatePIMSyncSettings(account, Mailbox.TYPE_CALENDAR, Calendar.AUTHORITY);
+ }
}
}
@@ -1512,15 +1537,6 @@
}
}
- private void releaseConnectivityLock(String reason) {
- // Clear i/o error holds for all accounts
- releaseSyncHolds(this, AbstractSyncService.EXIT_IO_ERROR, null);
- synchronized (sConnectivityLock) {
- sConnectivityLock.notifyAll();
- }
- kick(reason);
- }
-
public class ConnectivityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1533,7 +1549,10 @@
if (state == State.CONNECTED) {
info += " CONNECTED";
log(info);
- releaseConnectivityLock("connected");
+ synchronized (sConnectivityLock) {
+ sConnectivityLock.notifyAll();
+ }
+ kick("connected");
} else if (state == State.DISCONNECTED) {
info += " DISCONNECTED";
log(info);
@@ -1552,8 +1571,10 @@
// Otherwise, stop all syncs
} else {
log("Background data off: stop all syncs");
- for (Account account: SyncManager.getAccountList())
- SyncManager.stopAccountSyncs(account.mId);
+ synchronized (sAccountList) {
+ for (Account account : sAccountList)
+ SyncManager.stopAccountSyncs(account.mId);
+ }
}
}
}
@@ -1566,7 +1587,8 @@
* @param m the Mailbox on which the service will operate
*/
private void startServiceThread(AbstractSyncService service, Mailbox m) {
- synchronized (sSyncToken) {
+ if (m == null) return;
+ synchronized (sSyncLock) {
String mailboxName = m.mDisplayName;
String accountName = service.mAccount.mDisplayName;
Thread thread = new Thread(service, mailboxName + "(" + accountName + ")");
@@ -1601,8 +1623,8 @@
private void requestSync(Mailbox m, int reason, Request req) {
// Don't sync if there's no connectivity
- if (sConnectivityHold) return;
- synchronized (sSyncToken) {
+ if (sConnectivityHold || (m == null)) return;
+ synchronized (sSyncLock) {
Account acct = Account.restoreAccountWithId(this, m.mAccountKey);
if (acct != null) {
// Always make sure there's not a running instance of this service
@@ -1621,7 +1643,7 @@
}
private void stopServiceThreads() {
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
ArrayList<Long> toStop = new ArrayList<Long>();
// Keep track of which services to stop
@@ -1645,22 +1667,27 @@
}
private void waitForConnectivity() {
- int cnt = 0;
+ boolean waiting = false;
+ ConnectivityManager cm =
+ (ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
while (!mStop) {
- ConnectivityManager cm =
- (ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
- //log("NetworkInfo: " + info.getTypeName() + ", " + info.getState().name());
+ // We're done if there's an active network
+ if (waiting) {
+ // If we've been waiting, release any I/O error holds
+ releaseSyncHolds(this, AbstractSyncService.EXIT_IO_ERROR, null);
+ // And log what's still being held
+ logSyncHolds();
+ }
return;
} else {
-
- // If we're waiting for the long haul, shut down running service threads
- if (++cnt > 1) {
+ // If this is our first time through the loop, shut down running service threads
+ if (!waiting) {
+ waiting = true;
stopServiceThreads();
}
-
- // Wait until a network is connected, but let the device sleep
+ // Wait until a network is connected (or 10 mins), but let the device sleep
// We'll set an alarm just in case we don't get notified (bugs happen)
synchronized (sConnectivityLock) {
runAsleep(SYNC_MANAGER_ID, CONNECTIVITY_WAIT_TIME+5*SECONDS);
@@ -1670,6 +1697,7 @@
sConnectivityLock.wait(CONNECTIVITY_WAIT_TIME);
log("Connectivity lock released...");
} catch (InterruptedException e) {
+ // This is fine; we just go around the loop again
} finally {
sConnectivityHold = false;
}
@@ -1809,7 +1837,7 @@
private long checkMailboxes () {
// First, see if any running mailboxes have been deleted
ArrayList<Long> deletedMailboxes = new ArrayList<Long>();
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
for (long mailboxId: mServiceMap.keySet()) {
Mailbox m = Mailbox.restoreMailboxWithId(this, mailboxId);
if (m == null) {
@@ -1840,7 +1868,10 @@
// Start up threads that need it; use a query which finds eas mailboxes where the
// the sync interval is not "never". This is the set of mailboxes that we control
- if (mAccountObserver == null) return nextWait;
+ if (mAccountObserver == null) {
+ log("mAccountObserver null; service died??");
+ return nextWait;
+ }
Cursor c = getContentResolver().query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
mAccountObserver.getSyncableEasMailboxWhere(), null, null);
@@ -1851,7 +1882,7 @@
while (c.moveToNext()) {
long mid = c.getLong(Mailbox.CONTENT_ID_COLUMN);
AbstractSyncService service = null;
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
service = mServiceMap.get(mid);
}
if (service == null) {
@@ -1952,8 +1983,12 @@
}
} else {
Thread thread = service.mThread;
- // Look for threads that have died but aren't in an error state
- if (thread != null && !thread.isAlive() && !mSyncErrorMap.containsKey(mid)) {
+ // Look for threads that have died and remove them from the map
+ if (thread != null && !thread.isAlive()) {
+ if (Eas.USER_LOG) {
+ log("Dead thread, mailbox released: " +
+ c.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN));
+ }
releaseMailbox(mid);
// Restart this if necessary
if (nextWait > 3*SECONDS) {
@@ -1995,7 +2030,7 @@
Mailbox m = Mailbox.restoreMailboxWithId(syncManager, mailboxId);
// Never allow manual start of Drafts or Outbox via serviceRequest
if (m == null || m.mType == Mailbox.TYPE_DRAFTS || m.mType == Mailbox.TYPE_OUTBOX) {
- log("Ignoring serviceRequest for drafts/outbox");
+ log("Ignoring serviceRequest for drafts/outbox/null mailbox");
return;
}
try {
@@ -2073,7 +2108,7 @@
static public AbstractSyncService startManualSync(long mailboxId, int reason, Request req) {
SyncManager syncManager = INSTANCE;
if (syncManager == null) return null;
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
if (syncManager.mServiceMap.get(mailboxId) == null) {
syncManager.mSyncErrorMap.remove(mailboxId);
Mailbox m = Mailbox.restoreMailboxWithId(syncManager, mailboxId);
@@ -2090,7 +2125,7 @@
static private void stopManualSync(long mailboxId) {
SyncManager syncManager = INSTANCE;
if (syncManager == null) return;
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
AbstractSyncService svc = syncManager.mServiceMap.get(mailboxId);
if (svc != null) {
log("Stopping sync for " + svc.mMailboxName);
@@ -2123,7 +2158,7 @@
static public void accountUpdated(long acctId) {
SyncManager syncManager = INSTANCE;
if (syncManager == null) return;
- synchronized (sSyncToken) {
+ synchronized (sSyncLock) {
for (AbstractSyncService svc : syncManager.mServiceMap.values()) {
if (svc.mAccount.mId == acctId) {
svc.mAccount = Account.restoreAccountWithId(syncManager, acctId);
@@ -2139,7 +2174,7 @@
static public void removeFromSyncErrorMap(long mailboxId) {
SyncManager syncManager = INSTANCE;
if (syncManager == null) return;
- synchronized(sSyncToken) {
+ synchronized(sSyncLock) {
syncManager.mSyncErrorMap.remove(mailboxId);
}
}
@@ -2153,7 +2188,7 @@
static public void done(AbstractSyncService svc) {
SyncManager syncManager = INSTANCE;
if (syncManager == null) return;
- synchronized(sSyncToken) {
+ synchronized(sSyncLock) {
long mailboxId = svc.mMailboxId;
HashMap<Long, SyncError> errorMap = syncManager.mSyncErrorMap;
SyncError syncError = errorMap.get(mailboxId);
diff --git a/src/com/android/exchange/adapter/CalendarSyncAdapter.java b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
index 78152a0..580c0f0 100644
--- a/src/com/android/exchange/adapter/CalendarSyncAdapter.java
+++ b/src/com/android/exchange/adapter/CalendarSyncAdapter.java
@@ -1622,7 +1622,8 @@
for (String removedAttendee: originalAttendeeList) {
// Send a cancellation message to each of them
msg = CalendarUtilities.createMessageForEventId(mContext, eventId,
- Message.FLAG_OUTGOING_MEETING_CANCEL, clientId, mAccount);
+ Message.FLAG_OUTGOING_MEETING_CANCEL, clientId, mAccount,
+ false);
if (msg != null) {
// Just send it to the removed attendee
msg.mTo = removedAttendee;
diff --git a/src/com/android/exchange/adapter/ContactsSyncAdapter.java b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
index ccfaa32..ed45265 100644
--- a/src/com/android/exchange/adapter/ContactsSyncAdapter.java
+++ b/src/com/android/exchange/adapter/ContactsSyncAdapter.java
@@ -59,7 +59,7 @@
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
-import android.util.base64.Base64;
+import android.util.Base64;
import java.io.IOException;
import java.io.InputStream;
@@ -1017,7 +1017,10 @@
}
// Note Email.TYPE could be ANY type column; they are all defined in
// the private CommonColumns class in ContactsContract
- } else if (type < 0 || cv.getAsInteger(Email.TYPE) == type) {
+ // We'll accept either type < 0 (don't care), cv doesn't have a type,
+ // or the types are equal
+ } else if (type < 0 || !cv.containsKey(Email.TYPE) ||
+ cv.getAsInteger(Email.TYPE) == type) {
result = namedContentValues;
}
}
diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java
index e7caf86..5cade38 100644
--- a/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -45,7 +45,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
-import android.util.base64.Base64;
+import android.util.Base64;
import android.webkit.MimeTypeMap;
import java.io.IOException;
diff --git a/src/com/android/exchange/utility/CalendarUtilities.java b/src/com/android/exchange/utility/CalendarUtilities.java
index d8216ac..4981622 100644
--- a/src/com/android/exchange/utility/CalendarUtilities.java
+++ b/src/com/android/exchange/utility/CalendarUtilities.java
@@ -43,10 +43,9 @@
import android.provider.Calendar.Calendars;
import android.provider.Calendar.Events;
import android.provider.Calendar.EventsEntity;
-import android.provider.Calendar.Reminders;
import android.text.format.Time;
import android.util.Log;
-import android.util.base64.Base64;
+import android.util.Base64;
import java.io.IOException;
import java.text.DateFormat;
@@ -1210,6 +1209,12 @@
*/
static public EmailContent.Message createMessageForEntity(Context context, Entity entity,
int messageFlag, String uid, Account account) {
+ return createMessageForEntity(context, entity, messageFlag, uid, account,
+ true /*requireAddressees*/);
+ }
+
+ static public EmailContent.Message createMessageForEntity(Context context, Entity entity,
+ int messageFlag, String uid, Account account, boolean requireAddressees) {
ContentValues entityValues = entity.getEntityValues();
ArrayList<NamedContentValues> subValues = entity.getSubValues();
boolean isException = entityValues.containsKey(Events.ORIGINAL_EVENT);
@@ -1338,56 +1343,42 @@
if (titleId != 0) {
msg.mSubject = resources.getString(titleId, title);
}
+
+ // Build the text for the message, starting with an initial line describing the
+ // exception (if this is one)
+ StringBuilder sb = new StringBuilder();
+ if (isException && method.equals("REQUEST")) {
+ // Add the line, depending on whether this is a cancellation or update
+ Date date = new Date(entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME));
+ String dateString = DateFormat.getDateInstance().format(date);
+ if (titleId == R.string.meeting_canceled) {
+ sb.append(resources.getString(R.string.exception_cancel, dateString));
+ } else {
+ sb.append(resources.getString(R.string.exception_updated, dateString));
+ }
+ sb.append("\n\n");
+ }
+ String text =
+ CalendarUtilities.buildMessageTextFromEntityValues(context, entityValues, sb);
+
+ if (text.length() > 0) {
+ ics.writeTag("DESCRIPTION", text);
+ }
+ // And store the message text
+ msg.mText = text;
if (method.equals("REQUEST")) {
if (entityValues.containsKey(Events.ALL_DAY)) {
Integer ade = entityValues.getAsInteger(Events.ALL_DAY);
ics.writeTag("X-MICROSOFT-CDO-ALLDAYEVENT", ade == 0 ? "FALSE" : "TRUE");
}
- // Build the text for the message, starting with an initial line describing the
- // exception
- StringBuilder sb = new StringBuilder();
- if (isException) {
- // Add the line, depending on whether this is a cancellation or update
- Date date = new Date(entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME));
- String dateString = DateFormat.getDateInstance().format(date);
- if (titleId == R.string.meeting_canceled) {
- sb.append(resources.getString(R.string.exception_cancel, dateString));
- } else {
- sb.append(resources.getString(R.string.exception_updated, dateString));
- }
- sb.append("\n\n");
- }
- String text =
- CalendarUtilities.buildMessageTextFromEntityValues(context, entityValues, sb);
-
- // If we've got anything here, write it into the ics file
- if (text.length() > 0) {
- ics.writeTag("DESCRIPTION", text);
- }
- // And store the message text
- msg.mText = text;
-
String rrule = entityValues.getAsString(Events.RRULE);
if (rrule != null) {
ics.writeTag("RRULE", rrule);
}
- // Handle associated data EXCEPT for attendees, which have to be grouped
- for (NamedContentValues ncv: subValues) {
- Uri ncvUri = ncv.uri;
- if (ncvUri.equals(Reminders.CONTENT_URI)) {
- // TODO Consider sending alarm information in the meeting request, though
- // it's not obviously appropriate (i.e. telling the user what alarm to use)
- // This should be for REQUEST only
- // Here's what the VALARM would look like:
- // BEGIN:VALARM
- // ACTION:DISPLAY
- // DESCRIPTION:REMINDER
- // TRIGGER;RELATED=START:-PT15M
- // END:VALARM
- }
- }
+ // If we decide to send alarm information in the meeting request ics file,
+ // handle it here by looping through the subvalues
}
// Handle attendee data here; determine "to" list and add ATTENDEE tags to ics
@@ -1466,8 +1457,9 @@
}
}
- // If we have no "to" list, we're done
- if (toList.isEmpty()) return null;
+ // If we have no "to" list and addressees are required (the default), we're done
+ if (toList.isEmpty() && requireAddressees) return null;
+
// Write out the "to" list
Address[] toArray = new Address[toList.size()];
int i = 0;
@@ -1513,11 +1505,20 @@
* @param messageFlag the Message.FLAG_XXX constant indicating the type of email to be sent
* @param the unique id of this Event, or null if it can be retrieved from the Event
* @param the user's account
+ * @param requireAddressees if true (the default), no Message is returned if there aren't any
+ * addressees; if false, return the Message regardless (addressees will be filled in later)
* @return a Message with many fields pre-filled (more later)
* @throws RemoteException if there is an issue retrieving the Event from CalendarProvider
*/
static public EmailContent.Message createMessageForEventId(Context context, long eventId,
int messageFlag, String uid, Account account) throws RemoteException {
+ return createMessageForEventId(context, eventId, messageFlag, uid, account,
+ true /*requireAddressees*/);
+ }
+
+ static public EmailContent.Message createMessageForEventId(Context context, long eventId,
+ int messageFlag, String uid, Account account, boolean requireAddressees)
+ throws RemoteException {
ContentResolver cr = context.getContentResolver();
EntityIterator eventIterator =
EventsEntity.newEntityIterator(
@@ -1527,7 +1528,8 @@
try {
while (eventIterator.hasNext()) {
Entity entity = eventIterator.next();
- return createMessageForEntity(context, entity, messageFlag, uid, account);
+ return createMessageForEntity(context, entity, messageFlag, uid, account,
+ requireAddressees);
}
} finally {
eventIterator.close();
diff --git a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
index 53c0925..764a8df 100644
--- a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
+++ b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
@@ -244,8 +244,11 @@
// Now check some of the fields of the message
assertEquals(Address.pack(new Address[] {new Address(ORGANIZER)}), msg.mTo);
- String accept = getContext().getResources().getString(R.string.meeting_accepted, title);
+ Resources resources = getContext().getResources();
+ String accept = resources.getString(R.string.meeting_accepted, title);
assertEquals(accept, msg.mSubject);
+ assertNotNull(msg.mText);
+ assertTrue(msg.mText.contains(resources.getString(R.string.meeting_where, "")));
// And make sure we have an attachment
assertNotNull(msg.mAttachments);