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

Commit 04cf5dea authored by Jacky Wang's avatar Jacky Wang Committed by Android (Google) Code Review
Browse files

Merge "[Catalyst] Rebind preference immediately when restriction is changed" into main

parents 5a06b74e bb9cc081
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -48,11 +48,13 @@ import com.android.settings.core.CategoryMixin.CategoryListener;
import com.android.settings.core.PreferenceControllerListHelper;
import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.restriction.UserRestrictionBindingHelper;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.preference.PreferenceScreenBindingHelper;
import com.android.settingslib.preference.PreferenceScreenCreator;
import com.android.settingslib.search.Indexable;

@@ -92,6 +94,8 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
    private boolean mListeningToCategoryChange;
    private List<String> mSuppressInjectedTileKeys;

    private @Nullable UserRestrictionBindingHelper mUserRestrictionBindingHelper;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
@@ -178,6 +182,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
            // editing dialog is recreated (that would happen before onResume is called).
            updatePreferenceStates();
        }
        if (isCatalystEnabled()) {
            PreferenceScreenBindingHelper helper = getPreferenceScreenBindingHelper();
            if (helper != null) {
                mUserRestrictionBindingHelper = new UserRestrictionBindingHelper(requireContext(),
                        helper);
            }
        }
    }

    @Override
@@ -288,6 +299,15 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
        }
    }

    @Override
    public void onDestroy() {
        if (mUserRestrictionBindingHelper != null) {
            mUserRestrictionBindingHelper.close();
            mUserRestrictionBindingHelper = null;
        }
        super.onDestroy();
    }

    @Override
    protected final int getPreferenceScreenResId(@NonNull Context context) {
        return getPreferenceScreenResId();
+66 −0
Original line number Diff line number Diff line
/*
 * 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.restriction

import android.content.Context
import com.android.settings.PreferenceRestrictionMixin
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyedObserver
import com.android.settingslib.preference.PreferenceScreenBindingHelper
import com.android.settingslib.preference.PreferenceScreenBindingHelper.Companion.CHANGE_REASON_STATE

/** Helper to rebind preference immediately when user restriction is changed. */
class UserRestrictionBindingHelper(
    context: Context,
    private val screenBindingHelper: PreferenceScreenBindingHelper,
) : AutoCloseable {
    private val restrictionKeysToPreferenceKeys: Map<String, MutableSet<String>> =
        mutableMapOf<String, MutableSet<String>>()
            .apply {
                screenBindingHelper.forEachRecursively {
                    val metadata = it.metadata
                    if (metadata is PreferenceRestrictionMixin) {
                        getOrPut(metadata.restrictionKey) { mutableSetOf() }.add(metadata.key)
                    }
                }
            }
            .toMap()

    private val userRestrictionObserver: KeyedObserver<String?>?

    init {
        if (restrictionKeysToPreferenceKeys.isEmpty()) {
            userRestrictionObserver = null
        } else {
            val observer =
                KeyedObserver<String?> { restrictionKey, _ ->
                    restrictionKey?.let { notifyRestrictionChanged(it) }
                }
            UserRestrictions.addObserver(context, observer, HandlerExecutor.main)
            userRestrictionObserver = observer
        }
    }

    private fun notifyRestrictionChanged(restrictionKey: String) {
        val keys = restrictionKeysToPreferenceKeys[restrictionKey] ?: return
        for (key in keys) screenBindingHelper.notifyChange(key, CHANGE_REASON_STATE)
    }

    override fun close() {
        userRestrictionObserver?.let { UserRestrictions.removeObserver(it) }
    }
}
+83 −0
Original line number Diff line number Diff line
/*
 * 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.restriction

import android.content.Context
import android.os.Bundle
import android.os.IUserRestrictionsListener
import android.os.UserManager
import com.android.settingslib.datastore.KeyedDataObservable
import com.android.settingslib.datastore.KeyedObserver
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean

/** Helper class to monitor user restriction changes. */
object UserRestrictions {
    private val observable = KeyedDataObservable<String>()

    private val userRestrictionsListener =
        object : IUserRestrictionsListener.Stub() {
            override fun onUserRestrictionsChanged(
                userId: Int,
                newRestrictions: Bundle,
                prevRestrictions: Bundle,
            ) {
                // there is no API to remove listener, do a quick check to avoid unnecessary work
                if (!observable.hasAnyObserver()) return

                val changedKeys = mutableSetOf<String>()
                val keys = newRestrictions.keySet() + prevRestrictions.keySet()
                for (key in keys) {
                    if (newRestrictions.getBoolean(key) != prevRestrictions.getBoolean(key)) {
                        changedKeys.add(key)
                    }
                }

                for (key in changedKeys) observable.notifyChange(key, 0)
            }
        }

    private val listenerAdded = AtomicBoolean()

    fun addObserver(context: Context, observer: KeyedObserver<String?>, executor: Executor) {
        context.addUserRestrictionsListener()
        observable.addObserver(observer, executor)
    }

    fun addObserver(
        context: Context,
        key: String,
        observer: KeyedObserver<String>,
        executor: Executor,
    ) {
        context.addUserRestrictionsListener()
        observable.addObserver(key, observer, executor)
    }

    private fun Context.addUserRestrictionsListener() {
        if (listenerAdded.getAndSet(true)) return
        // surprisingly, there is no way to remove the listener
        applicationContext
            .getSystemService(UserManager::class.java)
            .addUserRestrictionsListener(userRestrictionsListener)
    }

    fun removeObserver(observer: KeyedObserver<String?>) = observable.removeObserver(observer)

    fun removeObserver(key: String, observer: KeyedObserver<String>) =
        observable.removeObserver(key, observer)
}