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/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 3a64fb2f1a0..bb315d9cae0 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -62,8 +62,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
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(
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/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/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