From 0dffe3afd7a2fdfb394573aa0d8d06dd90e9fe12 Mon Sep 17 00:00:00 2001 From: James Lemieux Date: Thu, 30 Jan 2014 12:00:03 -0800 Subject: [PATCH] Keep on getting a couldn't sign-in notification b/11551107 This is caused by ImapConnection.doLogin() only throwing AuthenticationFailedExceptions and not other varieties of exceptions. While fixing this bug, I discovered that the ConversationListFooterView that is displayed in response to a authentication failure contains a button called "Sign In" that didn't actually do anything. I made it navigate to the incoming account settings fragment where the user is free to change the relevant account credentials. Change-Id: I2c772ecab18f3e57059eceeae01de08f1fdab4c2 --- AndroidManifest.xml | 19 +++-- .../emailcommon/utility/IntentUtilities.java | 2 +- .../email/MessagingExceptionStrings.java | 57 --------------- .../email/activity/setup/AccountSettings.java | 7 +- .../setup/HeadlessAccountSettingsLoader.java | 70 +++++++++++++++++++ .../email/mail/store/ImapConnection.java | 47 +++++++++---- .../android/email/mail/store/ImapFolder.java | 4 +- .../android/email/mail/store/ImapStore.java | 17 ++--- .../email/mail/store/imap/ImapConstants.java | 5 ++ .../android/email/provider/EmailProvider.java | 16 +++++ .../service/PopImapSyncAdapterService.java | 19 ++--- 11 files changed, 167 insertions(+), 96 deletions(-) delete mode 100644 src/com/android/email/MessagingExceptionStrings.java create mode 100644 src/com/android/email/activity/setup/HeadlessAccountSettingsLoader.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dcf228f94..bafc088cf 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -266,10 +266,8 @@ android:exported="true" > - - + + @@ -285,6 +283,19 @@ + + + + + + + + + { + private final Context mContext; + + private LoadAccountIncomingSettingsAsyncTask(Context context) { + mContext = context; + } + + protected Account doInBackground(Long... params) { + return Account.restoreAccountWithId(mContext, params[0]); + } + + protected void onPostExecute(Account result) { + // create an Intent to view a new activity + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // we are navigating explicitly to the AccountSettings activity + intent.setClass(mContext, AccountSettings.class); + + // place the account in the intent as an extra + intent.putExtra(AccountSettings.EXTRA_ACCOUNT, result); + + // these extras show the "incoming fragment" in the AccountSettings activity by default + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, + AccountSetupIncomingFragment.class.getCanonicalName()); + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, + AccountSetupIncomingFragment.getArgs(true)); + intent.putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true); + + mContext.startActivity(intent); + + finish(); + } + } +} \ No newline at end of file diff --git a/src/com/android/email/mail/store/ImapConnection.java b/src/com/android/email/mail/store/ImapConnection.java index 88a8eaa91..2e9019edd 100644 --- a/src/com/android/email/mail/store/ImapConnection.java +++ b/src/com/android/email/mail/store/ImapConnection.java @@ -97,9 +97,10 @@ class ImapConnection { * Generates and returns the phrase to be used for authentication. This will be a LOGIN with * username and password, or an OAUTH authentication string, with username and access token. * Currently, these are the only two auth mechanisms supported. - * @return + * * @throws IOException * @throws AuthenticationFailedException + * @return the login command string to sent to the IMAP server */ String getLoginPhrase() throws MessagingException, IOException { // build the LOGIN string once (instead of over-and-over again.) @@ -197,7 +198,7 @@ class ImapConnection { /** * Closes the connection and releases all resources. This connection can not be used again - * until {@link #setStore(ImapStore, String, String)} is called. + * until {@link #setStore(ImapStore)} is called. */ void close() { if (mTransport != null) { @@ -255,7 +256,7 @@ class ImapConnection { } boolean isTransportOpenForTest() { - return mTransport != null ? mTransport.isOpen() : false; + return mTransport != null && mTransport.isOpen(); } ImapResponse readResponse() throws IOException, MessagingException { @@ -315,8 +316,7 @@ class ImapConnection { return tag; } - List executeSimpleCommand(String command) throws IOException, - MessagingException { + List executeSimpleCommand(String command) throws IOException, MessagingException { return executeSimpleCommand(command, false); } @@ -328,17 +328,25 @@ class ImapConnection { * @throws MessagingException */ List getCommandResponses() throws IOException, MessagingException { - ArrayList responses = new ArrayList(); + final List responses = new ArrayList(); ImapResponse response; do { response = mParser.readResponse(); responses.add(response); } while (!response.isTagged()); + if (!response.isOk()) { final String toString = response.toString(); final String alert = response.getAlertTextOrEmpty().getString(); + final String responseCode = response.getResponseCodeOrEmpty().getString(); destroyResponses(); - throw new ImapException(toString, alert); + + // if the response code indicates an error occurred within the server, indicate that + if (ImapConstants.UNAVAILABLE.equals(responseCode)) { + throw new MessagingException(MessagingException.SERVER_ERROR, alert); + } + + throw new ImapException(toString, alert, responseCode); } return responses; } @@ -476,8 +484,7 @@ class ImapConnection { /** * Logs into the IMAP server */ - private void doLogin() - throws IOException, MessagingException, AuthenticationFailedException { + private void doLogin() throws IOException, MessagingException, AuthenticationFailedException { try { if (mImapStore.getUseOAuth()) { // SASL authentication can take multiple steps. Currently the only SASL @@ -490,10 +497,17 @@ class ImapConnection { if (MailActivityEmail.DEBUG) { LogUtils.d(Logging.LOG_TAG, ie, "ImapException"); } - throw new AuthenticationFailedException(ie.getAlertText(), ie); - } catch (MessagingException me) { - throw new AuthenticationFailedException(null, me); + final String code = ie.getResponseCode(); + final String alertText = ie.getAlertText(); + + // if the response code indicates expired or bad credentials, throw a special exception + if (ImapConstants.AUTHENTICATIONFAILED.equals(code) || + ImapConstants.EXPIRED.equals(code)) { + throw new AuthenticationFailedException(alertText, ie); + } + + throw new MessagingException(alertText, ie); } } @@ -542,8 +556,15 @@ class ImapConnection { sendCommand("", true); response = readResponse(); } - return response; + // if the response code indicates an error occurred within the server, indicate that + final String responseCode = response.getResponseCodeOrEmpty().getString(); + if (ImapConstants.UNAVAILABLE.equals(responseCode)) { + final String alert = response.getAlertTextOrEmpty().getString(); + throw new MessagingException(MessagingException.SERVER_ERROR, alert); + } + + return response; } /** diff --git a/src/com/android/email/mail/store/ImapFolder.java b/src/com/android/email/mail/store/ImapFolder.java index 4f9588b00..30857360c 100644 --- a/src/com/android/email/mail/store/ImapFolder.java +++ b/src/com/android/email/mail/store/ImapFolder.java @@ -59,10 +59,10 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.Locale; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.TimeZone; class ImapFolder extends Folder { @@ -1236,7 +1236,7 @@ class ImapFolder extends Folder { mConnection = null; // To prevent close() from returning the connection to the pool. close(false); } - return new MessagingException("IO Error", ioe); + return new MessagingException(MessagingException.IOERROR, "IO Error", ioe); } @Override diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java index b14e58d34..82bb289b4 100644 --- a/src/com/android/email/mail/store/ImapStore.java +++ b/src/com/android/email/mail/store/ImapStore.java @@ -627,29 +627,26 @@ public class ImapStore extends Store { static class ImapException extends MessagingException { private static final long serialVersionUID = 1L; - String mAlertText; + private final String mAlertText; + private final String mResponseCode; - public ImapException(String message, String alertText, Throwable throwable) { - super(message, throwable); - mAlertText = alertText; - } - - public ImapException(String message, String alertText) { + public ImapException(String message, String alertText, String responseCode) { super(message); mAlertText = alertText; + mResponseCode = responseCode; } public String getAlertText() { return mAlertText; } - public void setAlertText(String alertText) { - mAlertText = alertText; + public String getResponseCode() { + return mResponseCode; } } public void closeConnections() { - ImapConnection connection = null; + ImapConnection connection; while ((connection = mConnectionPool.poll()) != null) { connection.close(); } diff --git a/src/com/android/email/mail/store/imap/ImapConstants.java b/src/com/android/email/mail/store/imap/ImapConstants.java index 5a3290420..9f4d59290 100644 --- a/src/com/android/email/mail/store/imap/ImapConstants.java +++ b/src/com/android/email/mail/store/imap/ImapConstants.java @@ -96,4 +96,9 @@ public final class ImapConstants { public static final String XOAUTH2 = "XOAUTH2"; public static final String APPENDUID = "APPENDUID"; public static final String NIL = "NIL"; + + /** response codes within IMAP responses */ + public static final String EXPIRED = "EXPIRED"; + public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED"; + public static final String UNAVAILABLE = "UNAVAILABLE"; } diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index e20e487d8..e4ab1a0fc 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -98,6 +98,7 @@ import com.android.emailcommon.service.EmailServiceStatus; import com.android.emailcommon.service.IEmailService; import com.android.emailcommon.service.SearchParams; import com.android.emailcommon.utility.AttachmentUtilities; +import com.android.emailcommon.utility.IntentUtilities; import com.android.emailcommon.utility.Utility; import com.android.ex.photo.provider.PhotoContract; import com.android.mail.preferences.MailPrefs; @@ -171,6 +172,9 @@ public class EmailProvider extends ContentProvider { private static final String EMAIL_ATTACHMENT_MIME_TYPE = "vnd.android.cursor.item/email-attachment"; + /** The base of the URI that navigates to the settings page to alter email auth credentials */ + private static Uri BASE_AUTH_URI; + /** Appended to the notification URI for delete operations */ private static final String NOTIFICATION_OP_DELETE = "delete"; /** Appended to the notification URI for insert operations */ @@ -1001,6 +1005,8 @@ public class EmailProvider extends ContentProvider { UIPROVIDER_RECENT_FOLDERS_NOTIFIER = Uri.parse("content://" + uiNotificationAuthority + "/uirecentfolders"); + BASE_AUTH_URI = Uri.parse("auth://" + EmailContent.EMAIL_PACKAGE_NAME + + ".INCOMING_SETTINGS/incoming/"); // All accounts sURIMatcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT); @@ -3024,6 +3030,12 @@ public class EmailProvider extends ContentProvider { .appendQueryParameter("account", account).build().toString(); } + private static String getExternalUriStringReathentication(long accountId) { + final Uri.Builder builder = BASE_AUTH_URI.buildUpon(); + IntentUtilities.setAccountId(builder, accountId); + return builder.build().toString(); + } + private static String getBits(int bitField) { StringBuilder sb = new StringBuilder(" "); for (int i = 0; i < 32; i++, bitField >>= 1) { @@ -3125,6 +3137,10 @@ public class EmailProvider extends ContentProvider { values.put(UIProvider.AccountColumns.COMPOSE_URI, getExternalUriStringEmail2("compose", id)); } + if (projectionColumns.contains(UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI)) { + values.put(UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI, + getExternalUriStringReathentication(accountId)); + } if (projectionColumns.contains(UIProvider.AccountColumns.MIME_TYPE)) { values.put(UIProvider.AccountColumns.MIME_TYPE, EMAIL_APP_MIME_TYPE); } diff --git a/src/com/android/email/service/PopImapSyncAdapterService.java b/src/com/android/email/service/PopImapSyncAdapterService.java index 4099be5c2..4b62860e0 100644 --- a/src/com/android/email/service/PopImapSyncAdapterService.java +++ b/src/com/android/email/service/PopImapSyncAdapterService.java @@ -144,24 +144,27 @@ public class PopImapSyncAdapterService extends Service { UIProvider.LastSyncResult.SUCCESS); } } catch (MessagingException e) { - int cause = e.getExceptionType(); - // XXX It's no good to put the MessagingException.cause here, that's not the - // same set of values that we use in EmailServiceStatus. - switch(cause) { + final int type = e.getExceptionType(); + // type must be translated into the domain of values used by EmailServiceStatus + switch(type) { case MessagingException.IOERROR: - EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0, + EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, type, 0, UIProvider.LastSyncResult.CONNECTION_ERROR); syncResult.stats.numIoExceptions++; break; case MessagingException.AUTHENTICATION_FAILED: - EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0, + EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, type, 0, UIProvider.LastSyncResult.AUTH_ERROR); syncResult.stats.numAuthExceptions++; break; + case MessagingException.SERVER_ERROR: + EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, type, 0, + UIProvider.LastSyncResult.SERVER_ERROR); + break; default: - EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0, - UIProvider.LastSyncResult.INTERNAL_ERROR); + EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, type, 0, + UIProvider.LastSyncResult.INTERNAL_ERROR); } } } finally {