From ffe54b4fd6c33f2b80b3375c02a9db391b0da140 Mon Sep 17 00:00:00 2001 From: Dongzhuo Zhang Date: Thu, 14 Nov 2024 00:45:08 +0000 Subject: [PATCH 1/5] Add OnAccountsUpdateListener in ContactsStorageSettings to refresh the account when there's account change. Test: atest SettingsRoboTests:com.android.settings.applications.contacts.ContactsStorageSettingsTest Bug: 368641291 Flag: com.android.settings.flags.enable_contacts_default_account_in_settings Change-Id: Ic51ac292d4321ddeb16a4ea1ee44ba02dcc6e02b --- .../contacts/ContactsStorageSettings.java | 38 +++++-- .../contacts/ContactsStorageSettingsTest.java | 105 +++++++++++++++--- 2 files changed, 120 insertions(+), 23 deletions(-) diff --git a/src/com/android/settings/applications/contacts/ContactsStorageSettings.java b/src/com/android/settings/applications/contacts/ContactsStorageSettings.java index 5e48dd98ec6..4b70d465fb1 100644 --- a/src/com/android/settings/applications/contacts/ContactsStorageSettings.java +++ b/src/com/android/settings/applications/contacts/ContactsStorageSettings.java @@ -25,7 +25,6 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState; @@ -58,7 +57,8 @@ import java.util.Map; */ @SearchIndexable public class ContactsStorageSettings extends DashboardFragment - implements SelectorWithWidgetPreference.OnClickListener, OnPreferenceClickListener { + implements SelectorWithWidgetPreference.OnClickListener, OnPreferenceClickListener, + AuthenticatorHelper.OnAccountsUpdateListener { public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.contacts_storage_settings); private static final String TAG = "ContactsStorageSettings"; @@ -72,13 +72,15 @@ public class ContactsStorageSettings extends DashboardFragment public void onAttach(@NonNull Context context) { super.onAttach(context); mAuthenticatorHelper = new AuthenticatorHelper(context, - new UserHandle(UserHandle.myUserId()), null); - String[] accountTypes = getEligibleAccountTypes(); - for (String accountType : accountTypes) { - // Preload the drawable for the account type to avoid the latency when rendering the - // account preference. - mAuthenticatorHelper.preloadDrawableForType(context, accountType); - } + new UserHandle(UserHandle.myUserId()), this); + mAuthenticatorHelper.listenToAccountUpdates(); + preloadEligibleAccountIcon(); + } + + @Override + public void onDetach() { + super.onDetach(); + mAuthenticatorHelper.stopListeningToAccountUpdates(); } @UiThread @@ -126,6 +128,12 @@ public class ContactsStorageSettings extends DashboardFragment return false; } + @Override + public void onAccountsUpdate(UserHandle userHandle) { + preloadEligibleAccountIcon(); + refreshUI(); + } + @Override public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) { @@ -139,6 +147,7 @@ public class ContactsStorageSettings extends DashboardFragment // when creating eligible account preferences. mAccountMap.clear(); final PreferenceGroup preferenceGroup = findPreference(PREF_KEY_ACCOUNT_CATEGORY); + preferenceGroup.removeAll(); // If the default account is SIM, we should show in the page, otherwise don't show. SelectorWithWidgetPreference simAccountPreference = buildSimAccountPreference(); if (simAccountPreference != null) { @@ -152,12 +161,21 @@ public class ContactsStorageSettings extends DashboardFragment // If there's no eligible account types, the "Add Account" preference should // not be shown to the users. if (getEligibleAccountTypes().length > 0) { - getPreferenceScreen().addPreference(buildAddAccountPreference(accounts.isEmpty())); + preferenceGroup.addPreference(buildAddAccountPreference(accounts.isEmpty())); } setupDeviceOnlyPreference(); setDefaultAccountPreference(preferenceGroup); } + private void preloadEligibleAccountIcon() { + String[] accountTypes = getEligibleAccountTypes(); + for (String accountType : accountTypes) { + // Preload the drawable for the account type to avoid the latency when rendering the + // account preference. + mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType); + } + } + private void setupDeviceOnlyPreference() { SelectorWithWidgetPreference preference = findPreference(PREF_KEY_DEVICE_ONLY); if (preference != null) { diff --git a/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java b/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java index a2debd24ee2..735508ab9f8 100644 --- a/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java +++ b/tests/robotests/src/com/android/settings/applications/contacts/ContactsStorageSettingsTest.java @@ -38,10 +38,13 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.UserHandle; import android.provider.ContactsContract; import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState; import android.provider.SearchIndexableResource; +import android.text.TextUtils; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; @@ -51,7 +54,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.accounts.AddAccountSettings; -import com.android.settings.testutils.shadow.ShadowAuthenticationHelper; +import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.widget.SelectorWithWidgetPreference; import org.junit.Before; @@ -66,22 +69,24 @@ import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowAuthenticationHelper.class) +@Config(shadows = ContactsStorageSettingsTest.ShadowAuthenticatorHelper.class) public class ContactsStorageSettingsTest { private static final String PREF_KEY_DEVICE_ONLY = "device_only_account_preference"; private static final String PREF_KEY_ACCOUNT_CATEGORY = "account_category"; private static final String PREF_KEY_ADD_ACCOUNT = "add_account"; - private static final Account TEST_ACCOUNT1 = new Account("test@gmail.com", "type1"); + private static final Account TEST_ACCOUNT1 = new Account("test@gmail.com", "com.google"); - private static final Account TEST_ACCOUNT2 = new Account("test@samsung.com", "type2"); + private static final Account TEST_ACCOUNT2 = new Account("test@samsung.com", "com.samsung"); - private static final Account TEST_ACCOUNT3 = new Account("test@outlook.com", "type3"); + private static final Account TEST_ACCOUNT3 = new Account("test@outlook.com", "com.outlook"); private static final Account SIM_ACCOUNT = new Account("SIM", "SIM"); @@ -100,7 +105,8 @@ public class ContactsStorageSettingsTest { @Before public void setUp() throws Exception { - mContactsStorageSettings = spy(new TestContactsStorageSettings(mContext, mContentResolver)); + mContactsStorageSettings = spy( + new TestContactsStorageSettings(mContext, mContentResolver)); when(mContentResolver.acquireContentProviderClient( eq(ContactsContract.AUTHORITY_URI))).thenReturn(mContentProviderClient); mPreferenceManager = new PreferenceManager(mContext); @@ -116,6 +122,7 @@ public class ContactsStorageSettingsTest { when(mContactsStorageSettings.findPreference(eq(PREF_KEY_ACCOUNT_CATEGORY))).thenReturn( accountCategory); when(mContactsStorageSettings.getPreferenceScreen()).thenReturn(mScreen); + mContactsStorageSettings.setEligibleAccountTypes(new String[]{"com.google"}); mContactsStorageSettings.onAttach(mContext); } @@ -179,7 +186,6 @@ public class ContactsStorageSettingsTest { new ArrayList<>()); when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(), any())).thenReturn(eligibleAccountBundle); - mContactsStorageSettings.setEligibleAccountTypes(new String[]{"com.google"}); mContactsStorageSettings.refreshUI(); @@ -244,13 +250,13 @@ public class ContactsStorageSettingsTest { SelectorWithWidgetPreference account1Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT1.hashCode())); - assertThat(account1Preference.getTitle()).isEqualTo("Device and LABEL1"); + assertThat(account1Preference.getTitle()).isEqualTo("Device and Google"); assertThat(account1Preference.getSummary()).isEqualTo("test@gmail.com"); assertThat(account1Preference.getIcon()).isNotNull(); SelectorWithWidgetPreference account2Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT2.hashCode())); - assertThat(account2Preference.getTitle()).isEqualTo("Device and LABEL2"); + assertThat(account2Preference.getTitle()).isEqualTo("Device and Samsung"); assertThat(account2Preference.getSummary()).isEqualTo("test@samsung.com"); assertThat(account2Preference.getIcon()).isNotNull(); @@ -265,7 +271,7 @@ public class ContactsStorageSettingsTest { assertThat(setAccountBundle.getString(ContactsContract.Settings.ACCOUNT_NAME)).isEqualTo( "test@samsung.com"); assertThat(setAccountBundle.getString(ContactsContract.Settings.ACCOUNT_TYPE)).isEqualTo( - "type2"); + "com.samsung"); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).startActivity(intentCaptor.capture()); @@ -298,19 +304,19 @@ public class ContactsStorageSettingsTest { SelectorWithWidgetPreference account1Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT1.hashCode())); - assertThat(account1Preference.getTitle()).isEqualTo("Device and LABEL1"); + assertThat(account1Preference.getTitle()).isEqualTo("Device and Google"); assertThat(account1Preference.getSummary()).isEqualTo("test@gmail.com"); assertThat(account1Preference.getIcon()).isNotNull(); SelectorWithWidgetPreference account2Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT2.hashCode())); - assertThat(account2Preference.getTitle()).isEqualTo("Device and LABEL2"); + assertThat(account2Preference.getTitle()).isEqualTo("Device and Samsung"); assertThat(account2Preference.getSummary()).isEqualTo("test@samsung.com"); assertThat(account2Preference.getIcon()).isNotNull(); SelectorWithWidgetPreference account3Preference = accountCategory.findPreference( String.valueOf(TEST_ACCOUNT3.hashCode())); - assertThat(account3Preference.getTitle()).isEqualTo("Device and LABEL3"); + assertThat(account3Preference.getTitle()).isEqualTo("Device and Outlook"); assertThat(account3Preference.getSummary()).isEqualTo("test@outlook.com"); assertThat(account3Preference.getIcon()).isNotNull(); @@ -345,6 +351,40 @@ public class ContactsStorageSettingsTest { assertThat(simPreference.isChecked()).isTrue(); } + @Test + public void verifyAccountPreference_newAccountAdded_accountAddedToAccountPreference() + throws Exception { + Bundle currentDefaultAccount = new Bundle(); + currentDefaultAccount.putInt(KEY_DEFAULT_ACCOUNT_STATE, + DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD); + currentDefaultAccount.putString(ContactsContract.Settings.ACCOUNT_NAME, TEST_ACCOUNT1.name); + currentDefaultAccount.putString(ContactsContract.Settings.ACCOUNT_TYPE, TEST_ACCOUNT1.type); + when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(), + any())).thenReturn(currentDefaultAccount); + Bundle eligibleAccountBundle = new Bundle(); + ArrayList eligibleAccounts = new ArrayList<>( + List.of(TEST_ACCOUNT1, TEST_ACCOUNT2)); + eligibleAccountBundle.putParcelableArrayList(KEY_ELIGIBLE_DEFAULT_ACCOUNTS, + eligibleAccounts); + when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(), + any())).thenReturn(eligibleAccountBundle); + + mContactsStorageSettings.onAccountsUpdate(null); + + // onAccountsUpdate should refresh the icon and layouts. + SelectorWithWidgetPreference account1Preference = accountCategory.findPreference( + String.valueOf(TEST_ACCOUNT1.hashCode())); + assertThat(account1Preference.getTitle()).isEqualTo("Device and Google"); + assertThat(account1Preference.getSummary()).isEqualTo("test@gmail.com"); + assertThat(account1Preference.getIcon()).isNotNull(); + + SelectorWithWidgetPreference account2Preference = accountCategory.findPreference( + String.valueOf(TEST_ACCOUNT2.hashCode())); + assertThat(account2Preference.getTitle()).isEqualTo("Device and Samsung"); + assertThat(account2Preference.getSummary()).isEqualTo("test@samsung.com"); + assertThat(account2Preference.getIcon()).isNotNull(); + } + @Test public void searchIndexProvider_shouldIndexResource() { final List indexRes = @@ -388,4 +428,43 @@ public class ContactsStorageSettingsTest { mEligibleAccountTypes = eligibleAccountTypes; } } + + @Implements(AuthenticatorHelper.class) + public static class ShadowAuthenticatorHelper { + + boolean preloadDrawableForType = false; + + @Implementation + public void listenToAccountUpdates() { + } + + @Implementation + public void onAccountsUpdated(Account[] accounts) { + + } + @Implementation + public void preloadDrawableForType(final Context context, final String accountType) { + preloadDrawableForType = true; + } + + @Implementation + protected Drawable getDrawableForType(Context context, final String accountType) { + if (preloadDrawableForType) { + return context.getPackageManager().getDefaultActivityIcon(); + } + return null; + } + + @Implementation + protected CharSequence getLabelForType(Context context, final String accountType) { + if (TextUtils.equals(accountType, "com.google")) { + return "Google"; + } else if (TextUtils.equals(accountType, "com.samsung")) { + return "Samsung"; + } else if (TextUtils.equals(accountType, "com.outlook")) { + return "Outlook"; + } + return null; + } + } } From 8d3be114188ae7d84c05297e9914982f1680aadb Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Fri, 15 Nov 2024 16:06:27 +0800 Subject: [PATCH 2/5] Refactor duplicate isAdaptiveSleepSupported Bug: 368359967 Flag: EXEMPT refactor Test: atest Change-Id: I41d465f67fb50e83d9bd23af54597afb6bd467cf --- .../AdaptiveSleepPreferenceController.java | 24 +------------ .../display/ScreenTimeoutSettings.java | 10 +++--- src/com/android/settings/display/Utils.kt | 35 +++++++++++++++++++ 3 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 src/com/android/settings/display/Utils.kt diff --git a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java index 725b956a60b..4057f660899 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; @@ -144,25 +141,6 @@ 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( diff --git a/src/com/android/settings/display/ScreenTimeoutSettings.java b/src/com/android/settings/display/ScreenTimeoutSettings.java index 2d229a3cfe7..c6d4c067541 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; @@ -222,7 +224,7 @@ 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); @@ -352,10 +354,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 +421,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 +} From e6ef4c24430c84299363d4467410a69790f5fc76 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Fri, 15 Nov 2024 14:08:09 +0800 Subject: [PATCH 3/5] [Catalyst] Add initial ScreenTimeoutScreen Bug: 368359967 Flag: com.android.settings.flags.catalyst_screen_timeout Test: atest Change-Id: I13d23e4e5164c3dd4a4a01b00b854642f748b020 --- .../settings/display/ScreenTimeoutScreen.kt | 47 +++++++++++++++++++ .../display/ScreenTimeoutSettings.java | 6 +++ 2 files changed, 53 insertions(+) create mode 100644 src/com/android/settings/display/ScreenTimeoutScreen.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..6595a4e43d3 --- /dev/null +++ b/src/com/android/settings/display/ScreenTimeoutScreen.kt @@ -0,0 +1,47 @@ +/* + * 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.flags.Flags +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) {} + + 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 c6d4c067541..7800651fe8f 100644 --- a/src/com/android/settings/display/ScreenTimeoutSettings.java +++ b/src/com/android/settings/display/ScreenTimeoutSettings.java @@ -36,6 +36,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceScreen; @@ -309,6 +310,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; From 3a20528ae430b2b721c72d21e68754857597e215 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sat, 16 Nov 2024 03:22:58 +0800 Subject: [PATCH 4/5] [Catalyst] Support RadioButtonPickerFragment Bug: 368359967 Flag: com.android.settings.flags.catalyst_screen_timeout Test: manual Change-Id: Ibd3a1c52b152054bf01e37848785c5bda588df5f --- .../settings/SettingsPreferenceFragment.java | 21 +++++++++++++++++++ .../core/InstrumentedPreferenceFragment.java | 2 +- .../settings/dashboard/DashboardFragment.java | 20 +----------------- .../widget/RadioButtonPickerFragment.java | 8 ++++++- 4 files changed, 30 insertions(+), 21 deletions(-) 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/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(), From dec8370d258646495165f903fa3fda217f7b213f Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Sat, 16 Nov 2024 03:36:13 +0800 Subject: [PATCH 5/5] [Catalyst] Migrate "Screen attention" Bug: 368359967 Flag: com.android.settings.flags.catalyst_screen_timeout Test: devtool Change-Id: I3c990e8cb6d1414395408b884616574cd6130f81 --- .../display/AdaptiveSleepPreference.kt | 137 ++++++++++++++++++ .../AdaptiveSleepPreferenceController.java | 4 +- .../settings/display/ScreenTimeoutScreen.kt | 9 +- .../display/ScreenTimeoutSettings.java | 34 ++++- 4 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 src/com/android/settings/display/AdaptiveSleepPreference.kt 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 4057f660899..82a8709df5e 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java +++ b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java @@ -42,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; @@ -147,3 +148,4 @@ public class AdaptiveSleepPreferenceController { 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 index 6595a4e43d3..9dcd1021621 100644 --- a/src/com/android/settings/display/ScreenTimeoutScreen.kt +++ b/src/com/android/settings/display/ScreenTimeoutScreen.kt @@ -18,7 +18,10 @@ 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 @@ -39,7 +42,11 @@ class ScreenTimeoutScreen : PreferenceScreenCreator { override fun hasCompleteHierarchy() = false - override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} + 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 7800651fe8f..d4ca48ebfb7 100644 --- a/src/com/android/settings/display/ScreenTimeoutSettings.java +++ b/src/com/android/settings/display/ScreenTimeoutSettings.java @@ -38,6 +38,7 @@ 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; @@ -83,7 +84,9 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment @Override public void onReceive(Context context, Intent intent) { mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); - mAdaptiveSleepController.updatePreference(); + if (!isCatalystEnabled()) { + mAdaptiveSleepController.updatePreference(); + } } }; @@ -126,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 = @@ -139,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); } @@ -169,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); @@ -182,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(); @@ -228,7 +240,13 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment 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); }