diff --git a/res/layout/modes_edit_done_button.xml b/res/layout/modes_edit_done_button.xml
new file mode 100644
index 00000000000..dc1f54d74ad
--- /dev/null
+++ b/res/layout/modes_edit_done_button.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/modes_edit_name.xml b/res/layout/modes_edit_name.xml
new file mode 100644
index 00000000000..0b086c746ed
--- /dev/null
+++ b/res/layout/modes_edit_name.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 40b160a3915..f346710894c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -9451,6 +9451,18 @@
Change icon
+
+ Rename
+
+
+ Add mode
+
+
+ Custom mode
+
+
+ Mode name
+
Warning
diff --git a/res/xml/modes_icon_picker.xml b/res/xml/modes_edit_name_icon.xml
similarity index 71%
rename from res/xml/modes_icon_picker.xml
rename to res/xml/modes_edit_name_icon.xml
index cb0ff302672..2109c776ac8 100644
--- a/res/xml/modes_icon_picker.xml
+++ b/res/xml/modes_edit_name_icon.xml
@@ -18,20 +18,30 @@
+ android:title="@string/zen_mode_rename_title">
+
+
+
+
+
diff --git a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
index 2561a7b630e..c3f38d02063 100644
--- a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
@@ -51,7 +51,7 @@ class ZenModeActionsPreferenceController extends AbstractZenModePreferenceContro
Bundle bundle = new Bundle();
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
new SubSettingLauncher(mContext)
- .setDestination(ZenModeIconPickerFragment.class.getName())
+ .setDestination(ZenModeEditNameIconFragment.class.getName())
// TODO: b/332937635 - Update metrics category
.setSourceMetricsCategory(0)
.setArguments(bundle)
diff --git a/src/com/android/settings/notification/modes/ZenModeEditDonePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeEditDonePreferenceController.java
new file mode 100644
index 00000000000..de1adc7cb0a
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeEditDonePreferenceController.java
@@ -0,0 +1,60 @@
+/*
+ * 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.content.Context;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.widget.LayoutPreference;
+
+class ZenModeEditDonePreferenceController extends AbstractZenModePreferenceController {
+
+ private final Runnable mConfirmSave;
+ @Nullable private Button mButton;
+
+ ZenModeEditDonePreferenceController(@NonNull Context context, @NonNull String key,
+ Runnable confirmSave) {
+ super(context, key);
+ mConfirmSave = confirmSave;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ LayoutPreference pref = screen.findPreference(getPreferenceKey());
+ if (pref != null) {
+ mButton = pref.findViewById(R.id.done);
+ if (mButton != null) {
+ mButton.setOnClickListener(v -> mConfirmSave.run());
+ }
+ }
+ }
+
+ @Override
+ void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ if (mButton != null) {
+ mButton.setEnabled(!zenMode.getName().isBlank());
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
new file mode 100644
index 00000000000..7889dd71273
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+
+public class ZenModeEditNameIconFragment extends ZenModeEditNameIconFragmentBase {
+
+ @Nullable
+ @Override
+ protected ZenMode onCreateInstantiateZenMode() {
+ String modeId = getModeIdFromArguments();
+ return modeId != null ? requireBackend().getMode(modeId) : null;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ requireActivity().setTitle(R.string.zen_mode_icon_picker_title);
+ }
+
+ @Override
+ void saveMode(ZenMode mode) {
+ String modeId = getModeIdFromArguments();
+ ZenMode modeToUpdate = modeId != null ? requireBackend().getMode(modeId) : null;
+ if (modeToUpdate == null) {
+ // Huh, maybe it was deleted while we were choosing the icon? Unusual...
+ Log.w(getLogTag(), "Couldn't fetch mode with id " + modeId
+ + " from the backend for saving. Discarding changes!");
+ finish();
+ return;
+ }
+
+ modeToUpdate.getRule().setName(mode.getRule().getName());
+ modeToUpdate.getRule().setIconResId(mode.getRule().getIconResId());
+ requireBackend().updateMode(modeToUpdate);
+ finish();
+ }
+
+ @Nullable
+ private String getModeIdFromArguments() {
+ Bundle bundle = getArguments();
+ if (bundle != null && bundle.containsKey(EXTRA_AUTOMATIC_ZEN_RULE_ID)) {
+ return bundle.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - make this the correct metrics category
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return "ZenModeEditNameIconFragment";
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeEditNameIconFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragmentBase.java
new file mode 100644
index 00000000000..d666254cfb8
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragmentBase.java
@@ -0,0 +1,191 @@
+/*
+ * 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.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+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 com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Base class for the "add a mode" and "edit mode name and icon" fragments. In both cases we are
+ * editing a {@link ZenMode}, but the mode shouldn't be saved immediately after each atomic change
+ * -- instead, it will be saved to the backend upon user confirmation.
+ *
+ *
As a result, instead of using {@link ZenModesBackend} to apply each change, we instead modify
+ * an in-memory {@link ZenMode}, that is preserved/restored in extras. This also means we don't
+ * listen to changes -- whatever the user sees should be applied.
+ */
+public abstract class ZenModeEditNameIconFragmentBase extends DashboardFragment {
+
+ private static final String MODE_KEY = "ZenMode";
+
+ @Nullable private ZenMode mZenMode;
+
+ private ZenModesBackend mBackend;
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ void setBackend(ZenModesBackend backend) {
+ mBackend = backend;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (mBackend == null) {
+ mBackend = ZenModesBackend.getInstance(context);
+ }
+ }
+
+ @Override
+ public final void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mZenMode = icicle != null
+ ? icicle.getParcelable(MODE_KEY, ZenMode.class)
+ : onCreateInstantiateZenMode();
+
+ if (mZenMode == null) {
+ finish();
+ }
+ }
+
+ /**
+ * Provides the mode that will be edited. Called in {@link #onCreate}, the first time (the
+ * value returned here is persisted on Fragment recreation).
+ *
+ *
If {@code null} is returned, the fragment will {@link #finish()}.
+ */
+ @Nullable
+ protected abstract ZenMode onCreateInstantiateZenMode();
+
+ @Override
+ protected final int getPreferenceScreenResId() {
+ return R.xml.modes_edit_name_icon;
+ }
+
+ @Override
+ protected final List createPreferenceControllers(
+ Context context) {
+ return ImmutableList.of(
+ new ZenModeIconPickerIconPreferenceController(context, "chosen_icon", this),
+ new ZenModeEditNamePreferenceController(context, "name", this::setModeName),
+ new ZenModeIconPickerListPreferenceController(context, "icon_list",
+ this::setModeIcon),
+ new ZenModeEditDonePreferenceController(context, "done", this::saveMode)
+ );
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ @Nullable
+ ZenMode getZenMode() {
+ return mZenMode;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ updateControllers();
+ }
+
+ @VisibleForTesting
+ final void setModeName(String name) {
+ checkNotNull(mZenMode).getRule().setName(Strings.nullToEmpty(name));
+ updateControllers(); // Updates confirmation button.
+ }
+
+ @VisibleForTesting
+ final void setModeIcon(@DrawableRes int iconResId) {
+ checkNotNull(mZenMode).getRule().setIconResId(iconResId);
+ updateControllers(); // Updates icon at the top.
+ }
+
+ protected void updateControllers() {
+ PreferenceScreen screen = getPreferenceScreen();
+ Collection> controllers = getPreferenceControllers();
+ if (mZenMode == null || screen == null || controllers == null) {
+ return;
+ }
+ for (List list : controllers) {
+ for (AbstractPreferenceController controller : list) {
+ try {
+ final String key = controller.getPreferenceKey();
+ final Preference preference = screen.findPreference(key);
+ if (preference != null) {
+ AbstractZenModePreferenceController zenController =
+ (AbstractZenModePreferenceController) controller;
+ zenController.updateZenMode(preference, mZenMode);
+ } else {
+ Log.d(getLogTag(),
+ String.format("Cannot find preference with key %s in Controller %s",
+ key, controller.getClass().getSimpleName()));
+ }
+ controller.displayPreference(screen);
+ } catch (ClassCastException e) {
+ // Skip any controllers that aren't AbstractZenModePreferenceController.
+ Log.d(getLogTag(), "Could not cast: " + controller.getClass().getSimpleName());
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final void saveMode() {
+ saveMode(checkNotNull(mZenMode));
+ }
+
+ /**
+ * Called to actually save the mode, after the user confirms. This method is also responsible
+ * for calling {@link #finish()}, if appropriate.
+ *
+ *
Note that {@code mode} is the in-memory mode and, as such, may have obsolete
+ * data. If the concrete fragment is editing an existing mode, it should first fetch it from
+ * the backend, and copy the new name and icon before saving. */
+ abstract void saveMode(ZenMode mode);
+
+ @NonNull
+ protected ZenModesBackend requireBackend() {
+ checkState(mBackend != null);
+ return mBackend;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(MODE_KEY, mZenMode);
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeEditNamePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeEditNamePreferenceController.java
new file mode 100644
index 00000000000..78cbfe0488b
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeEditNamePreferenceController.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.function.Consumer;
+
+class ZenModeEditNamePreferenceController extends AbstractZenModePreferenceController {
+
+ private final Consumer mModeNameSetter;
+ @Nullable private EditText mEditText;
+ private boolean mIsSettingText;
+
+ ZenModeEditNamePreferenceController(@NonNull Context context, @NonNull String key,
+ @NonNull Consumer modeNameSetter) {
+ super(context, key);
+ mModeNameSetter = modeNameSetter;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (mEditText == null) {
+ LayoutPreference pref = checkNotNull(screen.findPreference(getPreferenceKey()));
+ mEditText = pref.findViewById(android.R.id.edit);
+
+ mEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) { }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (!mIsSettingText) {
+ mModeNameSetter.accept(s.toString());
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ void updateState(Preference preference, @NonNull ZenMode zenMode) {
+ if (mEditText != null) {
+ mIsSettingText = true;
+ try {
+ String currentText = mEditText.getText().toString();
+ String modeName = zenMode.getName();
+ if (!modeName.equals(currentText)) {
+ mEditText.setText(modeName);
+ }
+ } finally {
+ mIsSettingText = false;
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java b/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
deleted file mode 100644
index f065af239c4..00000000000
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerFragment.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.settings.SettingsEnums;
-import android.content.Context;
-
-import com.android.settings.R;
-import com.android.settingslib.core.AbstractPreferenceController;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.List;
-
-public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
- @Override
- protected int getPreferenceScreenResId() {
- return R.xml.modes_icon_picker;
- }
-
- @Override
- public int getMetricsCategory() {
- // TODO: b/332937635 - make this the correct metrics category
- return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
- }
-
- @Override
- protected List createPreferenceControllers(Context context) {
- return ImmutableList.of(
- new ZenModeIconPickerIconPreferenceController(context, "current_icon", this),
- new ZenModeIconPickerListPreferenceController(context, "icon_list",
- mIconPickerListener));
- }
-
- private final ZenModeIconPickerListPreferenceController.IconPickerListener mIconPickerListener =
- new ZenModeIconPickerListPreferenceController.IconPickerListener() {
- @Override
- public void onIconSelected(int iconResId) {
- saveMode(mode -> mode.getRule().setIconResId(iconResId));
- finish();
- }
- };
-}
diff --git a/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java b/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
new file mode 100644
index 00000000000..6086c0c7b38
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
@@ -0,0 +1,68 @@
+/*
+ * 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 androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+
+import com.google.common.base.Strings;
+
+public class ZenModeNewCustomFragment extends ZenModeEditNameIconFragmentBase {
+
+ @Nullable
+ @Override
+ protected ZenMode onCreateInstantiateZenMode() {
+ return ZenMode.newCustomManual(
+ requireContext().getString(R.string.zen_mode_new_custom_default_name),
+ /* iconResId= */ 0);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ requireActivity().setTitle(R.string.zen_mode_new_custom_title);
+ }
+
+ @Override
+ void saveMode(ZenMode mode) {
+ String modeName = Strings.isNullOrEmpty(mode.getName())
+ ? requireContext().getString(R.string.zen_mode_new_custom_default_name)
+ : mode.getName();
+
+ ZenMode created = requireBackend().addCustomManualMode(modeName,
+ mode.getRule().getIconResId());
+ if (created != null) {
+ // Open the mode view fragment and close the "add mode" fragment, so exiting the mode
+ // view goes back to previous screen (which should be the modes list).
+ ZenSubSettingLauncher.forMode(requireContext(), created.getId()).launch();
+ finish();
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - make this the correct metrics category
+ return 0;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return "ZenModeNewCustomFragment";
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index 4622996141f..be458b331df 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.OnAddModeListener;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -37,7 +38,6 @@ import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Optional;
-import java.util.Random;
@SearchIndexable
public class ZenModesListFragment extends ZenModesFragmentBase {
@@ -100,13 +100,12 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
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();
- }
+ // Custom-manual mode -> "add a mode" screen.
+ // TODO: b/332937635 - set metrics categories correctly
+ new SubSettingLauncher(requireContext())
+ .setDestination(ZenModeNewCustomFragment.class.getName())
+ .setSourceMetricsCategory(0)
+ .launch();
}
}
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 fdb57010e58..0b6a732d8bb 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
@@ -54,6 +54,16 @@ class TestModeBuilder {
mConfigZenRule.pkg = "some_package";
}
+ TestModeBuilder(ZenMode previous) {
+ mId = previous.getId();
+ mRule = previous.getRule();
+
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = previous.getRule().isEnabled();
+ mConfigZenRule.pkg = previous.getRule().getPackageName();
+ setActive(previous.isActive());
+ }
+
TestModeBuilder setId(String id) {
mId = id;
return this;
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditDonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditDonePreferenceControllerTest.java
new file mode 100644
index 00000000000..a7163fef8d5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditDonePreferenceControllerTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Context;
+import android.widget.Button;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.widget.LayoutPreference;
+
+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;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeEditDonePreferenceControllerTest {
+
+ private ZenModeEditDonePreferenceController mController;
+ private LayoutPreference mPreference;
+ private Button mButton;
+ @Mock private Runnable mConfirmSave;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = RuntimeEnvironment.application;
+ PreferenceManager preferenceManager = new PreferenceManager(context);
+ PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(context,
+ R.xml.modes_edit_name_icon, null);
+ mPreference = preferenceScreen.findPreference("done");
+
+ mController = new ZenModeEditDonePreferenceController(context, "done", mConfirmSave);
+ mController.displayPreference(preferenceScreen);
+
+ mButton = mPreference.findViewById(R.id.done);
+ assertThat(mButton).isNotNull();
+ }
+
+ @Test
+ public void updateState_nameNonEmpty_buttonEnabled() {
+ ZenMode mode = new TestModeBuilder().setName("Such a nice name").build();
+
+ mController.updateState(mPreference, mode);
+
+ assertThat(mButton.isEnabled()).isTrue();
+ verifyNoMoreInteractions(mConfirmSave);
+ }
+
+ @Test
+ public void updateState_nameEmpty_buttonDisabled() {
+ ZenMode aModeHasNoName = new TestModeBuilder().setName("").build();
+
+ mController.updateState(mPreference, aModeHasNoName);
+
+ assertThat(mButton.isEnabled()).isFalse();
+ verifyNoMoreInteractions(mConfirmSave);
+ }
+
+ @Test
+ public void onButtonClick_callsConfirmSave() {
+ mButton.performClick();
+
+ verify(mConfirmSave).run();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditNameIconFragmentTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditNameIconFragmentTest.java
new file mode 100644
index 00000000000..b476b000db2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditNameIconFragmentTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.testing.FragmentScenario;
+import androidx.lifecycle.Lifecycle;
+
+import com.android.internal.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeEditNameIconFragmentTest {
+
+ private static final ZenMode MODE = new TestModeBuilder().setId("id").setName("Mode").build();
+
+ private Activity mActivity;
+ private ZenModeEditNameIconFragment mFragment;
+ private FragmentScenario mScenario;
+
+ @Mock
+ private ZenModesBackend mBackend;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ // Note: Each test should call startFragment() to set mScenario, mFragment and mActivity.
+ }
+
+ @After
+ public void tearDown() {
+ if (mScenario != null) {
+ mScenario.close();
+ }
+ }
+
+ @Test
+ public void onCreate_loadsMode() {
+ when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
+
+ startFragment(MODE.getId());
+
+ assertThat(mFragment.getZenMode()).isEqualTo(MODE);
+ assertThat(mActivity.isFinishing()).isFalse();
+ }
+
+ @Test
+ public void onCreate_noModeId_exits() {
+ when(mBackend.getMode(any())).thenReturn(MODE);
+
+ startFragment(null);
+
+ assertThat(mActivity.isFinishing()).isTrue();
+ verifyNoMoreInteractions(mBackend);
+ }
+ @Test
+ public void onCreate_missingMode_exits() {
+ when(mBackend.getMode(any())).thenReturn(null);
+
+ startFragment(MODE.getId());
+
+ assertThat(mActivity.isFinishing()).isTrue();
+ verify(mBackend).getMode(MODE.getId());
+ }
+
+ @Test
+ public void saveMode_appliesChangesAndFinishes() {
+ when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
+ startFragment(MODE.getId());
+
+ mFragment.setModeName("A new name");
+ mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
+ mFragment.setModeName("A newer name");
+
+ mFragment.saveMode();
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ ZenMode savedMode = captor.getValue();
+ assertThat(savedMode.getName()).isEqualTo("A newer name");
+ assertThat(savedMode.getRule().getIconResId()).isEqualTo(
+ R.drawable.ic_zen_mode_type_theater);
+
+ assertThat(mActivity.isFinishing()).isTrue();
+ }
+
+ @Test
+ public void saveMode_appliesOnyNameAndIconChanges() {
+ when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
+ startFragment(MODE.getId());
+ mFragment.setModeName("A new name");
+ mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
+ // Before the user saves, something else about the mode was modified by someone else.
+ ZenMode newerMode = new TestModeBuilder(MODE).setTriggerDescription("Whenever").build();
+ when(mBackend.getMode(MODE.getId())).thenReturn(newerMode);
+
+ mFragment.saveMode();
+
+ // Verify that saving only wrote the mode name, and didn't accidentally stomp over
+ // unrelated fields of the mode.
+ ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ ZenMode savedMode = captor.getValue();
+ assertThat(savedMode.getName()).isEqualTo("A new name");
+ assertThat(savedMode.getTriggerDescription()).isEqualTo("Whenever");
+ }
+
+ @Test
+ public void saveMode_forModeThatDisappeared_ignoresSave() {
+ when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
+ startFragment(MODE.getId());
+ mFragment.setModeName("A new name");
+ mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
+ // Before the user saves, the mode was removed by someone else.
+ when(mBackend.getMode(MODE.getId())).thenReturn(null);
+
+ mFragment.saveMode();
+
+ verify(mBackend, never()).updateMode(any());
+ assertThat(mActivity.isFinishing()).isTrue();
+ }
+
+ @Test
+ public void setModeFields_withoutSaveMode_doesNotSaveChanges() {
+ when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
+ startFragment(MODE.getId());
+
+ mFragment.setModeName("Not a good idea");
+ mFragment.setModeIcon(R.drawable.emergency_icon);
+ mActivity.finish();
+
+ verify(mBackend, never()).updateMode(any());
+ }
+
+ @Test
+ public void onCreate_whenRecreating_preservesEdits() {
+ when(mBackend.getMode(MODE.getId())).thenReturn(MODE);
+ startFragment(MODE.getId());
+
+ mFragment.setModeName("A better name");
+ mScenario.recreate().onFragment(newFragment -> {
+ assertThat(newFragment).isNotSameInstanceAs(mFragment);
+ newFragment.setBackend(mBackend);
+ mActivity = newFragment.getActivity();
+ mFragment = newFragment;
+ });
+ mFragment.saveMode();
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class);
+ verify(mBackend).updateMode(captor.capture());
+ ZenMode savedMode = captor.getValue();
+ assertThat(savedMode.getName()).isEqualTo("A better name");
+ assertThat(mActivity.isFinishing()).isTrue();
+ }
+
+ private void startFragment(@Nullable String modeId) {
+ Bundle fragmentArgs = null;
+ if (modeId != null) {
+ fragmentArgs = new Bundle();
+ fragmentArgs.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId);
+ }
+
+ mScenario = FragmentScenario.launch(ZenModeEditNameIconFragment.class, fragmentArgs, 0,
+ Lifecycle.State.INITIALIZED);
+
+ mScenario.onFragment(fragment -> {
+ fragment.setBackend(mBackend); // Before onCreate().
+ mFragment = fragment;
+ });
+
+ mScenario.moveToState(Lifecycle.State.RESUMED).onFragment(fragment -> {
+ mActivity = fragment.requireActivity();
+ });
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditNamePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditNamePreferenceControllerTest.java
new file mode 100644
index 00000000000..0c3f4e03b85
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeEditNamePreferenceControllerTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Context;
+import android.widget.EditText;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.widget.LayoutPreference;
+
+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.function.Consumer;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeEditNamePreferenceControllerTest {
+
+ private ZenModeEditNamePreferenceController mController;
+ private LayoutPreference mPreference;
+ private EditText mEditText;
+ @Mock private Consumer mNameSetter;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = RuntimeEnvironment.application;
+ PreferenceManager preferenceManager = new PreferenceManager(context);
+ PreferenceScreen preferenceScreen = preferenceManager.inflateFromResource(context,
+ R.xml.modes_edit_name_icon, null);
+ mPreference = preferenceScreen.findPreference("name");
+
+ mController = new ZenModeEditNamePreferenceController(context, "name", mNameSetter);
+ mController.displayPreference(preferenceScreen);
+ mEditText = mPreference.findViewById(android.R.id.edit);
+ assertThat(mEditText).isNotNull();
+ }
+
+ @Test
+ public void updateState_showsName() {
+ ZenMode mode = new TestModeBuilder().setName("A fancy name").build();
+
+ mController.updateState(mPreference, mode);
+
+ assertThat(mEditText.getText().toString()).isEqualTo("A fancy name");
+ verifyNoMoreInteractions(mNameSetter);
+ }
+
+ @Test
+ public void onEditText_callsNameSetter() {
+ ZenMode mode = new TestModeBuilder().setName("A fancy name").build();
+ mController.updateState(mPreference, mode);
+ EditText editText = mPreference.findViewById(android.R.id.edit);
+
+ editText.setText("An even fancier name");
+
+ verify(mNameSetter).accept("An even fancier name");
+ verifyNoMoreInteractions(mNameSetter);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNewCustomFragmentTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNewCustomFragmentTest.java
new file mode 100644
index 00000000000..b18ce21bf76
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNewCustomFragmentTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.fragment.app.testing.EmptyFragmentActivity;
+import androidx.fragment.app.testing.FragmentScenario;
+import androidx.lifecycle.Lifecycle;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.internal.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
+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.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeNewCustomFragmentTest {
+
+ @Rule
+ public ActivityScenarioRule mActivityScenario =
+ new ActivityScenarioRule<>(EmptyFragmentActivity.class);
+
+ private Activity mActivity;
+ private ZenModeNewCustomFragment mFragment;
+ @Mock
+ private ZenModesBackend mBackend;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFragment = new ZenModeNewCustomFragment();
+ mFragment.setBackend(mBackend); // before onAttach()
+
+ mActivityScenario.getScenario().onActivity(activity -> {
+ mActivity = activity;
+ activity.getSupportFragmentManager().beginTransaction()
+ .add(mFragment, "tag").commitNow();
+ });
+ }
+
+ @Test
+ public void saveMode_addsCustomManualMode() {
+ mFragment.setModeName("The first name");
+ mFragment.setModeIcon(R.drawable.ic_zen_mode_type_theater);
+ mFragment.setModeName("Actually no, this name");
+
+ mFragment.saveMode();
+
+ verify(mBackend).addCustomManualMode("Actually no, this name",
+ R.drawable.ic_zen_mode_type_theater);
+ }
+
+ @Test
+ public void saveMode_withoutEdits_addsModeWithDefaultValues() {
+ mFragment.saveMode();
+
+ verify(mBackend).addCustomManualMode("Custom mode", 0);
+ }
+
+ @Test
+ public void saveMode_redirectsToModeView() {
+ when(mBackend.addCustomManualMode(any(), anyInt())).then(
+ (Answer) invocationOnMock -> new TestModeBuilder()
+ .setId("Id of a mode named " + invocationOnMock.getArgument(0))
+ .setName(invocationOnMock.getArgument(0))
+ .setIconResId(invocationOnMock.getArgument(1))
+ .build());
+
+ mFragment.setModeName("something");
+ mFragment.setModeIcon(R.drawable.ic_zen_mode_type_immersive);
+ mFragment.saveMode();
+
+ Intent nextIntent = shadowOf(mActivity).getNextStartedActivity();
+ assertThat(nextIntent.getStringExtra(EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(ZenModeFragment.class.getName());
+ Bundle fragmentArgs = nextIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(fragmentArgs).isNotNull();
+ assertThat(fragmentArgs.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo(
+ "Id of a mode named something");
+ }
+
+ @Test
+ public void onCreate_whenRecreating_preservesEdits() {
+ FragmentScenario scenario =
+ FragmentScenario.launch(ZenModeNewCustomFragment.class, /* bundle= */ null, 0,
+ Lifecycle.State.INITIALIZED);
+ scenario.onFragment(first -> {
+ first.setBackend(mBackend);
+ mFragment = first;
+ });
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ // Perform some edits in the first fragment.
+ mFragment.setModeName("Don't forget me!");
+ mFragment.setModeIcon(R.drawable.ic_zen_mode_type_immersive);
+
+ // Destroy the first fragment and creates a new one (which should restore state).
+ scenario.recreate().onFragment(second -> {
+ assertThat(second).isNotSameInstanceAs(mFragment);
+ second.setBackend(mBackend);
+ mFragment = second;
+ });
+
+ mFragment.saveMode();
+ verify(mBackend).addCustomManualMode("Don't forget me!",
+ R.drawable.ic_zen_mode_type_immersive);
+ scenario.close();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java
index 661f8ba1245..4703b8e9ce7 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListFragmentTest.java
@@ -57,6 +57,9 @@ 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 ModeType CUSTOM_MANUAL_TYPE = new ModeType("Custom", new ColorDrawable(),
+ null, null); // null creationActivityIntent means custom_manual.
+
private static final ImmutableList EXISTING_MODES = ImmutableList.of(
new TestModeBuilder().setId("A").build(),
new TestModeBuilder().setId("B").build(),
@@ -94,6 +97,16 @@ public class ZenModesListFragmentTest {
assertThat(intent.intent).isEqualTo(APP_PROVIDED_MODE_TYPE.creationActivityIntent());
}
+ @Test
+ public void onChosenModeTypeForAdd_customManualMode_startsNameAndIconPicker() {
+ mFragment.onChosenModeTypeForAdd(CUSTOM_MANUAL_TYPE);
+
+ Intent nextIntent = shadowOf(mActivity).getNextStartedActivity();
+ assertThat(nextIntent).isNotNull();
+ assertThat(nextIntent.getStringExtra(EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(ZenModeNewCustomFragment.class.getName());
+ }
+
@Test
public void onActivityResult_modeWasCreated_opensIt() {
when(mBackend.getModes()).thenReturn(EXISTING_MODES);