approvedServiceFinder) {
+
+ String owner = zenMode.getRule().getPackageName();
+ ComponentName configActivity = null;
+ if (zenMode.getRule().getConfigurationActivity() != null) {
+ // If a configuration activity is present, use that directly in the intent
+ configActivity = zenMode.getRule().getConfigurationActivity();
+ } else {
+ // Otherwise, look for a condition provider service for the rule's package
+ ComponentInfo ci = approvedServiceFinder.apply(zenMode.getRule().getOwner());
+ if (ci != null) {
+ configActivity = extractConfigurationActivityFromComponent(ci);
+ }
+ }
+
+ if (configActivity != null
+ && (owner == null || isSameOwnerPackage(owner, configActivity))
+ && isResolvableActivity(configActivity)) {
+ return new Intent()
+ .setComponent(configActivity)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(ConditionProviderService.EXTRA_RULE_ID, zenMode.getId())
+ .putExtra(EXTRA_AUTOMATIC_RULE_ID, zenMode.getId());
+ } else {
+ return null;
+ }
+ }
+
+ @Nullable
+ ComponentName getConfigurationActivityFromApprovedComponent(ComponentInfo ci) {
+ ComponentName configActivity = extractConfigurationActivityFromComponent(ci);
+ if (configActivity != null
+ && isSameOwnerPackage(ci.packageName, configActivity)
+ && isResolvableActivity(configActivity)) {
+ return configActivity;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Extract the {@link ComponentName} corresponding to the mode configuration activity
+ * from the component declaring the rule (which may be the Activity itself, or a CPS that points
+ * to the activity in question in its metadata).
+ *
+ * This method doesn't perform any validation, so the activity may or may not exist.
+ */
+ @Nullable
+ private ComponentName extractConfigurationActivityFromComponent(ComponentInfo ci) {
+ if (ci instanceof ActivityInfo) {
+ // New (activity-backed) rule.
+ return new ComponentName(ci.packageName, ci.name);
+ } else if (ci.metaData != null) {
+ // Old (service-backed) rule.
+ final String configurationActivity = ci.metaData.getString(
+ ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY);
+ if (configurationActivity != null) {
+ return ComponentName.unflattenFromString(configurationActivity);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Verifies that the activity is the same package as the rule owner.
+ */
+ private boolean isSameOwnerPackage(String ownerPkg, ComponentName activityName) {
+ try {
+ int ownerUid = mPm.getPackageUid(ownerPkg, 0);
+ int configActivityOwnerUid = mPm.getPackageUid(activityName.getPackageName(), 0);
+ if (ownerUid == configActivityOwnerUid) {
+ return true;
+ } else {
+ Log.w(TAG, String.format("Config activity (%s) not in owner package (%s)",
+ activityName, ownerPkg));
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to find config activity " + activityName);
+ return false;
+ }
+ }
+
+ /** Verifies that the activity exists and hasn't been disabled. */
+ private boolean isResolvableActivity(ComponentName activityName) {
+ Intent intent = new Intent().setComponent(activityName);
+ List results = mPm.queryIntentActivities(intent, /* flags= */ 0);
+
+ if (intent.resolveActivity(mPm) == null || results.isEmpty()) {
+ Log.w(TAG, "Cannot resolve: " + activityName);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index 56967c89d00..d07abf34b79 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -19,14 +19,17 @@ package com.android.settings.notification.modes;
import static com.google.common.base.Preconditions.checkNotNull;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
+import android.view.Gravity;
import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.Px;
import com.android.settings.R;
import com.android.settingslib.Utils;
@@ -49,32 +52,52 @@ class IconUtil {
}
/**
- * Returns a variant of the supplied {@code icon} to be used in the icon picker. The inner icon
- * is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up so that
- * selection and pressed states are represented in the color.
+ * Returns a variant of the supplied {@code icon} to be used as the header in the icon picker.
+ * The inner icon is 48x48dp and it's contained into a circle of diameter 90dp.
*/
- static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
+ static Drawable makeBigIconCircle(@NonNull Context context, Drawable icon) {
+ return composeIconCircle(
+ Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorSecondaryContainer),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_header_circle_diameter),
+ icon,
+ Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorOnSecondaryContainer),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_header_icon_size));
+ }
+
+ /**
+ * Returns a variant of the supplied {@code icon} to be used as an option in the icon picker.
+ * The inner icon is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up
+ * so that selection and pressed states are represented in the color.
+ */
+ static Drawable makeSmallIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
+ return composeIconCircle(
+ context.getColorStateList(R.color.modes_icon_picker_item_background),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_item_circle_diameter),
+ checkNotNull(context.getDrawable(iconResId)),
+ context.getColorStateList(R.color.modes_icon_picker_item_icon),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_item_icon_size));
+ }
+
+ private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
+ Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
- background.setTintList(
- context.getColorStateList(R.color.modes_icon_picker_item_background));
- icon = icon.mutate();
- icon.setTintList(
- context.getColorStateList(R.color.modes_icon_picker_item_icon));
+ background.setTintList(circleColor);
+ Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable().mutate();
+ foreground.setTintList(iconColor);
- LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });
+ LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
- int circleDiameter = context.getResources().getDimensionPixelSize(
- R.dimen.zen_mode_icon_list_circle_diameter);
- int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.zen_mode_icon_list_icon_size);
- int iconPadding = (circleDiameter - iconSize) / 2;
- layerDrawable.setBounds(0, 0, circleDiameter, circleDiameter);
- layerDrawable.setLayerInset(1, iconPadding, iconPadding, iconPadding, iconPadding);
+ layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
+ layerDrawable.setLayerSize(0, circleDiameterPx, circleDiameterPx);
+ layerDrawable.setLayerGravity(1, Gravity.CENTER);
+ layerDrawable.setLayerSize(1, iconSizePx, iconSizePx);
return layerDrawable;
}
-
- static Drawable makeIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
- return makeIconCircle(context, checkNotNull(context.getDrawable(iconResId)));
- }
}
diff --git a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
index 914683fb86c..2561a7b630e 100644
--- a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
@@ -22,20 +22,17 @@ import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.ActionButtonsPreference;
class ZenModeActionsPreferenceController extends AbstractZenModePreferenceController {
- ZenModeActionsPreferenceController(@NonNull Context context, @NonNull String key,
- @Nullable ZenModesBackend backend) {
- super(context, key, backend);
+ ZenModeActionsPreferenceController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
}
@Override
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
index 1d807ed47b4..522f191c37f 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
@@ -24,7 +24,6 @@ import android.os.Bundle;
import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -44,7 +43,7 @@ public class ZenModeAppsPreferenceController extends
String mModeId;
public ZenModeAppsPreferenceController(@NonNull Context context,
- @NonNull String key, @Nullable ZenModesBackend backend) {
+ @NonNull String key, @NonNull ZenModesBackend backend) {
super(context, key, backend);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
index e5c1e480ee6..d8850191762 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
@@ -26,15 +26,14 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeCallsLinkPreferenceController(Context context, String key,
- ZenModesBackend backend, ZenHelperBackend helperBackend) {
- super(context, key, backend);
+ ZenHelperBackend helperBackend) {
+ super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(context, helperBackend);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java b/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
index 8c3b8261f74..38ac8f31072 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
@@ -35,7 +35,7 @@ public class ZenModeDisplayFragment extends ZenModeFragmentBase {
protected List createPreferenceControllers(Context context) {
List prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeNotifVisLinkPreferenceController(
- context, "notification_visibility", mBackend, mHelperBackend));
+ context, "notification_visibility", mHelperBackend));
prefControllers.add(new ZenModeDisplayEffectPreferenceController(
context, "effect_greyscale", mBackend));
prefControllers.add(new ZenModeDisplayEffectPreferenceController(
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 3a64fb2f1a0..748c7351c29 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -24,7 +24,9 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
+import androidx.core.view.MenuProvider;
import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
@@ -39,6 +41,8 @@ public class ZenModeFragment extends ZenModeFragmentBase {
// for mode deletion menu
private static final int DELETE_MODE = 1;
+ private ModeMenuProvider mModeMenuProvider;
+
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_rule_settings;
@@ -47,23 +51,22 @@ public class ZenModeFragment extends ZenModeFragmentBase {
@Override
protected List createPreferenceControllers(Context context) {
List prefControllers = new ArrayList<>();
- prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
+ prefControllers.add(new ZenModeHeaderController(context, "header", this));
prefControllers.add(
new ZenModeButtonPreferenceController(context, "activate", this, mBackend));
- prefControllers.add(new ZenModeActionsPreferenceController(context, "actions", mBackend));
+ prefControllers.add(new ZenModeActionsPreferenceController(context, "actions"));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
- context, "zen_mode_people", mBackend, mHelperBackend));
+ context, "zen_mode_people", mHelperBackend));
prefControllers.add(new ZenModeAppsLinkPreferenceController(
context, "zen_mode_apps", this,
ApplicationsState.getInstance((Application) context.getApplicationContext()),
mBackend, mHelperBackend));
prefControllers.add(new ZenModeOtherLinkPreferenceController(
- context, "zen_other_settings", mBackend, mHelperBackend));
+ context, "zen_other_settings", mHelperBackend));
prefControllers.add(new ZenModeDisplayLinkPreferenceController(
context, "mode_display_settings", mBackend, mHelperBackend));
prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context,
- "zen_automatic_trigger_category", this, mBackend,
- context.getPackageManager()));
+ "zen_automatic_trigger_category", this, mBackend));
prefControllers.add(new InterruptionFilterPreferenceController(
context, "allow_filtering", mBackend));
prefControllers.add(new ManualDurationPreferenceController(
@@ -85,11 +88,22 @@ public class ZenModeFragment extends ZenModeFragmentBase {
// Set title for the entire screen
ZenMode mode = getMode();
- if (mode != null) {
- requireActivity().setTitle(mode.getName());
+ ComponentActivity activity = getActivity();
+ if (mode != null && activity != null) {
+ activity.setTitle(mode.getName());
+ mModeMenuProvider = new ModeMenuProvider(mode);
+ activity.addMenuProvider(mModeMenuProvider);
}
}
+ @Override
+ public void onStop() {
+ if (getActivity() != null) {
+ getActivity().removeMenuProvider(mModeMenuProvider);
+ }
+ super.onStop();
+ }
+
@Override
public void onDetach() {
use(ManualDurationPreferenceController.class).unregisterSettingsObserver();
@@ -102,35 +116,6 @@ public class ZenModeFragment extends ZenModeFragmentBase {
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
}
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- menu.add(Menu.NONE, DELETE_MODE, Menu.NONE, R.string.zen_mode_menu_delete_mode);
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
- switch (item.getItemId()) {
- case DELETE_MODE:
- new AlertDialog.Builder(mContext)
- .setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
- zenMode.getRule().getName()))
- .setPositiveButton(R.string.zen_mode_schedule_delete,
- (dialog, which) -> {
- // start finishing before calling removeMode() so that we don't
- // try to update this activity with a nonexistent mode when the
- // zen mode config is updated
- finish();
- mBackend.removeMode(zenMode);
- })
- .setNegativeButton(R.string.cancel, null)
- .show();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
@Override
protected void updateZenModeState() {
// Because this fragment may be asked to finish by the delete menu but not be done doing
@@ -140,4 +125,42 @@ public class ZenModeFragment extends ZenModeFragmentBase {
}
super.updateZenModeState();
}
+
+ private class ModeMenuProvider implements MenuProvider {
+ private ZenMode mZenMode;
+ ModeMenuProvider(ZenMode mode) {
+ mZenMode = mode;
+ }
+
+ @Override
+ public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
+ if (mZenMode != null && mZenMode.canBeDeleted()) {
+ // Only deleteable modes should get a delete menu option.
+ menu.add(Menu.NONE, DELETE_MODE, Menu.NONE, R.string.zen_mode_menu_delete_mode);
+ }
+ }
+
+ @Override
+ public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
+ if (mZenMode != null) {
+ if (menuItem.getItemId() == DELETE_MODE) {
+ new AlertDialog.Builder(mContext)
+ .setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
+ mZenMode.getRule().getName()))
+ .setPositiveButton(R.string.zen_mode_schedule_delete,
+ (dialog, which) -> {
+ // start finishing before calling removeMode() so that we
+ // don't try to update this activity with a nonexistent mode
+ // when the zen mode config is updated
+ finish();
+ mBackend.removeMode(mZenMode);
+ })
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
}
diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
index b0ad7956a84..f461fc3511c 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
@@ -21,7 +21,6 @@ import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
-import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -117,18 +116,6 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
updateControllers();
}
- @Override
- public final boolean onOptionsItemSelected(MenuItem item) {
- if (mZenMode != null) {
- return onOptionsItemSelected(item, mZenMode);
- }
- return super.onOptionsItemSelected(item);
- }
-
- protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
- return true;
- }
-
private void updateControllers() {
if (getPreferenceControllers() == null || mZenMode == null) {
return;
diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
index 1845ee8f190..545abffe2f1 100644
--- a/src/com/android/settings/notification/modes/ZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
@@ -19,7 +19,6 @@ import android.app.Flags;
import android.content.Context;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
@@ -27,7 +26,6 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
class ZenModeHeaderController extends AbstractZenModePreferenceController {
@@ -38,9 +36,8 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
ZenModeHeaderController(
@NonNull Context context,
@NonNull String key,
- @NonNull DashboardFragment fragment,
- @Nullable ZenModesBackend backend) {
- super(context, key, backend);
+ @NonNull DashboardFragment fragment) {
+ super(context, key);
mFragment = fragment;
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
index 43d9dba1b54..f065af239c4 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
@@ -41,10 +41,9 @@ public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
@Override
protected List createPreferenceControllers(Context context) {
return ImmutableList.of(
- new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
- mBackend),
+ new ZenModeIconPickerIconPreferenceController(context, "current_icon", this),
new ZenModeIconPickerListPreferenceController(context, "icon_list",
- mIconPickerListener, new IconOptionsProviderImpl(mContext), mBackend));
+ mIconPickerListener));
}
private final ZenModeIconPickerListPreferenceController.IconPickerListener mIconPickerListener =
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
index d1d53af9ddf..70df9b651ee 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
@@ -17,9 +17,10 @@
package com.android.settings.notification.modes;
import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.ImageView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
@@ -27,17 +28,17 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
+/** Controller used for displaying the currently-chosen icon at the top of the icon picker. */
class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenceController {
private final DashboardFragment mFragment;
private EntityHeaderController mHeaderController;
ZenModeIconPickerIconPreferenceController(@NonNull Context context, @NonNull String key,
- @NonNull DashboardFragment fragment, @Nullable ZenModesBackend backend) {
- super(context, key, backend);
+ @NonNull DashboardFragment fragment) {
+ super(context, key);
mFragment = fragment;
}
@@ -51,11 +52,19 @@ class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenc
mFragment.getActivity(),
mFragment,
pref.findViewById(R.id.entity_header));
+
+ ImageView iconView = pref.findViewById(R.id.entity_header_icon);
+ ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
+ int imageSizePx = iconView.getContext().getResources().getDimensionPixelSize(
+ R.dimen.zen_mode_icon_list_header_circle_diameter);
+ layoutParams.width = imageSizePx;
+ layoutParams.height = imageSizePx;
+ iconView.setLayoutParams(layoutParams);
}
FutureUtil.whenDone(
zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
- icon -> mHeaderController.setIcon(IconUtil.applyNormalTint(mContext, icon))
+ icon -> mHeaderController.setIcon(IconUtil.makeBigIconCircle(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
index e663354231e..512dabb4437 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
@@ -26,6 +26,7 @@ import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.GridLayoutManager;
@@ -33,8 +34,8 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
@@ -51,9 +52,15 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
private @DrawableRes int mCurrentIconResId;
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
- @NonNull IconPickerListener listener, @NonNull IconOptionsProvider iconOptionsProvider,
- @Nullable ZenModesBackend backend) {
- super(context, key, backend);
+ @NonNull IconPickerListener listener) {
+ this(context, key, listener, new IconOptionsProviderImpl(context));
+ }
+
+ @VisibleForTesting
+ ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
+ @NonNull IconPickerListener listener,
+ @NonNull IconOptionsProvider iconOptionsProvider) {
+ super(context, key);
mListener = listener;
mIconOptionsProvider = iconOptionsProvider;
}
@@ -80,7 +87,11 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
@Override
void updateState(Preference preference, @NonNull ZenMode zenMode) {
- updateIconSelection(zenMode.getRule().getIconResId());
+ @DrawableRes int iconResId = zenMode.getRule().getIconResId();
+ if (iconResId == 0) {
+ iconResId = ZenIconLoader.getIconResourceIdFromType(zenMode.getType());
+ }
+ updateIconSelection(iconResId);
}
private void updateIconSelection(@DrawableRes int iconResId) {
@@ -145,7 +156,7 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
- info -> IconUtil.makeIconCircle(mContext, info.resId()));
+ info -> IconUtil.makeSmallIconCircle(mContext, info.resId()));
holder.bindIcon(iconInfo, iconDrawable);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
index 9b7c8a1b757..4c0b758e7cb 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
@@ -26,14 +26,13 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeMessagesLinkPreferenceController(Context context, String key,
- ZenModesBackend backend, ZenHelperBackend helperBackend) {
- super(context, key, backend);
+ ZenHelperBackend helperBackend) {
+ super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(context, helperBackend);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
index a2d941108ae..622c4a2db48 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
@@ -27,15 +27,14 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryBuilder;
public ZenModeNotifVisLinkPreferenceController(Context context, String key,
- ZenModesBackend backend, ZenHelperBackend helperBackend) {
- super(context, key, backend);
+ ZenHelperBackend helperBackend) {
+ super(context, key);
mSummaryBuilder = new ZenModeSummaryHelper(context, helperBackend);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 99625ebc574..248ef1dd585 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -27,7 +27,6 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what other sounds can break through the mode
@@ -37,8 +36,8 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeOtherLinkPreferenceController(Context context, String key,
- ZenModesBackend backend, ZenHelperBackend helperBackend) {
- super(context, key, backend);
+ ZenHelperBackend helperBackend) {
+ super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
}
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleFragment.java b/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
index 448712ce0e8..f541d132010 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
@@ -35,9 +35,9 @@ public class ZenModePeopleFragment extends ZenModeFragmentBase {
protected List createPreferenceControllers(Context context) {
List prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeCallsLinkPreferenceController(
- context, "zen_mode_people_calls", mBackend, mHelperBackend));
+ context, "zen_mode_people_calls", mHelperBackend));
prefControllers.add(new ZenModeMessagesLinkPreferenceController(
- context, "zen_mode_people_messages", mBackend, mHelperBackend));
+ context, "zen_mode_people_messages", mHelperBackend));
return prefControllers;
}
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 1613a010c35..936cea6ce5c 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -27,7 +27,6 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what calls and messages can break through the mode
@@ -37,8 +36,8 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModePeopleLinkPreferenceController(Context context, String key,
- ZenModesBackend backend, ZenHelperBackend helperBackend) {
- super(context, key, backend);
+ ZenHelperBackend helperBackend) {
+ super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
}
diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
index 7328d918941..86135a96190 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
@@ -18,20 +18,12 @@ package com.android.settings.notification.modes;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
-import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ComponentInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.service.notification.ConditionProviderService;
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -39,14 +31,10 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.utils.ManagedServiceSettings;
-import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
-import java.util.List;
-
/**
* Preference controller for the link to an individual mode's configuration page.
*/
@@ -56,23 +44,25 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
@VisibleForTesting
protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings";
- private static final ManagedServiceSettings.Config CONFIG =
- ZenModesListFragment.getConditionProviderConfig();
-
- private ZenServiceListing mServiceListing;
- private final PackageManager mPm;
+ private final ConfigurationActivityHelper mConfigurationActivityHelper;
+ private final ZenServiceListing mServiceListing;
private final DashboardFragment mFragment;
ZenModeSetTriggerLinkPreferenceController(Context context, String key,
- DashboardFragment fragment, ZenModesBackend backend,
- PackageManager packageManager) {
- super(context, key, backend);
- mFragment = fragment;
- mPm = packageManager;
+ DashboardFragment fragment, ZenModesBackend backend) {
+ this(context, key, fragment, backend,
+ new ConfigurationActivityHelper(context.getPackageManager()),
+ new ZenServiceListing(context));
}
@VisibleForTesting
- protected void setServiceListing(ZenServiceListing serviceListing) {
+ ZenModeSetTriggerLinkPreferenceController(Context context, String key,
+ DashboardFragment fragment, ZenModesBackend backend,
+ ConfigurationActivityHelper configurationActivityHelper,
+ ZenServiceListing serviceListing) {
+ super(context, key, backend);
+ mFragment = fragment;
+ mConfigurationActivityHelper = configurationActivityHelper;
mServiceListing = serviceListing;
}
@@ -83,11 +73,9 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
@Override
public void displayPreference(PreferenceScreen screen, @NonNull ZenMode zenMode) {
- if (mServiceListing == null) {
- mServiceListing = new ZenServiceListing(
- mContext, CONFIG, zenMode.getRule().getPackageName());
- }
- mServiceListing.reloadApprovedServices();
+ // Preload approved components, but only for the package that owns the rule (since it's the
+ // only package that can have a valid configurationActivity).
+ mServiceListing.loadApprovedComponents(zenMode.getRule().getPackageName());
}
@Override
@@ -130,8 +118,9 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
});
}
} else {
- Intent intent = getAppRuleIntent(zenMode);
- if (intent != null && isValidIntent(intent)) {
+ Intent intent = mConfigurationActivityHelper.getConfigurationActivityIntentForMode(
+ zenMode, mServiceListing::findService);
+ if (intent != null) {
preference.setVisible(true);
switchPref.setTitle(R.string.zen_mode_configuration_link_title);
switchPref.setSummary(zenMode.getRule().getTriggerDescription());
@@ -161,68 +150,4 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
});
// TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen?
};
-
- @VisibleForTesting
- protected @Nullable Intent getAppRuleIntent(ZenMode zenMode) {
- Intent intent = new Intent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- .putExtra(ConditionProviderService.EXTRA_RULE_ID, zenMode.getId())
- .putExtra(EXTRA_AUTOMATIC_RULE_ID, zenMode.getId());
- String owner = zenMode.getRule().getPackageName();
- ComponentName configActivity = null;
- if (zenMode.getRule().getConfigurationActivity() != null) {
- // If a configuration activity is present, use that directly in the intent
- configActivity = zenMode.getRule().getConfigurationActivity();
- } else {
- // Otherwise, look for a condition provider service for the rule's package
- ComponentInfo ci = mServiceListing.findService(zenMode.getRule().getOwner());
- if (ci == null) {
- // do nothing
- } else if (ci instanceof ActivityInfo) {
- // new activity backed rule
- intent.setComponent(new ComponentName(ci.packageName, ci.name));
- return intent;
- } else if (ci.metaData != null) {
- // old service backed rule
- final String configurationActivity = ci.metaData.getString(
- ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY);
- if (configurationActivity != null) {
- configActivity = ComponentName.unflattenFromString(configurationActivity);
- }
- }
- }
-
- if (configActivity != null) {
- // verify that the owner of the rule owns the configuration activity, but only if
- // owner exists
- intent.setComponent(configActivity);
- if (owner == null) {
- return intent;
- }
- try {
- int ownerUid = mPm.getPackageUid(owner, 0);
- int configActivityOwnerUid = mPm.getPackageUid(configActivity.getPackageName(), 0);
- if (ownerUid == configActivityOwnerUid) {
- return intent;
- } else {
- Log.w(TAG, "Config activity not in owner package for "
- + zenMode.getRule().getName());
- return null;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Failed to find config activity");
- return null;
- }
- }
- return null;
- }
-
- private boolean isValidIntent(Intent intent) {
- List results = mPm.queryIntentActivities(
- intent, PackageManager.ResolveInfoFlags.of(0));
- if (intent.resolveActivity(mPm) == null || results.size() == 0) {
- Log.w(TAG, "intent for zen rule invalid: " + intent);
- return false;
- }
- return true;
- }
}
diff --git a/src/com/android/settings/notification/modes/ZenModesFragmentBase.java b/src/com/android/settings/notification/modes/ZenModesFragmentBase.java
index e1156fef159..0bc06173fab 100644
--- a/src/com/android/settings/notification/modes/ZenModesFragmentBase.java
+++ b/src/com/android/settings/notification/modes/ZenModesFragmentBase.java
@@ -26,6 +26,8 @@ import android.os.UserManager;
import android.provider.Settings.Global;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settingslib.notification.modes.ZenModesBackend;
@@ -57,6 +59,11 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
return TAG;
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ void setBackend(ZenModesBackend backend) {
+ mBackend = backend;
+ }
+
@Override
public void onAttach(@NonNull Context context) {
mContext = context;
diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java
index ba74b93aad9..b4657a37a5b 100644
--- a/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java
@@ -16,27 +16,82 @@
package com.android.settings.notification.modes;
+import android.app.NotificationManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.service.notification.ConditionProviderService;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import androidx.preference.Preference;
-import com.android.settings.utils.ZenServiceListing;
+import com.android.settings.R;
+import com.android.settingslib.Utils;
import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
-import java.util.Random;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
class ZenModesListAddModePreferenceController extends AbstractPreferenceController {
- private final ZenModesBackend mBackend;
private final ZenServiceListing mServiceListing;
+ private final OnAddModeListener mOnAddModeListener;
- ZenModesListAddModePreferenceController(Context context, ZenModesBackend backend,
- ZenServiceListing serviceListing) {
+ private final ConfigurationActivityHelper mConfigurationActivityHelper;
+ private final NotificationManager mNotificationManager;
+ private final PackageManager mPackageManager;
+ private final Function mAppIconRetriever;
+ private final ListeningExecutorService mBackgroundExecutor;
+ private final Executor mUiThreadExecutor;
+
+ record ModeType(String name, Drawable icon, @Nullable String summary,
+ @Nullable Intent creationActivityIntent) { }
+
+ interface OnAddModeListener {
+ void onAvailableModeTypesForAdd(List types);
+ }
+
+ ZenModesListAddModePreferenceController(Context context, OnAddModeListener onAddModeListener) {
+ this(context, onAddModeListener, new ZenServiceListing(context),
+ new ConfigurationActivityHelper(context.getPackageManager()),
+ context.getSystemService(NotificationManager.class), context.getPackageManager(),
+ applicationInfo -> Utils.getBadgedIcon(context, applicationInfo),
+ Executors.newCachedThreadPool(), context.getMainExecutor());
+ }
+
+ @VisibleForTesting
+ ZenModesListAddModePreferenceController(Context context,
+ OnAddModeListener onAddModeListener, ZenServiceListing serviceListing,
+ ConfigurationActivityHelper configurationActivityHelper,
+ NotificationManager notificationManager, PackageManager packageManager,
+ Function appIconRetriever,
+ ExecutorService backgroundExecutor, Executor uiThreadExecutor) {
super(context);
- mBackend = backend;
+ mOnAddModeListener = onAddModeListener;
mServiceListing = serviceListing;
+ mConfigurationActivityHelper = configurationActivityHelper;
+ mNotificationManager = notificationManager;
+ mPackageManager = packageManager;
+ mAppIconRetriever = appIconRetriever;
+ mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor);
+ mUiThreadExecutor = uiThreadExecutor;
}
@Override
@@ -52,12 +107,79 @@ class ZenModesListAddModePreferenceController extends AbstractPreferenceControll
@Override
public void updateState(Preference preference) {
preference.setOnPreferenceClickListener(pref -> {
- // TODO: b/326442408 - Launch the proper mode creation flow (using mServiceListing).
- ZenMode mode = mBackend.addCustomMode("New mode #" + new Random().nextInt(1000));
- if (mode != null) {
- ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch();
- }
+ onClickAddMode();
return true;
});
}
+
+ @VisibleForTesting
+ void onClickAddMode() {
+ FutureUtil.whenDone(
+ mBackgroundExecutor.submit(this::getModeProviders),
+ mOnAddModeListener::onAvailableModeTypesForAdd,
+ mUiThreadExecutor);
+ }
+
+ @WorkerThread
+ private ImmutableList getModeProviders() {
+ ImmutableSet approvedComponents = mServiceListing.loadApprovedComponents();
+
+ ArrayList appProvidedModes = new ArrayList<>();
+ for (ComponentInfo ci: approvedComponents) {
+ ModeType modeType = getValidNewModeTypeFromComponent(ci);
+ if (modeType != null) {
+ appProvidedModes.add(modeType);
+ }
+ }
+
+ return ImmutableList.builder()
+ .add(new ModeType(
+ mContext.getString(R.string.zen_mode_new_option_custom),
+ mContext.getDrawable(R.drawable.ic_zen_mode_new_option_custom),
+ null, null))
+ .addAll(appProvidedModes.stream()
+ .sorted(Comparator.comparing(ModeType::name))
+ .toList())
+ .build();
+ }
+
+ /**
+ * Returns a {@link ModeType} object corresponding to the approved {@link ComponentInfo} that
+ * specifies a creatable rule, if such a mode can actually be created (has an associated and
+ * enabled configuration activity, has not exceeded the rule instance limit, etc). Otherwise,
+ * returns {@code null}.
+ */
+ @WorkerThread
+ @Nullable
+ private ModeType getValidNewModeTypeFromComponent(ComponentInfo ci) {
+ if (ci.metaData == null) {
+ return null;
+ }
+
+ String ruleType = (ci instanceof ServiceInfo)
+ ? ci.metaData.getString(ConditionProviderService.META_DATA_RULE_TYPE)
+ : ci.metaData.getString(NotificationManager.META_DATA_AUTOMATIC_RULE_TYPE);
+ if (ruleType == null || ruleType.trim().isEmpty()) {
+ return null;
+ }
+
+ int ruleInstanceLimit = (ci instanceof ServiceInfo)
+ ? ci.metaData.getInt(ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1)
+ : ci.metaData.getInt(NotificationManager.META_DATA_RULE_INSTANCE_LIMIT, -1);
+ if (ruleInstanceLimit > 0 && mNotificationManager.getRuleInstanceCount(
+ ci.getComponentName()) >= ruleInstanceLimit) {
+ return null; // Would exceed instance limit.
+ }
+
+ ComponentName configurationActivity =
+ mConfigurationActivityHelper.getConfigurationActivityFromApprovedComponent(ci);
+ if (configurationActivity == null) {
+ return null;
+ }
+
+ String appName = ci.applicationInfo.loadLabel(mPackageManager).toString();
+ Drawable appIcon = mAppIconRetriever.apply(ci.applicationInfo);
+ Intent configActivityIntent = new Intent().setComponent(configurationActivity);
+ return new ModeType(ruleType, appIcon, appName, configActivityIntent);
+ }
}
diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
new file mode 100644
index 00000000000..57d3bf96c2b
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+public class ZenModesListAddModeTypeChooserDialog extends InstrumentedDialogFragment {
+
+ private static final String TAG = "ZenModesListAddModeTypeChooserDialog";
+
+ private OnChooseModeTypeListener mChooseModeTypeListener;
+ private ImmutableList mOptions;
+
+ interface OnChooseModeTypeListener {
+ void onTypeSelected(ModeType type);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - Update metrics category
+ return 0;
+ }
+
+ static void show(DashboardFragment parent,
+ OnChooseModeTypeListener onChooseModeTypeListener,
+ List options) {
+ ZenModesListAddModeTypeChooserDialog dialog = new ZenModesListAddModeTypeChooserDialog();
+ dialog.mChooseModeTypeListener = onChooseModeTypeListener;
+ dialog.mOptions = ImmutableList.copyOf(options);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getParentFragmentManager(), TAG);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ checkState(getContext() != null);
+ return new AlertDialog.Builder(getContext())
+ .setTitle(R.string.zen_mode_new_title)
+ .setAdapter(new OptionsAdapter(getContext(), mOptions),
+ (dialog, which) -> mChooseModeTypeListener.onTypeSelected(
+ mOptions.get(which)))
+ .setNegativeButton(R.string.cancel, null)
+ .create();
+ }
+
+ private static class OptionsAdapter extends ArrayAdapter {
+
+ private final LayoutInflater mInflater;
+
+ private OptionsAdapter(Context context,
+ ImmutableList availableModeProviders) {
+ super(context, R.layout.zen_mode_type_item, availableModeProviders);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.zen_mode_type_item, parent, false);
+ }
+ ImageView imageView = checkNotNull(convertView.findViewById(R.id.icon));
+ TextView title = checkNotNull(convertView.findViewById(R.id.title));
+ TextView subtitle = checkNotNull(convertView.findViewById(R.id.subtitle));
+
+ ModeType option = checkNotNull(getItem(position));
+ imageView.setImageDrawable(option.icon());
+ title.setText(option.name());
+ subtitle.setText(option.summary());
+ subtitle.setVisibility(
+ Strings.isNullOrEmpty(option.summary()) ? View.GONE : View.VISIBLE);
+
+ return convertView;
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index 1883945944d..4622996141f 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -16,47 +16,51 @@
package com.android.settings.notification.modes;
-import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
+import android.content.ComponentName;
import android.content.Context;
-import android.service.notification.ConditionProviderService;
+import android.content.Intent;
import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
+import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
+import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
+import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.OnAddModeListener;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.utils.ManagedServiceSettings;
-import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexable;
import com.google.common.collect.ImmutableList;
import java.util.List;
+import java.util.Optional;
+import java.util.Random;
@SearchIndexable
public class ZenModesListFragment extends ZenModesFragmentBase {
- private static final ManagedServiceSettings.Config CONFIG = getConditionProviderConfig();
+ static final int REQUEST_NEW_MODE = 101;
+
+ @Nullable private ComponentName mActivityInvokedForAddNew;
+ @Nullable private ImmutableList mZenModeIdsBeforeAddNew;
@Override
protected List createPreferenceControllers(Context context) {
- ZenServiceListing serviceListing = new ZenServiceListing(getContext(), CONFIG);
- serviceListing.reloadApprovedServices();
- return buildPreferenceControllers(context, this, serviceListing);
+ return buildPreferenceControllers(context, this::onAvailableModeTypesForAdd);
}
private static List buildPreferenceControllers(Context context,
- @Nullable Fragment parent, @Nullable ZenServiceListing serviceListing) {
+ OnAddModeListener onAddModeListener) {
// We need to redefine ZenModesBackend here even though mBackend exists so that this method
// can be static; it must be static to be able to be used in SEARCH_INDEX_DATA_PROVIDER.
ZenModesBackend backend = ZenModesBackend.getInstance(context);
return ImmutableList.of(
- new ZenModesListPreferenceController(context, parent, backend),
- new ZenModesListAddModePreferenceController(context, backend, serviceListing)
+ new ZenModesListPreferenceController(context, backend),
+ new ZenModesListAddModePreferenceController(context, onAddModeListener)
);
}
@@ -78,14 +82,55 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
}
- static ManagedServiceSettings.Config getConditionProviderConfig() {
- return new ManagedServiceSettings.Config.Builder()
- .setTag(TAG)
- .setIntentAction(ConditionProviderService.SERVICE_INTERFACE)
- .setConfigurationIntentAction(NotificationManager.ACTION_AUTOMATIC_ZEN_RULE)
- .setPermission(android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE)
- .setNoun("condition provider")
- .build();
+ private void onAvailableModeTypesForAdd(List types) {
+ if (types.size() > 1) {
+ // Show dialog to choose the mode to be created. Continue once the user chooses.
+ ZenModesListAddModeTypeChooserDialog.show(this, this::onChosenModeTypeForAdd, types);
+ } else {
+ // Will be custom_manual.
+ onChosenModeTypeForAdd(types.get(0));
+ }
+ }
+
+ @VisibleForTesting
+ void onChosenModeTypeForAdd(ModeType type) {
+ if (type.creationActivityIntent() != null) {
+ mActivityInvokedForAddNew = type.creationActivityIntent().getComponent();
+ mZenModeIdsBeforeAddNew = ImmutableList.copyOf(
+ mBackend.getModes().stream().map(ZenMode::getId).toList());
+ startActivityForResult(type.creationActivityIntent(), REQUEST_NEW_MODE);
+ } else {
+ // Custom-manual mode.
+ // TODO: b/326442408 - Transition to the choose-name-and-icon fragment.
+ ZenMode mode = mBackend.addCustomManualMode(
+ "Mode #" + new Random().nextInt(100), 0);
+ if (mode != null) {
+ ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch();
+ }
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ // If coming back after starting a 3rd-party configuration activity to create a new mode,
+ // try to identify the created mode. Ideally this would be part of the resultCode/data, but
+ // the existing API doesn't work that way...
+ ComponentName activityInvoked = mActivityInvokedForAddNew;
+ ImmutableList previousIds = mZenModeIdsBeforeAddNew;
+ mActivityInvokedForAddNew = null;
+ mZenModeIdsBeforeAddNew = null;
+ if (requestCode != REQUEST_NEW_MODE || previousIds == null || activityInvoked == null) {
+ return;
+ }
+
+ // If we find a new mode owned by the same package, presumably that's it. Open its page.
+ Optional createdZenMode = mBackend.getModes().stream()
+ .filter(m -> !previousIds.contains(m.getId()))
+ .filter(m -> m.getRule().getPackageName().equals(activityInvoked.getPackageName()))
+ .findFirst();
+ createdZenMode.ifPresent(
+ mode -> ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch());
}
/**
@@ -106,7 +151,7 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
@Override
public List createPreferenceControllers(
Context context) {
- return buildPreferenceControllers(context, null, null);
+ return buildPreferenceControllers(context, ignoredType -> {});
}
};
}
diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
index fb07078cd39..ba12b9ac842 100644
--- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
@@ -20,8 +20,6 @@ import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -43,14 +41,10 @@ import java.util.Map;
class ZenModesListPreferenceController extends BasePreferenceController {
protected static final String KEY = "zen_modes_list";
- @Nullable
- protected Fragment mParent;
protected ZenModesBackend mBackend;
- public ZenModesListPreferenceController(Context context, @Nullable Fragment parent,
- @NonNull ZenModesBackend backend) {
+ ZenModesListPreferenceController(Context context, @NonNull ZenModesBackend backend) {
super(context, KEY);
- mParent = parent;
mBackend = backend;
}
diff --git a/src/com/android/settings/notification/modes/ZenServiceListing.java b/src/com/android/settings/notification/modes/ZenServiceListing.java
new file mode 100644
index 00000000000..ccecec5f929
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenServiceListing.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.notification.modes;
+
+import android.app.ActivityManager;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.service.notification.ConditionProviderService;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.settings.utils.ManagedServiceSettings;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+class ZenServiceListing {
+
+ static final ManagedServiceSettings.Config CONFIGURATION =
+ new ManagedServiceSettings.Config.Builder()
+ .setTag("ZenServiceListing")
+ .setIntentAction(ConditionProviderService.SERVICE_INTERFACE)
+ .setConfigurationIntentAction(NotificationManager.ACTION_AUTOMATIC_ZEN_RULE)
+ .setPermission(android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE)
+ .setNoun("condition provider")
+ .build();
+
+ private final Context mContext;
+ private final Set mApprovedComponents = new ArraySet<>();
+ private final List mZenCallbacks = new ArrayList<>();
+ private final NotificationManager mNm;
+
+ ZenServiceListing(Context context) {
+ mContext = context;
+ mNm = context.getSystemService(NotificationManager.class);
+ }
+
+ public ComponentInfo findService(final ComponentName cn) {
+ if (cn == null) {
+ return null;
+ }
+ for (ComponentInfo component : mApprovedComponents) {
+ final ComponentName ci = new ComponentName(component.packageName, component.name);
+ if (ci.equals(cn)) {
+ return component;
+ }
+ }
+ return null;
+ }
+
+ public void addZenCallback(Callback callback) {
+ mZenCallbacks.add(callback);
+ }
+
+ public void removeZenCallback(Callback callback) {
+ mZenCallbacks.remove(callback);
+ }
+
+ @WorkerThread
+ public ImmutableSet loadApprovedComponents() {
+ return loadApprovedComponents(null);
+ }
+
+ @WorkerThread
+ public ImmutableSet loadApprovedComponents(@Nullable String restrictToPkg) {
+ mApprovedComponents.clear();
+
+ List enabledNotificationListenerPkgs = mNm.getEnabledNotificationListenerPackages();
+ List components = new ArrayList<>();
+ getServices(CONFIGURATION, components, mContext.getPackageManager(), restrictToPkg);
+ getActivities(CONFIGURATION, components, mContext.getPackageManager(), restrictToPkg);
+ for (ComponentInfo componentInfo : components) {
+ final String pkg = componentInfo.getComponentName().getPackageName();
+ if (mNm.isNotificationPolicyAccessGrantedForPackage(pkg)
+ || enabledNotificationListenerPkgs.contains(pkg)) {
+ mApprovedComponents.add(componentInfo);
+ }
+ }
+
+ if (!mApprovedComponents.isEmpty()) {
+ for (Callback callback : mZenCallbacks) {
+ callback.onComponentsReloaded(mApprovedComponents);
+ }
+ }
+
+ return ImmutableSet.copyOf(mApprovedComponents);
+ }
+
+ private static void getServices(ManagedServiceSettings.Config c, List list,
+ PackageManager pm, @Nullable String restrictToPkg) {
+ final int user = ActivityManager.getCurrentUser();
+
+ Intent queryIntent = new Intent(c.intentAction);
+ if (restrictToPkg != null) {
+ queryIntent.setPackage(restrictToPkg);
+ }
+ List installedServices = pm.queryIntentServicesAsUser(
+ queryIntent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ user);
+
+ for (int i = 0, count = installedServices.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedServices.get(i);
+ ServiceInfo info = resolveInfo.serviceInfo;
+
+ if (!c.permission.equals(info.permission)) {
+ Slog.w(c.tag, "Skipping " + c.noun + " service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + c.permission);
+ continue;
+ }
+ if (list != null) {
+ list.add(info);
+ }
+ }
+ }
+
+ private static void getActivities(ManagedServiceSettings.Config c, List list,
+ PackageManager pm, @Nullable String restrictToPkg) {
+ final int user = ActivityManager.getCurrentUser();
+
+ Intent queryIntent = new Intent(c.configIntentAction);
+ if (restrictToPkg != null) {
+ queryIntent.setPackage(restrictToPkg);
+ }
+ List resolveInfos = pm.queryIntentActivitiesAsUser(
+ queryIntent,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA,
+ user);
+
+ for (int i = 0, count = resolveInfos.size(); i < count; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ ActivityInfo info = resolveInfo.activityInfo;
+ if (list != null) {
+ list.add(info);
+ }
+ }
+ }
+
+ public interface Callback {
+ void onComponentsReloaded(Set components);
+ }
+}
diff --git a/src/com/android/settings/password/BiometricFragment.java b/src/com/android/settings/password/BiometricFragment.java
index 02f5b861ea3..a7a039e8485 100644
--- a/src/com/android/settings/password/BiometricFragment.java
+++ b/src/com/android/settings/password/BiometricFragment.java
@@ -16,8 +16,11 @@
package com.android.settings.password;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED;
+
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
@@ -137,7 +140,7 @@ public class BiometricFragment extends InstrumentedFragment {
BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext())
.setTitle(promptInfo.getTitle())
.setUseDefaultTitle() // use default title if title is null/empty
- .setDeviceCredentialAllowed(true)
+ .setAllowedAuthenticators(promptInfo.getAuthenticators())
.setSubtitle(promptInfo.getSubtitle())
.setDescription(promptInfo.getDescription())
.setTextForDeviceCredential(
@@ -170,6 +173,15 @@ public class BiometricFragment extends InstrumentedFragment {
if (promptInfo.isUseDefaultSubtitle()) {
promptBuilder.setUseDefaultSubtitle();
}
+
+ if ((promptInfo.getAuthenticators()
+ & BiometricManager.Authenticators.DEVICE_CREDENTIAL) == 0) {
+ promptBuilder.setNegativeButton(promptInfo.getNegativeButtonText(),
+ getContext().getMainExecutor(),
+ (dialog, which) -> mAuthenticationCallback.onAuthenticationError(
+ BIOMETRIC_ERROR_USER_CANCELED,
+ null /* errString */));
+ }
mBiometricPrompt = promptBuilder.build();
}
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index ce9a5667dfc..4c18309384c 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -160,11 +160,13 @@ public class ChooseLockGeneric extends SettingsActivity {
static final int CHOOSE_LOCK_BEFORE_BIOMETRIC_REQUEST = 103;
@VisibleForTesting
static final int SKIP_FINGERPRINT_REQUEST = 104;
+ private static final int BIOMETRIC_AUTH_REQUEST = 105;
private LockPatternUtils mLockPatternUtils;
private DevicePolicyManager mDpm;
private boolean mRequestGatekeeperPasswordHandle = false;
private boolean mPasswordConfirmed = false;
+ private boolean mBiometricsAuthSuccessful = false;
private boolean mWaitingForConfirmation = false;
private boolean mWaitingForActivityResult = false;
private LockscreenCredential mUserPassword;
@@ -488,6 +490,17 @@ public class ChooseLockGeneric extends SettingsActivity {
? data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD)
: null;
updatePreferencesOrFinish(false /* isRecreatingActivity */);
+ if (Utils.requestBiometricAuthenticationForMandatoryBiometrics(getContext(),
+ mBiometricsAuthSuccessful, mWaitingForConfirmation)) {
+ mWaitingForConfirmation = true;
+ Utils.launchBiometricPromptForMandatoryBiometrics(this, BIOMETRIC_AUTH_REQUEST);
+ }
+ } else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ mBiometricsAuthSuccessful = true;
+ } else {
+ finish();
+ }
} else if (requestCode == CHOOSE_LOCK_REQUEST) {
if (resultCode != RESULT_CANCELED) {
getActivity().setResult(resultCode, data);
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index 7f362c32904..c0b3093c2f8 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -17,10 +17,10 @@
package com.android.settings.password;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
-import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static com.android.systemui.biometrics.Utils.toBitmap;
@@ -40,6 +40,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.PromptInfo;
@@ -76,6 +77,9 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
/** Use this extra value to provide a custom logo description for the biometric prompt. **/
public static final String CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY =
"custom_logo_description";
+ public static final String BIOMETRIC_PROMPT_AUTHENTICATORS = "biometric_prompt_authenticators";
+ public static final String BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT =
+ "biometric_prompt_negative_button_text";
public static class InternalActivity extends ConfirmDeviceCredentialActivity {
}
@@ -177,6 +181,11 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
mDetails = intent.getCharSequenceExtra(KeyguardManager.EXTRA_DESCRIPTION);
String alternateButton = intent.getStringExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
+ final int authenticators = intent.getIntExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
+ BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ | BiometricManager.Authenticators.BIOMETRIC_WEAK);
+ final String negativeButtonText = intent.getStringExtra(
+ BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT);
final boolean frp =
KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
final boolean repairMode =
@@ -213,6 +222,8 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
promptInfo.setTitle(mTitle);
promptInfo.setDescription(mDetails);
promptInfo.setDisallowBiometricsIfPolicyExists(mCheckDevicePolicyManager);
+ promptInfo.setAuthenticators(authenticators);
+ promptInfo.setNegativeButtonText(negativeButtonText);
if (android.multiuser.Flags.enablePrivateSpaceFeatures()
&& android.multiuser.Flags.usePrivateSpaceIconInBiometricPrompt()
diff --git a/src/com/android/settings/privacy/PrivacyHubPreferenceController.java b/src/com/android/settings/privacy/PrivacyHubPreferenceController.java
index 20e5290bcf9..4c59f804a2e 100644
--- a/src/com/android/settings/privacy/PrivacyHubPreferenceController.java
+++ b/src/com/android/settings/privacy/PrivacyHubPreferenceController.java
@@ -18,7 +18,6 @@ package com.android.settings.privacy;
import android.content.Context;
import android.content.Intent;
-import android.provider.DeviceConfig;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
@@ -30,7 +29,6 @@ import com.android.settings.core.BasePreferenceController;
* The preference controller for privacy hub top level preference.
*/
public final class PrivacyHubPreferenceController extends BasePreferenceController {
- public static final String PROPERTY_PRIVACY_HUB_ENABLED = "privacy_hub_enabled";
public PrivacyHubPreferenceController(@NonNull Context context, @NonNull String key) {
super(context, key);
@@ -38,8 +36,7 @@ public final class PrivacyHubPreferenceController extends BasePreferenceControll
@Override
public int getAvailabilityStatus() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_PRIVACY_HUB_ENABLED, true) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ return AVAILABLE;
}
@Override
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index 0c57b014506..77de7496046 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -20,6 +20,10 @@ import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
+import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_AUTHENTICATORS;
+import static com.android.settings.password.ConfirmDeviceCredentialActivity.BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
@@ -35,10 +39,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActionBar;
+import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyResourcesManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -47,6 +53,8 @@ import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.VectorDrawable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -61,21 +69,28 @@ import android.os.UserManager;
import android.os.storage.DiskInfo;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.IconDrawableFactory;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.core.graphics.drawable.IconCompat;
+import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -92,6 +107,9 @@ import java.util.List;
@Config(shadows = ShadowLockPatternUtils.class)
public class UtilsTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String PACKAGE_NAME = "com.android.app";
private static final int USER_ID = 1;
@@ -113,6 +131,11 @@ public class UtilsTest {
private IconDrawableFactory mIconDrawableFactory;
@Mock
private ApplicationInfo mApplicationInfo;
+ @Mock
+ private BiometricManager mBiometricManager;
+ @Mock
+ private Fragment mFragment;
+
private Context mContext;
private UserManager mUserManager;
private static final int FLAG_SYSTEM = 0x00000000;
@@ -128,6 +151,7 @@ public class UtilsTest {
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
.thenReturn(connectivityManager);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
}
@After
@@ -503,6 +527,62 @@ public class UtilsTest {
assertThat(Utils.isFaceNotConvenienceBiometric(mContext)).isFalse();
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testRequestBiometricAuthentication_biometricManagerNull_shouldReturnFalse() {
+ when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(null);
+ assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ false /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnTrue()
+ throws InterruptedException {
+ when(mBiometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
+ boolean requestBiometricAuthenticationForMandatoryBiometrics =
+ Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ true /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */);
+ assertThat(requestBiometricAuthenticationForMandatoryBiometrics).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnFalse() {
+ when(mBiometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+ .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
+ false /* biometricsSuccessfullyAuthenticated */,
+ false /* biometricsAuthenticationRequested */)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void testLaunchBiometricPrompt_checkIntentValues() {
+ when(mFragment.getContext()).thenReturn(mContext);
+
+ final int requestCode = 1;
+ final ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ Utils.launchBiometricPromptForMandatoryBiometrics(mFragment, requestCode);
+
+ verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(), eq(requestCode));
+
+ final Intent intent = intentArgumentCaptor.getValue();
+
+ assertThat(intent.getExtra(BIOMETRIC_PROMPT_AUTHENTICATORS)).isEqualTo(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ assertThat(intent.getExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT)).isNotNull();
+ assertThat(intent.getExtra(KeyguardManager.EXTRA_DESCRIPTION)).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).isEqualTo(SETTINGS_PACKAGE_NAME);
+ assertThat(intent.getComponent().getClassName()).isEqualTo(
+ ConfirmDeviceCredentialActivity.class.getName());
+ }
+
private void setUpForConfirmCredentialString(boolean isEffectiveUserManagedProfile) {
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
when(mMockUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(USER_ID);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
index b7e65906fab..851dc79a03f 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
@@ -48,6 +48,7 @@ import com.android.settings.testutils.BatteryTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.widget.UsageView;
import com.android.settingslib.fuelgauge.Estimate;
+import com.android.settingslib.utils.PowerUtil;
import org.junit.After;
import org.junit.Before;
@@ -93,7 +94,8 @@ public class BatteryInfoTest {
Map.of(
ChargingType.WIRED, BatteryManager.BATTERY_PLUGGED_AC,
ChargingType.WIRELESS, BatteryManager.BATTERY_PLUGGED_WIRELESS,
- ChargingType.DOCKED, BatteryManager.BATTERY_PLUGGED_DOCK);
+ ChargingType.DOCKED, BatteryManager.BATTERY_PLUGGED_DOCK,
+ ChargingType.NONE, 0);
private static final Map CHARGING_SPEED_MAP =
Map.of(
ChargingSpeed.FAST, 1501000,
@@ -823,6 +825,92 @@ public class BatteryInfoTest {
assertThat(batteryInfo.isLongLife).isFalse();
}
+ @Test
+ public void getBatteryInfo_plugTypeNoneWithLonglifeAndChargeOptimization_chargingString() {
+ prepareTestGetBatteryInfoEnvironment(
+ /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(),
+ /* chargingStringV2Enabled= */ false);
+ Intent batteryIntent =
+ createIntentForGetBatteryInfoTest(
+ ChargingType.NONE,
+ ChargingSpeed.REGULAR,
+ /* batteryLevel= */ 85,
+ BatteryManager.BATTERY_STATUS_DISCHARGING,
+ /* isLonglife= */ true);
+ var expectedRemainingLabel = "Expected remaining label";
+ var expectedChargeLabel = "85% - " + expectedRemainingLabel;
+ when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext))
+ .thenReturn(true);
+ when(mFeatureFactory.batterySettingsFeatureProvider.getChargingOptimizationRemainingLabel(
+ eq(mContext), anyInt(), anyInt(), anyLong(), anyLong()))
+ .thenReturn(expectedRemainingLabel);
+ when(mFeatureFactory.batterySettingsFeatureProvider.getChargingOptimizationChargeLabel(
+ eq(mContext), anyInt(), anyString(), anyLong(), anyLong()))
+ .thenReturn(expectedChargeLabel);
+ var expectedStatusLabel = "Not charging";
+
+ assertGetBatteryInfo(
+ batteryIntent,
+ /* currentTimeMillis= */ UNUSED_TIME_MS,
+ expectedStatusLabel,
+ expectedRemainingLabel,
+ expectedChargeLabel);
+ }
+
+ @Test
+ public void getBatteryInfo_plugTypeNoneNotChargeOptimizationLonglife_dischargingString() {
+ prepareTestGetBatteryInfoEnvironment(
+ /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(),
+ /* chargingStringV2Enabled= */ false);
+ Intent batteryIntent =
+ createIntentForGetBatteryInfoTest(
+ ChargingType.NONE,
+ ChargingSpeed.REGULAR,
+ /* batteryLevel= */ 85,
+ BatteryManager.BATTERY_STATUS_DISCHARGING,
+ /* isLonglife= */ true);
+ var expectedRemainingLabel =
+ PowerUtil.getBatteryRemainingShortStringFormatted(
+ mContext, PowerUtil.convertUsToMs(1000L));
+ when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext))
+ .thenReturn(false);
+ var expectedStatusLabel = "Not charging";
+
+ assertGetBatteryInfo(
+ batteryIntent,
+ /* currentTimeMillis= */ UNUSED_TIME_MS,
+ expectedStatusLabel,
+ expectedRemainingLabel,
+ expectedRemainingLabel);
+ }
+
+ @Test
+ public void getBatteryInfo_plugTypeNoneChargeOptimizationNotLonglife_dischargingString() {
+ prepareTestGetBatteryInfoEnvironment(
+ /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(),
+ /* chargingStringV2Enabled= */ false);
+ Intent batteryIntent =
+ createIntentForGetBatteryInfoTest(
+ ChargingType.NONE,
+ ChargingSpeed.REGULAR,
+ /* batteryLevel= */ 85,
+ BatteryManager.BATTERY_STATUS_DISCHARGING,
+ /* isLonglife= */ false);
+ var expectedRemainingLabel =
+ PowerUtil.getBatteryRemainingShortStringFormatted(
+ mContext, PowerUtil.convertUsToMs(1000L));
+ when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext))
+ .thenReturn(true);
+ var expectedStatusLabel = "Not charging";
+
+ assertGetBatteryInfo(
+ batteryIntent,
+ /* currentTimeMillis= */ UNUSED_TIME_MS,
+ expectedStatusLabel,
+ expectedRemainingLabel,
+ expectedRemainingLabel);
+ }
+
private enum ChargingSpeed {
FAST,
REGULAR,
@@ -832,10 +920,11 @@ public class BatteryInfoTest {
private enum ChargingType {
WIRED,
WIRELESS,
- DOCKED
+ DOCKED,
+ NONE
}
- private Intent createIntentForLongLifeTest(Boolean hasLongLife) {
+ private static Intent createIntentForLongLifeTest(Boolean hasLongLife) {
return new Intent(Intent.ACTION_BATTERY_CHANGED)
.putExtra(
BatteryManager.EXTRA_CHARGING_STATUS,
@@ -844,16 +933,33 @@ public class BatteryInfoTest {
: BatteryManager.CHARGING_POLICY_DEFAULT);
}
- private Intent createIntentForGetBatteryInfoTest(
+ private static Intent createIntentForGetBatteryInfoTest(
ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) {
+ return createIntentForGetBatteryInfoTest(
+ chargingType,
+ chargingSpeed,
+ batteryLevel,
+ BatteryManager.BATTERY_STATUS_CHARGING,
+ /* isLonglife= */ false);
+ }
+
+ private static Intent createIntentForGetBatteryInfoTest(
+ ChargingType chargingType,
+ ChargingSpeed chargingSpeed,
+ int batteryLevel,
+ int chargingStatus,
+ boolean isLonglife) {
return createBatteryIntent(
- CHARGING_TYPE_MAP.get(chargingType),
- batteryLevel,
- BatteryManager.BATTERY_STATUS_CHARGING)
+ CHARGING_TYPE_MAP.get(chargingType), batteryLevel, chargingStatus)
.putExtra(
BatteryManager.EXTRA_MAX_CHARGING_CURRENT,
CHARGING_SPEED_MAP.get(chargingSpeed))
- .putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, 5000000);
+ .putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, 5000000)
+ .putExtra(
+ BatteryManager.EXTRA_CHARGING_STATUS,
+ isLonglife
+ ? BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
+ : BatteryManager.CHARGING_POLICY_DEFAULT);
}
private void prepareTestGetBatteryInfoEnvironment(
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ConfigurationActivityHelperTest.java b/tests/robotests/src/com/android/settings/notification/modes/ConfigurationActivityHelperTest.java
new file mode 100644
index 00000000000..1c72e879c45
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ConfigurationActivityHelperTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID;
+import static android.service.notification.ConditionProviderService.EXTRA_RULE_ID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.service.notification.ConditionProviderService;
+
+import com.android.settingslib.notification.modes.ZenMode;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+@RunWith(RobolectricTestRunner.class)
+public class ConfigurationActivityHelperTest {
+
+ private Context mContext;
+ private ConfigurationActivityHelper mHelper;
+
+ @Mock private PackageManager mPm;
+ @Mock private Function mApprovedServiceFinder;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.getApplication();
+ mHelper = new ConfigurationActivityHelper(mPm);
+
+ when(mPm.queryIntentActivities(any(), anyInt())).thenReturn(List.of(new ResolveInfo()));
+ }
+
+ @Test
+ public void getConfigurationActivityIntentForMode_configActivity() throws Exception {
+ ZenMode mode = new TestModeBuilder()
+ .setId("id")
+ .setPackage(mContext.getPackageName())
+ .setConfigurationActivity(new ComponentName(mContext.getPackageName(), "test"))
+ .build();
+ when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
+
+ Intent res = mHelper.getConfigurationActivityIntentForMode(mode, mApprovedServiceFinder);
+
+ assertThat(res).isNotNull();
+ assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id");
+ assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id");
+ assertThat(res.getComponent()).isEqualTo(
+ new ComponentName(mContext.getPackageName(), "test"));
+ }
+
+ @Test
+ public void getConfigurationActivityIntentForMode_configActivityNotResolvable_returnsNull()
+ throws Exception {
+ ZenMode mode = new TestModeBuilder()
+ .setId("id")
+ .setPackage(mContext.getPackageName())
+ .setConfigurationActivity(new ComponentName(mContext.getPackageName(), "test"))
+ .build();
+ when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
+ when(mPm.queryIntentActivities(any(), anyInt())).thenReturn(new ArrayList<>());
+
+ Intent res = mHelper.getConfigurationActivityIntentForMode(mode, mApprovedServiceFinder);
+
+ assertThat(res).isNull();
+ }
+
+ @Test
+ public void getConfigurationActivityIntentForMode_configActivityAndWrongPackage_returnsNull()
+ throws Exception {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(mContext.getPackageName())
+ .setConfigurationActivity(new ComponentName("another", "test"))
+ .build();
+ when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
+
+ Intent res = mHelper.getConfigurationActivityIntentForMode(mode, mApprovedServiceFinder);
+
+ assertThat(res).isNull();
+ }
+
+ @Test
+ public void getConfigurationActivityIntentForMode_configActivityAndUnspecifiedOwner()
+ throws Exception {
+ ZenMode mode = new TestModeBuilder()
+ .setId("id")
+ .setPackage(null)
+ .setConfigurationActivity(new ComponentName("another", "test"))
+ .build();
+ when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
+
+ Intent res = mHelper.getConfigurationActivityIntentForMode(mode, mApprovedServiceFinder);
+
+ assertThat(res).isNotNull();
+ assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id");
+ assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id");
+ assertThat(res.getComponent()).isEqualTo(new ComponentName("another", "test"));
+ }
+
+ @Test
+ public void getConfigurationActivityIntentForMode_cps() throws Exception {
+ ZenMode mode = new TestModeBuilder()
+ .setId("id")
+ .setPackage(mContext.getPackageName())
+ .setOwner(new ComponentName(mContext.getPackageName(), "service"))
+ .build();
+ ComponentInfo ci = new ComponentInfo();
+ ci.packageName = mContext.getPackageName();
+ ci.metaData = new Bundle();
+ ci.metaData.putString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY,
+ ComponentName.flattenToShortString(
+ new ComponentName(mContext.getPackageName(), "activity")));
+ when(mApprovedServiceFinder.apply(new ComponentName(mContext.getPackageName(), "service")))
+ .thenReturn(ci);
+ when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
+
+ Intent res = mHelper.getConfigurationActivityIntentForMode(mode, mApprovedServiceFinder);
+
+ assertThat(res).isNotNull();
+ assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id");
+ assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id");
+ assertThat(res.getComponent()).isEqualTo(
+ new ComponentName(mContext.getPackageName(), "activity"));
+ }
+
+ @Test
+ public void getConfigurationActivityIntentForMode_cpsAndWrongPackage_returnsNull()
+ throws Exception {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage("other")
+ .setOwner(new ComponentName(mContext.getPackageName(), "service"))
+ .build();
+ ComponentInfo ci = new ComponentInfo();
+ ci.packageName = mContext.getPackageName();
+ ci.metaData = new Bundle();
+ ci.metaData.putString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY,
+ ComponentName.flattenToShortString(
+ new ComponentName(mContext.getPackageName(), "activity")));
+ when(mApprovedServiceFinder.apply(new ComponentName(mContext.getPackageName(), "service")))
+ .thenReturn(ci);
+ when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
+
+ Intent res = mHelper.getConfigurationActivityIntentForMode(mode, mApprovedServiceFinder);
+
+ assertThat(res).isNull();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
index 6a1f47409be..fdb57010e58 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
@@ -84,6 +84,18 @@ class TestModeBuilder {
return this;
}
+ TestModeBuilder setOwner(ComponentName owner) {
+ mRule.setOwner(owner);
+ mConfigZenRule.component = owner;
+ return this;
+ }
+
+ TestModeBuilder setConfigurationActivity(ComponentName configActivity) {
+ mRule.setConfigurationActivity(configActivity);
+ mConfigZenRule.configurationActivity = configActivity;
+ return this;
+ }
+
TestModeBuilder setConditionId(Uri conditionId) {
mRule.setConditionId(conditionId);
mConfigZenRule.conditionId = conditionId;
@@ -150,18 +162,6 @@ class TestModeBuilder {
return this;
}
- TestModeBuilder setConfigurationActivity(ComponentName configActivity) {
- mRule.setConfigurationActivity(configActivity);
- mConfigZenRule.configurationActivity = configActivity;
- return this;
- }
-
- TestModeBuilder setOwner(ComponentName owner) {
- mRule.setOwner(owner);
- mConfigZenRule.component = owner;
- return this;
- }
-
ZenMode build() {
return new ZenMode(mId, mRule, mConfigZenRule);
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java
index 058b2d7d566..e50d2941fc3 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java
@@ -27,8 +27,6 @@ import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
-import com.android.settingslib.notification.modes.ZenModesBackend;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -47,7 +45,6 @@ public final class ZenModeCallsLinkPreferenceControllerTest {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
- @Mock private ZenModesBackend mBackend;
@Mock private ZenHelperBackend mHelperBackend;
@Before
@@ -57,7 +54,7 @@ public final class ZenModeCallsLinkPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new ZenModeCallsLinkPreferenceController(
- mContext, "something", mBackend, mHelperBackend);
+ mContext, "something", mHelperBackend);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
index e0ca306c71c..4d30ce95ee1 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
@@ -19,7 +19,6 @@ package com.android.settings.notification.modes;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,7 +33,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
-import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
@@ -64,7 +62,7 @@ public class ZenModeIconPickerListPreferenceControllerTest {
mController = new ZenModeIconPickerListPreferenceController(
RuntimeEnvironment.getApplication(), "icon_list", mListener,
- new TestIconOptionsProvider(), mock(ZenModesBackend.class));
+ new TestIconOptionsProvider());
mRecyclerView = new RecyclerView(mContext);
mRecyclerView.setId(R.id.icon_list);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java
index 288359aeb57..dda58ae7482 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java
@@ -27,8 +27,6 @@ import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
-import com.android.settingslib.notification.modes.ZenModesBackend;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -47,7 +45,6 @@ public final class ZenModeMessagesLinkPreferenceControllerTest {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
- @Mock private ZenModesBackend mBackend;
@Mock private ZenHelperBackend mHelperBackend;
@Before
@@ -57,7 +54,7 @@ public final class ZenModeMessagesLinkPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new ZenModeMessagesLinkPreferenceController(
- mContext, "something", mBackend, mHelperBackend);
+ mContext, "something", mHelperBackend);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java
index ee7340bd526..bfa99a0a927 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java
@@ -27,8 +27,6 @@ import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
-import com.android.settingslib.notification.modes.ZenModesBackend;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -47,7 +45,6 @@ public final class ZenModeNotifVisLinkPreferenceControllerTest {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
- @Mock private ZenModesBackend mBackend;
@Mock private ZenHelperBackend mHelperBackend;
@Before
@@ -57,7 +54,7 @@ public final class ZenModeNotifVisLinkPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new ZenModeNotifVisLinkPreferenceController(
- mContext, "something", mBackend, mHelperBackend);
+ mContext, "something", mHelperBackend);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index c4d03fe3360..39ba9e8aa82 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -27,8 +27,6 @@ import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
-import com.android.settingslib.notification.modes.ZenModesBackend;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -48,7 +46,6 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
- @Mock private ZenModesBackend mBackend;
@Mock private ZenHelperBackend mHelperBackend;
@Before
@@ -58,7 +55,7 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new ZenModeOtherLinkPreferenceController(
- mContext, "something", mBackend, mHelperBackend);
+ mContext, "something", mHelperBackend);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index 6591b724c58..9d9dd9892a7 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -27,8 +27,6 @@ import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
-import com.android.settingslib.notification.modes.ZenModesBackend;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -48,8 +46,6 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
private Context mContext;
@Mock
- private ZenModesBackend mBackend;
- @Mock
private ZenHelperBackend mHelperBackend;
@Before
@@ -59,7 +55,7 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
mContext = RuntimeEnvironment.application;
mController = new ZenModePeopleLinkPreferenceController(
- mContext, "something", mBackend, mHelperBackend);
+ mContext, "something", mHelperBackend);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
index 4ba21469fad..ffd239b602b 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
@@ -19,30 +19,26 @@ package com.android.settings.notification.modes;
import static android.app.AutomaticZenRule.TYPE_OTHER;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
-import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-import static android.service.notification.ConditionProviderService.EXTRA_RULE_ID;
import static com.android.settings.notification.modes.ZenModeSetTriggerLinkPreferenceController.AUTOMATIC_TRIGGER_PREF_KEY;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AutomaticZenRule;
import android.app.Flags;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.Bundle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ConditionProviderService;
import android.service.notification.SystemZenRules;
import android.service.notification.ZenModeConfig;
@@ -52,7 +48,6 @@ import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
@@ -80,10 +75,10 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
private PrimarySwitchPreference mPreference;
- @Mock
- private ZenServiceListing mServiceListing;
@Mock
private PackageManager mPm;
+ @Mock
+ private ConfigurationActivityHelper mConfigurationActivityHelper;
@Mock
private PreferenceCategory mPrefCategory;
@@ -98,8 +93,9 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
mContext = ApplicationProvider.getApplicationContext();
mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext,
- "zen_automatic_trigger_category", mFragment, mBackend, mPm);
- mPrefController.setServiceListing(mServiceListing);
+ "zen_automatic_trigger_category", mFragment, mBackend,
+ mConfigurationActivityHelper,
+ mock(ZenServiceListing.class));
mPreference = new PrimarySwitchPreference(mContext);
when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference);
@@ -225,6 +221,40 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
assertThat(mPreference.getOnPreferenceClickListener()).isNotNull();
}
+ @Test
+ public void testRuleLink_appWithConfigActivity_linksToConfigActivity() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage("some.package")
+ .setTriggerDescription("When The Music's Over")
+ .build();
+ Intent configurationIntent = new Intent("configure the mode");
+ when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
+ .thenReturn(configurationIntent);
+
+ mPrefController.updateZenMode(mPrefCategory, mode);
+
+ assertThat(mPreference.getTitle()).isNotNull();
+ assertThat(mPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.zen_mode_configuration_link_title));
+ assertThat(mPreference.getSummary()).isNotNull();
+ assertThat(mPreference.getSummary().toString()).isEqualTo("When The Music's Over");
+ assertThat(mPreference.getIntent()).isEqualTo(configurationIntent);
+ }
+
+ @Test
+ public void testRuleLink_appWithoutConfigActivity_hidden() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage("some.package")
+ .setTriggerDescription("Will not be shown :(")
+ .build();
+ when(mConfigurationActivityHelper.getConfigurationActivityIntentForMode(any(), any()))
+ .thenReturn(null);
+
+ mPrefController.updateZenMode(mPrefCategory, mode);
+
+ assertThat(mPrefCategory.isVisible()).isFalse();
+ }
+
@Test
public void onScheduleChosen_updatesMode() {
ZenMode originalMode = new TestModeBuilder()
@@ -253,109 +283,4 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest {
assertThat(updatedMode.getRule().getOwner()).isEqualTo(
ZenModeConfig.getScheduleConditionProvider());
}
-
- @Test
- public void testGetAppRuleIntent_configActivity() throws Exception {
- ZenMode mode = new TestModeBuilder()
- .setId("id")
- .setPackage(mContext.getPackageName())
- .setConfigurationActivity(new ComponentName(mContext.getPackageName(), "test"))
- .setType(TYPE_OTHER)
- .setTriggerDescription("some rule")
- .build();
-
- when(mPm.getPackageUid(null, 0)).thenReturn(-1);
- when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
-
- Intent res = mPrefController.getAppRuleIntent(mode);
- assertThat(res).isNotNull();
- assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id");
- assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id");
- assertThat(res.getComponent()).isEqualTo(
- new ComponentName(mContext.getPackageName(), "test"));
- }
-
- @Test
- public void testGetAppRuleIntent_configActivity_wrongPackage() throws Exception {
- ZenMode mode = new TestModeBuilder()
- .setPackage(mContext.getPackageName())
- .setConfigurationActivity(new ComponentName("another", "test"))
- .setType(TYPE_OTHER)
- .build();
-
- when(mPm.getPackageUid(null, 0)).thenReturn(-1);
- when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
-
- Intent res = mPrefController.getAppRuleIntent(mode);
- assertThat(res).isNull();
- }
-
- @Test
- public void testGetAppRuleIntent_configActivity_unspecifiedOwner() throws Exception {
- ZenMode mode = new TestModeBuilder()
- .setId("id")
- .setPackage(null)
- .setConfigurationActivity(new ComponentName("another", "test"))
- .setType(TYPE_OTHER)
- .build();
-
- when(mPm.getPackageUid(null, 0)).thenReturn(-1);
- when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
-
- Intent res = mPrefController.getAppRuleIntent(mode);
- assertThat(res).isNotNull();
- assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id");
- assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id");
- assertThat(res.getComponent()).isEqualTo(new ComponentName("another", "test"));
- }
-
- @Test
- public void testGetAppRuleIntent_cps() throws Exception {
- ZenMode mode = new TestModeBuilder()
- .setId("id")
- .setPackage(mContext.getPackageName())
- .setOwner(new ComponentName(mContext.getPackageName(), "service"))
- .build();
-
- ComponentInfo ci = new ComponentInfo();
- ci.packageName = mContext.getPackageName();
- ci.metaData = new Bundle();
- ci.metaData.putString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY,
- ComponentName.flattenToShortString(
- new ComponentName(mContext.getPackageName(), "activity")));
-
- when(mServiceListing.findService(new ComponentName(mContext.getPackageName(), "service")))
- .thenReturn(ci);
- when(mPm.getPackageUid(null, 0)).thenReturn(-1);
- when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
-
- Intent res = mPrefController.getAppRuleIntent(mode);
- assertThat(res).isNotNull();
- assertThat(res.getStringExtra(EXTRA_RULE_ID)).isEqualTo("id");
- assertThat(res.getStringExtra(EXTRA_AUTOMATIC_RULE_ID)).isEqualTo("id");
- assertThat(res.getComponent()).isEqualTo(
- new ComponentName(mContext.getPackageName(), "activity"));
- }
-
- @Test
- public void testGetAppRuleIntent_cps_wrongPackage() throws Exception {
- ZenMode mode = new TestModeBuilder()
- .setPackage("other")
- .setOwner(new ComponentName(mContext.getPackageName(), "service"))
- .setType(TYPE_OTHER)
- .build();
-
- ComponentInfo ci = new ComponentInfo();
- ci.packageName = mContext.getPackageName();
- ci.metaData = new Bundle();
- ci.metaData.putString(ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY,
- ComponentName.flattenToShortString(
- new ComponentName(mContext.getPackageName(), "activity")));
-
- when(mPm.getPackageUid(null, 0)).thenReturn(-1);
- when(mPm.getPackageUid(mContext.getPackageName(), 0)).thenReturn(1);
-
- Intent res = mPrefController.getAppRuleIntent(mode);
- assertThat(res).isNull();
- }
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceControllerTest.java
new file mode 100644
index 00000000000..fe530c10d18
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceControllerTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static android.app.NotificationManager.META_DATA_AUTOMATIC_RULE_TYPE;
+import static android.app.NotificationManager.META_DATA_RULE_INSTANCE_LIMIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+
+import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.List;
+import java.util.function.Function;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModesListAddModePreferenceControllerTest {
+
+ private Context mContext;
+ private ZenModesListAddModePreferenceController mController;
+
+ @Mock private ZenModesListAddModePreferenceController.OnAddModeListener mListener;
+ @Mock private ZenServiceListing mZenServiceListing;
+ @Mock private ConfigurationActivityHelper mConfigurationActivityHelper;
+ @Mock private NotificationManager mNm;
+ @Mock private PackageManager mPm;
+
+ @Captor private ArgumentCaptor> mListenerCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.getApplication();
+ Function appIconRetriever = appInfo -> new ColorDrawable();
+
+ mController = new ZenModesListAddModePreferenceController(mContext, mListener,
+ mZenServiceListing, mConfigurationActivityHelper, mNm, mPm, appIconRetriever,
+ MoreExecutors.newDirectExecutorService(), MoreExecutors.directExecutor());
+
+ when(mConfigurationActivityHelper.getConfigurationActivityFromApprovedComponent(any()))
+ .thenAnswer((Answer) invocationOnMock -> {
+ // By default, assume the ComponentInfo is also the configurationActivity.
+ ComponentInfo ci = invocationOnMock.getArgument(0);
+ return ci != null ? ci.getComponentName() : null;
+ });
+ }
+
+ @Test
+ public void onClickAddMode_noAppProviders_onlyOptionIsCustom() {
+ when(mZenServiceListing.loadApprovedComponents()).thenReturn(ImmutableSet.of());
+
+ mController.onClickAddMode();
+
+ verify(mListener).onAvailableModeTypesForAdd(mListenerCaptor.capture());
+ List options = mListenerCaptor.getValue();
+ assertThat(options).hasSize(1);
+ assertThat(options.get(0).name()).isEqualTo("Custom");
+ assertThat(options.get(0).summary()).isNull();
+ assertThat(options.get(0).icon()).isNotNull();
+ assertThat(options.get(0).creationActivityIntent()).isNull();
+ }
+
+ @Test
+ public void onClickAddMode_someAppProviders_includedInOptions() {
+ ImmutableSet approvedComponents = ImmutableSet.of(
+ newComponentInfoWithValidMetadata("pkg1"),
+ newComponentInfoWithValidMetadata("pkg2"));
+ when(mZenServiceListing.loadApprovedComponents()).thenReturn(approvedComponents);
+
+ mController.onClickAddMode();
+
+ verify(mListener).onAvailableModeTypesForAdd(mListenerCaptor.capture());
+ List options = mListenerCaptor.getValue();
+ assertThat(options).hasSize(3);
+
+ assertThat(options.get(1).name()).isEqualTo("Rule by pkg1");
+ assertThat(options.get(1).summary()).isEqualTo("A package called pkg1");
+ assertThat(options.get(1).icon()).isNotNull();
+ assertThat(options.get(1).creationActivityIntent()).isNotNull();
+ assertThat(options.get(1).creationActivityIntent().getComponent()).isEqualTo(
+ new ComponentName("pkg1", "pkg1.activity"));
+
+ assertThat(options.get(0).name()).isEqualTo("Custom");
+ assertThat(options.get(2).name()).isEqualTo("Rule by pkg2");
+ }
+
+ @Test
+ public void onClickAddMode_someAppProviders_optionsAreSorted() {
+ ImmutableSet approvedComponents = ImmutableSet.of(
+ newComponentInfoWithValidMetadata("pkg_Z"),
+ newComponentInfoWithValidMetadata("pkg_A"),
+ newComponentInfoWithValidMetadata("pkg_F"),
+ newComponentInfoWithValidMetadata("pkg_C"));
+ when(mZenServiceListing.loadApprovedComponents()).thenReturn(approvedComponents);
+
+ mController.onClickAddMode();
+
+ verify(mListener).onAvailableModeTypesForAdd(mListenerCaptor.capture());
+ List options = mListenerCaptor.getValue();
+ assertThat(options).hasSize(5);
+ assertThat(options.stream().map(o -> o.name()).toList())
+ .containsExactly("Custom", "Rule by pkg_A", "Rule by pkg_C", "Rule by pkg_F",
+ "Rule by pkg_Z")
+ .inOrder();
+ }
+
+ @Test
+ public void onClickAddMode_appProviderWithMissingMetadata_notAnOption() {
+ ComponentInfo componentWithoutRuleType = newComponentInfoWithValidMetadata("pkg1");
+ componentWithoutRuleType.metaData.remove(META_DATA_AUTOMATIC_RULE_TYPE);
+ ImmutableSet approvedComponents = ImmutableSet.of(
+ componentWithoutRuleType, newComponentInfoWithValidMetadata("pkg2"));
+ when(mZenServiceListing.loadApprovedComponents()).thenReturn(approvedComponents);
+
+ mController.onClickAddMode();
+
+ verify(mListener).onAvailableModeTypesForAdd(mListenerCaptor.capture());
+ List options = mListenerCaptor.getValue();
+ assertThat(options).hasSize(2);
+ assertThat(options.get(0).name()).isEqualTo("Custom");
+ assertThat(options.get(1).name()).isEqualTo("Rule by pkg2");
+ }
+
+ @Test
+ public void onClickAddMode_appProviderWithRuleLimitExceeded_notAnOption() {
+ ComponentInfo componentWithLimitThreeRules = newComponentInfoWithValidMetadata("pkg1");
+ componentWithLimitThreeRules.metaData.putInt(META_DATA_RULE_INSTANCE_LIMIT, 3);
+ ImmutableSet approvedComponents = ImmutableSet.of(
+ componentWithLimitThreeRules, newComponentInfoWithValidMetadata("pkg2"));
+ when(mZenServiceListing.loadApprovedComponents()).thenReturn(approvedComponents);
+ when(mNm.getRuleInstanceCount(any())).thenReturn(3); // Already 3 created rules.
+
+ mController.onClickAddMode();
+
+ verify(mListener).onAvailableModeTypesForAdd(mListenerCaptor.capture());
+ List options = mListenerCaptor.getValue();
+ assertThat(options).hasSize(2);
+ assertThat(options.get(0).name()).isEqualTo("Custom");
+ assertThat(options.get(1).name()).isEqualTo("Rule by pkg2");
+ verify(mNm).getRuleInstanceCount(eq(componentWithLimitThreeRules.getComponentName()));
+ }
+
+ @Test
+ public void onClickAddMode_appProviderWithoutConfigurationActivity_notAnOption() {
+ ComponentInfo componentWithoutConfigActivity = newComponentInfoWithValidMetadata("pkg2");
+ ImmutableSet approvedComponents = ImmutableSet.of(
+ newComponentInfoWithValidMetadata("pkg1"), componentWithoutConfigActivity);
+ when(mZenServiceListing.loadApprovedComponents()).thenReturn(approvedComponents);
+ when(mConfigurationActivityHelper.getConfigurationActivityFromApprovedComponent(any()))
+ .thenAnswer((Answer) invocationOnMock -> {
+ ComponentInfo ci = invocationOnMock.getArgument(0);
+ if (ci == componentWithoutConfigActivity) {
+ return null;
+ } else {
+ return ci.getComponentName();
+ }
+ });
+
+ mController.onClickAddMode();
+
+ verify(mListener).onAvailableModeTypesForAdd(mListenerCaptor.capture());
+ List options = mListenerCaptor.getValue();
+ assertThat(options).hasSize(2);
+ assertThat(options.get(0).name()).isEqualTo("Custom");
+ assertThat(options.get(1).name()).isEqualTo("Rule by pkg1");
+ }
+
+ private ComponentInfo newComponentInfoWithValidMetadata(String pkg) {
+ ComponentInfo ci = new ActivityInfo();
+
+ ci.applicationInfo = mock(ApplicationInfo.class);
+ when(ci.applicationInfo.loadLabel(any())).thenReturn("A package called " + pkg);
+ when(ci.applicationInfo.loadUnbadgedIcon(any())).thenReturn(new ColorDrawable());
+ ci.packageName = pkg;
+ ci.name = pkg + ".activity";
+ ci.metaData = new Bundle();
+ ci.metaData.putString(META_DATA_AUTOMATIC_RULE_TYPE, "Rule by " + pkg);
+
+ return ci;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java
new file mode 100644
index 00000000000..661f8ba1245
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+
+import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
+import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
+import static com.android.settings.notification.modes.ZenModesListFragment.REQUEST_NEW_MODE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.testing.EmptyFragmentActivity;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowActivity.IntentForResult;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModesListFragmentTest {
+
+ private static final ModeType APP_PROVIDED_MODE_TYPE = new ModeType("Mode", new ColorDrawable(),
+ "Details", new Intent().setComponent(new ComponentName("pkg", "configActivity")));
+
+ private static final ImmutableList EXISTING_MODES = ImmutableList.of(
+ new TestModeBuilder().setId("A").build(),
+ new TestModeBuilder().setId("B").build(),
+ new TestModeBuilder().setId("C").build());
+
+ @Rule
+ public ActivityScenarioRule mActivityScenario =
+ new ActivityScenarioRule<>(EmptyFragmentActivity.class);
+
+ private FragmentActivity mActivity;
+ private ZenModesListFragment mFragment;
+ @Mock private ZenModesBackend mBackend;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFragment = new ZenModesListFragment();
+ mActivityScenario.getScenario().onActivity(activity -> {
+ activity.getSupportFragmentManager().beginTransaction()
+ .add(mFragment, "tag").commitNow();
+ mActivity = activity;
+ });
+
+ mFragment.setBackend(mBackend); // after onAttach()
+ }
+
+ @Test
+ public void onChosenModeTypeForAdd_appProvidedMode_startsCreationActivity() {
+ when(mBackend.getModes()).thenReturn(EXISTING_MODES);
+
+ mFragment.onChosenModeTypeForAdd(APP_PROVIDED_MODE_TYPE);
+
+ IntentForResult intent = shadowOf(mActivity).getNextStartedActivityForResult();
+ assertThat(intent).isNotNull();
+ assertThat(intent.intent).isEqualTo(APP_PROVIDED_MODE_TYPE.creationActivityIntent());
+ }
+
+ @Test
+ public void onActivityResult_modeWasCreated_opensIt() {
+ when(mBackend.getModes()).thenReturn(EXISTING_MODES);
+ mFragment.onChosenModeTypeForAdd(APP_PROVIDED_MODE_TYPE);
+
+ // App creates the new mode.
+ ZenMode createdMode = new TestModeBuilder().setId("new_id").setPackage("pkg").build();
+ when(mBackend.getModes()).thenReturn(new ImmutableList.Builder()
+ .addAll(EXISTING_MODES)
+ .add(createdMode)
+ .build());
+ mFragment.onActivityResult(REQUEST_NEW_MODE, 0, new Intent());
+
+ Intent openModePageIntent = shadowOf(mActivity).getNextStartedActivity();
+ assertThat(openModePageIntent.getStringExtra(EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(ZenModeFragment.class.getName());
+ Bundle fragmentArgs = openModePageIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(fragmentArgs).isNotNull();
+ assertThat(fragmentArgs.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo("new_id");
+ }
+
+ @Test
+ public void onActivityResult_secondTime_doesNothing() {
+ when(mBackend.getModes()).thenReturn(EXISTING_MODES);
+ mFragment.onChosenModeTypeForAdd(APP_PROVIDED_MODE_TYPE);
+ // App creates a new mode, we redirect to its page when coming back.
+ ZenMode createdMode = new TestModeBuilder().setId("new_id").setPackage("pkg").build();
+ when(mBackend.getModes()).thenReturn(new ImmutableList.Builder()
+ .addAll(EXISTING_MODES)
+ .add(createdMode)
+ .build());
+ mFragment.onActivityResult(REQUEST_NEW_MODE, 0, new Intent());
+ shadowOf(mActivity).clearNextStartedActivities();
+
+ mFragment.onActivityResult(REQUEST_NEW_MODE, 0, new Intent());
+
+ Intent nextIntent = shadowOf(mActivity).getNextStartedActivity();
+ assertThat(nextIntent).isNull();
+ }
+
+ @Test
+ public void onActivityResult_modeWasNotCreated_doesNothing() {
+ when(mBackend.getModes()).thenReturn(EXISTING_MODES);
+ mFragment.onChosenModeTypeForAdd(APP_PROVIDED_MODE_TYPE);
+ shadowOf(mActivity).clearNextStartedActivities();
+
+ // Returning to settings without creating a new mode.
+ mFragment.onActivityResult(REQUEST_NEW_MODE, 0, new Intent());
+
+ Intent nextIntent = shadowOf(mActivity).getNextStartedActivity();
+ assertThat(nextIntent).isNull();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java
index f2624acd9e9..c0f96bea887 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java
@@ -99,7 +99,7 @@ public class ZenModesListPreferenceControllerTest {
PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
preferenceScreen.addPreference(mPreference);
- mPrefController = new ZenModesListPreferenceController(mContext, null, mBackend);
+ mPrefController = new ZenModesListPreferenceController(mContext, mBackend);
}
@Test