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
This commit is contained in:
Todd Kennedy 2011-01-25 16:09:16 -08:00
parent a7f49a7c0b
commit 6c5ee59c4f
5 changed files with 130 additions and 16 deletions

View File

@ -104,6 +104,20 @@
android:fadingEdge="none"
android:divider="@drawable/list_div_email_widget_holo"
android:cacheColorHint="#00000000" />
<TextView
android:id="@+id/tap_to_configure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="1"
android:layout_gravity="fill"
android:gravity="center"
android:paddingTop="6dip"
android:text="@string/widget_touch_to_configure"
android:textSize="16sp"
android:textColor="@android:color/white"
android:visibility="gone"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -171,6 +171,16 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
return i;
}
/**
* Create an {@link Intent} that can start the message compose activity. If accountId -1,
* the default account will be used; otherwise, the specified account is used.
*/
public static Intent getMessageComposeIntent(Context context, long accountId) {
Intent i = getBaseIntent(context);
i.putExtra(EXTRA_ACCOUNT_ID, accountId);
return i;
}
/**
* Compose a new message using the given account. If account is -1 the default account
* will be used.
@ -179,8 +189,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
*/
public static void actionCompose(Context context, long accountId) {
try {
Intent i = getBaseIntent(context);
i.putExtra(EXTRA_ACCOUNT_ID, accountId);
Intent i = getMessageComposeIntent(context, accountId);
context.startActivity(i);
} catch (ActivityNotFoundException anfe) {
// Swallow it - this is usually a race condition, especially under automated test.
@ -199,10 +208,9 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
*/
public static boolean actionCompose(Context context, String uriString, long accountId) {
try {
Intent i = getBaseIntent(context);
Intent i = getMessageComposeIntent(context, accountId);
i.setAction(Intent.ACTION_SEND);
i.setData(Uri.parse(uriString));
i.putExtra(EXTRA_ACCOUNT_ID, accountId);
context.startActivity(i);
return true;
} catch (ActivityNotFoundException anfe) {

View File

@ -99,7 +99,8 @@ public class Welcome extends Activity {
}
/**
* Create an Intent to open account's inbox.
* Create an Intent to open email activity. If <code>accountId</code> 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);

View File

@ -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 <code>true</code>, 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<Boolean, Void, Boolean> {
@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<Cursor>() {
@ -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 <code>true</code>.
* 2. If the view is {@link ViewType#ALL_INBOX}, returns <code>true</code> if more than
* one account is defined. Otherwise, returns <code>false</code>.
* 3. If the view is {@link ViewType#ACCOUNT}, returns <code>true</code> if the account
* is defined. Otherwise, returns <code>false</code>.
*/
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<? extends Activity> 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<EmailWidget> 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

View File

@ -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();
}
}