/* * 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 androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.notification.modes.ZenIconLoader; 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.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) { for (var controller : getZenPreferenceControllers()) { controller.setZenMode(mZenMode); } } else { 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, ZenIconLoader.getInstance(), "chosen_icon", this), new ZenModeEditNamePreferenceController(context, "name", this::setModeName), new ZenModeIconPickerListPreferenceController(context, "icon_list", this::setModeIcon), new ZenModeEditDonePreferenceController(context, "done", this::saveMode) ); } private Iterable getZenPreferenceControllers() { return getPreferenceControllers().stream() .flatMap(List::stream) .filter(AbstractZenModePreferenceController.class::isInstance) .map(AbstractZenModePreferenceController.class::cast) .toList(); } @VisibleForTesting(otherwise = VisibleForTesting.NONE) @Nullable ZenMode getZenMode() { return mZenMode; } @VisibleForTesting final void setModeName(String name) { checkNotNull(mZenMode).setName(Strings.nullToEmpty(name)); forceUpdatePreferences(); // Updates confirmation button. } @VisibleForTesting final void setModeIcon(@DrawableRes int iconResId) { checkNotNull(mZenMode).setIconResId(iconResId); forceUpdatePreferences(); // Updates icon at the top. } @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); } }