From 1615abff3503968fa690d24646016b69768c84c9 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 24 Aug 2010 13:29:00 -0700 Subject: [PATCH 01/10] Explicitly set theme to all activities. The default theme change to Holo caused a crash in MessageCompose, because Holo has actionbar, which MessageCompose doesn't expect at this point. Explicitly set theme to prevent crashes. Bug 2945294. Change-Id: Ic5f833690eeb1048e8c4c3ac7a42c2d454c61876 --- AndroidManifest.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 47afa28c7..f4d45df47 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -56,8 +56,16 @@ android:description="@string/permission_access_provider_desc"/> + + android:name="Email" + android:theme="@android:style/Theme" + > From 696d7ddcd9e171d8302154d4b06a1106d71c22a0 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Mon, 22 Nov 2010 11:36:17 -0800 Subject: [PATCH 02/10] Correctly set intent to new message notification Bug 3219763 Change-Id: I7826e762713228ee4ca1ca1d57b4d7603df81a66 --- src/com/android/email/NotificationController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/email/NotificationController.java b/src/com/android/email/NotificationController.java index 4ff00c1c3..0a120a367 100644 --- a/src/com/android/email/NotificationController.java +++ b/src/com/android/email/NotificationController.java @@ -31,7 +31,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.media.AudioManager; import android.net.Uri; import android.text.TextUtils; @@ -186,7 +185,8 @@ public class NotificationController { .setTicker(mContext.getString(R.string.notification_new_title)) .setLargeIcon(senderPhoto) .setContentTitle(notificationTitle) - .setContentText(subject + "\n" + numNewMessages); + .setContentText(subject + "\n" + numNewMessages) + .setContentIntent(contentIntent); Notification notification = builder.getNotification(); From 6a208f025d98c3ab1e76d585b4da39bfc0e92aac Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Thu, 2 Dec 2010 12:08:58 -0800 Subject: [PATCH 03/10] Workaround for improper timeout for Ping commands * We're seeing our ping timeouts complete after 30 seconds (although set to 5-17 minutes), due to the fact that our reused sockets are not having their timeouts reset (i.e. they use the original value of 30 seconds from when the socket was first created) * Until the underlying issue is resolved, we'll avoid reusing sockets for Ping commands Bug: 3241899 Change-Id: I90b53c0d28b866a91507efafacbb3c4c0df2324c --- src/com/android/exchange/EasSyncService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index 2504b9415..d633e9f4e 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -1309,7 +1309,12 @@ public class EasSyncService extends AbstractSyncService { HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, timeout); HttpConnectionParams.setSocketBufferSize(params, 8192); - HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params); + //HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params); + // STOPSHIP Replace this line with the previous, commented-out line + // The underlying problem (socket timeout is not being updated for reused, pooled + // connections) needs to be fixed prior to ship + HttpClient client = new DefaultHttpClient( + timeout == COMMAND_TIMEOUT ? getClientConnectionManager() : null, params); return client; } From e5e51fe97418db39c6b7247019e24b9c46c90639 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Fri, 3 Dec 2010 23:20:02 -0800 Subject: [PATCH 04/10] Merge I31258a5fbcca1f489c8bf6fb2ed8f3dcad5d2e26 into master * This is the fix to bug 3008626 that was created in Gingerbread and backported to Froyo for an MR release * The bug prevented validation of EAS w/ Exchange Server 2010 SP1 * For some reason, this fix did not get merged forward into master resulting in the bug referenced below Bug: 3254512 Change-Id: I4e48a8f95b31048f09a036cc16db867da4116f04 --- src/com/android/email/mail/store/ExchangeStore.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/com/android/email/mail/store/ExchangeStore.java b/src/com/android/email/mail/store/ExchangeStore.java index dccf89f18..0d06e7693 100644 --- a/src/com/android/email/mail/store/ExchangeStore.java +++ b/src/com/android/email/mail/store/ExchangeStore.java @@ -21,6 +21,8 @@ import com.android.email.mail.Folder; import com.android.email.mail.MessagingException; import com.android.email.mail.Store; import com.android.email.mail.StoreSynchronizer; +import com.android.email.service.EmailServiceProxy; +import com.android.email.service.IEmailService; import android.content.Context; import android.os.Bundle; @@ -200,8 +202,13 @@ public class ExchangeStore extends Store { boolean tssl = uri.getScheme().contains("+trustallcerts"); try { int port = ssl ? 443 : 80; - return ExchangeUtils.getExchangeService(mContext, null) - .validate("eas", mHost, mUsername, mPassword, port, ssl, tssl); + IEmailService svc = ExchangeUtils.getExchangeService(mContext, null); + // Use a longer timeout for the validate command. Note that the instanceof check + // shouldn't be necessary; we'll do it anyway, just to be safe + if (svc instanceof EmailServiceProxy) { + ((EmailServiceProxy)svc).setTimeout(90); + } + return svc.validate("eas", mHost, mUsername, mPassword, port, ssl, tssl); } catch (RemoteException e) { throw new MessagingException("Call to validate generated an exception", e); } From 07d5b33d78865d947b36934df69d67a4253c76b2 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 20 Dec 2010 23:14:01 -0800 Subject: [PATCH 05/10] Update to use new Loader callbacks. Change-Id: Ib478c2f2314bbae4a380539e22892d85dd9a34b5 --- .../android/email/activity/ContactStatusLoader.java | 6 +++--- .../android/email/activity/MoveMessageToDialog.java | 6 +++--- src/com/android/email/data/MailboxAccountLoader.java | 6 +++--- .../android/email/data/ThrottlingCursorLoader.java | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/com/android/email/activity/ContactStatusLoader.java b/src/com/android/email/activity/ContactStatusLoader.java index 9b879ccd0..bef168f1e 100644 --- a/src/com/android/email/activity/ContactStatusLoader.java +++ b/src/com/android/email/activity/ContactStatusLoader.java @@ -141,18 +141,18 @@ public class ContactStatusLoader extends AsyncTaskLoader Date: Fri, 14 Jan 2011 15:33:44 -0800 Subject: [PATCH 06/10] When syncing folders, read all data first, then process Bug: 3353035 Change-Id: I80212b225eae48d0351f47f0d601f77578d2fc96 --- src/com/android/exchange/EasSyncService.java | 8 ++ src/com/android/exchange/ExchangeService.java | 2 +- .../exchange/adapter/FolderSyncParser.java | 107 ++++++++++-------- 3 files changed, 69 insertions(+), 48 deletions(-) diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index 3b0c173a8..2a1560d24 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -1883,6 +1883,7 @@ public class EasSyncService extends AbstractSyncService { ArrayList readyMailboxes = new ArrayList(); ArrayList notReadyMailboxes = new ArrayList(); int pingWaitCount = 0; + long inboxId = -1; while ((System.currentTimeMillis() < endTime) && !mStop) { // Count of pushable mailboxes @@ -1898,6 +1899,10 @@ public class EasSyncService extends AbstractSyncService { AND_FREQUENCY_PING_PUSH_AND_NOT_ACCOUNT_MAILBOX, null, null); notReadyMailboxes.clear(); readyMailboxes.clear(); + // Look for an inbox, and remember its id + if (inboxId == -1) { + inboxId = Mailbox.findMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_INBOX); + } try { // Loop through our pushed boxes seeing what is available to push while (c.moveToNext()) { @@ -2082,6 +2087,9 @@ public class EasSyncService extends AbstractSyncService { // we're in one of the other possible states. userLog("pingLoop waiting for initial sync of ", uninitCount, " box(es)"); sleep(10*SECONDS, true); + } else if (inboxId == -1) { + // In this case, we're still syncing mailboxes, so sleep for only a short time + sleep(45*SECONDS, true); } else { // We've got nothing to do, so we'll check again in 20 minutes at which time // we'll update the folder list, check for policy changes and/or remote wipe, etc. diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java index 3d1466b93..e9e2af7e4 100644 --- a/src/com/android/exchange/ExchangeService.java +++ b/src/com/android/exchange/ExchangeService.java @@ -2336,7 +2336,7 @@ public class ExchangeService extends Service implements Runnable { } // DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP - static private void stopManualSync(long mailboxId) { + static public void stopManualSync(long mailboxId) { ExchangeService exchangeService = INSTANCE; if (exchangeService == null) return; synchronized (sSyncLock) { diff --git a/src/com/android/exchange/adapter/FolderSyncParser.java b/src/com/android/exchange/adapter/FolderSyncParser.java index 0d36a0e18..ae20fd958 100644 --- a/src/com/android/exchange/adapter/FolderSyncParser.java +++ b/src/com/android/exchange/adapter/FolderSyncParser.java @@ -20,11 +20,11 @@ package com.android.exchange.adapter; import com.android.email.Utility; import com.android.email.provider.AttachmentProvider; import com.android.email.provider.EmailContent; -import com.android.email.provider.EmailProvider; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.AccountColumns; import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.EmailContent.MailboxColumns; +import com.android.email.provider.EmailProvider; import com.android.exchange.Eas; import com.android.exchange.ExchangeService; import com.android.exchange.MockParserStream; @@ -361,9 +361,9 @@ public class FolderSyncParser extends AbstractSyncParser { } } - private void commitMailboxes(ArrayList validMailboxes, + private boolean commitMailboxes(ArrayList validMailboxes, ArrayList userMailboxes, HashMap mailboxMap, - ArrayList ops) throws IOException { + ArrayList ops) { // Go through the generic user mailboxes; we'll call them valid if any parent is valid for (Mailbox m: userMailboxes) { @@ -388,44 +388,26 @@ public class FolderSyncParser extends AbstractSyncParser { // If it IS repeatable, there's no good result, since the folder list will be invalid try { mContentResolver.applyBatch(EmailProvider.EMAIL_AUTHORITY, mOperations); + return true; } catch (RemoteException e) { - throw new IOException("RemoteException committing folders."); + userLog("RemoteException in commitMailboxes"); + return false; } catch (OperationApplicationException e) { - throw new IOException("OperationApplicationException committing folders."); + userLog("OperationApplicationException in commitMailboxes"); + return false; } } - public void changesParser(ArrayList ops, boolean initialSync) - throws IOException { - // Mailboxes that we known contain email - ArrayList validMailboxes = new ArrayList(); - // Mailboxes that we're unsure about - ArrayList userMailboxes = new ArrayList(); - // Maps folder serverId to mailbox type - HashMap mailboxMap = new HashMap(); + public void changesParser(final ArrayList ops, + final boolean initialSync) throws IOException { + // Array of added mailboxes + final ArrayList addMailboxes = new ArrayList(); - int mailboxAddCount = 0; while (nextTag(Tags.FOLDER_CHANGES) != END) { if (tag == Tags.FOLDER_ADD) { Mailbox mailbox = addParser(); if (mailbox != null) { - // Save away the type of this folder - mailboxMap.put(mailbox.mServerId, mailbox); - // And add the mailbox to the proper list - if (type == USER_MAILBOX_TYPE) { - userMailboxes.add(mailbox); - } else { - validMailboxes.add(mailbox); - } - // On initial sync, we commit what we have every 20 mailboxes - if (initialSync && (++mailboxAddCount == MAILBOX_COMMIT_SIZE)) { - commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops); - // Clear our arrays to prepare for more - userMailboxes.clear(); - validMailboxes.clear(); - ops.clear(); - mailboxAddCount = 0; - } + addMailboxes.add(mailbox); } } else if (tag == Tags.FOLDER_DELETE) { deleteParser(ops); @@ -437,22 +419,53 @@ public class FolderSyncParser extends AbstractSyncParser { skipTag(); } - // The mock stream is used for junit tests, so that the parsing code can be tested - // separately from the provider code. - // TODO Change tests to not require this; remove references to the mock stream - if (mMock != null) { - mMock.setResult(null); - return; - } - - // Commit the sync key and mailboxes - ContentValues cv = new ContentValues(); - cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); - ops.add(ContentProviderOperation.newUpdate( - ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId)) - .withValues(cv) - .build()); - commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops); + Utility.runAsync(new Runnable() { + @Override + public void run() { + // Synchronize on the parser to prevent this being run multiple times concurrently + // (an extremely unlikely event, but nonetheless possible) + synchronized (FolderSyncParser.this) { + // Mailboxes that we known contain email + ArrayList validMailboxes = new ArrayList(); + // Mailboxes that we're unsure about + ArrayList userMailboxes = new ArrayList(); + // Maps folder serverId to mailbox type + HashMap mailboxMap = new HashMap(); + int mailboxCommitCount = 0; + for (Mailbox mailbox : addMailboxes) { + // Save away the type of this folder + mailboxMap.put(mailbox.mServerId, mailbox); + // And add the mailbox to the proper list + if (type == USER_MAILBOX_TYPE) { + userMailboxes.add(mailbox); + } else { + validMailboxes.add(mailbox); + } + // On initial sync, we commit what we have every 20 mailboxes + if (initialSync && (++mailboxCommitCount == MAILBOX_COMMIT_SIZE)) { + if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) { + mService.stop(); + return; + } + // Clear our arrays to prepare for more + userMailboxes.clear(); + validMailboxes.clear(); + ops.clear(); + mailboxCommitCount = 0; + } + } + // Commit the sync key and mailboxes + ContentValues cv = new ContentValues(); + cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); + ops.add(ContentProviderOperation + .newUpdate( + ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId)) + .withValues(cv).build()); + if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) { + mService.stop(); + } + } + }}); } /** From 8db94fb00cad3f750546d23557b0f6cdf98ef306 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Wed, 19 Jan 2011 08:23:11 -0800 Subject: [PATCH 07/10] Fix regression re: bad folder sync key Bug: 3368400 Change-Id: I8bbba33ea0149b6c19a333dcd739208cec6c0475 --- .../android/exchange/adapter/FolderSyncParser.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/com/android/exchange/adapter/FolderSyncParser.java b/src/com/android/exchange/adapter/FolderSyncParser.java index 84c4be12d..7d6229b38 100644 --- a/src/com/android/exchange/adapter/FolderSyncParser.java +++ b/src/com/android/exchange/adapter/FolderSyncParser.java @@ -127,11 +127,16 @@ public class FolderSyncParser extends AbstractSyncParser { if (status != Eas.FOLDER_STATUS_OK) { mService.errorLog("FolderSync failed: " + status); if (status == Eas.FOLDER_STATUS_INVALID_KEY) { - mAccount.mSyncKey = "0"; mService.errorLog("Bad sync key; RESET and delete all folders"); - // Delete PIM data first + // Reset the sync key and save + mAccount.mSyncKey = "0"; + ContentValues cv = new ContentValues(); + cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); + mContentResolver.update(ContentUris.withAppendedId(Account.CONTENT_URI, + mAccount.mId), cv, null, null); + // Delete PIM data ExchangeService.deleteAccountPIMData(mAccountId); - // Then, delete mailboxes + // And only then, delete mailboxes mContentResolver.delete(Mailbox.CONTENT_URI, ALL_BUT_ACCOUNT_MAILBOX, new String[] {Long.toString(mAccountId)}); // Stop existing syncs and reconstruct _main From 778fa969ab429f1fc3834a7521159961ba7b5dbd Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Wed, 19 Jan 2011 09:12:49 -0800 Subject: [PATCH 08/10] Fix another case of HTML leakage in snippets * Add test for this case Bug: 3285281 Change-Id: If45d33624ac5dbd16d28b45428be29e5a8c51046 --- src/com/android/email/Snippet.java | 5 ++--- tests/src/com/android/email/SnippetTests.java | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/com/android/email/Snippet.java b/src/com/android/email/Snippet.java index 4604bb3a3..cb25f47aa 100644 --- a/src/com/android/email/Snippet.java +++ b/src/com/android/email/Snippet.java @@ -45,8 +45,6 @@ public class Snippet { static final String[] STRIP_TAGS = new String[] {"title ", "script", "style ", "applet"}; static final int STRIP_TAG_LENGTH = 6; - // Note: ESCAPE_STRINGS is taken from the StringUtil class which is part of the - // unbundled_google package static final Map ESCAPE_STRINGS; static { // HTML character entity references as defined in HTML 4 @@ -372,9 +370,10 @@ public class Snippet { // Strip content of title, script, style and applet tags if (i < (length - (STRIP_TAG_LENGTH + 2))) { String tag = text.substring(i + 1, i + STRIP_TAG_LENGTH + 1); + String tagLowerCase = tag.toLowerCase(); boolean stripContent = false; for (String stripTag: STRIP_TAGS) { - if (stripTag.equals(tag)) { + if (stripTag.equals(tagLowerCase)) { stripContent = true; break; } diff --git a/tests/src/com/android/email/SnippetTests.java b/tests/src/com/android/email/SnippetTests.java index 0708ac11d..3d3a018f6 100644 --- a/tests/src/com/android/email/SnippetTests.java +++ b/tests/src/com/android/email/SnippetTests.java @@ -118,6 +118,8 @@ public class SnippetTests extends AndroidTestCase { public void testStripContent() { assertEquals("Visible", Snippet.fromHtmlText( "Visible")); + assertEquals("Visible", Snippet.fromHtmlText( + "Visible")); assertEquals("IsVisible", Snippet.fromHtmlText( "IsVisible")); assertEquals("Visible", Snippet.fromHtmlText( From 4ff77b1e4f746aec45a7857ae3bd770173281bfa Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Mon, 7 Feb 2011 13:19:59 -0800 Subject: [PATCH 09/10] Import revised translations. DO NOT MERGE Change-Id: Ic8a992a03e32a62f023995a29ee671dede5aab18 --- res/values-ca/strings.xml | 2 +- res/values-de/strings.xml | 4 ++-- res/values-ja/strings.xml | 2 +- res/values-pl/strings.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index def441cb2..2cc61b07b 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -90,7 +90,7 @@ "Cc" "Cco" "Assumpte" - "Redacta un missatge" + "Redacta" \n\n"-------- Missatge original --------"\n"Assumpte: %1$s"\n"De: %2$s"\n"Per a: %3$s"\n"Cc: %4$s"\n\n \n\n"%s ha escrit:"\n\n "Text citat" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index ee8a6555e..e0c5b5033 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -17,9 +17,9 @@ "Anhänge lesen" - "Erlaubt der Anwendung, Ihre Anhänge zu lesen." + "Ermöglicht der Anwendung, Ihre Anhänge zu lesen." "Auf Daten des E-Mail-Providers zugreifen" - "Erlaubt dieser Anwendung, auf Ihre E-Mail-Datenbank zuzugreifen, einschließlich erhaltener Nachrichten, gesendeter Nachrichten, Benutzernamen und Passwörtern." + "Ermöglicht dieser Anwendung, auf Ihre E-Mail-Datenbank zuzugreifen, einschließlich erhaltener Nachrichten, gesendeter Nachrichten, Benutzernamen und Passwörtern." "E-Mail" "Schreiben" "Fehler suchen" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 1f62f9f28..84aec516c 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -191,7 +191,7 @@ "このアカウントから連絡先を同期します。" "このアカウントからカレンダーを同期" "セットアップできません" - "同期する量" + "同期する期間" "1日" "3日間" "1週間" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 2749089c5..bbd8a7372 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -178,7 +178,7 @@ "Użyj bezpiecznego połączenia (SSL)" "Akceptuj wszystkie certyfikaty SSL" "Opcje konta" - "Częstotliwość sprawdzania skrzynki odbiorczej" + "Sprawdzaj pocztę" "Nigdy" "Automatycznie (tryb Push)" "Co 5 minut" @@ -221,7 +221,7 @@ "Powiadomienia e-mail" "Częstotliwość synchronizacji, powiadomienia itd." "Wyświetl powiadomienie na pasku stanu po otrzymaniu e-maila" - "Częstotliwość sprawdzania skrzynki odbiorczej" + "Sprawdzaj pocztę" "Poczta przychodząca" "Poczta wychodząca" "Nazwa konta" From 1efbcec0a71056adc0421a26b2f269561183b9a2 Mon Sep 17 00:00:00 2001 From: Andy Stadler Date: Mon, 28 Feb 2011 15:24:05 -0800 Subject: [PATCH 10/10] DO NOT MERGE: Backport fix for IMAP delete bug Original CL comment: Always set a delete policy for legacy accounts The delete policy can only be set for POP3 accounts. However, the delete policy is used for all legacy accounts (that includes IMAP). As such, we need to make sure IMAP accounts also have their policy set; even though the setting is not configurable by the user. The delete policy does not mean anything for Exchange accounts, so, we do not need to modify the account setup code for them. bug 3074164 Original Change-Id: Iab10d2997404b3b0c10a60a64fb652540c0d2d1a Change-Id: Idc290aa1b8ff4f17a0c8fd57333523becef0c8e5 --- .../setup/AccountSetupIncomingFragment.java | 15 +- .../EmailBroadcastProcessorService.java | 43 ++++++ .../EmailBroadcastProcessorServiceTests.java | 138 ++++++++++++++++++ 3 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java diff --git a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java index d7e038fa3..5c8884a5c 100644 --- a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java @@ -20,6 +20,7 @@ import com.android.email.AccountBackupRestore; import com.android.email.Email; import com.android.email.R; import com.android.email.Utility; +import com.android.email.mail.Store; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent.Account; @@ -334,17 +335,21 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment { mPasswordView.setText(password); } - if (uri.getScheme().startsWith("pop3")) { - mLoadedDeletePolicy = account.getDeletePolicy(); - SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy); - } else if (uri.getScheme().startsWith("imap")) { + if (uri.getScheme().startsWith(Store.STORE_SCHEME_IMAP)) { if (uri.getPath() != null && uri.getPath().length() > 0) { mImapPathPrefixView.setText(uri.getPath().substring(1)); } - } else { + } else if (!uri.getScheme().startsWith(Store.STORE_SCHEME_POP3)) { + // Account must either be IMAP or POP3 throw new Error("Unknown account type: " + account.getStoreUri(mContext)); } + // The delete policy is set for all accounts. For POP3 accounts, the user sets + // the policy explicitly. For IMAP accounts, the policy is set when the Account object + // is created. @see AccountSetupBasics#populateSetupData + mLoadedDeletePolicy = account.getDeletePolicy(); + SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy); + for (int i = 0; i < mAccountSchemes.length; i++) { if (mAccountSchemes[i].equals(uri.getScheme())) { SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); diff --git a/src/com/android/email/service/EmailBroadcastProcessorService.java b/src/com/android/email/service/EmailBroadcastProcessorService.java index ea930922e..a70694766 100644 --- a/src/com/android/email/service/EmailBroadcastProcessorService.java +++ b/src/com/android/email/service/EmailBroadcastProcessorService.java @@ -22,14 +22,23 @@ import com.android.email.Preferences; import com.android.email.SecurityPolicy; import com.android.email.VendorPolicyLoader; import com.android.email.activity.setup.AccountSettingsXL; +import com.android.email.mail.Store; +import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.AccountColumns; +import com.android.email.provider.EmailContent.HostAuth; import com.android.email.provider.WidgetProvider; import android.accounts.AccountManager; import android.app.IntentService; import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; import android.util.Log; /** @@ -155,6 +164,12 @@ public class EmailBroadcastProcessorService extends IntentService { ExchangeUtils.enableEasCalendarSync(this); } + if (progress < 2) { + Log.i(Email.LOG_TAG, "Onetime initialization: 2"); + progress = 2; + setImapDeletePolicy(this); + } + // Add your initialization steps here. // Use "progress" to skip the initializations that's already done before. // Using this preference also makes it safe when a user skips an upgrade. (i.e. upgrading @@ -166,6 +181,34 @@ public class EmailBroadcastProcessorService extends IntentService { } } + /** + * Sets the delete policy to the correct value for all IMAP accounts. This will have no + * effect on either EAS or POP3 accounts. + */ + /*package*/ static void setImapDeletePolicy(Context context) { + ContentResolver resolver = context.getContentResolver(); + Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, + null, null, null); + try { + while (c.moveToNext()) { + long recvAuthKey = c.getLong(Account.CONTENT_HOST_AUTH_KEY_RECV_COLUMN); + HostAuth recvAuth = HostAuth.restoreHostAuthWithId(context, recvAuthKey); + if (Store.STORE_SCHEME_IMAP.equals(recvAuth.mProtocol)) { + int flags = c.getInt(Account.CONTENT_FLAGS_COLUMN); + flags &= ~Account.FLAGS_DELETE_POLICY_MASK; + flags |= Account.DELETE_POLICY_ON_DELETE << Account.FLAGS_DELETE_POLICY_SHIFT; + ContentValues cv = new ContentValues(); + cv.put(AccountColumns.FLAGS, flags); + long accountId = c.getLong(Account.CONTENT_ID_COLUMN); + Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); + resolver.update(uri, cv, null, null); + } + } + } finally { + c.close(); + } + } + private void setComponentEnabled(Class clazz, boolean enabled) { final ComponentName c = new ComponentName(this, clazz.getName()); getPackageManager().setComponentEnabledSetting(c, diff --git a/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java b/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java new file mode 100644 index 000000000..d68915151 --- /dev/null +++ b/tests/src/com/android/email/service/EmailBroadcastProcessorServiceTests.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2011 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.email.service; + +import com.android.email.AccountTestCase; +import com.android.email.Utility; +import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.AccountColumns; +import com.android.email.provider.EmailContent.HostAuth; +import com.android.email.provider.ProviderTestUtils; + +import android.content.ContentUris; +import android.content.Context; +import android.net.Uri; + +import java.util.NoSuchElementException; + +/** + * Tests of the Email provider. + * + * You can run this entire test case with: + * runtest -c com.android.email.service.EmailBroadcastProcessorServiceTests email + */ +public class EmailBroadcastProcessorServiceTests extends AccountTestCase { + + Context mMockContext; + + public EmailBroadcastProcessorServiceTests() { + super(); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + mMockContext = getMockContext(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Create a simple HostAuth with protocol + */ + private HostAuth setupSimpleHostAuth(String protocol) { + HostAuth hostAuth = ProviderTestUtils.setupHostAuth(protocol, "name", 1L, false, mContext); + hostAuth.mProtocol = protocol; + return hostAuth; + } + + /** + * Returns the flags for the specified account. Throws an exception if the account cannot + * be found. + */ + private int getAccountFlags(long accountId) throws NoSuchElementException { + Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); + Integer flags = Utility.getFirstRowInt(mMockContext, uri, + new String[] { AccountColumns.FLAGS }, null, null, null, 0); + if (flags == null) { + throw new NoSuchElementException("No cursor"); + } + return flags; + } + + /** + * Initial testing on setupSyncReportsLocked, making sure that EAS accounts aren't scheduled + */ + public void testSetImapDeletePolicy() { + // Setup accounts of each type, all with manual sync at different intervals + Account account1 = ProviderTestUtils.setupAccount("eas-account1", false, mMockContext); + account1.mHostAuthRecv = setupSimpleHostAuth("eas"); + account1.mHostAuthSend = account1.mHostAuthRecv; + account1.save(mMockContext); + long accountId1 = account1.mId; + Account account2 = ProviderTestUtils.setupAccount("pop-account1", false, mMockContext); + account2.mHostAuthRecv = setupSimpleHostAuth("pop3"); + account2.mHostAuthSend = setupSimpleHostAuth("smtp"); + account2.mFlags = 0x08; // set delete policy + account2.save(mMockContext); + long accountId2 = account2.mId; + Account account3 = ProviderTestUtils.setupAccount("pop-account2", false, mMockContext); + account3.mHostAuthRecv = setupSimpleHostAuth("pop3"); + account3.mHostAuthSend = setupSimpleHostAuth("smtp"); + account3.save(mMockContext); + long accountId3 = account3.mId; + Account account4 = ProviderTestUtils.setupAccount("imap-account1", false, mMockContext); + account4.mHostAuthRecv = setupSimpleHostAuth("imap"); + account4.mHostAuthSend = setupSimpleHostAuth("smtp"); + account4.mFlags = 0xa5a5a5a5; // Alternating bits; includes bad delete policy + account4.save(mMockContext); + long accountId4 = account4.mId; + Account account5 = ProviderTestUtils.setupAccount("imap-account2", false, mMockContext); + account5.mHostAuthRecv = setupSimpleHostAuth("imap"); + account5.mHostAuthSend = setupSimpleHostAuth("smtp"); + account5.mFlags = 0x0c; // All delete policy bits set + account5.save(mMockContext); + long accountId5 = account5.mId; + Account account6 = ProviderTestUtils.setupAccount("imap-account3", false, mMockContext); + account6.mHostAuthRecv = setupSimpleHostAuth("imap"); + account6.mHostAuthSend = setupSimpleHostAuth("smtp"); + account6.mFlags = 0; // No delete policy bits set + account6.save(mMockContext); + long accountId6 = account6.mId; + + // Run the account migration + EmailBroadcastProcessorService.setImapDeletePolicy(mMockContext); + + // Test the results + int accountFlags1 = getAccountFlags(accountId1); + assertEquals(4, accountFlags1); // not IMAP; no changes + int accountFlags2 = getAccountFlags(accountId2); + assertEquals(8, accountFlags2); // not IMAP; no changes + int accountFlags3 = getAccountFlags(accountId3); + assertEquals(4, accountFlags3); // not IMAP; no changes + int accountFlags4 = getAccountFlags(accountId4); + assertEquals(0xa5a5a5a9, accountFlags4); // Only update delete policy bits + int accountFlags5 = getAccountFlags(accountId5); + assertEquals(0x00000008, accountFlags5); + int accountFlags6 = getAccountFlags(accountId6); + assertEquals(0x00000008, accountFlags6); + } + +}