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
diff --git a/src/com/android/settings/CustomListPreference.java b/src/com/android/settings/CustomListPreference.java
index ae83013..e7c7600 100644
--- a/src/com/android/settings/CustomListPreference.java
+++ b/src/com/android/settings/CustomListPreference.java
@@ -18,8 +18,13 @@
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 @@
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 @@
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 @@
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 @@
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 90dd9e0..0725386 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -1150,5 +1150,14 @@
}
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 f0a97b1..dd4dc2e 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.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 @@
}
@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 fdaf7ad..5689c83 100644
--- a/src/com/android/settings/applications/DefaultPhonePreference.java
+++ b/src/com/android/settings/applications/DefaultPhonePreference.java
@@ -24,23 +24,28 @@
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 9315102..96ac9a2 100644
--- a/src/com/android/settings/applications/DefaultSmsPreference.java
+++ b/src/com/android/settings/applications/DefaultSmsPreference.java
@@ -22,19 +22,20 @@
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 @@
}
@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 1d4fa67..2c27700 100755
--- a/src/com/android/settings/inputmethod/InputMethodPreference.java
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -35,6 +35,7 @@
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 @@
}
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 @@
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 @@
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();