Merge "Add mode: Choose name and icon for new custom modes" into main

This commit is contained in:
Matías Hernández
2024-07-09 02:20:07 +00:00
committed by Android (Google) Code Review
18 changed files with 1154 additions and 69 deletions

View File

@@ -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)

View File

@@ -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());
}
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
};
}

View File

@@ -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";
}
}

View File

@@ -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();
}
}