diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 29e39c48d..01a8da323 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -40,6 +40,8 @@ + + - + diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index 38c7757d1..e87207f12 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -89,6 +89,7 @@ import com.android.mail.utils.Utils; import com.android.mail.widget.BaseWidgetProvider; import com.android.mail.widget.WidgetProvider; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import java.io.File; import java.util.ArrayList; @@ -97,6 +98,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; /** @@ -2670,51 +2672,83 @@ outer: * @return the SQLite query to be executed on the EmailProvider database */ private String genQueryAccount(String[] uiProjection, String id) { - ContentValues values = new ContentValues(); - long accountId = Long.parseLong(id); + final ContentValues values = new ContentValues(); + final long accountId = Long.parseLong(id); - // Get account capabilities from the service - EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(getContext(), - mServiceCallback, accountId); - int capabilities = 0; - try { - capabilities = service.getCapabilities(accountId); - } catch (RemoteException e) { + final Set projectionColumns = ImmutableSet.copyOf(uiProjection); + + if (projectionColumns.contains(UIProvider.AccountColumns.CAPABILITIES)) { + // Get account capabilities from the service + EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(getContext(), + mServiceCallback, accountId); + int capabilities = 0; + try { + capabilities = service.getCapabilities(accountId); + } catch (RemoteException e) { + } + values.put(UIProvider.AccountColumns.CAPABILITIES, capabilities); + } + if (projectionColumns.contains(UIProvider.AccountColumns.SETTINGS_INTENT_URI)) { + values.put(UIProvider.AccountColumns.SETTINGS_INTENT_URI, + getExternalUriString("settings", id)); + } + if (projectionColumns.contains(UIProvider.AccountColumns.COMPOSE_URI)) { + values.put(UIProvider.AccountColumns.COMPOSE_URI, + getExternalUriStringEmail2("compose", id)); + } + if (projectionColumns.contains(UIProvider.AccountColumns.MIME_TYPE)) { + values.put(UIProvider.AccountColumns.MIME_TYPE, EMAIL_APP_MIME_TYPE); + } + if (projectionColumns.contains(UIProvider.AccountColumns.COLOR)) { + values.put(UIProvider.AccountColumns.COLOR, ACCOUNT_COLOR); } - values.put(UIProvider.AccountColumns.CAPABILITIES, capabilities); - values.put(UIProvider.AccountColumns.SETTINGS_INTENT_URI, - getExternalUriString("settings", id)); - values.put(UIProvider.AccountColumns.COMPOSE_URI, - getExternalUriStringEmail2("compose", id)); - values.put(UIProvider.AccountColumns.MIME_TYPE, EMAIL_APP_MIME_TYPE); - values.put(UIProvider.AccountColumns.COLOR, ACCOUNT_COLOR); - - Preferences prefs = Preferences.getPreferences(getContext()); - values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE, - prefs.getConfirmDelete() ? "1" : "0"); - values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND, - prefs.getConfirmSend() ? "1" : "0"); - values.put(UIProvider.AccountColumns.SettingsColumns.HIDE_CHECKBOXES, - prefs.getHideCheckboxes() ? "1" : "0"); - int autoAdvance = prefs.getAutoAdvanceDirection(); - values.put(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE, - autoAdvanceToUiValue(autoAdvance)); - int textZoom = prefs.getTextZoom(); - values.put(UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, - textZoomToUiValue(textZoom)); - // Set default inbox, if we've got an inbox; otherwise, say initial sync needed + final Preferences prefs = Preferences.getPreferences(getContext()); + if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE)) { + values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE, + prefs.getConfirmDelete() ? "1" : "0"); + } + if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND)) { + values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND, + prefs.getConfirmSend() ? "1" : "0"); + } + if (projectionColumns.contains( + UIProvider.AccountColumns.SettingsColumns.HIDE_CHECKBOXES)) { + values.put(UIProvider.AccountColumns.SettingsColumns.HIDE_CHECKBOXES, + prefs.getHideCheckboxes() ? "1" : "0"); + } + if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE)) { + int autoAdvance = prefs.getAutoAdvanceDirection(); + values.put(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE, + autoAdvanceToUiValue(autoAdvance)); + } + if (projectionColumns.contains( + UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE)) { + int textZoom = prefs.getTextZoom(); + values.put(UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, + textZoomToUiValue(textZoom)); + } + // Set default inbox, if we've got an inbox; otherwise, say initial sync needed final Context context = getContext(); long mailboxId = Mailbox.findMailboxOfType(context, accountId, Mailbox.TYPE_INBOX); - if (mailboxId != Mailbox.NO_MAILBOX) { + if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX) && + mailboxId != Mailbox.NO_MAILBOX) { values.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX, uiUriString("uifolder", mailboxId)); + } + if (projectionColumns.contains( + UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME) && + mailboxId != Mailbox.NO_MAILBOX) { values.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME, Mailbox.getDisplayName(context, mailboxId)); - values.put(UIProvider.AccountColumns.SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); - } else { - values.put(UIProvider.AccountColumns.SYNC_STATUS, - UIProvider.SyncStatus.INITIAL_SYNC_NEEDED); + } + if (projectionColumns.contains(UIProvider.AccountColumns.SYNC_STATUS)) { + if (mailboxId != Mailbox.NO_MAILBOX) { + values.put(UIProvider.AccountColumns.SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); + } else { + values.put(UIProvider.AccountColumns.SYNC_STATUS, + UIProvider.SyncStatus.INITIAL_SYNC_NEEDED); + } } StringBuilder sb = genSelect(sAccountListMap, uiProjection, values); diff --git a/src/com/android/email/provider/WidgetProvider.java b/src/com/android/email/provider/WidgetProvider.java new file mode 100644 index 000000000..423d7cc27 --- /dev/null +++ b/src/com/android/email/provider/WidgetProvider.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2012 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.provider; + +import android.appwidget.AppWidgetManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.net.Uri; +import android.provider.BaseColumns; + +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.Mailbox; +import com.android.mail.providers.Folder; +import com.android.mail.providers.UIProvider; +import com.android.mail.providers.UIProvider.AccountColumns; +import com.android.mail.utils.LogTag; +import com.android.mail.utils.LogUtils; +import com.android.mail.widget.BaseWidgetProvider; +import com.android.mail.widget.WidgetService; + +public class WidgetProvider extends BaseWidgetProvider { + private static final String LEGACY_PREFS_NAME = "com.android.email.widget.WidgetManager"; + private static final String LEGACY_ACCOUNT_ID_PREFIX = "accountId_"; + private static final String LEGACY_MAILBOX_ID_PREFIX = "mailboxId_"; + + private static final String LOG_TAG = LogTag.getLogTag(); + + // This projection is needed, as if we were to request the capabilities of the account, + // that provider attempts to bind to the email service to get this information. It is not + // valid to bind to a service in a broadcast receiver, as the bind just blocks, for the amount + // of time specified in the timeout. + // Instead, this projection doesn't include the capabilities column. The cursor wrapper then + // makes sure that the Account objects can find all of the columns it expects. + private static final String[] WIDGET_ACCOUNTS_PROJECTION = { + BaseColumns._ID, + AccountColumns.NAME, + AccountColumns.PROVIDER_VERSION, + AccountColumns.URI, + AccountColumns.FOLDER_LIST_URI, + AccountColumns.FULL_FOLDER_LIST_URI, + AccountColumns.SEARCH_URI, + AccountColumns.ACCOUNT_FROM_ADDRESSES, + AccountColumns.SAVE_DRAFT_URI, + AccountColumns.SEND_MAIL_URI, + AccountColumns.EXPUNGE_MESSAGE_URI, + AccountColumns.UNDO_URI, + AccountColumns.SETTINGS_INTENT_URI, + AccountColumns.SYNC_STATUS, + AccountColumns.HELP_INTENT_URI, + AccountColumns.SEND_FEEDBACK_INTENT_URI, + AccountColumns.COMPOSE_URI, + AccountColumns.MIME_TYPE, + AccountColumns.RECENT_FOLDER_LIST_URI, + AccountColumns.COLOR, + AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI, + AccountColumns.MANUAL_SYNC_URI, + AccountColumns.SettingsColumns.SIGNATURE, + AccountColumns.SettingsColumns.AUTO_ADVANCE, + AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, + AccountColumns.SettingsColumns.SNAP_HEADERS, + AccountColumns.SettingsColumns.REPLY_BEHAVIOR, + AccountColumns.SettingsColumns.HIDE_CHECKBOXES, + AccountColumns.SettingsColumns.CONFIRM_DELETE, + AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, + AccountColumns.SettingsColumns.CONFIRM_SEND, + AccountColumns.SettingsColumns.DEFAULT_INBOX, + AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME, + AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT, + AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE + }; + + + @Override + public void onReceive(Context context, Intent intent) { + // We want to migrate any legacy Email widget information to the new format + migrateAllLegacyWidgetInformation(context); + + super.onReceive(context, intent); + } + + /** + * Update all widgets in the list + */ + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + migrateLegacyWidgetInformation(context, appWidgetIds); + super.onUpdate(context, appWidgetManager, appWidgetIds); + } + + /** + * Remove preferences when deleting widget + */ + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + super.onDeleted(context, appWidgetIds); + + // Remove any legacy Email widget information + final SharedPreferences prefs = context.getSharedPreferences(LEGACY_PREFS_NAME, 0); + final SharedPreferences.Editor editor = prefs.edit(); + for (int widgetId : appWidgetIds) { + // Remove the account in the preference + editor.remove(LEGACY_ACCOUNT_ID_PREFIX + widgetId); + editor.remove(LEGACY_MAILBOX_ID_PREFIX + widgetId); + } + editor.apply(); + } + + @Override + protected com.android.mail.providers.Account getAccountObject( + Context context, String accountUri) { + final ContentResolver resolver = context.getContentResolver(); + final Cursor sparseAccountCursor = resolver.query(Uri.parse(accountUri), + WIDGET_ACCOUNTS_PROJECTION, null, null, null); + + return getPopulatedAccountObject(sparseAccountCursor); + } + + + @Override + protected boolean isAccountValid(Context context, com.android.mail.providers.Account account) { + if (account != null) { + final ContentResolver resolver = context.getContentResolver(); + final Cursor sparseAccountCursor = resolver.query(account.uri, + WIDGET_ACCOUNTS_PROJECTION, null, null, null); + if (sparseAccountCursor != null) { + try { + return sparseAccountCursor.getCount() > 0; + } finally { + sparseAccountCursor.close(); + } + } + } + return false; + } + + + + private void migrateAllLegacyWidgetInformation(Context context) { + final int[] currentWidgetIds = getCurrentWidgetIds(context); + migrateLegacyWidgetInformation(context, currentWidgetIds); + } + + private void migrateLegacyWidgetInformation(Context context, int[] widgetIds) { + final SharedPreferences prefs = context.getSharedPreferences(LEGACY_PREFS_NAME, 0); + final SharedPreferences.Editor editor = prefs.edit(); + + for (int widgetId : widgetIds) { + long accountId = loadAccountIdPref(context, widgetId); + long mailboxId = loadMailboxIdPref(context, widgetId); + // Legacy support; if preferences haven't been saved for this widget, load something + if (accountId == Account.NO_ACCOUNT || mailboxId == Mailbox.NO_MAILBOX) { + LogUtils.d(LOG_TAG, "Couldn't load account or mailbox. accountId: %d" + + " mailboxId: %d widgetId %d", accountId, mailboxId); + continue; + } + + // Get Account and folder objects for the account id and mailbox id + final com.android.mail.providers.Account uiAccount = getAccount(context, accountId); + final Folder uiFolder = getFolder(context, mailboxId); + + if (uiAccount != null && uiFolder != null) { + WidgetService.saveWidgetInformation(context, widgetId, uiAccount, uiFolder); + + updateWidgetInternal(context, widgetId, uiAccount, uiFolder); + + // Now remove the old legacy preference value + editor.remove(LEGACY_ACCOUNT_ID_PREFIX + widgetId); + editor.remove(LEGACY_MAILBOX_ID_PREFIX + widgetId); + } + } + editor.apply(); + } + + private com.android.mail.providers.Account getAccount(Context context, long accountId) { + final ContentResolver resolver = context.getContentResolver(); + final Cursor ac = resolver.query(EmailProvider.uiUri("uiaccount", accountId), + WIDGET_ACCOUNTS_PROJECTION, null, null, null); + + com.android.mail.providers.Account uiAccount = getPopulatedAccountObject(ac); + + return uiAccount; + } + + private com.android.mail.providers.Account getPopulatedAccountObject(final Cursor ac) { + if (ac == null) { + LogUtils.e(LOG_TAG, "Null account cursor"); + return null; + } + + final Cursor accountCursor = new SparseAccountCursorWrapper(ac); + + com.android.mail.providers.Account uiAccount = null; + try { + if (accountCursor.moveToFirst()) { + uiAccount = new com.android.mail.providers.Account(accountCursor); + } + } finally { + accountCursor.close(); + } + return uiAccount; + } + + private Folder getFolder(Context context, long mailboxId) { + final ContentResolver resolver = context.getContentResolver(); + final Cursor fc = resolver.query(EmailProvider.uiUri("uifolder", mailboxId), + UIProvider.FOLDERS_PROJECTION, null, null, null); + + if (fc == null) { + LogUtils.e(LOG_TAG, "Null folder cursor for mailboxId %d", mailboxId); + return null; + } + + Folder uiFolder = null; + try { + if (fc.moveToFirst()) { + uiFolder = new Folder(fc); + } + } finally { + fc.close(); + } + return uiFolder; + } + + /** + * Returns the saved account ID for the given widget. Otherwise, + * {@link com.android.emailcommon.provider.Account#NO_ACCOUNT} if + * the account ID was not previously saved. + */ + static long loadAccountIdPref(Context context, int appWidgetId) { + final SharedPreferences prefs = context.getSharedPreferences(LEGACY_PREFS_NAME, 0); + long accountId = prefs.getLong(LEGACY_ACCOUNT_ID_PREFIX + appWidgetId, Account.NO_ACCOUNT); + return accountId; + } + + /** + * Returns the saved mailbox ID for the given widget. Otherwise, + * {@link com.android.emailcommon.provider.Mailbox#NO_MAILBOX} if + * the mailbox ID was not previously saved. + */ + static long loadMailboxIdPref(Context context, int appWidgetId) { + final SharedPreferences prefs = context.getSharedPreferences(LEGACY_PREFS_NAME, 0); + long mailboxId = prefs.getLong(LEGACY_MAILBOX_ID_PREFIX + appWidgetId, Mailbox.NO_MAILBOX); + return mailboxId; + } + + private class SparseAccountCursorWrapper extends CursorWrapper { + public SparseAccountCursorWrapper(Cursor cursor) { + super(cursor); + } + + @Override + public int getColumnCount () { + return UIProvider.ACCOUNTS_PROJECTION.length; + } + + @Override + public int getColumnIndex (String columnName) { + for (int i = 0; i < UIProvider.ACCOUNTS_PROJECTION.length; i++) { + if (UIProvider.ACCOUNTS_PROJECTION[i].equals(columnName)) { + return i; + } + } + return -1; + } + + @Override + public String getColumnName (int columnIndex) { + return UIProvider.ACCOUNTS_PROJECTION[columnIndex]; + } + + @Override + public String[] getColumnNames () { + return UIProvider.ACCOUNTS_PROJECTION; + } + + @Override + public int getInt (int columnIndex) { + if (columnIndex == UIProvider.ACCOUNT_CAPABILITIES_COLUMN) { + return 0; + } + return super.getInt(convertColumnIndex(columnIndex)); + } + + @Override + public long getLong (int columnIndex) { + return super.getLong(convertColumnIndex(columnIndex)); + } + + @Override + public String getString (int columnIndex) { + return super.getString(convertColumnIndex(columnIndex)); + } + + private int convertColumnIndex(int columnIndex) { + // Since this sparse cursor doesn't have the capabilities column, + // we need to adjust all of the column indexes that come after where the + // capabilities column should be + if (columnIndex > UIProvider.ACCOUNT_CAPABILITIES_COLUMN) { + return columnIndex - 1; + } + return columnIndex; + } + } + +}