From 4a8136b51b746961aac6f5ce2459effef8c22680 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Wed, 27 Jul 2016 12:53:34 -0600 Subject: [PATCH] Warn users when selecting non-Direct Boot apps. Certain apps like Phone, SMS, Emergency Info, and IME are critical enough that they ideally need to be runnable before the device is unlocked after a reboot. Users can still pick non-Direct Boot aware apps, but this change now warns users that the selected app won't be runnable until after unlocking. Bug: 27196876 Change-Id: I0498904d2f664fb41e8c1e6bb30d1cbf437cf4b9 --- src/com/android/settings/CustomListPreference.java | 95 ++++++++++++++++++---- src/com/android/settings/Utils.java | 11 ++- .../applications/DefaultEmergencyPreference.java | 11 ++- .../applications/DefaultPhonePreference.java | 13 ++- .../applications/DefaultSmsPreference.java | 11 ++- .../inputmethod/InputMethodPreference.java | 70 ++++++++++++---- 6 files changed, 170 insertions(+), 41 deletions(-) diff --git a/src/com/android/settings/CustomListPreference.java b/src/com/android/settings/CustomListPreference.java index ae83013718..e7c7600a19 100644 --- a/src/com/android/settings/CustomListPreference.java +++ b/src/com/android/settings/CustomListPreference.java @@ -18,8 +18,13 @@ package com.android.settings; import android.app.AlertDialog; import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; import android.os.Bundle; import android.support.v14.preference.ListPreferenceDialogFragment; import android.support.v7.preference.ListPreference; @@ -50,6 +55,18 @@ public class CustomListPreference extends ListPreference { return true; } + /** + * Called when a user is about to choose the given value, to determine if we + * should show a confirmation dialog. + * + * @param value the value the user is about to choose + * @return the message to show in a confirmation dialog, or {@code null} to + * not request confirmation + */ + protected CharSequence getConfirmationMessage(String value) { + return null; + } + protected void onDialogStateRestored(Dialog dialog, Bundle savedInstanceState) { } @@ -82,9 +99,7 @@ public class CustomListPreference extends ListPreference { builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - CustomListPreferenceDialogFragment.this.onClick(dialog, - DialogInterface.BUTTON_POSITIVE); - dialog.dismiss(); + onItemChosen(); } }); } @@ -115,18 +130,11 @@ public class CustomListPreference extends ListPreference { protected DialogInterface.OnClickListener getOnItemClickListener() { return new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { setClickedDialogEntryIndex(which); - - if (getCustomizablePreference().isAutoClosePreference()) { - /* - * Clicking on an item simulates the positive button - * click, and dismisses the dialog. - */ - CustomListPreferenceDialogFragment.this.onClick(dialog, - DialogInterface.BUTTON_POSITIVE); - dialog.dismiss(); + onItemChosen(); } } }; @@ -136,17 +144,74 @@ public class CustomListPreference extends ListPreference { mClickedDialogEntryIndex = which; } + private String getValue() { + final ListPreference preference = getCustomizablePreference(); + if (mClickedDialogEntryIndex >= 0 && preference.getEntryValues() != null) { + return preference.getEntryValues()[mClickedDialogEntryIndex].toString(); + } else { + return null; + } + } + + /** + * Called when user has made a concrete item choice, but we might need + * to make a quick detour to confirm that choice with a second dialog. + */ + protected void onItemChosen() { + final CharSequence message = getCustomizablePreference() + .getConfirmationMessage(getValue()); + if (message != null) { + final Fragment f = new ConfirmDialogFragment(); + final Bundle args = new Bundle(); + args.putCharSequence(Intent.EXTRA_TEXT, message); + f.setArguments(args); + f.setTargetFragment(CustomListPreferenceDialogFragment.this, 0); + final FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.add(f, getTag() + "-Confirm"); + ft.commitAllowingStateLoss(); + } else { + onItemConfirmed(); + } + } + + /** + * Called when user has made a concrete item choice and we've fully + * confirmed they want to move forward (if we took a detour above). + */ + protected void onItemConfirmed() { + onClick(getDialog(), DialogInterface.BUTTON_POSITIVE); + getDialog().dismiss(); + } + @Override public void onDialogClosed(boolean positiveResult) { getCustomizablePreference().onDialogClosed(positiveResult); final ListPreference preference = getCustomizablePreference(); - if (positiveResult && mClickedDialogEntryIndex >= 0 && - preference.getEntryValues() != null) { - String value = preference.getEntryValues()[mClickedDialogEntryIndex].toString(); + final String value = getValue(); + if (positiveResult && value != null) { if (preference.callChangeListener(value)) { preference.setValue(value); } } } } + + public static class ConfirmDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage(getArguments().getCharSequence(Intent.EXTRA_TEXT)) + .setPositiveButton(android.R.string.ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + final Fragment f = getTargetFragment(); + if (f != null) { + ((CustomListPreferenceDialogFragment) f).onItemConfirmed(); + } + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + } } diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 90dd9e0aa0..0725386af7 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -1150,5 +1150,14 @@ public final class Utils extends com.android.settingslib.Utils { } return false; } -} + public static boolean isPackageDirectBootAware(Context context, String packageName) { + try { + final ApplicationInfo ai = context.getPackageManager().getApplicationInfo( + packageName, 0); + return ai.isDirectBootAware() || ai.isPartiallyDirectBootAware(); + } catch (NameNotFoundException ignored) { + } + return false; + } +} diff --git a/src/com/android/settings/applications/DefaultEmergencyPreference.java b/src/com/android/settings/applications/DefaultEmergencyPreference.java index f0a97b1b4c..dd4dc2ea41 100644 --- a/src/com/android/settings/applications/DefaultEmergencyPreference.java +++ b/src/com/android/settings/applications/DefaultEmergencyPreference.java @@ -16,7 +16,6 @@ package com.android.settings.applications; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -30,9 +29,11 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; -import com.android.internal.telephony.SmsApplication; + import com.android.settings.AppListPreference; +import com.android.settings.R; import com.android.settings.SelfAvailablePreference; +import com.android.settings.Utils; import java.util.List; import java.util.Objects; @@ -57,6 +58,12 @@ public class DefaultEmergencyPreference extends AppListPreference } @Override + protected CharSequence getConfirmationMessage(String value) { + return Utils.isPackageDirectBootAware(getContext(), value) ? null + : getContext().getText(R.string.direct_boot_unaware_dialog_message); + } + + @Override protected boolean persistString(String value) { String previousValue = Settings.Secure.getString(mContentResolver, Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION); diff --git a/src/com/android/settings/applications/DefaultPhonePreference.java b/src/com/android/settings/applications/DefaultPhonePreference.java index fdaf7add3f..5689c83833 100644 --- a/src/com/android/settings/applications/DefaultPhonePreference.java +++ b/src/com/android/settings/applications/DefaultPhonePreference.java @@ -24,23 +24,28 @@ import android.telecom.TelecomManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.AttributeSet; + import com.android.settings.AppListPreference; +import com.android.settings.R; import com.android.settings.SelfAvailablePreference; +import com.android.settings.Utils; import java.util.List; import java.util.Objects; public class DefaultPhonePreference extends AppListPreference implements SelfAvailablePreference { - private final Context mContext; - public DefaultPhonePreference(Context context, AttributeSet attrs) { super(context, attrs); - - mContext = context.getApplicationContext(); loadDialerApps(); } @Override + protected CharSequence getConfirmationMessage(String value) { + return Utils.isPackageDirectBootAware(getContext(), value) ? null + : getContext().getText(R.string.direct_boot_unaware_dialog_message); + } + + @Override protected boolean persistString(String value) { if (!TextUtils.isEmpty(value) && !Objects.equals(value, getDefaultPackage())) { DefaultDialerManager.setDefaultDialerApplication(getContext(), value, mUserId); diff --git a/src/com/android/settings/applications/DefaultSmsPreference.java b/src/com/android/settings/applications/DefaultSmsPreference.java index 9315102015..96ac9a2898 100644 --- a/src/com/android/settings/applications/DefaultSmsPreference.java +++ b/src/com/android/settings/applications/DefaultSmsPreference.java @@ -22,19 +22,20 @@ import android.os.UserManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.AttributeSet; + import com.android.internal.telephony.SmsApplication; import com.android.internal.telephony.SmsApplication.SmsApplicationData; import com.android.settings.AppListPreference; +import com.android.settings.R; import com.android.settings.SelfAvailablePreference; +import com.android.settings.Utils; import java.util.Collection; import java.util.Objects; public class DefaultSmsPreference extends AppListPreference implements SelfAvailablePreference { - public DefaultSmsPreference(Context context, AttributeSet attrs) { super(context, attrs); - loadSmsApps(); } @@ -60,6 +61,12 @@ public class DefaultSmsPreference extends AppListPreference implements SelfAvail } @Override + protected CharSequence getConfirmationMessage(String value) { + return Utils.isPackageDirectBootAware(getContext(), value) ? null + : getContext().getText(R.string.direct_boot_unaware_dialog_message); + } + + @Override protected boolean persistString(String value) { if (!TextUtils.isEmpty(value) && !Objects.equals(value, getDefaultPackage())) { SmsApplication.setDefaultApplication(value, getContext()); diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java index 1d4fa67dda..2c277002b2 100755 --- a/src/com/android/settings/inputmethod/InputMethodPreference.java +++ b/src/com/android/settings/inputmethod/InputMethodPreference.java @@ -35,6 +35,7 @@ import android.widget.Toast; import com.android.internal.inputmethod.InputMethodUtils; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedSwitchPreference; @@ -142,18 +143,22 @@ class InputMethodPreference extends RestrictedSwitchPreference implements OnPref } if (isChecked()) { // Disable this IME. - setChecked(false); - mOnSaveListener.onSaveInputMethodPreference(this); + setCheckedInternal(false); return false; } if (InputMethodUtils.isSystemIme(mImi)) { - // Enable a system IME. No need to show a security warning dialog. - setChecked(true); - mOnSaveListener.onSaveInputMethodPreference(this); - return false; + // Enable a system IME. No need to show a security warning dialog, + // but we might need to prompt if it's not Direct Boot aware. + if (Utils.isPackageDirectBootAware(getContext(), mImi.getPackageName())) { + setCheckedInternal(true); + } else { + showDirectBootWarnDialog(); + } + } else { + // Once security is confirmed, we might prompt if the IME isn't + // Direct Boot aware. + showSecurityWarnDialog(); } - // Enable a 3rd party IME. - showSecurityWarnDialog(mImi); return false; } @@ -218,7 +223,13 @@ class InputMethodPreference extends RestrictedSwitchPreference implements OnPref subtypes, getContext(), mImi); } - private void showSecurityWarnDialog(final InputMethodInfo imi) { + private void setCheckedInternal(boolean checked) { + super.setChecked(checked); + mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this); + notifyChanged(); + } + + private void showSecurityWarnDialog() { if (mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); } @@ -226,25 +237,50 @@ class InputMethodPreference extends RestrictedSwitchPreference implements OnPref final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setCancelable(true /* cancelable */); builder.setTitle(android.R.string.dialog_alert_title); - final CharSequence label = imi.getServiceInfo().applicationInfo.loadLabel( + final CharSequence label = mImi.getServiceInfo().applicationInfo.loadLabel( context.getPackageManager()); builder.setMessage(context.getString(R.string.ime_security_warning, label)); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { - // The user confirmed to enable a 3rd party IME. - setChecked(true); - mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this); - notifyChanged(); + // The user confirmed to enable a 3rd party IME, but we might + // need to prompt if it's not Direct Boot aware. + if (Utils.isPackageDirectBootAware(getContext(), mImi.getPackageName())) { + setCheckedInternal(true); + } else { + showDirectBootWarnDialog(); + } } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { // The user canceled to enable a 3rd party IME. - setChecked(false); - mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this); - notifyChanged(); + setCheckedInternal(false); + } + }); + mDialog = builder.create(); + mDialog.show(); + } + + private void showDirectBootWarnDialog() { + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + } + final Context context = getContext(); + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setCancelable(true /* cancelable */); + builder.setMessage(context.getText(R.string.direct_boot_unaware_dialog_message)); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + setCheckedInternal(true); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + setCheckedInternal(false); } }); mDialog = builder.create(); -- 2.11.0