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