Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 149bd0a5 authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Improve lifecycle of ZenModeFragment & friends" into main

parents 691c0c3f b8b897e5
Loading
Loading
Loading
Loading
+22 −21
Original line number Diff line number Diff line
@@ -25,8 +25,8 @@ import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
@@ -92,29 +92,14 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
        return true;
    }

    // Called by parent Fragment onAttach, for any methods (such as isAvailable()) that need
    // zen mode info before onStart. Most callers should use updateZenMode instead, which will
    // do any further necessary propagation.
    protected final void setZenMode(@NonNull ZenMode zenMode) {
        mZenMode = zenMode;
    }

    // Called by the parent Fragment onStart, which means it will happen before resume.
    public void updateZenMode(@NonNull Preference preference, @NonNull ZenMode zenMode) {
    /**
     * Assigns the {@link ZenMode} of this controller, so that it can be used later from
     * {@link #isAvailable()} and {@link #updateState(Preference)}.
     */
    final void setZenMode(@NonNull ZenMode zenMode) {
        mZenMode = zenMode;
        updateState(preference);
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        if (mZenMode != null) {
            displayPreference(screen, mZenMode);
        }
    }

    public void displayPreference(PreferenceScreen screen, @NonNull ZenMode zenMode) {}

    @Override
    public final void updateState(Preference preference) {
        super.updateState(preference);
@@ -167,4 +152,20 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
            return mode;
        });
    }

    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    @Nullable
    ZenMode getZenMode() {
        return mZenMode;
    }

    /**
     * Convenience method for tests. Assigns the {@link ZenMode} of this controller, and calls
     * {@link #updateState(Preference)} immediately.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    final void updateZenMode(@NonNull Preference preference, @NonNull ZenMode zenMode) {
        mZenMode = zenMode;
        updateState(preference);
    }
}
+4 −3
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settings.notification.modes;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -49,12 +50,12 @@ public class ManualDurationPreferenceController extends AbstractZenModePreferenc
        return zenMode.isManualDnd();
    }

    // Called by parent fragment onAttach().
    // Called by parent fragment onStart().
    void registerSettingsObserver() {
        mSettingsObserver.register();
    }

    // Called by parent fragment onDetach().
    // Called by parent fragment onStop().
    void unregisterSettingsObserver() {
        mSettingsObserver.unregister();
    }
@@ -69,7 +70,7 @@ public class ManualDurationPreferenceController extends AbstractZenModePreferenc
    }

    @Override
    public void updateState(Preference preference, ZenMode unusedZenMode) {
    public void updateState(Preference preference, @NonNull ZenMode unusedZenMode) {
        // This controller is a link between a Settings value (ZEN_DURATION) and the manual DND
        // mode. The status of the zen mode object itself doesn't affect the preference
        // value, as that comes from settings; that value from settings will determine the
+15 −41
Original line number Diff line number Diff line
@@ -21,14 +21,11 @@ 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;
@@ -39,7 +36,6 @@ 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;

/**
@@ -79,7 +75,11 @@ public abstract class ZenModeEditNameIconFragmentBase extends DashboardFragment
                ? icicle.getParcelable(MODE_KEY, ZenMode.class)
                : onCreateInstantiateZenMode();

        if (mZenMode == null) {
        if (mZenMode != null) {
            for (var controller : getZenPreferenceControllers()) {
                controller.setZenMode(mZenMode);
            }
        } else {
            finish();
        }
    }
@@ -110,59 +110,33 @@ public abstract class ZenModeEditNameIconFragmentBase extends DashboardFragment
        );
    }

    private Iterable<AbstractZenModePreferenceController> 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;
    }

    @Override
    public void onStart() {
        super.onStart();
        updateControllers();
    }

    @VisibleForTesting
    final void setModeName(String name) {
        checkNotNull(mZenMode).getRule().setName(Strings.nullToEmpty(name));
        updateControllers(); // Updates confirmation button.
        forceUpdatePreferences(); // 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());
                }
            }
        }
        forceUpdatePreferences();  // Updates icon at the top.
    }


    @VisibleForTesting
    final void saveMode() {
        saveMode(checkNotNull(mZenMode));
+6 −16
Original line number Diff line number Diff line
@@ -79,14 +79,6 @@ public class ZenModeFragment extends ZenModeFragmentBase {
        return prefControllers;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);

        // allow duration preference controller to listen for settings changes
        use(ManualDurationPreferenceController.class).registerSettingsObserver();
    }

    @Override
    public void onStart() {
        super.onStart();
@@ -99,6 +91,9 @@ public class ZenModeFragment extends ZenModeFragmentBase {
            mModeMenuProvider = new ModeMenuProvider(mode);
            activity.addMenuProvider(mModeMenuProvider);
        }

        // allow duration preference controller to listen for settings changes
        use(ManualDurationPreferenceController.class).registerSettingsObserver();
    }

    @Override
@@ -106,13 +101,8 @@ public class ZenModeFragment extends ZenModeFragmentBase {
        if (getActivity() != null) {
            getActivity().removeMenuProvider(mModeMenuProvider);
        }
        super.onStop();
    }

    @Override
    public void onDetach() {
        use(ManualDurationPreferenceController.class).unregisterSettingsObserver();
        super.onDetach();
        super.onStop();
    }

    @Override
@@ -122,13 +112,13 @@ public class ZenModeFragment extends ZenModeFragmentBase {
    }

    @Override
    protected void updateZenModeState() {
    protected void onUpdatedZenModeState() {
        // Because this fragment may be asked to finish by the delete menu but not be done doing
        // so yet, ignore any attempts to update info in that case.
        if (getActivity() != null && getActivity().isFinishing()) {
            return;
        }
        super.updateZenModeState();
        super.onUpdatedZenModeState();
    }

    private class ModeMenuProvider implements MenuProvider {
+58 −90
Original line number Diff line number Diff line
@@ -18,24 +18,18 @@ package com.android.settings.notification.modes;

import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.lifecycle.Lifecycle;

import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;

import com.google.common.base.Preconditions;

import java.util.List;
import java.util.function.Consumer;

/**
 * Base class for Settings pages used to configure individual modes.
@@ -43,13 +37,27 @@ import java.util.function.Consumer;
abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
    static final String TAG = "ZenModeSettings";

    @Nullable  // only until reloadMode() is called
    private ZenMode mZenMode;
    @Nullable private ZenMode mZenMode;
    @Nullable private ZenMode mModeOnLastControllerUpdate;

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
    public void onCreate(Bundle icicle) {
        mZenMode = loadModeFromArguments();
        if (mZenMode != null) {
            // Propagate mode info through to controllers. Must be done before super.onCreate(),
            // because that one calls AbstractPreferenceController.isAvailable().
            for (var controller : getZenPreferenceControllers()) {
                controller.setZenMode(mZenMode);
            }
        } else {
            toastAndFinish();
        }

        super.onCreate(icicle);
    }

    @Nullable
    private ZenMode loadModeFromArguments() {
        String id = null;
        if (getActivity() != null && getActivity().getIntent() != null) {
            id = getActivity().getIntent().getStringExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID);
@@ -60,93 +68,65 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
        }
        if (id == null) {
            Log.d(TAG, "No id provided");
            toastAndFinish();
            return;
        }
        if (!reloadMode(id)) {
            Log.d(TAG, "Mode id " + id + " not found");
            toastAndFinish();
            return;
        }
        if (mZenMode != null) {
            // Propagate mode info through to controllers.
            for (List<AbstractPreferenceController> list : getPreferenceControllers()) {
                try {
                    for (AbstractPreferenceController controller : list) {
                        // mZenMode guaranteed non-null from reloadMode() above
                        ((AbstractZenModePreferenceController) controller).setZenMode(mZenMode);
                    }
                } catch (ClassCastException e) {
                    // ignore controllers that aren't AbstractZenModePreferenceController
                }
            return null;
        }

        ZenMode mode = mBackend.getMode(id);
        if (mode == null) {
            Log.d(TAG, "Mode with id " + id + " not found");
            return null;
        }
        return mode;
    }

    /**
     * Refresh stored ZenMode data.
     * @param id the mode ID
     * @return whether we successfully got mode data from the backend.
     */
    private boolean reloadMode(String id) {
        mZenMode = mBackend.getMode(id);
        if (mZenMode == null) {
            return false;
        }
        return true;
    private Iterable<AbstractZenModePreferenceController> getZenPreferenceControllers() {
        return getPreferenceControllers().stream()
                .flatMap(List::stream)
                .filter(AbstractZenModePreferenceController.class::isInstance)
                .map(AbstractZenModePreferenceController.class::cast)
                .toList();
    }

    /**
     * Refresh ZenMode data any time the system's zen mode state changes (either the zen mode value
     * itself, or the config), and also (once updated) update the info for all controllers.
     */
    @Override
    protected void updateZenModeState() {
    protected void onUpdatedZenModeState() {
        if (mZenMode == null) {
            // This shouldn't happen, but guard against it in case
            Log.wtf(TAG, "mZenMode is null in onUpdatedZenModeState");
            toastAndFinish();
            return;
        }

        String id = mZenMode.getId();
        if (!reloadMode(id)) {
        ZenMode mode = mBackend.getMode(id);
        if (mode == null) {
            Log.d(TAG, "Mode id=" + id + " not found");
            toastAndFinish();
            return;
        }
        updateControllers();
    }

    private void updateControllers() {
        if (getPreferenceControllers() == null || mZenMode == null) {
            return;
        mZenMode = mode;
        maybeUpdateControllersState(mode);
    }

        final PreferenceScreen screen = getPreferenceScreen();
        if (screen == null) {
            Log.d(TAG, "PreferenceScreen not found");
            return;
        }
        for (List<AbstractPreferenceController> list : getPreferenceControllers()) {
            for (AbstractPreferenceController controller : list) {
                try {
                    // Find preference associated with controller
                    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(TAG,
                                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(TAG, "Could not cast: " + controller.getClass().getSimpleName());
                }
    /**
     * Updates all {@link AbstractZenModePreferenceController} based on the loaded mode info.
     * For each controller, {@link AbstractZenModePreferenceController#setZenMode} will be called.
     * Then, {@link AbstractZenModePreferenceController#updateState} will be called as well, unless
     * we determine it's not necessary (for example, if we know that {@code DashboardFragment} will
     * do it soon).
     */
    private void maybeUpdateControllersState(@NonNull ZenMode zenMode) {
        boolean needsFullUpdate =
                getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
                && (mModeOnLastControllerUpdate == null
                        || !mModeOnLastControllerUpdate.equals(zenMode));
        mModeOnLastControllerUpdate = zenMode.copy();

        for (var controller : getZenPreferenceControllers()) {
            controller.setZenMode(zenMode);
        }

        if (needsFullUpdate) {
            forceUpdatePreferences();
        }
    }

@@ -163,16 +143,4 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
    public ZenMode getMode() {
        return mZenMode;
    }

    protected final boolean saveMode(Consumer<ZenMode> updater) {
        Preconditions.checkState(mBackend != null);
        ZenMode mode = mZenMode;
        if (mode == null) {
            Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");
            return false;
        }
        updater.accept(mode);
        mBackend.updateMode(mode);
        return true;
    }
}
Loading