diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index 609b96a9709..66ccb6f2648 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -35,6 +35,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.annotation.XmlRes; import androidx.fragment.app.DialogFragment; @@ -54,6 +56,7 @@ import com.android.settings.widget.LoadingViewController; import com.android.settingslib.CustomDialogPreferenceCompat; import com.android.settingslib.CustomEditTextPreferenceCompat; import com.android.settingslib.core.instrumentation.Instrumentable; +import com.android.settingslib.preference.PreferenceScreenCreator; import com.android.settingslib.search.Indexable; import com.android.settingslib.widget.LayoutPreference; @@ -176,6 +179,24 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF } } + @Override + protected final int getPreferenceScreenResId(@NonNull Context context) { + return getPreferenceScreenResId(); + } + + /** Returns if catalyst is enabled on current screen. */ + protected final boolean isCatalystEnabled() { + return getPreferenceScreenCreator() != null; + } + + protected @Nullable PreferenceScreenCreator getPreferenceScreenCreator() { + if (!Flags.catalyst()) { + return null; + } + Context context = getContext(); + return context != null ? getPreferenceScreenCreator(context) : null; + } + public View setPinnedHeaderView(int layoutResId) { final LayoutInflater inflater = getActivity().getLayoutInflater(); final View pinnedHeader = diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java index 9b03e9b16e2..ac87ea5abc6 100644 --- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java +++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java @@ -147,7 +147,7 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc mMetricsFeatureProvider.logClickedPreference(preference, getMetricsCategory()); } - private void updateActivityTitleWithScreenTitle(PreferenceScreen screen) { + protected void updateActivityTitleWithScreenTitle(PreferenceScreen screen) { if (screen != null) { final CharSequence title = screen.getTitle(); if (!TextUtils.isEmpty(title)) { diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index c6d1222adca..6a96089cc96 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -308,11 +308,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment super.onDestroy(); } - @Override - protected final int getPreferenceScreenResId(@NonNull Context context) { - return getPreferenceScreenResId(); - } - @Override protected abstract int getPreferenceScreenResId(); @@ -413,7 +408,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment removeControllersForHybridMode(); } setPreferenceScreen(screen); - requireActivity().setTitle(screen.getTitle()); + updateActivityTitleWithScreenTitle(screen); } else { addPreferencesFromResource(resId); screen = getPreferenceScreen(); @@ -447,19 +442,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment } } - /** Returns if catalyst is enabled on current screen. */ - protected final boolean isCatalystEnabled() { - return getPreferenceScreenCreator() != null; - } - - private @Nullable PreferenceScreenCreator getPreferenceScreenCreator() { - if (!Flags.catalyst()) { - return null; - } - Context context = getContext(); - return context != null ? getPreferenceScreenCreator(context) : null; - } - /** * Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)} * on all {@link AbstractPreferenceController}s. diff --git a/src/com/android/settings/display/AdaptiveSleepPreference.kt b/src/com/android/settings/display/AdaptiveSleepPreference.kt new file mode 100644 index 00000000000..f31959b1182 --- /dev/null +++ b/src/com/android/settings/display/AdaptiveSleepPreference.kt @@ -0,0 +1,137 @@ +/* + * 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.display + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.os.PowerManager +import android.os.UserManager +import android.provider.Settings +import com.android.settings.PreferenceRestrictionMixin +import com.android.settings.R +import com.android.settingslib.RestrictedSwitchPreference +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.KeyedObservableDelegate +import com.android.settingslib.datastore.SettingsSecureStore +import com.android.settingslib.datastore.SettingsStore +import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.PreferenceLifecycleContext +import com.android.settingslib.metadata.PreferenceLifecycleProvider +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.TwoStatePreference +import com.android.settingslib.preference.PreferenceBindingPlaceholder +import com.android.settingslib.preference.SwitchPreferenceBinding + +// LINT.IfChange +class AdaptiveSleepPreference : + TwoStatePreference, + SwitchPreferenceBinding, + PreferenceLifecycleProvider, + PreferenceBindingPlaceholder, // not needed once controller class is cleaned up + PreferenceAvailabilityProvider, + PreferenceRestrictionMixin { + + private var broadcastReceiver: BroadcastReceiver? = null + private var sensorPrivacyChangedListener: OnSensorPrivacyChangedListener? = null + + override val key: String + get() = KEY + + override val title: Int + get() = R.string.adaptive_sleep_title + + override val summary: Int + get() = R.string.adaptive_sleep_description + + override fun isIndexable(context: Context) = false + + override fun isEnabled(context: Context) = + super.isEnabled(context) && context.canBeEnabled() + + override val restrictionKeys: Array + get() = arrayOf(UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT) + + override fun isAvailable(context: Context) = context.isAdaptiveSleepSupported() + + override fun createWidget(context: Context) = RestrictedSwitchPreference(context) + + override fun storage(context: Context): KeyValueStore = Storage(context) + + override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + @Suppress("UNCHECKED_CAST") + private class Storage( + private val context: Context, + private val settingsStore: SettingsStore = SettingsSecureStore.get(context), + ) : KeyedObservableDelegate(settingsStore), KeyValueStore { + + override fun contains(key: String) = settingsStore.contains(key) + + override fun getValue(key: String, valueType: Class) = + (context.canBeEnabled() && settingsStore.getBoolean(key) == true) as T + + override fun setValue(key: String, valueType: Class, value: T?) = + settingsStore.setBoolean(key, value as Boolean?) + } + + override fun onStart(context: PreferenceLifecycleContext) { + val receiver = + object : BroadcastReceiver() { + override fun onReceive(receiverContext: Context, intent: Intent) { + context.notifyPreferenceChange(this@AdaptiveSleepPreference) + } + } + context.registerReceiver( + receiver, + IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED), + ) + broadcastReceiver = receiver + + val listener = OnSensorPrivacyChangedListener { _, _ -> + context.notifyPreferenceChange(this) + } + SensorPrivacyManager.getInstance(context).addSensorPrivacyListener(CAMERA, listener) + sensorPrivacyChangedListener = listener + } + + override fun onStop(context: PreferenceLifecycleContext) { + broadcastReceiver?.let { context.unregisterReceiver(it) } + sensorPrivacyChangedListener?.let { + SensorPrivacyManager.getInstance(context).removeSensorPrivacyListener(it) + } + } + + companion object { + const val KEY = Settings.Secure.ADAPTIVE_SLEEP + + @Suppress("DEPRECATION") + private fun Context.canBeEnabled() = + AdaptiveSleepPreferenceController.hasSufficientPermission(packageManager) && + getSystemService(PowerManager::class.java)?.isPowerSaveMode != true && + !SensorPrivacyManager.getInstance(this).isSensorPrivacyEnabled(CAMERA) + } +} +// LINT.ThenChange(AdaptiveSleepPreferenceController.java) diff --git a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java index 725b956a60b..82a8709df5e 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java +++ b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java @@ -20,19 +20,16 @@ import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; +import static com.android.settings.display.UtilsKt.isAdaptiveSleepSupported; import android.Manifest; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.hardware.SensorPrivacyManager; import android.os.PowerManager; import android.os.UserManager; import android.provider.Settings; -import android.service.attention.AttentionService; -import android.text.TextUtils; import androidx.preference.PreferenceScreen; @@ -45,9 +42,10 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.google.common.annotations.VisibleForTesting; +// LINT.IfChange /** The controller for Screen attention switch preference. */ public class AdaptiveSleepPreferenceController { - public static final String PREFERENCE_KEY = "adaptive_sleep"; + public static final String PREFERENCE_KEY = Settings.Secure.ADAPTIVE_SLEEP; private static final int DEFAULT_VALUE = 0; private final SensorPrivacyManager mPrivacyManager; private final RestrictionUtils mRestrictionUtils; @@ -144,28 +142,10 @@ public class AdaptiveSleepPreferenceController { : UNSUPPORTED_ON_DEVICE; } - static boolean isAdaptiveSleepSupported(Context context) { - return context.getResources().getBoolean( - com.android.internal.R.bool.config_adaptive_sleep_available) - && isAttentionServiceAvailable(context); - } - - private static boolean isAttentionServiceAvailable(Context context) { - final PackageManager packageManager = context.getPackageManager(); - final String resolvePackage = packageManager.getAttentionServicePackageName(); - if (TextUtils.isEmpty(resolvePackage)) { - return false; - } - final Intent intent = new Intent(AttentionService.SERVICE_INTERFACE).setPackage( - resolvePackage); - final ResolveInfo resolveInfo = packageManager.resolveService(intent, - PackageManager.MATCH_SYSTEM_ONLY); - return resolveInfo != null && resolveInfo.serviceInfo != null; - } - static boolean hasSufficientPermission(PackageManager packageManager) { final String attentionPackage = packageManager.getAttentionServicePackageName(); return attentionPackage != null && packageManager.checkPermission( Manifest.permission.CAMERA, attentionPackage) == PackageManager.PERMISSION_GRANTED; } } +// LINT.ThenChange(AdaptiveSleepPreference.kt) diff --git a/src/com/android/settings/display/ScreenTimeoutScreen.kt b/src/com/android/settings/display/ScreenTimeoutScreen.kt new file mode 100644 index 00000000000..9dcd1021621 --- /dev/null +++ b/src/com/android/settings/display/ScreenTimeoutScreen.kt @@ -0,0 +1,54 @@ +/* + * 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.display + +import android.content.Context +import com.android.settings.R +import com.android.settings.Settings.ScreenTimeoutActivity +import com.android.settings.flags.Flags +import com.android.settings.utils.makeLaunchIntent +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.ProvidePreferenceScreen +import com.android.settingslib.metadata.preferenceHierarchy +import com.android.settingslib.preference.PreferenceScreenCreator + +// TODO(b/368359967): The entry point logic is not yet migrated +@ProvidePreferenceScreen +class ScreenTimeoutScreen : PreferenceScreenCreator { + + override val key: String + get() = KEY + + override val title: Int + get() = R.string.screen_timeout + + override fun isFlagEnabled(context: Context) = Flags.catalystScreenTimeout() + + override fun fragmentClass() = ScreenTimeoutSettings::class.java + + override fun hasCompleteHierarchy() = false + + override fun getPreferenceHierarchy(context: Context) = + preferenceHierarchy(this) { +AdaptiveSleepPreference() } + + override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) = + makeLaunchIntent(context, ScreenTimeoutActivity::class.java, metadata?.key) + + companion object { + const val KEY = "screen_timeout" + } +} diff --git a/src/com/android/settings/display/ScreenTimeoutSettings.java b/src/com/android/settings/display/ScreenTimeoutSettings.java index 2d229a3cfe7..d4ca48ebfb7 100644 --- a/src/com/android/settings/display/ScreenTimeoutSettings.java +++ b/src/com/android/settings/display/ScreenTimeoutSettings.java @@ -20,6 +20,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.OTHER_OPT import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static com.android.settings.display.UtilsKt.isAdaptiveSleepSupported; + import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; @@ -34,7 +36,9 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -80,7 +84,9 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment @Override public void onReceive(Context context, Intent intent) { mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); - mAdaptiveSleepController.updatePreference(); + if (!isCatalystEnabled()) { + mAdaptiveSleepController.updatePreference(); + } } }; @@ -123,7 +129,6 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); mInitialEntries = getResources().getStringArray(R.array.screen_timeout_entries); mInitialValues = getResources().getStringArray(R.array.screen_timeout_values); - mAdaptiveSleepController = new AdaptiveSleepPreferenceController(context); mAdaptiveSleepPermissionController = new AdaptiveSleepPermissionPreferenceController(context); mAdaptiveSleepCameraStatePreferenceController = @@ -136,8 +141,12 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment mPrivacyPreference.setSelectable(false); mPrivacyPreference.setLayoutResource( com.android.settingslib.widget.preference.footer.R.layout.preference_footer); - mPrivacyManager = SensorPrivacyManager.getInstance(context); - mPrivacyChangedListener = (sensor, enabled) -> mAdaptiveSleepController.updatePreference(); + if (!isCatalystEnabled()) { + mPrivacyManager = SensorPrivacyManager.getInstance(context); + mAdaptiveSleepController = new AdaptiveSleepPreferenceController(context); + mPrivacyChangedListener = + (sensor, enabled) -> mAdaptiveSleepController.updatePreference(); + } mAdditionalTogglePreferenceController = FeatureFactory.getFeatureFactory() .getDisplayFeatureProvider().createAdditionalPreference(context); } @@ -166,10 +175,12 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment mAdaptiveSleepPermissionController.updateVisibility(); mAdaptiveSleepCameraStatePreferenceController.updateVisibility(); mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); - mAdaptiveSleepController.updatePreference(); mContext.registerReceiver( mReceiver, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); - mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener); + if (!isCatalystEnabled()) { + mAdaptiveSleepController.updatePreference(); + mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener); + } mIsUserAuthenticated = false; FeatureFactory.getFeatureFactory().getDisplayFeatureProvider().updatePreference( mAdditionalTogglePreferenceController); @@ -179,13 +190,17 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment public void onStop() { super.onStop(); mContext.unregisterReceiver(mReceiver); - mPrivacyManager.removeSensorPrivacyListener(CAMERA, mPrivacyChangedListener); + if (!isCatalystEnabled()) { + mPrivacyManager.removeSensorPrivacyListener(CAMERA, mPrivacyChangedListener); + } } @Override public void updateCandidates() { final String defaultKey = getDefaultKey(); final PreferenceScreen screen = getPreferenceScreen(); + // Adaptive sleep preference is added to the screen when catalyst is enabled + Preference adaptiveSleepPreference = screen.findPreference(AdaptiveSleepPreference.KEY); screen.removeAll(); final List candidateList = getCandidates(); @@ -222,10 +237,16 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment FeatureFactory.getFeatureFactory().getDisplayFeatureProvider() .addToScreen(mAdditionalTogglePreferenceController, screen); - if (isScreenAttentionAvailable(getContext())) { + if (isAdaptiveSleepSupported(getContext())) { mAdaptiveSleepPermissionController.addToScreen(screen); mAdaptiveSleepCameraStatePreferenceController.addToScreen(screen); - mAdaptiveSleepController.addToScreen(screen); + if (adaptiveSleepPreference != null) { + // reset order for appending + adaptiveSleepPreference.setOrder(Preference.DEFAULT_ORDER); + screen.addPreference(adaptiveSleepPreference); + } else { + mAdaptiveSleepController.addToScreen(screen); + } mAdaptiveSleepBatterySaverPreferenceController.addToScreen(screen); screen.addPreference(mPrivacyPreference); } @@ -307,6 +328,11 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment return R.xml.screen_timeout_settings; } + @Override + public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) { + return ScreenTimeoutScreen.KEY; + } + @Override public int getHelpResource() { return R.string.help_url_adaptive_sleep; @@ -352,10 +378,6 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment } } - private static boolean isScreenAttentionAvailable(Context context) { - return AdaptiveSleepPreferenceController.isAdaptiveSleepSupported(context); - } - private static long getTimeoutFromKey(String key) { return Long.parseLong(key); } @@ -423,7 +445,7 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment new BaseSearchIndexProvider(R.xml.screen_timeout_settings) { public List getRawDataToIndex( Context context, boolean enabled) { - if (!isScreenAttentionAvailable(context)) { + if (!isAdaptiveSleepSupported(context)) { return null; } final Resources res = context.getResources(); diff --git a/src/com/android/settings/display/Utils.kt b/src/com/android/settings/display/Utils.kt new file mode 100644 index 00000000000..7ee63cd9511 --- /dev/null +++ b/src/com/android/settings/display/Utils.kt @@ -0,0 +1,35 @@ +/* + * 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.display + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.service.attention.AttentionService + +fun Context.isAdaptiveSleepSupported() = + resources.getBoolean(com.android.internal.R.bool.config_adaptive_sleep_available) && + isAttentionServiceAvailable() + +private fun Context.isAttentionServiceAvailable(): Boolean { + val packageManager = getPackageManager() + val packageName = packageManager.attentionServicePackageName + if (packageName.isNullOrEmpty()) return false + val intent = Intent(AttentionService.SERVICE_INTERFACE).setPackage(packageName) + val resolveInfo = packageManager.resolveService(intent, PackageManager.MATCH_SYSTEM_ONLY) + return resolveInfo != null && resolveInfo.serviceInfo != null +} diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java index d68a91afca8..121458c714a 100644 --- a/src/com/android/settings/widget/RadioButtonPickerFragment.java +++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java @@ -86,7 +86,13 @@ public abstract class RadioButtonPickerFragment extends SettingsPreferenceFragme @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - super.onCreatePreferences(savedInstanceState, rootKey); + if (isCatalystEnabled()) { + PreferenceScreen preferenceScreen = createPreferenceScreen(); + setPreferenceScreen(preferenceScreen); + updateActivityTitleWithScreenTitle(preferenceScreen); + } else { + super.onCreatePreferences(savedInstanceState, rootKey); + } try { // Check if the xml specifies if static preferences should go on the top or bottom final List metadata = PreferenceXmlParserUtils.extractMetadata(getContext(),