Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.mail.widget;
     18 
     19 import android.app.PendingIntent;
     20 import android.appwidget.AppWidgetManager;
     21 import android.appwidget.AppWidgetProvider;
     22 import android.content.ComponentName;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.database.Cursor;
     27 import android.net.Uri;
     28 import android.os.AsyncTask;
     29 import android.os.Bundle;
     30 import android.text.TextUtils;
     31 import android.view.View;
     32 import android.widget.RemoteViews;
     33 
     34 import com.android.mail.R;
     35 import com.android.mail.preferences.MailPrefs;
     36 import com.android.mail.providers.Account;
     37 import com.android.mail.providers.Folder;
     38 import com.android.mail.providers.UIProvider;
     39 import com.android.mail.providers.UIProvider.FolderType;
     40 import com.android.mail.ui.MailboxSelectionActivity;
     41 import com.android.mail.utils.AccountUtils;
     42 import com.android.mail.utils.LogTag;
     43 import com.android.mail.utils.LogUtils;
     44 import com.android.mail.utils.Utils;
     45 import com.google.common.collect.Sets;
     46 import com.google.common.primitives.Ints;
     47 
     48 import java.util.Set;
     49 
     50 public abstract class BaseWidgetProvider extends AppWidgetProvider {
     51     public static final String EXTRA_FOLDER_TYPE = "folder-type";
     52     public static final String EXTRA_FOLDER_CAPABILITIES = "folder-capabilities";
     53     public static final String EXTRA_FOLDER_URI = "folder-uri";
     54     public static final String EXTRA_FOLDER_CONVERSATION_LIST_URI = "folder-conversation-list-uri";
     55     public static final String EXTRA_FOLDER_DISPLAY_NAME = "folder-display-name";
     56     public static final String EXTRA_UPDATE_ALL_WIDGETS = "update-all-widgets";
     57     public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
     58 
     59     public static final String ACCOUNT_FOLDER_PREFERENCE_SEPARATOR = " ";
     60 
     61 
     62     protected static final String ACTION_UPDATE_WIDGET = "com.android.mail.ACTION_UPDATE_WIDGET";
     63     protected static final String
     64             ACTION_VALIDATE_ALL_WIDGETS = "com.android.mail.ACTION_VALIDATE_ALL_WIDGETS";
     65     protected static final String EXTRA_WIDGET_ID = "widgetId";
     66 
     67     private static final String LOG_TAG = LogTag.getLogTag();
     68 
     69     /**
     70      * Remove preferences when deleting widget
     71      */
     72     @Override
     73     public void onDeleted(Context context, int[] appWidgetIds) {
     74         super.onDeleted(context, appWidgetIds);
     75 
     76         // TODO: (mindyp) save widget information.
     77         MailPrefs.get(context).clearWidgets(appWidgetIds);
     78     }
     79 
     80     public static String getProviderName(Context context) {
     81         return context.getString(R.string.widget_provider);
     82     }
     83 
     84     /**
     85      * Note: this method calls {@link BaseWidgetProvider#getProviderName} and thus returns widget
     86      * IDs based on the widget_provider string resource. When subclassing, be sure to either
     87      * override this method or provide the correct provider name in the string resource.
     88      *
     89      * @return the list ids for the currently configured widgets.
     90      */
     91     protected int[] getCurrentWidgetIds(Context context) {
     92         final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
     93         final ComponentName mailComponent = new ComponentName(context, getProviderName(context));
     94         return appWidgetManager.getAppWidgetIds(mailComponent);
     95     }
     96 
     97     /**
     98      * Get an array of account/mailbox string pairs for currently configured widgets
     99      * @return the account/mailbox string pairs
    100      */
    101     static public String[][] getWidgetInfo(Context context, int[] widgetIds) {
    102         final String[][] widgetInfo = new String[widgetIds.length][2];
    103         for (int i = 0; i < widgetIds.length; i++) {
    104             // Retrieve the persisted information for this widget from
    105             // preferences.
    106             final String accountFolder = MailPrefs.get(context).getWidgetConfiguration(
    107                     widgetIds[i]);
    108             // If the account matched, update the widget.
    109             if (accountFolder != null) {
    110                 widgetInfo[i] = TextUtils.split(accountFolder, ACCOUNT_FOLDER_PREFERENCE_SEPARATOR);
    111             }
    112         }
    113         return widgetInfo;
    114     }
    115 
    116     /**
    117      * Catches ACTION_NOTIFY_DATASET_CHANGED intent and update the corresponding
    118      * widgets.
    119      */
    120     @Override
    121     public void onReceive(Context context, Intent intent) {
    122         // We want to migrate any legacy Email widget information to the new format
    123         migrateAllLegacyWidgetInformation(context);
    124 
    125         super.onReceive(context, intent);
    126         LogUtils.d(LOG_TAG, "BaseWidgetProvider.onReceive: %s", intent);
    127 
    128         final String action = intent.getAction();
    129         if (ACTION_UPDATE_WIDGET.equals(action)) {
    130             final int widgetId = intent.getIntExtra(EXTRA_WIDGET_ID, -1);
    131             final Account account = Account.newInstance(intent.getStringExtra(Utils.EXTRA_ACCOUNT));
    132             final int folderType = intent.getIntExtra(EXTRA_FOLDER_TYPE, FolderType.DEFAULT);
    133             final int folderCapabilities = intent.getIntExtra(EXTRA_FOLDER_CAPABILITIES, 0);
    134             final Uri folderUri = intent.getParcelableExtra(EXTRA_FOLDER_URI);
    135             final Uri folderConversationListUri =
    136                     intent.getParcelableExtra(EXTRA_FOLDER_CONVERSATION_LIST_URI);
    137             final String folderDisplayName = intent.getStringExtra(EXTRA_FOLDER_DISPLAY_NAME);
    138 
    139             if (widgetId != -1 && account != null && folderUri != null) {
    140                 updateWidgetInternal(context, widgetId, account, folderType, folderCapabilities,
    141                         folderUri, folderConversationListUri, folderDisplayName);
    142             }
    143         } else if (ACTION_VALIDATE_ALL_WIDGETS.equals(action)) {
    144             validateAllWidgetInformation(context);
    145         } else if (Utils.ACTION_NOTIFY_DATASET_CHANGED.equals(action)) {
    146             // Receive notification for a certain account.
    147             final Bundle extras = intent.getExtras();
    148             final Uri accountUri = extras.getParcelable(Utils.EXTRA_ACCOUNT_URI);
    149             final Uri folderUri = extras.getParcelable(Utils.EXTRA_FOLDER_URI);
    150             final boolean updateAllWidgets = extras.getBoolean(EXTRA_UPDATE_ALL_WIDGETS, false);
    151 
    152             if (accountUri == null && Utils.isEmpty(folderUri) && !updateAllWidgets) {
    153                 return;
    154             }
    155             final Set<Integer> widgetsToUpdate = Sets.newHashSet();
    156             for (int id : getCurrentWidgetIds(context)) {
    157                 // Retrieve the persisted information for this widget from
    158                 // preferences.
    159                 final String accountFolder = MailPrefs.get(context).getWidgetConfiguration(id);
    160                 // If the account matched, update the widget.
    161                 if (accountFolder != null) {
    162                     final String[] parsedInfo = TextUtils.split(accountFolder,
    163                             ACCOUNT_FOLDER_PREFERENCE_SEPARATOR);
    164                     boolean updateThis = updateAllWidgets;
    165                     if (!updateThis) {
    166                         if (accountUri != null &&
    167                                 TextUtils.equals(accountUri.toString(), parsedInfo[0])) {
    168                             updateThis = true;
    169                         } else if (folderUri != null &&
    170                                 TextUtils.equals(folderUri.toString(), parsedInfo[1])) {
    171                             updateThis = true;
    172                         }
    173                     }
    174                     if (updateThis) {
    175                         widgetsToUpdate.add(id);
    176                     }
    177                 }
    178             }
    179             if (widgetsToUpdate.size() > 0) {
    180                 final int[] widgets = Ints.toArray(widgetsToUpdate);
    181                 AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(widgets,
    182                         R.id.conversation_list);
    183             }
    184         }
    185     }
    186 
    187     /**
    188      * Update all widgets in the list
    189      */
    190     @Override
    191     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    192         migrateLegacyWidgets(context, appWidgetIds);
    193 
    194         super.onUpdate(context, appWidgetManager, appWidgetIds);
    195         // Update each of the widgets with a remote adapter
    196 
    197         new BulkUpdateAsyncTask(context, appWidgetIds).execute((Void[]) null);
    198     }
    199 
    200     private class BulkUpdateAsyncTask extends AsyncTask<Void, Void, Void> {
    201         private final Context mContext;
    202         private final int[] mAppWidgetIds;
    203 
    204         public BulkUpdateAsyncTask(final Context context, final int[] appWidgetIds) {
    205             mContext = context;
    206             mAppWidgetIds = appWidgetIds;
    207         }
    208 
    209         @Override
    210         protected Void doInBackground(final Void... params) {
    211             for (int i = 0; i < mAppWidgetIds.length; ++i) {
    212                 // Get the account for this widget from preference
    213                 final String accountFolder = MailPrefs.get(mContext).getWidgetConfiguration(
    214                         mAppWidgetIds[i]);
    215                 String accountUri = null;
    216                 Uri folderUri = null;
    217                 if (!TextUtils.isEmpty(accountFolder)) {
    218                     final String[] parsedInfo = TextUtils.split(accountFolder,
    219                             ACCOUNT_FOLDER_PREFERENCE_SEPARATOR);
    220                     if (parsedInfo.length == 2) {
    221                         accountUri = parsedInfo[0];
    222                         folderUri = Uri.parse(parsedInfo[1]);
    223                     } else {
    224                         accountUri = accountFolder;
    225                         folderUri =  Uri.EMPTY;
    226                     }
    227                 }
    228                 // account will be null the first time a widget is created. This is
    229                 // OK, as isAccountValid will return false, allowing the widget to
    230                 // be configured.
    231 
    232                 // Lookup the account by URI.
    233                 Account account = null;
    234                 if (!TextUtils.isEmpty(accountUri)) {
    235                     account = getAccountObject(mContext, accountUri);
    236                 }
    237                 if (Utils.isEmpty(folderUri) && account != null) {
    238                     folderUri = account.settings.defaultInbox;
    239                 }
    240 
    241                 Folder folder = null;
    242 
    243                 if (folderUri != null) {
    244                     final Cursor folderCursor =
    245                             mContext.getContentResolver().query(folderUri,
    246                                     UIProvider.FOLDERS_PROJECTION, null, null, null);
    247 
    248                     if (folderCursor != null) {
    249                         try {
    250                             if (folderCursor.moveToFirst()) {
    251                                 folder = new Folder(folderCursor);
    252                             }
    253                         } finally {
    254                             folderCursor.close();
    255                         }
    256                     }
    257                 }
    258 
    259                 updateWidgetInternal(mContext, mAppWidgetIds[i], account,
    260                         folder == null ? FolderType.DEFAULT : folder.type,
    261                         folder == null ? 0 : folder.capabilities,
    262                         folderUri,
    263                         folder == null ? null : folder.conversationListUri,
    264                         folder == null ? null : folder.name);
    265             }
    266 
    267             return null;
    268         }
    269 
    270     }
    271 
    272     protected Account getAccountObject(Context context, String accountUri) {
    273         final ContentResolver resolver = context.getContentResolver();
    274         Account account = null;
    275         Cursor accountCursor = null;
    276         try {
    277             accountCursor = resolver.query(Uri.parse(accountUri),
    278                     UIProvider.ACCOUNTS_PROJECTION, null, null, null);
    279             if (accountCursor != null) {
    280                 if (accountCursor.moveToFirst()) {
    281                     account = Account.builder().buildFrom(accountCursor);
    282                 }
    283             }
    284         } finally {
    285             if (accountCursor != null) {
    286                 accountCursor.close();
    287             }
    288         }
    289         return account;
    290     }
    291 
    292     /**
    293      * Update the widget appWidgetId with the given account and folder
    294      */
    295     public static void updateWidget(Context context, int appWidgetId, Account account,
    296             final int folderType, final int folderCapabilities, final Uri folderUri,
    297             final Uri folderConversationListUri, final String folderDisplayName) {
    298         if (account == null || folderUri == null) {
    299             LogUtils.e(LOG_TAG,
    300                     "Missing account or folder.  account: %s folder %s", account, folderUri);
    301             return;
    302         }
    303         final Intent updateWidgetIntent = new Intent(ACTION_UPDATE_WIDGET);
    304 
    305         updateWidgetIntent.setType(account.mimeType);
    306         updateWidgetIntent.putExtra(EXTRA_WIDGET_ID, appWidgetId);
    307         updateWidgetIntent.putExtra(Utils.EXTRA_ACCOUNT, account.serialize());
    308         updateWidgetIntent.putExtra(EXTRA_FOLDER_TYPE, folderType);
    309         updateWidgetIntent.putExtra(EXTRA_FOLDER_CAPABILITIES, folderCapabilities);
    310         updateWidgetIntent.putExtra(EXTRA_FOLDER_URI, folderUri);
    311         updateWidgetIntent.putExtra(EXTRA_FOLDER_CONVERSATION_LIST_URI, folderConversationListUri);
    312         updateWidgetIntent.putExtra(EXTRA_FOLDER_DISPLAY_NAME, folderDisplayName);
    313 
    314         context.sendBroadcast(updateWidgetIntent);
    315     }
    316 
    317     public static void validateAllWidgets(Context context, String accountMimeType) {
    318         final Intent migrateAllWidgetsIntent = new Intent(ACTION_VALIDATE_ALL_WIDGETS);
    319         migrateAllWidgetsIntent.setType(accountMimeType);
    320         context.sendBroadcast(migrateAllWidgetsIntent);
    321     }
    322 
    323     protected void updateWidgetInternal(Context context, int appWidgetId, Account account,
    324             final int folderType, final int folderCapabilities, final Uri folderUri,
    325             final Uri folderConversationListUri, final String folderDisplayName) {
    326         final RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
    327 
    328         if (!isAccountValid(context, account) || !isFolderValid(context, folderUri)) {
    329             // Widget has not been configured yet
    330             remoteViews.setViewVisibility(R.id.widget_folder, View.GONE);
    331             remoteViews.setViewVisibility(R.id.widget_compose, View.GONE);
    332             remoteViews.setViewVisibility(R.id.conversation_list, View.GONE);
    333             remoteViews.setViewVisibility(R.id.empty_conversation_list, View.GONE);
    334             remoteViews.setViewVisibility(R.id.widget_folder_not_synced, View.GONE);
    335             remoteViews.setViewVisibility(R.id.widget_configuration, View.VISIBLE);
    336 
    337             remoteViews.setTextViewText(R.id.empty_conversation_list,
    338                     context.getString(R.string.loading_conversations));
    339 
    340             final Intent configureIntent = new Intent(context, MailboxSelectionActivity.class);
    341             configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
    342             configureIntent.setData(Uri.parse(configureIntent.toUri(Intent.URI_INTENT_SCHEME)));
    343             configureIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
    344             PendingIntent clickIntent = PendingIntent.getActivity(context, 0, configureIntent,
    345                     PendingIntent.FLAG_UPDATE_CURRENT);
    346             remoteViews.setOnClickPendingIntent(R.id.widget_configuration, clickIntent);
    347         } else {
    348             // Set folder to a space here to avoid flicker.
    349             configureValidAccountWidget(context, remoteViews, appWidgetId, account, folderType,
    350                     folderCapabilities, folderUri, folderConversationListUri,
    351                     folderDisplayName == null ? " " : folderDisplayName);
    352 
    353         }
    354         AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
    355     }
    356 
    357     protected boolean isAccountValid(Context context, Account account) {
    358         if (account != null) {
    359             Account[] accounts = AccountUtils.getSyncingAccounts(context);
    360             for (Account existing : accounts) {
    361                 if (existing != null && account.uri.equals(existing.uri)) {
    362                     return true;
    363                 }
    364             }
    365         }
    366         return false;
    367     }
    368 
    369     protected boolean isFolderValid(Context context, Uri folderUri) {
    370         if (!Utils.isEmpty(folderUri)) {
    371             final Cursor folderCursor =
    372                     context.getContentResolver().query(folderUri,
    373                             UIProvider.FOLDERS_PROJECTION, null, null, null);
    374 
    375             try {
    376                 if (folderCursor.moveToFirst()) {
    377                     return true;
    378                 }
    379             } finally {
    380                 folderCursor.close();
    381             }
    382         }
    383         return false;
    384     }
    385 
    386     protected void configureValidAccountWidget(Context context, RemoteViews remoteViews,
    387             int appWidgetId, Account account, final int folderType, final int folderCapabilities,
    388             final Uri folderUri, final Uri folderConversationListUri, String folderDisplayName) {
    389         WidgetService.configureValidAccountWidget(context, remoteViews, appWidgetId, account,
    390                 folderType, folderCapabilities, folderUri, folderConversationListUri, folderDisplayName,
    391                 WidgetService.class);
    392     }
    393 
    394     private void migrateAllLegacyWidgetInformation(Context context) {
    395         final int[] currentWidgetIds = getCurrentWidgetIds(context);
    396         migrateLegacyWidgets(context, currentWidgetIds);
    397     }
    398 
    399     private void migrateLegacyWidgets(Context context, int[] widgetIds) {
    400         for (int widgetId : widgetIds) {
    401             // We only want to bother to attempt to upgrade a widget if we don't already
    402             // have information about.
    403             if (!MailPrefs.get(context).isWidgetConfigured(widgetId)) {
    404                 migrateLegacyWidgetInformation(context, widgetId);
    405             }
    406         }
    407     }
    408 
    409     private void validateAllWidgetInformation(Context context) {
    410         final int[] widgetIds = getCurrentWidgetIds(context);
    411         for (int widgetId : widgetIds) {
    412             final String accountFolder = MailPrefs.get(context).getWidgetConfiguration(widgetId);
    413             String accountUri = null;
    414             Uri folderUri = null;
    415             if (!TextUtils.isEmpty(accountFolder)) {
    416                 final String[] parsedInfo = TextUtils.split(accountFolder,
    417                         ACCOUNT_FOLDER_PREFERENCE_SEPARATOR);
    418                 if (parsedInfo.length == 2) {
    419                     accountUri = parsedInfo[0];
    420                     folderUri = Uri.parse(parsedInfo[1]);
    421                 } else {
    422                     accountUri = accountFolder;
    423                     folderUri =  Uri.EMPTY;
    424                 }
    425             }
    426 
    427             Account account = null;
    428             if (!TextUtils.isEmpty(accountUri)) {
    429                 account = getAccountObject(context, accountUri);
    430             }
    431 
    432             // unconfigure the widget if it is not valid
    433             if (!isAccountValid(context, account) || !isFolderValid(context, folderUri)) {
    434                 updateWidgetInternal(context, widgetId, null, FolderType.DEFAULT, 0, null, null,
    435                         null);
    436             }
    437         }
    438     }
    439 
    440     /**
    441      * Abstract method allowing extending classes to perform widget migration
    442      */
    443     protected abstract void migrateLegacyWidgetInformation(Context context, int widgetId);
    444 }
    445