• 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_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
onDeleted(Context context, int[] appWidgetIds)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 
getProviderName(Context context)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      */
getCurrentWidgetIds(Context context)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      */
getWidgetInfo(Context context, int[] widgetIds)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
onReceive(Context context, Intent intent)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
onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)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 
BulkUpdateAsyncTask(final Context context, final int[] appWidgetIds)202         public BulkUpdateAsyncTask(final Context context, final int[] appWidgetIds) {
203             mContext = context;
204             mAppWidgetIds = appWidgetIds;
205         }
206 
207         @Override
doInBackground(final Void... params)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 
getAccountObject(Context context, String accountUri)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      */
updateWidget(Context context, int appWidgetId, Account account, final int folderType, final Uri folderUri, final Uri folderConversationListUri, final String folderDisplayName)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 
validateAllWidgets(Context context, String accountMimeType)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 
updateWidgetInternal(Context context, int appWidgetId, Account account, final int folderType, final Uri folderUri, final Uri folderConversationListUri, final String folderDisplayName)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 
isAccountValid(Context context, Account account)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 
isFolderValid(Context context, Uri folderUri)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 
configureValidAccountWidget(Context context, RemoteViews remoteViews, int appWidgetId, Account account, final int folderType, final Uri folderUri, final Uri folderConversationListUri, String folderDisplayName)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 
migrateAllLegacyWidgetInformation(Context context)390     private void migrateAllLegacyWidgetInformation(Context context) {
391         final int[] currentWidgetIds = getCurrentWidgetIds(context);
392         migrateLegacyWidgets(context, currentWidgetIds);
393     }
394 
migrateLegacyWidgets(Context context, int[] widgetIds)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 
validateAllWidgetInformation(Context context)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      */
migrateLegacyWidgetInformation(Context context, int widgetId)438     protected abstract void migrateLegacyWidgetInformation(Context context, int widgetId);
439 }
440