From 6c5ee59c4fcd5cb2992a53234245df8faeaf8a5d Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Tue, 25 Jan 2011 16:09:16 -0800 Subject: [PATCH] Add "tap to configure" text to widget If there are no email accounts defined, the widget should show a single string that allows the user to create a new account. Whenever there are changes to the defined accounts, the widget(s) will update their headers to ensure they are only displaying valid information. bug 3296594 Change-Id: I156c20cfc90692174297a2aededd85775e0ea196 --- res/layout/widget.xml | 14 +++ .../email/activity/MessageCompose.java | 16 ++- src/com/android/email/activity/Welcome.java | 3 +- .../email/provider/WidgetProvider.java | 109 ++++++++++++++++-- .../EmailBroadcastProcessorService.java | 4 + 5 files changed, 130 insertions(+), 16 deletions(-) diff --git a/res/layout/widget.xml b/res/layout/widget.xml index fac3ff5ef..f6453ae6c 100644 --- a/res/layout/widget.xml +++ b/res/layout/widget.xml @@ -104,6 +104,20 @@ android:fadingEdge="none" android:divider="@drawable/list_div_email_widget_holo" android:cacheColorHint="#00000000" /> + accountId is not -1, the + * specified account will be automatically be opened when the activity starts. */ public static Intent createOpenAccountInboxIntent(Context context, long accountId) { Intent i = Utility.createRestartAppIntent(context, Welcome.class); diff --git a/src/com/android/email/provider/WidgetProvider.java b/src/com/android/email/provider/WidgetProvider.java index cbecc3b3d..b3375a20f 100644 --- a/src/com/android/email/provider/WidgetProvider.java +++ b/src/com/android/email/provider/WidgetProvider.java @@ -22,6 +22,7 @@ import com.android.email.ResourceHelper; import com.android.email.Utility; import com.android.email.activity.MessageCompose; import com.android.email.activity.Welcome; +import com.android.email.activity.setup.AccountSetupBasics; import com.android.email.data.ThrottlingCursorLoader; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.AccountColumns; @@ -61,6 +62,7 @@ import android.widget.RemoteViewsService; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -183,8 +185,10 @@ public class WidgetProvider extends AppWidgetProvider { // Number of records in the cursor private int mCursorCount = TOTAL_COUNT_UNKNOWN; // The widget's loader (derived from ThrottlingCursorLoader) - private WidgetLoader mLoader; + private ViewCursorLoader mLoader; private final ResourceHelper mResourceHelper; + // Number of defined accounts + private int mAccountCount = TOTAL_COUNT_UNKNOWN; // The current view type (all mail, unread, or starred for now) /*package*/ ViewType mViewType = ViewType.STARRED; @@ -214,7 +218,7 @@ public class WidgetProvider extends AppWidgetProvider { Log.d(TAG, "Creating EmailWidget with id = " + _widgetId); } mWidgetId = _widgetId; - mLoader = new WidgetLoader(); + mLoader = new ViewCursorLoader(); if (sSubjectSnippetDivider == null) { // Initialize string, color, dimension resources Resources res = sContext.getResources(); @@ -231,13 +235,37 @@ public class WidgetProvider extends AppWidgetProvider { mResourceHelper = ResourceHelper.getInstance(sContext); } + /** + * Task for updating widget data (eg: the header, view list items, etc...) + * If parameter to {@link #execute(Boolean...)} is true, the current + * view is validated against the current set of accounts. And if the current view + * is determined to be invalid, the view will automatically progress to the next + * valid view. + */ + final class WidgetUpdateTask extends AsyncTask { + @Override + protected Boolean doInBackground(Boolean... validateView) { + mAccountCount = EmailContent.count(sContext, EmailContent.Account.CONTENT_URI); + // If displaying invalid view, switch to the next view + return !validateView[0] || isViewValid(); + } + + @Override + protected void onPostExecute(Boolean isValidView) { + updateHeader(); + if (!isValidView) { + new WidgetViewSwitcher(EmailWidget.this).execute(); + } + } + } + /** * The ThrottlingCursorLoader does all of the heavy lifting in managing the data loading * task; all we need is to register a listener so that we're notified when the load is * complete. */ - final class WidgetLoader extends ThrottlingCursorLoader { - protected WidgetLoader() { + final class ViewCursorLoader extends ThrottlingCursorLoader { + protected ViewCursorLoader() { super(sContext, Message.CONTENT_URI, WIDGET_PROJECTION, mViewType.selection, mViewType.selectionArgs, SORT_TIMESTAMP_DESCENDING); registerListener(0, new OnLoadCompleteListener() { @@ -280,6 +308,8 @@ public class WidgetProvider extends AppWidgetProvider { * Initialize to first appropriate view (depending on the number of accounts) */ private void init() { + // Just update the account count & header; no need to validate the view + new WidgetUpdateTask().execute(false); new WidgetViewSwitcher(this).execute(); } @@ -336,6 +366,35 @@ public class WidgetProvider extends AppWidgetProvider { } } + /** + * Returns whether the current view is valid. The following rules determine if a view is + * considered valid: + * 1. If the view is either {@link ViewType#STARRED} or {@link ViewType#UNREAD}, always + * returns true. + * 2. If the view is {@link ViewType#ALL_INBOX}, returns true if more than + * one account is defined. Otherwise, returns false. + * 3. If the view is {@link ViewType#ACCOUNT}, returns true if the account + * is defined. Otherwise, returns false. + */ + private boolean isViewValid() { + switch(mViewType) { + case ALL_INBOX: + // "all inbox" is valid only if there is more than one account + return (EmailContent.count(sContext, Account.CONTENT_URI) > 1); + case ACCOUNT: + // Ensure current account still exists + String idString = ViewType.ACCOUNT.selectionArgs[0]; + Cursor c = sResolver.query(Account.CONTENT_URI, ID_NAME_PROJECTION, "_id=?", + new String[] {idString}, SORT_ID_ASCENDING); + try { + return c.moveToFirst(); + } finally { + c.close(); + } + } + return true; + } + /** * Convenience method for creating an onClickPendingIntent that executes a command via * our command Uri. Used for the "next view" command; appends the widget id to the command @@ -356,15 +415,13 @@ public class WidgetProvider extends AppWidgetProvider { /** * Convenience method for creating an onClickPendingIntent that launches another activity - * directly. Used for the "Compose" button + * directly. * * @param views The RemoteViews we're inflating * @param buttonId the id of the button view - * @param activityClass the class of the activity to be launched + * @param intent The intent to be used when launching the activity */ - private void setActivityIntent(RemoteViews views, int buttonId, - Class activityClass) { - Intent intent = new Intent(sContext, activityClass); + private void setActivityIntent(RemoteViews views, int buttonId, Intent intent) { PendingIntent pendingIntent = PendingIntent.getActivity(sContext, 0, intent, 0); views.setOnClickPendingIntent(buttonId, pendingIntent); } @@ -418,8 +475,24 @@ public class WidgetProvider extends AppWidgetProvider { setupTitleAndCount(views); - // Set up "new" button (compose new message) and "next view" button - setActivityIntent(views, R.id.widget_compose, MessageCompose.class); + if (mAccountCount == 0) { + // Hide compose icon & show "touch to configure" text + views.setViewVisibility(R.id.widget_compose, View.INVISIBLE); + views.setViewVisibility(R.id.message_list, View.GONE); + views.setViewVisibility(R.id.tap_to_configure, View.VISIBLE); + // Create click intent for "touch to configure" target + intent = Welcome.createOpenAccountInboxIntent(sContext, -1); + setActivityIntent(views, R.id.tap_to_configure, intent); + } else { + // Show compose icon & message list + views.setViewVisibility(R.id.widget_compose, View.VISIBLE); + views.setViewVisibility(R.id.message_list, View.VISIBLE); + views.setViewVisibility(R.id.tap_to_configure, View.GONE); + // Create click intent for "compose email" target + intent = MessageCompose.getMessageComposeIntent(sContext, -1); + setActivityIntent(views, R.id.widget_compose, intent); + } + // Create click intent for "view rotation" target setCommandIntent(views, R.id.widget_logo, COMMAND_URI_SWITCH_LIST_VIEW); // Use a bare intent for our template; we need to fill everything in @@ -618,6 +691,20 @@ public class WidgetProvider extends AppWidgetProvider { } } + /** + * Updates all active widgets. If no widgets are active, does nothing. + */ + public static synchronized void updateAllWidgets() { + // Ignore if the widget is not active + if (sContext != null && sWidgetMap.size() > 0) { + Collection widgetSet = sWidgetMap.values(); + for (EmailWidget widget: widgetSet) { + // Anything could have changed; update widget & validate the current view + widget.new WidgetUpdateTask().execute(true); + } + } + } + /** * Force a context for widgets (used by unit tests) * @param context the Context to set diff --git a/src/com/android/email/service/EmailBroadcastProcessorService.java b/src/com/android/email/service/EmailBroadcastProcessorService.java index d6c086c1c..ea930922e 100644 --- a/src/com/android/email/service/EmailBroadcastProcessorService.java +++ b/src/com/android/email/service/EmailBroadcastProcessorService.java @@ -22,6 +22,7 @@ import com.android.email.Preferences; import com.android.email.SecurityPolicy; import com.android.email.VendorPolicyLoader; import com.android.email.activity.setup.AccountSettingsXL; +import com.android.email.provider.WidgetProvider; import android.accounts.AccountManager; import android.app.IntentService; @@ -180,5 +181,8 @@ public class EmailBroadcastProcessorService extends IntentService { // Let ExchangeService reconcile EAS accouts. // The service will stops itself it there's no EAS accounts. ExchangeUtils.startExchangeService(this); + + // Let all of the widgets update + WidgetProvider.updateAllWidgets(); } }