Add contextual dialogs after notifications

* When security settings notification is clicked, inform user that
  they need to change settings (before dumping them in security
  settings.)
* On an authentication failure, present a dialog to the user explaining
  that the username or password may be incorrect.
* When the device pin/password is expiring or expired, present a dialog
  to the user explaining that it needs to be updated.

Bug: 3238657
Change-Id: I8fca446fa3c1bf87a95938553dbdc362c3df220e
This commit is contained in:
Andy Stadler 2011-02-18 18:23:18 -08:00
parent a819783491
commit f489413142
9 changed files with 278 additions and 17 deletions

View File

@ -735,6 +735,16 @@ save attachment.</string>
to administer your device will delete all Email accounts that require it, along with their
email, contacts, calendar events, and other data.</string>
<!-- Dialog shown when the account requires some amount of device security provisioning,
just before jumping into system settings such as Device Policy grant, PIN/password,
or encryption setup. [CHAR_LIMIT=40] -->
<string name="account_security_dialog_title">Security update required</string>
<!-- Additional diagnostic text when the account requires some amount of device security
provisioning, just before jumping into system settings such as Device Policy grant,
PIN/password, or encryption setup. [CHAR LIMIT=none] -->
<string name="account_security_dialog_content_fmt">
<xliff:g id="account">%s</xliff:g> requires you to update your security settings.</string>
<!-- Notification ticker when device security required (note: unused in Holo XL) -->
<string name="security_notification_ticker_fmt">
Account \"<xliff:g id="account">%s</xliff:g>\" requires security settings update.
@ -765,10 +775,33 @@ save attachment.</string>
<!-- Notification content title when device password has expired [CHAR_LIMIT=28] -->
<string name="password_expired_content_title">Lock screen password expired</string>
<!-- Dialog title if device pin/password is going to expire soon. [CHAR_LIMIT=40] -->
<string name="password_expire_warning_dialog_title">Lock screen password expiring</string>
<!-- Dialog content device pin/password is going to expire soon. [CHAR_LIMIT=none] -->
<string name="password_expire_warning_dialog_content_fmt">
You must change your lock screen PIN or password soon, or the data for
<xliff:g id="account">%s</xliff:g> will be erased. Change it now?</string>
<!-- Dialog title if device pin/password has already expired. [CHAR_LIMIT=40] -->
<string name="password_expired_dialog_title">Lock screen password expired</string>
<!-- Dialog content device pin/password has already expired. [CHAR_LIMIT=none] -->
<string name="password_expired_dialog_content_fmt">
The data for <xliff:g id="account">%s</xliff:g> is being erased from your device.
You can restore it by changing your lock screen PIN or password. Change it now?</string>
<!-- On AccountSettingsXL, dialog text if you try to exit in/out/eas fragment (server settings)
without checking/saving [CHAR LIMIT=none]-->
<string name="account_settings_exit_server_settings">Discard unsaved changes?</string>
<!-- On AccountSettingsXL, dialog title if you were brought here due to a login failure.
[CHAR_LIMIT=40] -->
<string name="account_settings_login_dialog_title">Sign-in failed</string>
<!-- On AccountSettingsXL, dialog content if you were brought here due to a login failure.
[CHAR_LIMIT=none] -->
<string name="account_settings_login_dialog_content_fmt">
The username or password for <xliff:g id="account">%s</xliff:g> is incorrect.
Update them now?</string>
<!-- On Settings screen, section heading -->
<string name="account_settings_title_fmt">General settings</string>
<!-- On Settings screen, setting option name -->

View File

@ -27,7 +27,6 @@ import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.utility.Utility;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@ -331,7 +330,8 @@ public class NotificationController {
mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
mContext.getString(R.string.login_failed_title),
account.getDisplayName(),
AccountSettingsXL.createAccountSettingsIntent(mContext, accountId),
AccountSettingsXL.createAccountSettingsIntent(mContext, accountId,
account.mDisplayName),
getLoginFailedNotificationId(accountId));
}

View File

@ -468,7 +468,7 @@ public class SecurityPolicy {
account.getDisplayName());
String contentTitle = mContext.getString(R.string.security_notification_content_title);
String contentText = account.getDisplayName();
Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId, true);
NotificationController.getInstance(mContext).postAccountNotification(
account, tickerText, contentTitle, contentText, intent,
NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
@ -581,7 +581,8 @@ public class SecurityPolicy {
// the shortest-expiring account.
Account account = Account.restoreAccountWithId(context, nextExpiringAccountId);
if (account == null) return;
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(context,
nextExpiringAccountId, false);
String ticker = context.getString(
R.string.password_expire_warning_ticker_fmt, account.getDisplayName());
String contentTitle = context.getString(
@ -597,7 +598,8 @@ public class SecurityPolicy {
// Post notification
Account account = Account.restoreAccountWithId(context, nextExpiringAccountId);
if (account == null) return;
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(context,
nextExpiringAccountId, true);
String ticker = context.getString(R.string.password_expired_ticker);
String contentTitle = context.getString(R.string.password_expired_content_title);
String contentText = account.getDisplayName();

View File

@ -674,7 +674,7 @@ public class MessageList extends Activity implements OnClickListener,
public void onAccountSecurityHold(long accountId) {
// launch the security setup activity
Intent i = AccountSecurity.actionUpdateSecurityIntent(
MessageList.this, accountId);
MessageList.this, accountId, true);
MessageList.this.startActivityForResult(i, REQUEST_SECURITY);
}
}

View File

@ -534,7 +534,7 @@ public class MessageListXL extends Activity implements
@Override
public void onAccountSecurityHold(long accountId) {
startActivity(AccountSecurity.actionUpdateSecurityIntent(this, accountId));
startActivity(AccountSecurity.actionUpdateSecurityIntent(this, accountId, true));
}
@Override

View File

@ -24,9 +24,15 @@ import com.android.emailcommon.provider.EmailContent.HostAuth;
import com.android.emailcommon.utility.Utility;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
@ -43,7 +49,10 @@ import android.os.Bundle;
*/
public class AccountSecurity extends Activity {
private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity.setup.ACCOUNT_ID";
private static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID";
private static final String EXTRA_SHOW_DIALOG = "SHOW_DIALOG";
private static final String EXTRA_PASSWORD_EXPIRING = "EXPIRING";
private static final String EXTRA_PASSWORD_EXPIRED = "EXPIRED";
private static final int REQUEST_ENABLE = 1;
private static final int REQUEST_PASSWORD = 2;
@ -60,11 +69,29 @@ public class AccountSecurity extends Activity {
*
* @param context Calling context for building the intent
* @param accountId The account of interest
* @param showDialog If true, a simple warning dialog will be shown before kicking off
* the necessary system settings. Should be true anywhere the context of the security settings
* is not clear (e.g. any time after the account has been set up).
* @return an Intent which can be used to view that account
*/
public static Intent actionUpdateSecurityIntent(Context context, long accountId) {
public static Intent actionUpdateSecurityIntent(Context context, long accountId,
boolean showDialog) {
Intent intent = new Intent(context, AccountSecurity.class);
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
intent.putExtra(EXTRA_SHOW_DIALOG, showDialog);
return intent;
}
/**
* Used for generating intent for this activity (which is intended to be launched
* from a notification.) This is a special mode of this activity which exists only
* to give the user a dialog (for context) about a device pin/password expiration event.
*/
public static Intent actionDevicePasswordExpirationIntent(Context context, long accountId,
boolean expired) {
Intent intent = new Intent(context, AccountSecurity.class);
intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
intent.putExtra(expired ? EXTRA_PASSWORD_EXPIRED : EXTRA_PASSWORD_EXPIRING, true);
return intent;
}
@ -75,6 +102,9 @@ public class AccountSecurity extends Activity {
Intent i = getIntent();
final long accountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
final boolean showDialog = i.getBooleanExtra(EXTRA_SHOW_DIALOG, false);
final boolean passwordExpiring = i.getBooleanExtra(EXTRA_PASSWORD_EXPIRING, false);
final boolean passwordExpired = i.getBooleanExtra(EXTRA_PASSWORD_EXPIRED, false);
SecurityPolicy security = SecurityPolicy.getInstance(this);
security.clearNotification(accountId);
if (accountId == -1) {
@ -93,9 +123,36 @@ public class AccountSecurity extends Activity {
@Override
protected void onPostExecute(Account result) {
mAccount = result;
if (mAccount != null && mAccount.mSecurityFlags != 0) {
if (mAccount == null) {
finish();
return;
}
// Special handling for password expiration events
if (passwordExpiring || passwordExpired) {
FragmentManager fm = getFragmentManager();
if (fm.findFragmentByTag("password_expiration") == null) {
PasswordExpirationDialog dialog =
PasswordExpirationDialog.newInstance(mAccount.getDisplayName(),
passwordExpired);
dialog.show(fm, "password_expiration");
}
return;
}
// Otherwise, handle normal security settings flow
if (mAccount.mSecurityFlags != 0) {
// This account wants to control security
tryAdvanceSecurity(mAccount);
if (showDialog) {
// Show dialog first, unless already showing (e.g. after rotation)
FragmentManager fm = getFragmentManager();
if (fm.findFragmentByTag("security_needed") == null) {
SecurityNeededDialog dialog =
SecurityNeededDialog.newInstance(mAccount.getDisplayName());
dialog.show(fm, "security_needed");
}
} else {
// Go directly to security settings
tryAdvanceSecurity(mAccount);
}
return;
}
finish();
@ -214,4 +271,119 @@ public class AccountSecurity extends Activity {
}
});
}
/**
* Dialog briefly shown in some cases, to indicate the user that a security update is needed.
* If the user clicks OK, we proceed into the "tryAdvanceSecurity" flow. If the user cancels,
* we repost the notification and finish() the activity.
*/
public static class SecurityNeededDialog extends DialogFragment
implements DialogInterface.OnClickListener {
private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name";
/**
* Create a new dialog.
*/
public static SecurityNeededDialog newInstance(String accountName) {
final SecurityNeededDialog dialog = new SecurityNeededDialog();
Bundle b = new Bundle();
b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName);
dialog.setArguments(b);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME);
final Context context = getActivity();
final Resources res = context.getResources();
final AlertDialog.Builder b = new AlertDialog.Builder(context);
b.setTitle(R.string.account_security_dialog_title);
b.setIconAttribute(android.R.attr.alertDialogIcon);
b.setMessage(res.getString(R.string.account_security_dialog_content_fmt, accountName));
b.setPositiveButton(R.string.okay_action, this);
b.setNegativeButton(R.string.cancel_action, this);
return b.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
AccountSecurity activity = (AccountSecurity) getActivity();
if (activity.mAccount == null) {
// Clicked before activity fully restored - probably just monkey - exit quickly
activity.finish();
return;
}
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
activity.tryAdvanceSecurity(activity.mAccount);
break;
case DialogInterface.BUTTON_NEGATIVE:
activity.repostNotification(
activity.mAccount, SecurityPolicy.getInstance(activity));
activity.finish();
break;
}
}
}
/**
* Dialog briefly shown in some cases, to indicate the user that the PIN/Password is expiring
* or has expired. If the user clicks OK, we launch the password settings screen.
*/
public static class PasswordExpirationDialog extends DialogFragment
implements DialogInterface.OnClickListener {
private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name";
private static final String BUNDLE_KEY_EXPIRED = "expired";
/**
* Create a new dialog.
*/
public static PasswordExpirationDialog newInstance(String accountName, boolean expired) {
final PasswordExpirationDialog dialog = new PasswordExpirationDialog();
Bundle b = new Bundle();
b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName);
b.putBoolean(BUNDLE_KEY_EXPIRED, expired);
dialog.setArguments(b);
return dialog;
}
/**
* Note, this actually creates two slightly different dialogs (for expiring vs. expired)
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME);
final boolean expired = getArguments().getBoolean(BUNDLE_KEY_EXPIRED);
final int titleId = expired
? R.string.password_expired_dialog_title
: R.string.password_expire_warning_dialog_title;
final int contentId = expired
? R.string.password_expired_dialog_content_fmt
: R.string.password_expire_warning_dialog_content_fmt;
final Context context = getActivity();
final Resources res = context.getResources();
final AlertDialog.Builder b = new AlertDialog.Builder(context);
b.setTitle(titleId);
b.setIconAttribute(android.R.attr.alertDialogIcon);
b.setMessage(res.getString(contentId, accountName));
b.setPositiveButton(R.string.okay_action, this);
b.setNegativeButton(R.string.cancel_action, this);
return b.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
AccountSecurity activity = (AccountSecurity) getActivity();
if (which == DialogInterface.BUTTON_POSITIVE) {
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
activity.startActivity(intent);
}
activity.finish();
}
}
}

View File

@ -37,6 +37,7 @@ import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
@ -68,6 +69,7 @@ public class AccountSettingsXL extends PreferenceActivity {
// Intent extras for our internal activity launch
/* package */ static final String EXTRA_ACCOUNT_ID = "AccountSettingsXL.account_id";
private static final String EXTRA_ENABLE_DEBUG = "AccountSettingsXL.enable_debug";
private static final String EXTRA_LOGIN_WARNING_FOR_ACCOUNT = "AccountSettingsXL.for_account";
// Intent extras for launch directly from system account manager
// NOTE: This string must match the one in res/xml/account_preferences.xml
@ -122,11 +124,14 @@ public class AccountSettingsXL extends PreferenceActivity {
/**
* Create and return an intent to display (and edit) settings for a specific account, or -1
* for any/all accounts
* for any/all accounts. If an account name string is provided, a warning dialog will be
* displayed as well.
*/
public static Intent createAccountSettingsIntent(Context context, long accountId) {
public static Intent createAccountSettingsIntent(Context context, long accountId,
String loginWarningAccountName) {
Intent i = new Intent(context, AccountSettingsXL.class);
i.putExtra(EXTRA_ACCOUNT_ID, accountId);
i.putExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT, loginWarningAccountName);
return i;
}
@ -155,8 +160,14 @@ public class AccountSettingsXL extends PreferenceActivity {
mGetAccountIdFromAccountTask =
(GetAccountIdFromAccountTask) new GetAccountIdFromAccountTask().execute(i);
} else {
// Otherwise, we're called from within the Email app and look for our extra
// Otherwise, we're called from within the Email app and look for our extras
mRequestedAccountId = i.getLongExtra(EXTRA_ACCOUNT_ID, -1);
String loginWarningAccount = i.getStringExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT);
if (loginWarningAccount != null) {
// Show dialog (first time only - don't re-show on a rotation)
LoginWarningDialog dialog = LoginWarningDialog.newInstance(loginWarningAccount);
dialog.show(getFragmentManager(), "loginwarning");
}
}
}
mShowDebugMenu = i.getBooleanExtra(EXTRA_ENABLE_DEBUG, false);
@ -741,4 +752,48 @@ public class AccountSettingsXL extends PreferenceActivity {
}
}
/**
* Dialog briefly shown in some cases, to indicate the user that login failed. If the user
* clicks OK, we simply dismiss the dialog, leaving the user in the account settings for
* that account; If the user clicks "cancel", we exit account settings.
*/
public static class LoginWarningDialog extends DialogFragment
implements DialogInterface.OnClickListener {
private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name";
/**
* Create a new dialog.
*/
public static LoginWarningDialog newInstance(String accountName) {
final LoginWarningDialog dialog = new LoginWarningDialog();
Bundle b = new Bundle();
b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName);
dialog.setArguments(b);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME);
final Context context = getActivity();
final Resources res = context.getResources();
final AlertDialog.Builder b = new AlertDialog.Builder(context);
b.setTitle(R.string.account_settings_login_dialog_title);
b.setIconAttribute(android.R.attr.alertDialogIcon);
b.setMessage(res.getString(R.string.account_settings_login_dialog_content_fmt,
accountName));
b.setPositiveButton(R.string.okay_action, this);
b.setNegativeButton(R.string.cancel_action, this);
return b.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
if (which == DialogInterface.BUTTON_NEGATIVE) {
getActivity().finish();
}
}
}
}

View File

@ -22,7 +22,6 @@ import com.android.email.activity.ActivityHelper;
import com.android.email.activity.Welcome;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.utility.Utility;
import android.app.Activity;
import android.content.ContentValues;
@ -234,7 +233,7 @@ public class AccountSetupNames extends AccountSetupActivity implements OnClickLi
if (!isCancelled()) {
if (isSecurityHold) {
Intent i = AccountSecurity.actionUpdateSecurityIntent(
AccountSetupNames.this, mAccount.mId);
AccountSetupNames.this, mAccount.mId, false);
AccountSetupNames.this.startActivityForResult(i, REQUEST_SECURITY);
} else {
finishActivity();

View File

@ -326,7 +326,7 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
// If we've got policies for this account, ask the user to accept.
Account account = SetupData.getAccount();
if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
Intent intent = AccountSecurity.actionUpdateSecurityIntent(this, account.mId);
Intent intent = AccountSecurity.actionUpdateSecurityIntent(this, account.mId, false);
startActivityForResult(intent, AccountSetupOptions.REQUEST_CODE_ACCEPT_POLICIES);
return;
}