1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification.modes; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.base.Preconditions.checkState; 21 22 import android.content.Context; 23 import android.os.Bundle; 24 25 import androidx.annotation.DrawableRes; 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.annotation.VisibleForTesting; 29 30 import com.android.settings.R; 31 import com.android.settings.dashboard.DashboardFragment; 32 import com.android.settingslib.core.AbstractPreferenceController; 33 import com.android.settingslib.notification.modes.ZenIconLoader; 34 import com.android.settingslib.notification.modes.ZenMode; 35 import com.android.settingslib.notification.modes.ZenModesBackend; 36 37 import com.google.common.base.Strings; 38 import com.google.common.collect.ImmutableList; 39 40 import java.util.List; 41 42 /** 43 * Base class for the "add a mode" and "edit mode name and icon" fragments. In both cases we are 44 * editing a {@link ZenMode}, but the mode shouldn't be saved immediately after each atomic change 45 * -- instead, it will be saved to the backend upon user confirmation. 46 * 47 * <p>As a result, instead of using {@link ZenModesBackend} to apply each change, we instead modify 48 * an in-memory {@link ZenMode}, that is preserved/restored in extras. This also means we don't 49 * listen to changes -- whatever the user sees should be applied. 50 */ 51 public abstract class ZenModeEditNameIconFragmentBase extends DashboardFragment { 52 53 private static final String MODE_KEY = "ZenMode"; 54 55 @Nullable private ZenMode mZenMode; 56 57 private ZenModesBackend mBackend; 58 59 @VisibleForTesting(otherwise = VisibleForTesting.NONE) setBackend(ZenModesBackend backend)60 void setBackend(ZenModesBackend backend) { 61 mBackend = backend; 62 } 63 64 @Override onAttach(Context context)65 public void onAttach(Context context) { 66 super.onAttach(context); 67 if (mBackend == null) { 68 mBackend = ZenModesBackend.getInstance(context); 69 } 70 } 71 72 @Override onCreate(Bundle icicle)73 public final void onCreate(Bundle icicle) { 74 super.onCreate(icicle); 75 mZenMode = icicle != null 76 ? icicle.getParcelable(MODE_KEY, ZenMode.class) 77 : onCreateInstantiateZenMode(); 78 79 if (mZenMode != null) { 80 for (var controller : getZenPreferenceControllers()) { 81 controller.setZenMode(mZenMode); 82 } 83 } else { 84 finish(); 85 } 86 } 87 88 /** 89 * Provides the mode that will be edited. Called in {@link #onCreate}, the first time (the 90 * value returned here is persisted on Fragment recreation). 91 * 92 * <p>If {@code null} is returned, the fragment will {@link #finish()}. 93 */ 94 @Nullable onCreateInstantiateZenMode()95 protected abstract ZenMode onCreateInstantiateZenMode(); 96 97 @Override getPreferenceScreenResId()98 protected final int getPreferenceScreenResId() { 99 return R.xml.modes_edit_name_icon; 100 } 101 102 @Override createPreferenceControllers( Context context)103 protected final List<AbstractPreferenceController> createPreferenceControllers( 104 Context context) { 105 return ImmutableList.of( 106 new ZenModeIconPickerIconPreferenceController(context, ZenIconLoader.getInstance(), 107 "chosen_icon", this), 108 new ZenModeEditNamePreferenceController(context, "name", this::setModeName), 109 new ZenModeIconPickerListPreferenceController(context, "icon_list", 110 this::setModeIcon), 111 new ZenModeEditDonePreferenceController(context, "done", this::saveMode) 112 ); 113 } 114 getZenPreferenceControllers()115 private Iterable<AbstractZenModePreferenceController> getZenPreferenceControllers() { 116 return getPreferenceControllers().stream() 117 .flatMap(List::stream) 118 .filter(AbstractZenModePreferenceController.class::isInstance) 119 .map(AbstractZenModePreferenceController.class::cast) 120 .toList(); 121 } 122 123 @VisibleForTesting(otherwise = VisibleForTesting.NONE) 124 @Nullable getZenMode()125 ZenMode getZenMode() { 126 return mZenMode; 127 } 128 129 @VisibleForTesting setModeName(String name)130 final void setModeName(String name) { 131 checkNotNull(mZenMode).setName(Strings.nullToEmpty(name)); 132 forceUpdatePreferences(); // Updates confirmation button. 133 } 134 135 @VisibleForTesting setModeIcon(@rawableRes int iconResId)136 final void setModeIcon(@DrawableRes int iconResId) { 137 checkNotNull(mZenMode).setIconResId(iconResId); 138 forceUpdatePreferences(); // Updates icon at the top. 139 } 140 141 142 @VisibleForTesting saveMode()143 final void saveMode() { 144 saveMode(checkNotNull(mZenMode)); 145 } 146 147 /** 148 * Called to actually save the mode, after the user confirms. This method is also responsible 149 * for calling {@link #finish()}, if appropriate. 150 * 151 * <p>Note that {@code mode} is the <em>in-memory</em> mode and, as such, may have obsolete 152 * data. If the concrete fragment is editing an existing mode, it should first fetch it from 153 * the backend, and copy the new name and icon before saving. */ saveMode(ZenMode mode)154 abstract void saveMode(ZenMode mode); 155 156 @NonNull requireBackend()157 protected ZenModesBackend requireBackend() { 158 checkState(mBackend != null); 159 return mBackend; 160 } 161 162 @Override onSaveInstanceState(Bundle outState)163 public void onSaveInstanceState(Bundle outState) { 164 super.onSaveInstanceState(outState); 165 outState.putParcelable(MODE_KEY, mZenMode); 166 } 167 } 168