Loading src/com/android/settings/dashboard/DashboardFragment.java +20 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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(); Loading src/com/android/settings/restriction/UserRestrictionBindingHelper.kt 0 → 100644 +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) } } } src/com/android/settings/restriction/UserRestrictions.kt 0 → 100644 +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) } Loading
src/com/android/settings/dashboard/DashboardFragment.java +20 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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(); Loading
src/com/android/settings/restriction/UserRestrictionBindingHelper.kt 0 → 100644 +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) } } }
src/com/android/settings/restriction/UserRestrictions.kt 0 → 100644 +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) }