Merge "Add mode: Choose name and icon for new custom modes" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
bb41777f87
32
res/layout/modes_edit_done_button.xml
Normal file
32
res/layout/modes_edit_done_button.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/done"
|
||||||
|
android:text="@string/done"
|
||||||
|
style="@style/ActionPrimaryButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
/>
|
||||||
|
</FrameLayout>
|
35
res/layout/modes_edit_name.xml
Normal file
35
res/layout/modes_edit_name.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@android:id/edit"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:inputType="text|textCapSentences"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:selectAllOnFocus="true"
|
||||||
|
android:hint="@string/zen_mode_edit_name_hint" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@@ -9452,6 +9452,18 @@
|
|||||||
<!-- [CHAR LIMIT=40] Zen mode settings: Title for the "choose mode icon" screen -->
|
<!-- [CHAR LIMIT=40] Zen mode settings: Title for the "choose mode icon" screen -->
|
||||||
<string name="zen_mode_icon_picker_title">Change icon</string>
|
<string name="zen_mode_icon_picker_title">Change icon</string>
|
||||||
|
|
||||||
|
<!-- Priority Modes: Title for the "rename mode" screen [CHAR LIMIT=20] -->
|
||||||
|
<string name="zen_mode_rename_title">Rename</string>
|
||||||
|
|
||||||
|
<!-- Priority Modes: Title for the "add mode" screen [CHAR LIMIT=20] -->
|
||||||
|
<string name="zen_mode_new_custom_title">Add mode</string>
|
||||||
|
|
||||||
|
<!-- Priority Modes: Default name for new custom modes [CHAR LIMIT=30] -->
|
||||||
|
<string name="zen_mode_new_custom_default_name">Custom mode</string>
|
||||||
|
|
||||||
|
<!-- Priority Modes: Hint for the EditText for editing a mode's name [CHAR LIMIT=30] -->
|
||||||
|
<string name="zen_mode_edit_name_hint">Mode name</string>
|
||||||
|
|
||||||
<!-- Content description for help icon button [CHAR LIMIT=20] -->
|
<!-- Content description for help icon button [CHAR LIMIT=20] -->
|
||||||
<string name="warning_button_text">Warning</string>
|
<string name="warning_button_text">Warning</string>
|
||||||
|
|
||||||
|
@@ -18,20 +18,30 @@
|
|||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||||
android:key="zen_mode_icon_picker_page"
|
android:key="zen_mode_edit_name_icon"
|
||||||
settings:searchable="false"
|
settings:searchable="false"
|
||||||
android:title="@string/zen_mode_icon_picker_title">
|
android:title="@string/zen_mode_rename_title">
|
||||||
|
|
||||||
<com.android.settingslib.widget.LayoutPreference
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
android:key="current_icon"
|
android:key="chosen_icon"
|
||||||
android:layout="@layout/settings_entity_header" />
|
android:layout="@layout/settings_entity_header" />
|
||||||
|
|
||||||
<com.android.settings.applications.SpacePreference
|
<com.android.settings.applications.SpacePreference
|
||||||
android:layout_height="16dp" />
|
android:layout_height="16dp" />
|
||||||
|
|
||||||
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
|
android:key="name"
|
||||||
|
android:layout="@layout/modes_edit_name" />
|
||||||
|
|
||||||
|
<com.android.settings.applications.SpacePreference
|
||||||
|
android:layout_height="32dp" />
|
||||||
|
|
||||||
<com.android.settingslib.widget.LayoutPreference
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
android:key="icon_list"
|
android:key="icon_list"
|
||||||
android:selectable="false"
|
android:selectable="false"
|
||||||
android:layout="@layout/modes_icon_list"/>
|
android:layout="@layout/modes_icon_list"/>
|
||||||
|
|
||||||
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
|
android:key="done"
|
||||||
|
android:layout="@layout/modes_edit_done_button" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
@@ -51,7 +51,7 @@ class ZenModeActionsPreferenceController extends AbstractZenModePreferenceContro
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
|
bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
|
||||||
new SubSettingLauncher(mContext)
|
new SubSettingLauncher(mContext)
|
||||||
.setDestination(ZenModeIconPickerFragment.class.getName())
|
.setDestination(ZenModeEditNameIconFragment.class.getName())
|
||||||
// TODO: b/332937635 - Update metrics category
|
// TODO: b/332937635 - Update metrics category
|
||||||
.setSourceMetricsCategory(0)
|
.setSourceMetricsCategory(0)
|
||||||
.setArguments(bundle)
|
.setArguments(bundle)
|
||||||
|
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
@@ -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.
|
||||||
|
*
|
||||||
|
* <p>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).
|
||||||
|
*
|
||||||
|
* <p>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<AbstractPreferenceController> 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<List<AbstractPreferenceController>> controllers = getPreferenceControllers();
|
||||||
|
if (mZenMode == null || screen == null || controllers == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (List<AbstractPreferenceController> 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.
|
||||||
|
*
|
||||||
|
* <p>Note that {@code mode} is the <em>in-memory</em> 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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<String> mModeNameSetter;
|
||||||
|
@Nullable private EditText mEditText;
|
||||||
|
private boolean mIsSettingText;
|
||||||
|
|
||||||
|
ZenModeEditNamePreferenceController(@NonNull Context context, @NonNull String key,
|
||||||
|
@NonNull Consumer<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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<AbstractPreferenceController> 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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.settings.R;
|
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.ModeType;
|
||||||
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.OnAddModeListener;
|
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.OnAddModeListener;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
@@ -37,7 +38,6 @@ import com.google.common.collect.ImmutableList;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
@SearchIndexable
|
@SearchIndexable
|
||||||
public class ZenModesListFragment extends ZenModesFragmentBase {
|
public class ZenModesListFragment extends ZenModesFragmentBase {
|
||||||
@@ -100,13 +100,12 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
|
|||||||
mBackend.getModes().stream().map(ZenMode::getId).toList());
|
mBackend.getModes().stream().map(ZenMode::getId).toList());
|
||||||
startActivityForResult(type.creationActivityIntent(), REQUEST_NEW_MODE);
|
startActivityForResult(type.creationActivityIntent(), REQUEST_NEW_MODE);
|
||||||
} else {
|
} else {
|
||||||
// Custom-manual mode.
|
// Custom-manual mode -> "add a mode" screen.
|
||||||
// TODO: b/326442408 - Transition to the choose-name-and-icon fragment.
|
// TODO: b/332937635 - set metrics categories correctly
|
||||||
ZenMode mode = mBackend.addCustomManualMode(
|
new SubSettingLauncher(requireContext())
|
||||||
"Mode #" + new Random().nextInt(100), 0);
|
.setDestination(ZenModeNewCustomFragment.class.getName())
|
||||||
if (mode != null) {
|
.setSourceMetricsCategory(0)
|
||||||
ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch();
|
.launch();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -54,6 +54,16 @@ class TestModeBuilder {
|
|||||||
mConfigZenRule.pkg = "some_package";
|
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) {
|
TestModeBuilder setId(String id) {
|
||||||
mId = id;
|
mId = id;
|
||||||
return this;
|
return this;
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
@@ -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<ZenModeEditNameIconFragment> 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<ZenMode> 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<ZenMode> 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<ZenMode> 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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<EmptyFragmentActivity> 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<ZenMode>) 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<ZenModeNewCustomFragment> 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();
|
||||||
|
}
|
||||||
|
}
|
@@ -57,6 +57,9 @@ public class ZenModesListFragmentTest {
|
|||||||
private static final ModeType APP_PROVIDED_MODE_TYPE = new ModeType("Mode", new ColorDrawable(),
|
private static final ModeType APP_PROVIDED_MODE_TYPE = new ModeType("Mode", new ColorDrawable(),
|
||||||
"Details", new Intent().setComponent(new ComponentName("pkg", "configActivity")));
|
"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<ZenMode> EXISTING_MODES = ImmutableList.of(
|
private static final ImmutableList<ZenMode> EXISTING_MODES = ImmutableList.of(
|
||||||
new TestModeBuilder().setId("A").build(),
|
new TestModeBuilder().setId("A").build(),
|
||||||
new TestModeBuilder().setId("B").build(),
|
new TestModeBuilder().setId("B").build(),
|
||||||
@@ -94,6 +97,16 @@ public class ZenModesListFragmentTest {
|
|||||||
assertThat(intent.intent).isEqualTo(APP_PROVIDED_MODE_TYPE.creationActivityIntent());
|
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
|
@Test
|
||||||
public void onActivityResult_modeWasCreated_opensIt() {
|
public void onActivityResult_modeWasCreated_opensIt() {
|
||||||
when(mBackend.getModes()).thenReturn(EXISTING_MODES);
|
when(mBackend.getModes()).thenReturn(EXISTING_MODES);
|
||||||
|
Reference in New Issue
Block a user