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