From dc0c58b427ab0f1e7a2dd006bdd3f0c34e56545a Mon Sep 17 00:00:00 2001 From: Tetiana Meronyk Date: Wed, 1 May 2024 17:21:14 +0000 Subject: [PATCH 01/19] Make Multiuser toggle disabled for all non-main users Only let main user (Owner) change state of the toggle Bug: 336764498 Test: atest MultiUserSwitchBarControllerTest Change-Id: Ib694d1cb4685764c64633efc090765b470a0a015 --- .../users/MultiUserSwitchBarController.java | 3 +-- .../settings/users/UserCapabilities.java | 2 ++ .../android/settings/users/UserSettings.java | 2 +- .../MultiUserSwitchBarControllerTest.java | 23 +++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/users/MultiUserSwitchBarController.java b/src/com/android/settings/users/MultiUserSwitchBarController.java index 33651c31fc4..f57b7959c37 100644 --- a/src/com/android/settings/users/MultiUserSwitchBarController.java +++ b/src/com/android/settings/users/MultiUserSwitchBarController.java @@ -63,8 +63,7 @@ public class MultiUserSwitchBarController implements SwitchWidgetController.OnSw .checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_ADD_USER, UserHandle.myUserId())); } else { - mSwitchBar.setEnabled(!mUserCapabilities.mDisallowSwitchUser - && !mUserCapabilities.mIsGuest && mUserCapabilities.isAdmin()); + mSwitchBar.setEnabled(mUserCapabilities.mIsMain); } mSwitchBar.setListener(this); } diff --git a/src/com/android/settings/users/UserCapabilities.java b/src/com/android/settings/users/UserCapabilities.java index cf0e10b127d..590cb0cf11a 100644 --- a/src/com/android/settings/users/UserCapabilities.java +++ b/src/com/android/settings/users/UserCapabilities.java @@ -32,6 +32,7 @@ public class UserCapabilities { boolean mEnabled = true; boolean mCanAddUser = true; boolean mCanAddRestrictedProfile; + boolean mIsMain; boolean mIsAdmin; boolean mIsGuest; boolean mIsEphemeral; @@ -57,6 +58,7 @@ public class UserCapabilities { final UserInfo myUserInfo = userManager.getUserInfo(UserHandle.myUserId()); caps.mIsGuest = myUserInfo.isGuest(); caps.mIsAdmin = myUserInfo.isAdmin(); + caps.mIsMain = myUserInfo.isMain(); caps.mIsEphemeral = myUserInfo.isEphemeral(); DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 083608dde3e..bf21c9b913a 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -287,7 +287,7 @@ public class UserSettings extends SettingsPreferenceFragment final SettingsActivity activity = (SettingsActivity) getActivity(); final SettingsMainSwitchBar switchBar = activity.getSwitchBar(); switchBar.setTitle(getContext().getString(R.string.multiple_users_main_switch_title)); - if (isCurrentUserAdmin()) { + if (!mUserCaps.mIsGuest) { switchBar.show(); } else { switchBar.hide(); diff --git a/tests/robotests/src/com/android/settings/users/MultiUserSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/users/MultiUserSwitchBarControllerTest.java index 1cfb3f614b9..bfab257e44a 100644 --- a/tests/robotests/src/com/android/settings/users/MultiUserSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/users/MultiUserSwitchBarControllerTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.Context; +import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; @@ -79,4 +80,26 @@ public class MultiUserSwitchBarControllerTest { verify(mSwitchWidgetController, never()).setDisabledByAdmin(any()); } + + @Test + public void onStart_userIsNotMain_shouldNotBeEnabled() { + mUserManager.setUserRestriction(UserHandle.of(UserHandle.myUserId()), + UserManager.DISALLOW_USER_SWITCH, false); + mUserManager.addUser(10, "Test", UserInfo.FLAG_ADMIN); + mUserManager.switchUser(10); + new MultiUserSwitchBarController(mContext, mSwitchWidgetController, null); + + verify(mSwitchWidgetController, never()).setDisabledByAdmin(any()); + verify(mSwitchWidgetController).setEnabled(false); + } + + @Test + public void onStart_userIsMain_shouldBeEnabled() { + mUserManager.setUserRestriction(UserHandle.of(UserHandle.myUserId()), + UserManager.DISALLOW_USER_SWITCH, false); + new MultiUserSwitchBarController(mContext, mSwitchWidgetController, null); + + verify(mSwitchWidgetController, never()).setDisabledByAdmin(any()); + verify(mSwitchWidgetController).setEnabled(true); + } } From e9322f84fc2d305642178ab1a9f7990b75b11a46 Mon Sep 17 00:00:00 2001 From: Vlad Popa Date: Fri, 10 May 2024 15:36:26 -0700 Subject: [PATCH 02/19] Improve the BT settings audio device type strings Flag: EXEMPT string xml only update Test: manual Bug: 337044231 Change-Id: I717fda31ca937a9f09d548e037ca7497a7d2a829 --- res/values/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index a4d830565ed..12f58b18e95 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -12886,17 +12886,17 @@ Give your watch the same app permissions that you’ve allowed on this phone - Audio Device Type - - Unknown + Audio device type + + Not set Speaker Headphones - - Hearing Aid + + Hearing device - Car Kit + Car Other From 28da3e3b34367f8723adfbdad393bff0ded5f29e Mon Sep 17 00:00:00 2001 From: David Liu Date: Wed, 8 May 2024 23:45:27 +0000 Subject: [PATCH 03/19] Set noparent to avoid it recursively looks up. Android biometric team should approve all change in this subdirectories. Bug: 339519091 (cherry picked from https://android-review.googlesource.com/q/commit:836e14e665e5f0731a0a2925c2b0cf5684841240) Merged-In: Ib5bdc23660cc18af0f57ffdec1dbf582db734a43 Change-Id: Ib5bdc23660cc18af0f57ffdec1dbf582db734a43 --- src/com/android/settings/biometrics/OWNERS | 3 +++ src/com/android/settings/biometrics2/OWNERS | 3 +++ src/com/android/settings/password/OWNERS | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/com/android/settings/biometrics/OWNERS b/src/com/android/settings/biometrics/OWNERS index cb0d0341828..f804b0f545c 100644 --- a/src/com/android/settings/biometrics/OWNERS +++ b/src/com/android/settings/biometrics/OWNERS @@ -1,3 +1,6 @@ +# The Android Biometric team should approve all changes to biometrics subdirectories. +set noparent + graciecheng@google.com ilyamaty@google.com jaggies@google.com diff --git a/src/com/android/settings/biometrics2/OWNERS b/src/com/android/settings/biometrics2/OWNERS index a257ed861ad..ac806eb8d48 100644 --- a/src/com/android/settings/biometrics2/OWNERS +++ b/src/com/android/settings/biometrics2/OWNERS @@ -1 +1,4 @@ +# The Android Biometric team should approve all changes to biometrics2 subdirectories. +set noparent + include /src/com/android/settings/biometrics/OWNERS diff --git a/src/com/android/settings/password/OWNERS b/src/com/android/settings/password/OWNERS index aa03c591d0a..9cfaf7a01b2 100644 --- a/src/com/android/settings/password/OWNERS +++ b/src/com/android/settings/password/OWNERS @@ -1,3 +1,6 @@ +# The Android Biometric team should approve all changes to password subdirectories. +set noparent + # Default reviewers for this and subdirectories. curtislb@google.com graciecheng@google.com From 29f9abcd8bf5bf54dbf37502670156e074ad5f47 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 30 Apr 2024 19:50:29 +0000 Subject: [PATCH 04/19] Add Device Diagnostics to Settings. The new option will only appear if (1) the flag is enabled and (2) DeviceDiagnostics.apk is landed on the device (which it is by default on AOSP). Bug: 309886423 Test: Build AOSP Pixel and launch Settings Change-Id: If01d231664a301ff289d4da61445bf65a7506fdb --- ...gs_device_diagnostics_declarations.aconfig | 9 +++ res/drawable/ic_device_diagnostics.xml | 10 +++ res/values/config.xml | 3 + res/values/strings.xml | 5 ++ res/xml/system_dashboard_fragment.xml | 7 ++ .../DeviceDiagnosticsPreferenceController.kt | 65 +++++++++++++++++++ 6 files changed, 99 insertions(+) create mode 100644 aconfig/settings_device_diagnostics_declarations.aconfig create mode 100644 res/drawable/ic_device_diagnostics.xml create mode 100644 src/com/android/settings/system/DeviceDiagnosticsPreferenceController.kt diff --git a/aconfig/settings_device_diagnostics_declarations.aconfig b/aconfig/settings_device_diagnostics_declarations.aconfig new file mode 100644 index 00000000000..90a12dbb9ab --- /dev/null +++ b/aconfig/settings_device_diagnostics_declarations.aconfig @@ -0,0 +1,9 @@ +package: "com.android.settings.flags" +container: "system_ext" + +flag { + name: "enable_device_diagnostics_in_settings" + namespace: "phoenix" + description: "Enable the Device Diagnostics app in Settings" + bug: "309886423" +} diff --git a/res/drawable/ic_device_diagnostics.xml b/res/drawable/ic_device_diagnostics.xml new file mode 100644 index 00000000000..26953a70314 --- /dev/null +++ b/res/drawable/ic_device_diagnostics.xml @@ -0,0 +1,10 @@ + + + diff --git a/res/values/config.xml b/res/values/config.xml index 4d3a23348e4..4b638b25552 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -836,4 +836,7 @@ + + + com.android.devicediagnostics diff --git a/res/values/strings.xml b/res/values/strings.xml index 0fee1874cdb..3545fe6d4d7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13258,4 +13258,9 @@ Sync across devices + + + + + Device diagnostics diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml index 628eab94e4c..6225f4f8382 100644 --- a/res/xml/system_dashboard_fragment.xml +++ b/res/xml/system_dashboard_fragment.xml @@ -97,6 +97,13 @@ android:order="-40" settings:controller="com.android.settings.system.DeveloperOptionsController"/> + + Date: Mon, 13 May 2024 23:45:54 -0700 Subject: [PATCH 05/19] Fix the issue the if primary provider is disabled by work admin, it should be removed from the settings' storage. Test: locally built and tested Bug: 337040395 Change-Id: Ic572cd87f571d93ee0e7fa29157172444c7c36ce --- .../DefaultCombinedPreferenceController.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java index fccd969bece..57cd1d2d8da 100644 --- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java +++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java @@ -20,16 +20,20 @@ import android.content.Context; import android.content.Intent; import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; +import android.credentials.SetEnabledProvidersException; import android.graphics.drawable.Drawable; +import android.os.OutcomeReceiver; import android.os.UserHandle; import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; import android.text.TextUtils; +import android.util.Log; import android.view.autofill.AutofillManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.preference.Preference; import com.android.internal.annotations.VisibleForTesting; @@ -41,6 +45,7 @@ import com.android.settingslib.widget.TwoTargetPreference; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController { @@ -49,10 +54,12 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon private final AutofillManager mAutofillManager; private final CredentialManager mCredentialManager; + private final Executor mExecutor; public DefaultCombinedPreferenceController(Context context) { super(context); + mExecutor = ContextCompat.getMainExecutor(context); mAutofillManager = mContext.getSystemService(AutofillManager.class); if (CredentialManager.isServiceEnabled(context)) { @@ -158,6 +165,9 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon // Apply device admin restrictions to top provider. if (topProvider != null && topProvider.getDeviceAdminRestrictions(mContext, userId) != null) { + // This case means, the provider is blocked by device admin, but settings' storage has + // not be cleared correctly. So clean the storage here. + removePrimaryProvider(); return null; } @@ -209,4 +219,28 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon mContext.createContextAsUser(UserHandle.of(getUser()), /* flags= */ 0); return new Intent(context, CredentialsPickerActivity.class); } + + private void removePrimaryProvider() { + // Commit using the CredMan API. + if (mCredentialManager == null) { + return; + } + + mCredentialManager.setEnabledProviders( + List.of(), // empty primary provider. + List.of(), // empty enabled providers. + getUser(), + mExecutor, + new OutcomeReceiver() { + @Override + public void onResult(Void result) { + Log.i(TAG, "setEnabledProviders success"); + } + + @Override + public void onError(SetEnabledProvidersException e) { + Log.e(TAG, "setEnabledProviders error: " + e.toString()); + } + }); + } } From ef8194e7d9e189ff91842348392397c67be8580b Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Tue, 14 May 2024 11:00:02 +0000 Subject: [PATCH 06/19] Change default lock to every time device locks Fix: 339826512 Test: manual Test: atest PrivateSpaceMaintainerTest Test: atest AutoLockPreferenceControllerTest Change-Id: I9f5f028666ac409ab6c732a440b2202f31839c9f --- .../android/settings/privatespace/PrivateSpaceMaintainer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java index 38f724a4d7b..42e854426f4 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -19,7 +19,7 @@ package com.android.settings.privatespace; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT; import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK; -import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART; +import static android.provider.Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK; import static android.provider.Settings.Secure.SKIP_FIRST_USE_HINTS; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -69,7 +69,7 @@ public class PrivateSpaceMaintainer { /** Default value for private space auto lock settings. */ @Settings.Secure.PrivateSpaceAutoLockOption public static final int PRIVATE_SPACE_AUTO_LOCK_DEFAULT_VAL = - PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART; + PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK; /** Default value for the hide private space sensitive notifications on lockscreen. */ public static final int HIDE_PRIVATE_SPACE_SENSITIVE_NOTIFICATIONS_DISABLED_VAL = 0; From 6f8cc6f0b6bf9d7469f44dfc6f08eddbde16b0b2 Mon Sep 17 00:00:00 2001 From: songferngwang Date: Tue, 14 May 2024 10:45:10 +0000 Subject: [PATCH 07/19] Fix search indexed the item from the invisible sim When search start to index and run to MobileNetworkSettings.java, the TelephonyBasePreferenceController will find which subId can be the "AVAILABLE" from the active subscriptionInfo list. This active subscriptionInfo list does not be filtered, so the search uses the invisibile sim's item for user. Bug: 335509130 Test: atest MobileNetworkUtilsTest (pass) Change-Id: I2840e7de344347643197592e125f5524d27a068e --- .../network/telephony/MobileNetworkUtils.java | 7 ++--- .../telephony/MobileNetworkUtilsTest.java | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java index 603d915e98e..db95d701ce6 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java +++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java @@ -708,12 +708,13 @@ public class MobileNetworkUtils { return tm.getNetworkOperatorName(); } - private static int[] getActiveSubscriptionIdList(Context context) { + @VisibleForTesting + static int[] getActiveSubscriptionIdList(Context context) { final SubscriptionManager subscriptionManager = context.getSystemService( SubscriptionManager.class).createForAllUserProfiles(); final List subInfoList = - subscriptionManager.getActiveSubscriptionInfoList(); - if (subInfoList == null) { + SubscriptionUtil.getActiveSubscriptions(subscriptionManager); + if (subInfoList == null || subInfoList.isEmpty()) { return new int[0]; } int[] activeSubIds = new int[subInfoList.size()]; diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java index 947ba75db66..570a3204b80 100644 --- a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java @@ -239,6 +239,33 @@ public class MobileNetworkUtilsTest { .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } + @Test + public void getActiveSubscriptionIdList_nonActive_returnEmptyArray() { + int[] expectedList = new int[0]; + when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(new ArrayList<>()); + + assertThat(MobileNetworkUtils.getActiveSubscriptionIdList(mContext)) + .isEqualTo(expectedList); + } + + @Test + public void getActiveSubscriptionIdList_normalCaseTwoActiveSims_returnValidSubId() { + int[] expectedList = {SUB_ID_1, SUB_ID_2}; + + assertThat(MobileNetworkUtils.getActiveSubscriptionIdList(mContext)) + .isEqualTo(expectedList); + } + + @Test + public void getActiveSubscriptionIdList_TwoActiveSimsAndOneIsNtn_returnOneSubId() { + int[] expectedList = {SUB_ID_2}; + when(mSubscriptionInfo1.isEmbedded()).thenReturn(true); + when(mSubscriptionInfo1.isOnlyNonTerrestrialNetwork()).thenReturn(true); + + assertThat(MobileNetworkUtils.getActiveSubscriptionIdList(mContext)) + .isEqualTo(expectedList); + } + @Test public void shouldDisplayNetworkSelectOptions_HideCarrierNetwork_returnFalse() { mCarrierConfig.putBoolean(CarrierConfigManager.KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, From 6de4093876dd102803ac029766869592cf92489b Mon Sep 17 00:00:00 2001 From: Pawan Wagh Date: Tue, 14 May 2024 19:06:54 +0000 Subject: [PATCH 08/19] Turn off voice access in 16KB mode VoiceAccess doesn't support the 16KB mode yet. Skipping voice accesss service when in page-agnostic mode. Test: m Settings && adb install -r $ANDROID_PRODUCT_OUT/system_ext/priv-app/Settings/Settings.apk Bug: 335443194 Bug: 340231742 Change-Id: If4deae48aaa221c843af5eb65208659ad38a08b2 --- .../settings/accessibility/AccessibilitySettings.java | 8 ++++++++ .../accessibility/RestrictedPreferenceHelper.java | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 35fe6e4e600..2295dee8272 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -44,6 +44,7 @@ import com.android.internal.content.PackageMonitor; import com.android.settings.R; import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.development.Enable16kUtils; import com.android.settings.inputmethod.PhysicalKeyboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; @@ -98,6 +99,8 @@ public class AccessibilitySettings extends DashboardFragment implements static final String EXTRA_TIME_FOR_LOGGING = "start_time_to_log_a11y_tool"; static final String EXTRA_METRICS_CATEGORY = "metrics_category"; + public static final String VOICE_ACCESS_SERVICE = "android.apps.accessibility.voiceaccess"; + // Timeout before we update the services if packages are added/removed // since the AccessibilityManagerService has to do that processing first // to generate the AccessibilityServiceInfo we need for proper @@ -488,6 +491,11 @@ public class AccessibilitySettings extends DashboardFragment implements String[] services = getResources().getStringArray(key); PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey); for (int i = 0; i < services.length; i++) { + // TODO(b/335443194) Voice access is not available in 16kB mode. + if (services[i].contains(VOICE_ACCESS_SERVICE) + && Enable16kUtils.isPageAgnosticModeOn(getContext())) { + continue; + } ComponentName component = ComponentName.unflattenFromString(services[i]); mPreBundledServiceComponentToCategoryMap.put(component, category); } diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java index c806c0bc5a8..7455eea51ce 100644 --- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java +++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java @@ -16,6 +16,7 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.AccessibilitySettings.VOICE_ACCESS_SERVICE; import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; import android.accessibilityservice.AccessibilityServiceInfo; @@ -37,6 +38,7 @@ import androidx.core.content.ContextCompat; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.development.Enable16kUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -89,6 +91,11 @@ public class RestrictedPreferenceHelper { final AccessibilityServiceInfo info = installedServices.get(i); final ResolveInfo resolveInfo = info.getResolveInfo(); final String packageName = resolveInfo.serviceInfo.packageName; + // TODO(b/335443194) Voice access is not available in 16kB mode. + if (packageName.contains(VOICE_ACCESS_SERVICE) + && Enable16kUtils.isPageAgnosticModeOn(mContext)) { + continue; + } final ComponentName componentName = new ComponentName(packageName, resolveInfo.serviceInfo.name); From 042c8c73281ac4a4a3f85b8f370fc2c4ec5d9991 Mon Sep 17 00:00:00 2001 From: Roy Chou Date: Thu, 18 Apr 2024 16:34:26 +0000 Subject: [PATCH 09/19] feat(brightness suw): add brightness preferences in suw Add brightness level and auto brightness preference in accessibility_settings_for_setup_wizard.xml. Since the flagging has not be supported yet for non-Manifest resources, for now we add the preferences by default, and update the preferece availablity status in each preference controllers based on the flag. Bug: 311093618 Flag: ACONFIG com.android.settings.accessibility.add_brightness_settings_in_suw DEVELOPMENT Test: manually atest AccessibilitySettingsForSetupWizardTest atest AutoBrightnessPreferenceFragmentForSetupWizardTest atest AutoBrightnessPreferenceControllerTest atest BrightnessLevelPreferenceControllerTest Change-Id: I350d99138bdd14bf28828a39e42f707b5b1066c1 --- .../accessibility/accessibility_flags.aconfig | 7 ++ color-check-baseline.xml | 44 +++++++++ res/drawable/ic_brightness_auto.xml | 25 +++++ res/drawable/ic_brightness_medium.xml | 25 +++++ res/drawable/ic_suw_brightness_auto.xml | 26 +++++ res/drawable/ic_suw_brightness_level.xml | 26 +++++ res/values/strings.xml | 4 + ...ccessibility_settings_for_setup_wizard.xml | 16 ++++ res/xml/display_settings.xml | 4 +- .../AccessibilitySettingsForSetupWizard.java | 20 ++++ ...tnessPreferenceFragmentForSetupWizard.java | 81 ++++++++++++++++ .../core/gateway/SettingsGateway.java | 2 + .../AutoBrightnessPreferenceController.java | 19 +++- .../BrightnessLevelPreferenceController.java | 33 ++++--- ...cessibilitySettingsForSetupWizardTest.java | 23 ++++- ...sPreferenceFragmentForSetupWizardTest.java | 95 +++++++++++++++++++ ...utoBrightnessPreferenceControllerTest.java | 32 ++++++- ...ightnessLevelPreferenceControllerTest.java | 30 +++++- .../settings/testutils/BatteryTestUtils.java | 2 + 19 files changed, 490 insertions(+), 24 deletions(-) create mode 100644 res/drawable/ic_brightness_auto.xml create mode 100644 res/drawable/ic_brightness_medium.xml create mode 100644 res/drawable/ic_suw_brightness_auto.xml create mode 100644 res/drawable/ic_suw_brightness_level.xml create mode 100644 src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizard.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizardTest.java diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index c9e1ece1b3a..2c92547474d 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -3,6 +3,13 @@ container: "system_ext" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. +flag { + name: "add_brightness_settings_in_suw" + namespace: "accessibility" + description: "Whether to add brightness preference in SUW Vision Settings" + bug: "332974327" +} + flag { name: "edit_shortcuts_in_full_screen" namespace: "accessibility" diff --git a/color-check-baseline.xml b/color-check-baseline.xml index ecc795ee4c6..bd90b981ff5 100644 --- a/color-check-baseline.xml +++ b/color-check-baseline.xml @@ -1,6 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/res/drawable/ic_brightness_medium.xml b/res/drawable/ic_brightness_medium.xml new file mode 100644 index 00000000000..3e778857127 --- /dev/null +++ b/res/drawable/ic_brightness_medium.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/ic_suw_brightness_auto.xml b/res/drawable/ic_suw_brightness_auto.xml new file mode 100644 index 00000000000..a4221c540d1 --- /dev/null +++ b/res/drawable/ic_suw_brightness_auto.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_suw_brightness_level.xml b/res/drawable/ic_suw_brightness_level.xml new file mode 100644 index 00000000000..57bd1883849 --- /dev/null +++ b/res/drawable/ic_suw_brightness_level.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index e14a1073451..382f9eb032c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2730,6 +2730,10 @@ Adaptive + + brightness + + auto_brightness_entry Brightness level diff --git a/res/xml/accessibility_settings_for_setup_wizard.xml b/res/xml/accessibility_settings_for_setup_wizard.xml index 0926d2d3008..a69dc26ce3f 100644 --- a/res/xml/accessibility_settings_for_setup_wizard.xml +++ b/res/xml/accessibility_settings_for_setup_wizard.xml @@ -29,6 +29,22 @@ settings:keywords="text_reading_options" settings:controller="com.android.settings.accessibility.TextReadingFragmentForSuwController"/> + + + + createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + BrightnessLevelPreferenceController brightnessLevelPreferenceController = + new BrightnessLevelPreferenceController(context, getSettingsLifecycle()); + brightnessLevelPreferenceController.setInSetupWizard(true); + controllers.add(brightnessLevelPreferenceController); + String autoBrightnessKey = context.getString(R.string.preference_key_auto_brightness); + AutoBrightnessPreferenceController autoBrightnessPreferenceController = + new AutoBrightnessPreferenceController(context, autoBrightnessKey); + autoBrightnessPreferenceController.setInSetupWizard(true); + controllers.add(autoBrightnessPreferenceController); + return controllers; + } + /** * Returns accessibility service info by given package name and service name. * diff --git a/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizard.java new file mode 100644 index 00000000000..ad1ae96b03b --- /dev/null +++ b/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizard.java @@ -0,0 +1,81 @@ +/* + * 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.accessibility; + +import static android.app.Activity.RESULT_CANCELED; + +import android.app.settings.SettingsEnums; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.R; +import com.android.settings.display.AutoBrightnessSettings; +import com.android.settingslib.Utils; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupdesign.GlifPreferenceLayout; + +/** + * Fragment for adaptive brightness settings in the SetupWizard. + */ +public class AutoBrightnessPreferenceFragmentForSetupWizard extends AutoBrightnessSettings { + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (view instanceof GlifPreferenceLayout) { + final GlifPreferenceLayout layout = (GlifPreferenceLayout) view; + final String title = getContext().getString( + R.string.auto_brightness_title); + final Drawable icon = getContext().getDrawable(R.drawable.ic_accessibility_visibility); + icon.setTintList(Utils.getColorAttr(getContext(), android.R.attr.colorPrimary)); + AccessibilitySetupWizardUtils.updateGlifPreferenceLayout(getContext(), layout, title, + /* description= */ null, icon); + + final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); + AccessibilitySetupWizardUtils.setPrimaryButton(getContext(), mixin, R.string.done, + () -> { + setResult(RESULT_CANCELED); + finish(); + }); + } + } + + @NonNull + @Override + public RecyclerView onCreateRecyclerView(@NonNull LayoutInflater inflater, + @NonNull ViewGroup parent, @Nullable Bundle savedInstanceState) { + if (parent instanceof GlifPreferenceLayout) { + final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent; + return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); + } + return super.onCreateRecyclerView(inflater, parent, savedInstanceState); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS; + } +} diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index bee1da7af80..9176b81dd9a 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -28,6 +28,7 @@ import com.android.settings.accessibility.AccessibilityDetailsSettingsFragment; import com.android.settings.accessibility.AccessibilityHearingAidsFragment; import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.accessibility.AccessibilitySettingsForSetupWizard; +import com.android.settings.accessibility.AutoBrightnessPreferenceFragmentForSetupWizard; import com.android.settings.accessibility.CaptioningPropertiesFragment; import com.android.settings.accessibility.ColorAndMotionFragment; import com.android.settings.accessibility.ColorContrastFragment; @@ -258,6 +259,7 @@ public class SettingsGateway { EditShortcutsPreferenceFragment.class.getName(), TextReadingPreferenceFragment.class.getName(), TextReadingPreferenceFragmentForSetupWizard.class.getName(), + AutoBrightnessPreferenceFragmentForSetupWizard.class.getName(), CaptioningPropertiesFragment.class.getName(), ToggleDaltonizerPreferenceFragment.class.getName(), ToggleColorInversionPreferenceFragment.class.getName(), diff --git a/src/com/android/settings/display/AutoBrightnessPreferenceController.java b/src/com/android/settings/display/AutoBrightnessPreferenceController.java index 3f9be2fd6cf..3014f62b0a6 100644 --- a/src/com/android/settings/display/AutoBrightnessPreferenceController.java +++ b/src/com/android/settings/display/AutoBrightnessPreferenceController.java @@ -25,6 +25,7 @@ import android.provider.Settings; import androidx.preference.Preference; import com.android.settings.R; +import com.android.settings.accessibility.Flags; import com.android.settings.core.TogglePreferenceController; import com.android.settingslib.PrimarySwitchPreference; @@ -33,10 +34,16 @@ public class AutoBrightnessPreferenceController extends TogglePreferenceControll private final String SYSTEM_KEY = SCREEN_BRIGHTNESS_MODE; private final int DEFAULT_VALUE = SCREEN_BRIGHTNESS_MODE_MANUAL; + private boolean mInSetupWizard; + public AutoBrightnessPreferenceController(Context context, String key) { super(context, key); } + public void setInSetupWizard(boolean inSetupWizard) { + mInSetupWizard = inSetupWizard; + } + @Override public boolean isChecked() { return Settings.System.getInt(mContext.getContentResolver(), @@ -53,10 +60,14 @@ public class AutoBrightnessPreferenceController extends TogglePreferenceControll @Override @AvailabilityStatus public int getAvailabilityStatus() { - return mContext.getResources().getBoolean( - com.android.internal.R.bool.config_automatic_brightness_available) - ? AVAILABLE_UNSEARCHABLE - : UNSUPPORTED_ON_DEVICE; + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available)) { + return UNSUPPORTED_ON_DEVICE; + } + if (mInSetupWizard && !Flags.addBrightnessSettingsInSuw()) { + return CONDITIONALLY_UNAVAILABLE; + } + return AVAILABLE_UNSEARCHABLE; } @Override diff --git a/src/com/android/settings/display/BrightnessLevelPreferenceController.java b/src/com/android/settings/display/BrightnessLevelPreferenceController.java index 49b8da3d2e3..a32c965cb29 100644 --- a/src/com/android/settings/display/BrightnessLevelPreferenceController.java +++ b/src/com/android/settings/display/BrightnessLevelPreferenceController.java @@ -20,6 +20,7 @@ import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX; import static com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MIN; import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat; +import android.annotation.Nullable; import android.app.ActivityOptions; import android.content.ContentResolver; import android.content.Context; @@ -39,10 +40,12 @@ import android.text.TextUtils; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.accessibility.Flags; +import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.SettingsBaseActivity; -import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; @@ -51,17 +54,18 @@ import com.android.settingslib.transition.SettingsTransitionHelper; import java.text.NumberFormat; -public class BrightnessLevelPreferenceController extends AbstractPreferenceController implements +public class BrightnessLevelPreferenceController extends BasePreferenceController implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop { private static final String TAG = "BrightnessPrefCtrl"; - private static final String KEY_BRIGHTNESS = "brightness"; + private static final Uri BRIGHTNESS_ADJ_URI; private final ContentResolver mContentResolver; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final DisplayManager mDisplayManager; - + @Nullable private Preference mPreference; + private boolean mInSetupWizard; static { BRIGHTNESS_ADJ_URI = System.getUriFor(System.SCREEN_AUTO_BRIGHTNESS_ADJ); @@ -90,9 +94,12 @@ public class BrightnessLevelPreferenceController extends AbstractPreferenceContr } }; - public BrightnessLevelPreferenceController(Context context, Lifecycle lifecycle) { - super(context); + this(context, context.getString(R.string.preference_key_brightness_level), lifecycle); + } + + private BrightnessLevelPreferenceController(Context context, String key, Lifecycle lifecycle) { + super(context, key); mDisplayManager = context.getSystemService(DisplayManager.class); if (lifecycle != null) { @@ -101,20 +108,22 @@ public class BrightnessLevelPreferenceController extends AbstractPreferenceContr mContentResolver = mContext.getContentResolver(); } - @Override - public boolean isAvailable() { - return true; + public void setInSetupWizard(boolean inSetupWizard) { + mInSetupWizard = inSetupWizard; } @Override - public String getPreferenceKey() { - return KEY_BRIGHTNESS; + public int getAvailabilityStatus() { + if (mInSetupWizard && !Flags.addBrightnessSettingsInSuw()) { + return CONDITIONALLY_UNAVAILABLE; + } + return AVAILABLE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - mPreference = screen.findPreference(KEY_BRIGHTNESS); + mPreference = screen.findPreference(getPreferenceKey()); } @Override diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java index 78c88803abc..2ae598418ce 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java @@ -23,6 +23,7 @@ import static com.android.settings.accessibility.AccessibilitySettingsForSetupWi import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -45,7 +46,10 @@ import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.display.AutoBrightnessPreferenceController; +import com.android.settings.display.BrightnessLevelPreferenceController; import com.android.settingslib.RestrictedPreference; +import com.android.settingslib.core.AbstractPreferenceController; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupdesign.GlifPreferenceLayout; @@ -88,7 +92,7 @@ public class AccessibilitySettingsForSetupWizardTest { private GlifPreferenceLayout mGlifLayoutView; @Mock private FooterBarMixin mFooterBarMixin; - private AccessibilitySettingsForSetupWizard mFragment; + private TestAccessibilitySettingsForSetupWizard mFragment; @Before public void setUp() { @@ -141,6 +145,19 @@ public class AccessibilitySettingsForSetupWizardTest { assertThat(mFragment.mSelectToSpeakPreference.getKey()).isNull(); } + @Test + public void createPreferenceControllers_brightnessPreferencesControllersAreCreated() { + mFragment.onAttach(mContext); + + List controllers = + mFragment.createPreferenceControllers(mContext); + + assertTrue(controllers.stream().anyMatch( + controller -> controller instanceof BrightnessLevelPreferenceController)); + assertTrue(controllers.stream().anyMatch( + controller -> controller instanceof AutoBrightnessPreferenceController)); + } + private void addEnabledServiceInfo(ComponentName componentName, boolean isAccessibilityTool) { final AccessibilityServiceInfo a11yServiceInfo = mock(AccessibilityServiceInfo.class); when(a11yServiceInfo.getComponentName()).thenReturn(componentName); @@ -170,12 +187,14 @@ public class AccessibilitySettingsForSetupWizardTest { private final Context mContext; private final PreferenceManager mPreferenceManager; + final PreferenceScreen mPreferenceScreen; TestAccessibilitySettingsForSetupWizard(Context context) { super(); mContext = context; mPreferenceManager = new PreferenceManager(context); - mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context)); + mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(context)); + mPreferenceManager.setPreferences(mPreferenceScreen); mDisplayMagnificationPreference = new Preference(context); mScreenReaderPreference = new RestrictedPreference(context); mSelectToSpeakPreference = new RestrictedPreference(context); diff --git a/tests/robotests/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizardTest.java new file mode 100644 index 00000000000..1e6e068cae6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AutoBrightnessPreferenceFragmentForSetupWizardTest.java @@ -0,0 +1,95 @@ +/* + * 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.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import androidx.lifecycle.LifecycleOwner; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupdesign.GlifPreferenceLayout; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link AutoBrightnessPreferenceFragmentForSetupWizard}. */ +@RunWith(RobolectricTestRunner.class) +public class AutoBrightnessPreferenceFragmentForSetupWizardTest { + + @Rule + public final MockitoRule mMockito = MockitoJUnit.rule(); + + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private GlifPreferenceLayout mGlifLayoutView; + @Mock + private FooterBarMixin mFooterBarMixin; + private AutoBrightnessPreferenceFragmentForSetupWizard mFragment; + + @Before + public void setUp() { + mFragment = spy(new AutoBrightnessPreferenceFragmentForSetupWizard()); + doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner(); + doReturn(mContext).when(mFragment).getContext(); + when(mGlifLayoutView.getMixin(eq(FooterBarMixin.class))).thenReturn(mFooterBarMixin); + } + + @Test + public void setHeaderText_onViewCreated_verifyAction() { + final String title = "title"; + doReturn(title).when(mContext).getString(R.string.auto_brightness_title); + + mFragment.onViewCreated(mGlifLayoutView, null); + + verify(mGlifLayoutView).setHeaderText(title); + } + + @Test + public void getMetricsCategory_returnsCorrectCategory() { + assertThat(mFragment.getMetricsCategory()).isEqualTo( + SettingsEnums.SUW_ACCESSIBILITY_AUTO_BRIGHTNESS); + } + + @Test + public void onViewCreated_verifyAction() { + mFragment.onViewCreated(mGlifLayoutView, null); + + verify(mFooterBarMixin).setPrimaryButton(any()); + } +} diff --git a/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java index 02291731634..13cd86d5a2a 100644 --- a/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java @@ -21,18 +21,24 @@ import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; import static com.google.common.truth.Truth.assertThat; import android.content.ContentResolver; import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import com.android.settings.R; +import com.android.settings.accessibility.Flags; import com.android.settings.testutils.shadow.SettingsShadowResources; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -44,6 +50,9 @@ import org.robolectric.annotation.Config; @Config(shadows = {SettingsShadowResources.class}) public class AutoBrightnessPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String PREFERENCE_KEY = "auto_brightness"; private Context mContext; @@ -121,13 +130,34 @@ public class AutoBrightnessPreferenceControllerTest { } @Test - public void getAvailabilityStatus_configTrueSet_shouldReturnAvailableUnsearchable() { + public void getAvailabilityStatusNotInSUW_configTrueSet_shouldReturnAvailableUnsearchable() { SettingsShadowResources.overrideResource( com.android.internal.R.bool.config_automatic_brightness_available, true); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); } + @Test + @EnableFlags(Flags.FLAG_ADD_BRIGHTNESS_SETTINGS_IN_SUW) + public void getAvailabilityStatusInSUW_configTrueAndFlagOn_shouldReturnAvailableUnsearchable() { + SettingsShadowResources.overrideResource( + com.android.internal.R.bool.config_automatic_brightness_available, true); + mController.setInSetupWizard(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); + } + + @Test + @DisableFlags(Flags.FLAG_ADD_BRIGHTNESS_SETTINGS_IN_SUW) + public void + getAvailabilityStatusInSUW_configTrueAndFlagOff_shouldReturnConditionallyUnavailable() { + SettingsShadowResources.overrideResource( + com.android.internal.R.bool.config_automatic_brightness_available, true); + mController.setInSetupWizard(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + @Test public void getAvailabilityStatus_configFalseSet_shouldReturnUnsupportedOnDevice() { SettingsShadowResources.overrideResource( diff --git a/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java index bbc9cf32b03..009ca95e4d3 100644 --- a/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/BrightnessLevelPreferenceControllerTest.java @@ -33,16 +33,22 @@ import android.content.Context; import android.content.Intent; import android.hardware.display.BrightnessInfo; import android.os.PowerManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings.System; import android.view.Display; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.R; +import com.android.settings.accessibility.Flags; import com.android.settings.core.SettingsBaseActivity; import com.android.settingslib.transition.SettingsTransitionHelper; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -58,6 +64,9 @@ import org.robolectric.shadows.ShadowContentResolver; @RunWith(RobolectricTestRunner.class) public class BrightnessLevelPreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private PowerManager mPowerManager; @Mock @@ -90,10 +99,24 @@ public class BrightnessLevelPreferenceControllerTest { } @Test - public void isAvailable_shouldAlwaysReturnTrue() { + public void isAvailable_shouldAlwaysReturnTrueWhenNotInSetupWizard() { assertThat(mController.isAvailable()).isTrue(); } + @Test + @EnableFlags(Flags.FLAG_ADD_BRIGHTNESS_SETTINGS_IN_SUW) + public void isAvailable_inSetupWizardAndFlagOn_shouldReturnTrue() { + mController.setInSetupWizard(true); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @DisableFlags(Flags.FLAG_ADD_BRIGHTNESS_SETTINGS_IN_SUW) + public void isAvailable_inSetupWizardAndFlagOff_shouldReturnFalse() { + mController.setInSetupWizard(true); + assertThat(mController.isAvailable()).isFalse(); + } + @Test public void onStart_shouldRegisterObserver() { BrightnessLevelPreferenceController controller = @@ -169,13 +192,14 @@ public class BrightnessLevelPreferenceControllerTest { final BrightnessLevelPreferenceController controller = new BrightnessLevelPreferenceController(activity, null); final ShadowActivity shadowActivity = shadowOf(activity); - when(mPreference.getKey()).thenReturn("brightness"); + + String preferenceKey = mContext.getString(R.string.preference_key_brightness_level); + when(mPreference.getKey()).thenReturn(preferenceKey); controller.handlePreferenceTreeClick(mPreference); final Intent intent = shadowActivity.getNextStartedActivity(); assertThat(intent.getIntExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE, 0)) .isEqualTo(SettingsTransitionHelper.TransitionType.TRANSITION_NONE); - } } diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java index 4bddcd1b847..915231f2573 100644 --- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java @@ -228,6 +228,8 @@ public class BatteryTestUtils { /** Create a power anomaly event proto of adaptive brightness. */ public static PowerAnomalyEvent createAdaptiveBrightnessAnomalyEvent(boolean changeSettings) { + // TODO: migrate "auto_brightness_entry" to use R.string.preference_key_auto_brightness + // if we can access the Context here. (b/338314718) WarningBannerInfo.Builder warningBannerInfoBuilder = WarningBannerInfo.newBuilder() .setMainButtonDestination(DisplaySettings.class.getName()) From 4fc959d8c1c3812962bb08afb79a463a7ba5766d Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 15 May 2024 03:58:23 +0000 Subject: [PATCH 10/19] Ignore LinkifyUtilsTest to investigate on ClassNotFoundException Bug: 340657656 Test: atest Change-Id: I7837b5eead0e58f47719cd250b3270cd344c3e59 --- tests/unit/src/com/android/settings/LinkifyUtilsTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/src/com/android/settings/LinkifyUtilsTest.java b/tests/unit/src/com/android/settings/LinkifyUtilsTest.java index 34586d56186..5fc98d6e0c1 100644 --- a/tests/unit/src/com/android/settings/LinkifyUtilsTest.java +++ b/tests/unit/src/com/android/settings/LinkifyUtilsTest.java @@ -25,10 +25,12 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) +@Ignore("b/340657656") public class LinkifyUtilsTest { private static final String TEST_STRING = "to LINK_BEGINscanning settingsLINK_END."; private static final String WRONG_STRING = "to scanning settingsLINK_END."; From 12158eb1fe17d6712d80b6a146c723f66f97f7ce Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 4 Mar 2024 19:14:52 +0800 Subject: [PATCH 11/19] Create NetworkScanRepository And migrate network scan to flow for better maintenance and prevent ANR. Fix: 323105271 Test: manual - Choose network Test: unit test Change-Id: I5c49d195fc202143c0131ffd78bc3adc168b119c Merged-In: I5c49d195fc202143c0131ffd78bc3adc168b119c --- .../network/telephony/CellInfoUtil.kt | 2 +- .../network/telephony/NetworkScanHelper.java | 346 ------------------ .../telephony/NetworkSelectSettings.java | 241 +++--------- .../telephony/scan/NetworkScanRepository.kt | 167 +++++++++ .../scan/NetworkScanRepositoryTest.kt | 265 ++++++++++++++ .../telephony/NetworkScanHelperTest.java | 260 ------------- .../telephony/NetworkSelectSettingsTest.java | 95 +---- 7 files changed, 495 insertions(+), 881 deletions(-) delete mode 100644 src/com/android/settings/network/telephony/NetworkScanHelper.java create mode 100644 src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt create mode 100644 tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt delete mode 100644 tests/unit/src/com/android/settings/network/telephony/NetworkScanHelperTest.java diff --git a/src/com/android/settings/network/telephony/CellInfoUtil.kt b/src/com/android/settings/network/telephony/CellInfoUtil.kt index c7b6b24b521..51f60e796ff 100644 --- a/src/com/android/settings/network/telephony/CellInfoUtil.kt +++ b/src/com/android/settings/network/telephony/CellInfoUtil.kt @@ -82,7 +82,7 @@ object CellInfoUtil { */ @JvmStatic fun cellInfoListToString(cellInfos: List): String = - cellInfos.joinToString { cellInfo -> cellInfo.readableString() } + cellInfos.joinToString(System.lineSeparator()) { cellInfo -> cellInfo.readableString() } /** * Convert [CellInfo] to a readable string without sensitive info. diff --git a/src/com/android/settings/network/telephony/NetworkScanHelper.java b/src/com/android/settings/network/telephony/NetworkScanHelper.java deleted file mode 100644 index 19613291231..00000000000 --- a/src/com/android/settings/network/telephony/NetworkScanHelper.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (C) 2018 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.network.telephony; - -import android.annotation.IntDef; -import android.content.Context; -import android.telephony.AccessNetworkConstants.AccessNetworkType; -import android.telephony.CellInfo; -import android.telephony.NetworkScan; -import android.telephony.NetworkScanRequest; -import android.telephony.PhoneCapability; -import android.telephony.RadioAccessSpecifier; -import android.telephony.TelephonyManager; -import android.telephony.TelephonyScanManager; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import com.android.internal.telephony.CellNetworkScanResult; - -import com.android.settings.R; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.Executor; -import java.util.stream.Collectors; - -/** - * A helper class that builds the common interface and performs the network scan for two different - * network scan APIs. - */ -public class NetworkScanHelper { - public static final String TAG = "NetworkScanHelper"; - - /** - * Callbacks interface to inform the network scan results. - */ - public interface NetworkScanCallback { - /** - * Called when the results is returned from {@link TelephonyManager}. This method will be - * called at least one time if there is no error occurred during the network scan. - * - *

This method can be called multiple times in one network scan, until - * {@link #onComplete()} or {@link #onError(int)} is called. - * - * @param results - */ - void onResults(List results); - - /** - * Called when the current network scan process is finished. No more - * {@link #onResults(List)} will be called for the current network scan after this method is - * called. - */ - void onComplete(); - - /** - * Called when an error occurred during the network scan process. - * - *

There is no more result returned from {@link TelephonyManager} if an error occurred. - * - *

{@link #onComplete()} will not be called if an error occurred. - * - * @see {@link NetworkScan.ScanErrorCode} - */ - void onError(int errorCode); - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef({NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS, NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS}) - public @interface NetworkQueryType {} - - /** - * Performs the network scan using {@link TelephonyManager#getAvailableNetworks()}. The network - * scan results won't be returned to the caller until the network scan is completed. - * - *

This is typically used when the modem doesn't support the new network scan api - * {@link TelephonyManager#requestNetworkScan( - * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}. - */ - public static final int NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS = 1; - - /** - * Performs the network scan using {@link TelephonyManager#requestNetworkScan( - * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)} The network scan - * results will be returned to the caller periodically in a small time window until the network - * scan is completed. The complete results should be returned in the last called of - * {@link NetworkScanCallback#onResults(List)}. - * - *

This is recommended to be used if modem supports the new network scan api - * {@link TelephonyManager#requestNetworkScan( - * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)} - */ - public static final int NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS = 2; - - /** The constants below are used in the async network scan. */ - @VisibleForTesting - static final boolean INCREMENTAL_RESULTS = true; - @VisibleForTesting - static final int SEARCH_PERIODICITY_SEC = 5; - @VisibleForTesting - static final int MAX_SEARCH_TIME_SEC = 300; - @VisibleForTesting - static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3; - - private final NetworkScanCallback mNetworkScanCallback; - private final TelephonyManager mTelephonyManager; - private final TelephonyScanManager.NetworkScanCallback mInternalNetworkScanCallback; - private final Executor mExecutor; - - private int mMaxSearchTimeSec = MAX_SEARCH_TIME_SEC; - private NetworkScan mNetworkScanRequester; - - /** Callbacks for sync network scan */ - private ListenableFuture> mNetworkScanFuture; - - public NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor) { - mTelephonyManager = tm; - mNetworkScanCallback = callback; - mInternalNetworkScanCallback = new NetworkScanCallbackImpl(); - mExecutor = executor; - } - - public NetworkScanHelper(Context context, TelephonyManager tm, NetworkScanCallback callback, - Executor executor) { - this(tm, callback, executor); - mMaxSearchTimeSec = context.getResources().getInteger( - R.integer.config_network_scan_helper_max_search_time_sec); - } - - @VisibleForTesting - NetworkScanRequest createNetworkScanForPreferredAccessNetworks() { - long networkTypeBitmap3gpp = mTelephonyManager.getPreferredNetworkTypeBitmask() - & TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP; - - List radioAccessSpecifiers = new ArrayList<>(); - // If the allowed network types are unknown or if they are of the right class, scan for - // them; otherwise, skip them to save scan time and prevent users from being shown networks - // that they can't connect to. - if (networkTypeBitmap3gpp == 0 - || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_2G) != 0) { - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)); - } - if (networkTypeBitmap3gpp == 0 - || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_3G) != 0) { - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkType.UTRAN, null, null)); - } - if (networkTypeBitmap3gpp == 0 - || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_4G) != 0) { - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkType.EUTRAN, null, null)); - } - // If a device supports 5G stand-alone then the code below should be re-enabled; however - // a device supporting only non-standalone mode cannot perform PLMN selection and camp on - // a 5G network, which means that it shouldn't scan for 5G at the expense of battery as - // part of the manual network selection process. - // - if (networkTypeBitmap3gpp == 0 - || (hasNrSaCapability() - && (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_5G) != 0)) { - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkType.NGRAN, null, null)); - Log.d(TAG, "radioAccessSpecifiers add NGRAN."); - } - - return new NetworkScanRequest( - NetworkScanRequest.SCAN_TYPE_ONE_SHOT, - radioAccessSpecifiers.toArray( - new RadioAccessSpecifier[radioAccessSpecifiers.size()]), - SEARCH_PERIODICITY_SEC, - mMaxSearchTimeSec, - INCREMENTAL_RESULTS, - INCREMENTAL_RESULTS_PERIODICITY_SEC, - null /* List of PLMN ids (MCC-MNC) */); - } - - /** - * Performs a network scan for the given type {@code type}. - * {@link #NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS} is recommended if modem supports - * {@link TelephonyManager#requestNetworkScan( - * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}. - * - * @param type used to tell which network scan API should be used. - */ - public void startNetworkScan(@NetworkQueryType int type) { - if (type == NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS) { - mNetworkScanFuture = SettableFuture.create(); - Futures.addCallback(mNetworkScanFuture, new FutureCallback>() { - @Override - public void onSuccess(List result) { - onResults(result); - onComplete(); - } - - @Override - public void onFailure(Throwable t) { - if (t instanceof CancellationException) { - return; - } - int errCode = Integer.parseInt(t.getMessage()); - onError(errCode); - } - }, MoreExecutors.directExecutor()); - mExecutor.execute(new NetworkScanSyncTask( - mTelephonyManager, (SettableFuture) mNetworkScanFuture)); - } else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) { - if (mNetworkScanRequester != null) { - return; - } - mNetworkScanRequester = mTelephonyManager.requestNetworkScan( - createNetworkScanForPreferredAccessNetworks(), - mExecutor, - mInternalNetworkScanCallback); - if (mNetworkScanRequester == null) { - onError(NetworkScan.ERROR_RADIO_INTERFACE_ERROR); - } - } - } - - /** - * The network scan of type {@link #NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS} can't be stopped, - * however, the result of the current network scan won't be returned to the callback after - * calling this method. - */ - public void stopNetworkQuery() { - if (mNetworkScanRequester != null) { - mNetworkScanRequester.stopScan(); - mNetworkScanRequester = null; - } - - if (mNetworkScanFuture != null) { - mNetworkScanFuture.cancel(true /* mayInterruptIfRunning */); - mNetworkScanFuture = null; - } - } - - private void onResults(List cellInfos) { - mNetworkScanCallback.onResults(cellInfos); - } - - private void onComplete() { - mNetworkScanCallback.onComplete(); - } - - private void onError(int errCode) { - mNetworkScanCallback.onError(errCode); - } - - private boolean hasNrSaCapability() { - return Arrays.stream( - mTelephonyManager.getPhoneCapability().getDeviceNrCapabilities()) - .anyMatch(i -> i == PhoneCapability.DEVICE_NR_CAPABILITY_SA); - } - - /** - * Converts the status code of {@link CellNetworkScanResult} to one of the - * {@link NetworkScan.ScanErrorCode}. - * @param errCode status code from {@link CellNetworkScanResult}. - * - * @return one of the scan error code from {@link NetworkScan.ScanErrorCode}. - */ - private static int convertToScanErrorCode(int errCode) { - switch (errCode) { - case CellNetworkScanResult.STATUS_RADIO_NOT_AVAILABLE: - return NetworkScan.ERROR_RADIO_INTERFACE_ERROR; - case CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE: - default: - return NetworkScan.ERROR_MODEM_ERROR; - } - } - - private final class NetworkScanCallbackImpl extends TelephonyScanManager.NetworkScanCallback { - public void onResults(List results) { - Log.d(TAG, "Async scan onResults() results = " - + CellInfoUtil.cellInfoListToString(results)); - NetworkScanHelper.this.onResults(results); - } - - public void onComplete() { - Log.d(TAG, "async scan onComplete()"); - NetworkScanHelper.this.onComplete(); - } - - public void onError(@NetworkScan.ScanErrorCode int errCode) { - Log.d(TAG, "async scan onError() errorCode = " + errCode); - NetworkScanHelper.this.onError(errCode); - } - } - - private static final class NetworkScanSyncTask implements Runnable { - private final SettableFuture> mCallback; - private final TelephonyManager mTelephonyManager; - - NetworkScanSyncTask( - TelephonyManager telephonyManager, SettableFuture> callback) { - mTelephonyManager = telephonyManager; - mCallback = callback; - } - - @Override - public void run() { - final CellNetworkScanResult result = mTelephonyManager.getAvailableNetworks(); - if (result.getStatus() == CellNetworkScanResult.STATUS_SUCCESS) { - final List cellInfos = result.getOperators() - .stream() - .map(operatorInfo - -> CellInfoUtil.convertOperatorInfoToCellInfo(operatorInfo)) - .collect(Collectors.toList()); - Log.d(TAG, "Sync network scan completed, cellInfos = " - + CellInfoUtil.cellInfoListToString(cellInfos)); - mCallback.set(cellInfos); - } else { - final Throwable error = new Throwable( - Integer.toString(convertToScanErrorCode(result.getStatus()))); - mCallback.setException(error); - Log.d(TAG, "Sync network scan error, ex = " + error); - } - } - } -} diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java index c8617afb38c..33ece6be6fe 100644 --- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java +++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java @@ -16,7 +16,6 @@ package com.android.settings.network.telephony; -import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; @@ -39,6 +38,7 @@ import android.util.Log; import android.view.View; import androidx.annotation.Keep; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -49,14 +49,21 @@ import com.android.internal.telephony.OperatorInfo; import com.android.internal.telephony.flags.Flags; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.network.telephony.scan.NetworkScanRepository; +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos; +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete; +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError; +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.utils.ThreadUtils; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; @@ -71,12 +78,8 @@ public class NetworkSelectSettings extends DashboardFragment { private static final String TAG = "NetworkSelectSettings"; private static final int EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE = 1; - private static final int EVENT_NETWORK_SCAN_RESULTS = 2; - private static final int EVENT_NETWORK_SCAN_ERROR = 3; - private static final int EVENT_NETWORK_SCAN_COMPLETED = 4; private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference"; - private static final int MIN_NUMBER_OF_SCAN_REQUIRED = 2; private PreferenceCategory mPreferenceCategory; @VisibleForTesting @@ -91,18 +94,14 @@ public class NetworkSelectSettings extends DashboardFragment { private CarrierConfigManager mCarrierConfigManager; private List mForbiddenPlmns; private boolean mShow4GForLTE = false; - private NetworkScanHelper mNetworkScanHelper; private final ExecutorService mNetworkScanExecutor = Executors.newFixedThreadPool(1); private MetricsFeatureProvider mMetricsFeatureProvider; - private boolean mUseNewApi; - private long mRequestIdManualNetworkSelect; - private long mRequestIdManualNetworkScan; - private long mWaitingForNumberOfScanResults; - @VisibleForTesting - boolean mIsAggregationEnabled = false; private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean(); + private NetworkScanRepository mNetworkScanRepository; + private boolean mUpdateScanResult = false; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -114,7 +113,6 @@ public class NetworkSelectSettings extends DashboardFragment { @Initializer protected void onCreateInitialization() { Context context = getContext(); - mUseNewApi = enableNewAutoSelectNetworkUI(context); mSubId = getSubId(); mPreferenceCategory = getPreferenceCategory(PREF_KEY_NETWORK_OPERATORS); @@ -124,8 +122,6 @@ public class NetworkSelectSettings extends DashboardFragment { mTelephonyManager = getTelephonyManager(context, mSubId); mSatelliteManager = getSatelliteManager(context); mCarrierConfigManager = getCarrierConfigManager(context); - mNetworkScanHelper = new NetworkScanHelper( - mTelephonyManager, mCallback, mNetworkScanExecutor); PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mSubId, CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL); @@ -136,30 +132,13 @@ public class NetworkSelectSettings extends DashboardFragment { true)); mMetricsFeatureProvider = getMetricsFeatureProvider(context); - mIsAggregationEnabled = enableAggregation(context); - Log.d(TAG, "init: mUseNewApi:" + mUseNewApi - + " ,mIsAggregationEnabled:" + mIsAggregationEnabled + " ,mSubId:" + mSubId); mCarrierConfigChangeListener = (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigChanged( subId); mCarrierConfigManager.registerCarrierConfigChangeListener(mNetworkScanExecutor, mCarrierConfigChangeListener); - - } - - @Keep - @VisibleForTesting - protected boolean enableNewAutoSelectNetworkUI(Context context) { - return context.getResources().getBoolean( - com.android.internal.R.bool.config_enableNewAutoSelectNetworkUI); - } - - @Keep - @VisibleForTesting - protected boolean enableAggregation(Context context) { - return context.getResources().getBoolean( - R.bool.config_network_selection_list_aggregation_enabled); + mNetworkScanRepository = new NetworkScanRepository(context, mSubId); } @Keep @@ -218,17 +197,42 @@ public class NetworkSelectSettings extends DashboardFragment { } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - final Activity activity = getActivity(); - if (activity != null) { - mProgressHeader = setPinnedHeaderView( - com.android.settingslib.widget.progressbar.R.layout.progress_header) - .findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation); - setProgressBarVisible(false); - } + mProgressHeader = setPinnedHeaderView( + com.android.settingslib.widget.progressbar.R.layout.progress_header + ).findViewById(com.android.settingslib.widget.progressbar.R.id.progress_bar_animation); forceUpdateConnectedPreferenceCategory(); + launchNetworkScan(); + } + + private void launchNetworkScan() { + mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), new Function1<>() { + @Override + public Unit invoke(@NonNull NetworkScanResult networkScanResult) { + if (!mUpdateScanResult) { + // Not update UI if not in scan mode. + return Unit.INSTANCE; + } + if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) { + scanResultHandler(networkScanCellInfos.getCellInfos()); + return Unit.INSTANCE; + } + if (!isPreferenceScreenEnabled()) { + clearPreferenceSummary(); + enablePreferenceScreen(true); + } else if (networkScanResult instanceof NetworkScanComplete + && mCellInfoList == null) { + // In case the scan timeout before getting any results + addMessagePreference(R.string.empty_networks_list); + } else if (networkScanResult instanceof NetworkScanError) { + addMessagePreference(R.string.network_query_error); + } + + return Unit.INSTANCE; + } + }); } @Override @@ -236,12 +240,8 @@ public class NetworkSelectSettings extends DashboardFragment { super.onStart(); updateForbiddenPlmns(); - if (isProgressBarVisible()) { - return; - } - if (mWaitingForNumberOfScanResults <= 0) { - startNetworkQuery(); - } + setProgressBarVisible(true); + mUpdateScanResult = true; } /** @@ -256,14 +256,6 @@ public class NetworkSelectSettings extends DashboardFragment { : new ArrayList<>(); } - @Override - public void onStop() { - if (mWaitingForNumberOfScanResults <= 0) { - stopNetworkQuery(); - } - super.onStop(); - } - @Override public boolean onPreferenceTreeClick(Preference preference) { if (preference == mSelectedPreference) { @@ -275,7 +267,7 @@ public class NetworkSelectSettings extends DashboardFragment { return false; } - stopNetworkQuery(); + mUpdateScanResult = false; // Refresh the last selected item in case users reselect network. clearPreferenceSummary(); @@ -294,8 +286,6 @@ public class NetworkSelectSettings extends DashboardFragment { // Disable the screen until network is manually set enablePreferenceScreen(false); - mRequestIdManualNetworkSelect = getNewRequestId(); - mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED; final OperatorInfo operator = mSelectedPreference.getOperatorInfo(); ThreadUtils.postOnBackgroundThread(() -> { final Message msg = mHandler.obtainMessage( @@ -329,7 +319,6 @@ public class NetworkSelectSettings extends DashboardFragment { switch (msg.what) { case EVENT_SET_NETWORK_SELECTION_MANUALLY_DONE: final boolean isSucceed = (boolean) msg.obj; - stopNetworkQuery(); setProgressBarVisible(false); enablePreferenceScreen(true); @@ -341,86 +330,15 @@ public class NetworkSelectSettings extends DashboardFragment { Log.e(TAG, "No preference to update!"); } break; - case EVENT_NETWORK_SCAN_RESULTS: - scanResultHandler((List) msg.obj); - break; - - case EVENT_NETWORK_SCAN_ERROR: - stopNetworkQuery(); - Log.i(TAG, "Network scan failure " + msg.arg1 + ":" - + " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan) - + ", waiting for scan results = " + mWaitingForNumberOfScanResults - + ", select request 0x" - + Long.toHexString(mRequestIdManualNetworkSelect)); - if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { - break; - } - if (!isPreferenceScreenEnabled()) { - clearPreferenceSummary(); - enablePreferenceScreen(true); - } else { - addMessagePreference(R.string.network_query_error); - } - break; - - case EVENT_NETWORK_SCAN_COMPLETED: - stopNetworkQuery(); - Log.d(TAG, "Network scan complete:" - + " scan request 0x" + Long.toHexString(mRequestIdManualNetworkScan) - + ", waiting for scan results = " + mWaitingForNumberOfScanResults - + ", select request 0x" - + Long.toHexString(mRequestIdManualNetworkSelect)); - if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { - break; - } - if (!isPreferenceScreenEnabled()) { - clearPreferenceSummary(); - enablePreferenceScreen(true); - } else if (mCellInfoList == null) { - // In case the scan timeout before getting any results - addMessagePreference(R.string.empty_networks_list); - } - break; } - return; } }; - @VisibleForTesting - List doAggregation(List cellInfoListInput) { - if (!mIsAggregationEnabled) { - Log.d(TAG, "no aggregation"); - return new ArrayList<>(cellInfoListInput); - } - ArrayList aggregatedList = new ArrayList<>(); - for (CellInfo cellInfo : cellInfoListInput) { - String plmn = CellInfoUtil.getNetworkTitle(cellInfo.getCellIdentity()); - Class className = cellInfo.getClass(); - - Optional itemInTheList = aggregatedList.stream().filter( - item -> { - String itemPlmn = CellInfoUtil.getNetworkTitle(item.getCellIdentity()); - return itemPlmn.equals(plmn) && item.getClass().equals(className); - }) - .findFirst(); - if (itemInTheList.isPresent()) { - if (cellInfo.isRegistered() && !itemInTheList.get().isRegistered()) { - // Adding the registered cellinfo item into list. If there are two registered - // cellinfo items, then select first one from source list. - aggregatedList.set(aggregatedList.indexOf(itemInTheList.get()), cellInfo); - } - continue; - } - aggregatedList.add(cellInfo); - } - - return filterOutSatellitePlmn(aggregatedList); - } - /* We do not want to expose carrier satellite plmns to the user when manually scan the cellular network. Therefore, it is needed to filter out satellite plmns from current cell info list */ - private List filterOutSatellitePlmn(List cellInfoList) { + @VisibleForTesting + List filterOutSatellitePlmn(List cellInfoList) { List aggregatedSatellitePlmn = getSatellitePlmnsForCarrierWrapper(); if (!mShouldFilterOutSatellitePlmn.get() || aggregatedSatellitePlmn.isEmpty()) { return cellInfoList; @@ -455,39 +373,10 @@ public class NetworkSelectSettings extends DashboardFragment { } } - private final NetworkScanHelper.NetworkScanCallback mCallback = - new NetworkScanHelper.NetworkScanCallback() { - public void onResults(List results) { - final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results); - msg.sendToTarget(); - } - - public void onComplete() { - final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED); - msg.sendToTarget(); - } - - public void onError(int error) { - final Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error, - 0 /* arg2 */); - msg.sendToTarget(); - } - }; - @Keep @VisibleForTesting protected void scanResultHandler(List results) { - if (mRequestIdManualNetworkScan < mRequestIdManualNetworkSelect) { - Log.d(TAG, "CellInfoList (drop): " - + CellInfoUtil.cellInfoListToString(new ArrayList<>(results))); - return; - } - mWaitingForNumberOfScanResults--; - if ((mWaitingForNumberOfScanResults <= 0) && (!isResumed())) { - stopNetworkQuery(); - } - - mCellInfoList = doAggregation(results); + mCellInfoList = filterOutSatellitePlmn(results); Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList)); if (mCellInfoList != null && mCellInfoList.size() != 0) { final NetworkOperatorPreference connectedPref = updateAllPreferenceCategory(); @@ -646,11 +535,6 @@ public class NetworkSelectSettings extends DashboardFragment { } } - private long getNewRequestId() { - return Math.max(mRequestIdManualNetworkSelect, - mRequestIdManualNetworkScan) + 1; - } - private boolean isProgressBarVisible() { if (mProgressHeader == null) { return false; @@ -671,29 +555,8 @@ public class NetworkSelectSettings extends DashboardFragment { mPreferenceCategory.addPreference(mStatusMessagePreference); } - private void startNetworkQuery() { - setProgressBarVisible(true); - if (mNetworkScanHelper != null) { - mRequestIdManualNetworkScan = getNewRequestId(); - mWaitingForNumberOfScanResults = MIN_NUMBER_OF_SCAN_REQUIRED; - mNetworkScanHelper.startNetworkScan( - mUseNewApi - ? NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS - : NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS); - } - } - - private void stopNetworkQuery() { - setProgressBarVisible(false); - if (mNetworkScanHelper != null) { - mWaitingForNumberOfScanResults = 0; - mNetworkScanHelper.stopNetworkQuery(); - } - } - @Override public void onDestroy() { - stopNetworkQuery(); mNetworkScanExecutor.shutdown(); super.onDestroy(); } diff --git a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt new file mode 100644 index 00000000000..2067b8c3be5 --- /dev/null +++ b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt @@ -0,0 +1,167 @@ +/* + * 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.network.telephony.scan + +import android.content.Context +import android.telephony.AccessNetworkConstants.AccessNetworkType +import android.telephony.CellInfo +import android.telephony.NetworkScanRequest +import android.telephony.PhoneCapability +import android.telephony.RadioAccessSpecifier +import android.telephony.TelephonyManager +import android.telephony.TelephonyScanManager +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import com.android.settings.network.telephony.CellInfoUtil +import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle +import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOn + +class NetworkScanRepository(context: Context, subId: Int) { + sealed interface NetworkScanResult + + data class NetworkScanCellInfos(val cellInfos: List) : NetworkScanResult + data object NetworkScanComplete : NetworkScanResult + data class NetworkScanError(val error: Int) : NetworkScanResult + + private val telephonyManager = + context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId) + + /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ + fun launchNetworkScan(lifecycleOwner: LifecycleOwner, onResult: (NetworkScanResult) -> Unit) { + networkScanFlow().collectLatestWithLifecycle(lifecycleOwner, action = onResult) + } + + data class CellInfoScanKey( + val title: String?, + val className: String, + val isRegistered: Boolean, + ) { + constructor(cellInfo: CellInfo) : this( + title = cellInfo.cellIdentity.getNetworkTitle(), + className = cellInfo.javaClass.name, + isRegistered = cellInfo.isRegistered, + ) + } + + fun networkScanFlow(): Flow = callbackFlow { + val callback = object : TelephonyScanManager.NetworkScanCallback() { + override fun onResults(results: List) { + val cellInfos = results.distinctBy { CellInfoScanKey(it) } + trySend(NetworkScanCellInfos(cellInfos)) + Log.d(TAG, "CellInfoList: ${CellInfoUtil.cellInfoListToString(cellInfos)}") + } + + override fun onComplete() { + trySend(NetworkScanComplete) + close() + Log.d(TAG, "onComplete") + } + + override fun onError(error: Int) { + trySend(NetworkScanError(error)) + close() + Log.d(TAG, "onError: $error") + } + } + + val networkScan = telephonyManager.requestNetworkScan( + createNetworkScan(), + Dispatchers.Default.asExecutor(), + callback, + ) + + awaitClose { networkScan.stopScan() } + }.flowOn(Dispatchers.Default) + + /** Create network scan for allowed network types. */ + private fun createNetworkScan(): NetworkScanRequest { + val allowedNetworkTypes = getAllowedNetworkTypes() + Log.d(TAG, "createNetworkScan: allowedNetworkTypes = $allowedNetworkTypes") + val radioAccessSpecifiers = allowedNetworkTypes + .map { RadioAccessSpecifier(it, null, null) } + .toTypedArray() + return NetworkScanRequest( + NetworkScanRequest.SCAN_TYPE_ONE_SHOT, + radioAccessSpecifiers, + NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC, // one shot, not used + MAX_SEARCH_TIME_SEC, + true, + INCREMENTAL_RESULTS_PERIODICITY_SEC, + null, + ) + } + + private fun getAllowedNetworkTypes(): List { + val networkTypeBitmap3gpp: Long = + telephonyManager.getAllowedNetworkTypesBitmask() and + TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP + return buildList { + // If the allowed network types are unknown or if they are of the right class, scan for + // them; otherwise, skip them to save scan time and prevent users from being shown + // networks that they can't connect to. + if (networkTypeBitmap3gpp == 0L + || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_2G != 0L + ) { + add(AccessNetworkType.GERAN) + } + if (networkTypeBitmap3gpp == 0L + || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_3G != 0L + ) { + add(AccessNetworkType.UTRAN) + } + if (networkTypeBitmap3gpp == 0L + || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_4G != 0L + ) { + add(AccessNetworkType.EUTRAN) + } + // If a device supports 5G stand-alone then the code below should be re-enabled; however + // a device supporting only non-standalone mode cannot perform PLMN selection and camp + // on a 5G network, which means that it shouldn't scan for 5G at the expense of battery + // as part of the manual network selection process. + // + if (networkTypeBitmap3gpp == 0L + || (networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_5G != 0L && + hasNrSaCapability()) + ) { + add(AccessNetworkType.NGRAN) + Log.d(TAG, "radioAccessSpecifiers add NGRAN.") + } + } + } + + private fun hasNrSaCapability(): Boolean { + val phoneCapability = telephonyManager.getPhoneCapability() + return PhoneCapability.DEVICE_NR_CAPABILITY_SA in phoneCapability.deviceNrCapabilities + } + + companion object { + private const val TAG = "NetworkScanRepository" + + @VisibleForTesting + val MAX_SEARCH_TIME_SEC = 300 + + @VisibleForTesting + val INCREMENTAL_RESULTS_PERIODICITY_SEC = 3 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt new file mode 100644 index 00000000000..070c779b5ea --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt @@ -0,0 +1,265 @@ +/* + * 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.network.telephony.scan + +import android.content.Context +import android.telephony.AccessNetworkConstants.AccessNetworkType +import android.telephony.CellIdentityCdma +import android.telephony.CellIdentityGsm +import android.telephony.CellIdentityLte +import android.telephony.CellInfoCdma +import android.telephony.CellInfoGsm +import android.telephony.CellInfoLte +import android.telephony.NetworkScan +import android.telephony.NetworkScanRequest +import android.telephony.PhoneCapability +import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.NETWORK_CLASS_BITMASK_5G +import android.telephony.TelephonyScanManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spa.testutils.toListWithTimeout +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class NetworkScanRepositoryTest { + + private var callback: TelephonyScanManager.NetworkScanCallback? = null + + private val mockTelephonyManager = mock { + on { createForSubscriptionId(SUB_ID) } doReturn mock + on { requestNetworkScan(any(), any(), any()) } doAnswer { + callback = it.arguments[2] as TelephonyScanManager.NetworkScanCallback + mock() + } + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } + + private val repository = NetworkScanRepository(context, SUB_ID) + + @Test + fun networkScanFlow_initial() = runBlocking { + val result = repository.networkScanFlow().firstWithTimeoutOrNull() + + assertThat(result).isNull() + } + + @Test + fun networkScanFlow_onResults(): Unit = runBlocking { + val cellInfos = listOf(CellInfoCdma().apply { cellIdentity = CELL_IDENTITY_CDMA }) + val listDeferred = async { + repository.networkScanFlow().toListWithTimeout() + } + delay(100) + + callback?.onResults(cellInfos) + + assertThat(listDeferred.await()).containsExactly(NetworkScanCellInfos(cellInfos)) + } + + @Test + fun networkScanFlow_onComplete(): Unit = runBlocking { + val listDeferred = async { + repository.networkScanFlow().toListWithTimeout() + } + delay(100) + + callback?.onComplete() + + assertThat(listDeferred.await()).containsExactly(NetworkScanComplete) + } + + @Test + fun networkScanFlow_onError(): Unit = runBlocking { + val listDeferred = async { + repository.networkScanFlow().toListWithTimeout() + } + delay(100) + + callback?.onError(1) + + assertThat(listDeferred.await()).containsExactly(NetworkScanError(1)) + } + + @Test + fun networkScanFlow_hasDuplicateItems(): Unit = runBlocking { + val cellInfos = listOf( + createCellInfoLte("123", false), + createCellInfoLte("123", false), + createCellInfoLte("124", true), + createCellInfoLte("124", true), + createCellInfoGsm("123", false), + createCellInfoGsm("123", false), + ) + val listDeferred = async { + repository.networkScanFlow().toListWithTimeout() + } + delay(100) + + callback?.onResults(cellInfos) + + assertThat(listDeferred.await()).containsExactly( + NetworkScanCellInfos( + listOf( + createCellInfoLte("123", false), + createCellInfoLte("124", true), + createCellInfoGsm("123", false), + ) + ) + ) + } + + + @Test + fun networkScanFlow_noDuplicateItems(): Unit = runBlocking { + val cellInfos = listOf( + createCellInfoLte("123", false), + createCellInfoLte("123", true), + createCellInfoLte("124", false), + createCellInfoLte("124", true), + createCellInfoGsm("456", false), + createCellInfoGsm("456", true), + ) + val listDeferred = async { + repository.networkScanFlow().toListWithTimeout() + } + delay(100) + + callback?.onResults(cellInfos) + + assertThat(listDeferred.await()).containsExactly( + NetworkScanCellInfos( + listOf( + createCellInfoLte("123", false), + createCellInfoLte("123", true), + createCellInfoLte("124", false), + createCellInfoLte("124", true), + createCellInfoGsm("456", false), + createCellInfoGsm("456", true), + ) + ) + ) + } + + @Test + fun createNetworkScan_deviceHasNrSa_requestNgran(): Unit = runBlocking { + mockTelephonyManager.stub { + on { getAllowedNetworkTypesBitmask() } doReturn NETWORK_CLASS_BITMASK_5G + on { getPhoneCapability() } doReturn + createPhoneCapability(intArrayOf(PhoneCapability.DEVICE_NR_CAPABILITY_SA)) + } + + repository.networkScanFlow().firstWithTimeoutOrNull() + + verify(mockTelephonyManager).requestNetworkScan(argThat { + specifiers.any { it.radioAccessNetwork == AccessNetworkType.NGRAN } + }, any(), any()) + } + + @Test + fun createNetworkScan_deviceNoNrSa_noNgran(): Unit = runBlocking { + mockTelephonyManager.stub { + on { getAllowedNetworkTypesBitmask() } doReturn NETWORK_CLASS_BITMASK_5G + on { getPhoneCapability() } doReturn + createPhoneCapability(intArrayOf(PhoneCapability.DEVICE_NR_CAPABILITY_NSA)) + } + + repository.networkScanFlow().firstWithTimeoutOrNull() + + verify(mockTelephonyManager).requestNetworkScan(argThat { + specifiers.none { it.radioAccessNetwork == AccessNetworkType.NGRAN } + }, any(), any()) + } + + private companion object { + const val SUB_ID = 1 + const val LONG = "Long" + const val SHORT = "Short" + + val CELL_IDENTITY_CDMA = CellIdentityCdma( + /* nid = */ 1, + /* sid = */ 2, + /* bid = */ 3, + /* lon = */ 4, + /* lat = */ 5, + /* alphal = */ LONG, + /* alphas = */ SHORT, + ) + + private fun createCellInfoLte(alphaLong: String, registered: Boolean): CellInfoLte { + val cellIdentityLte = CellIdentityLte( + /* ci = */ 1, + /* pci = */ 2, + /* tac = */ 3, + /* earfcn = */ 4, + /* bands = */ intArrayOf(1, 2), + /* bandwidth = */ 10000, + /* mccStr = */ null, + /* mncStr = */ null, + /* alphal = */ alphaLong, + /* alphas = */ null, + /* additionalPlmns = */ emptyList(), + /* csgInfo = */ null, + ) + return CellInfoLte().apply { + cellIdentity = cellIdentityLte + isRegistered = registered + } + } + + private fun createCellInfoGsm(alphaLong: String, registered: Boolean): CellInfoGsm { + val cellIdentityGsm = CellIdentityGsm( + /* lac = */ 1, + /* cid = */ 2, + /* arfcn = */ 3, + /* bsic = */ 4, + /* mccStr = */ "123", + /* mncStr = */ "01", + /* alphal = */ alphaLong, + /* alphas = */ null, + /* additionalPlmns = */ emptyList(), + ) + return CellInfoGsm().apply { + cellIdentity = cellIdentityGsm + isRegistered = registered + } + } + + private fun createPhoneCapability(deviceNrCapabilities: IntArray) = + PhoneCapability.Builder().setDeviceNrCapabilities(deviceNrCapabilities).build() + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkScanHelperTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkScanHelperTest.java deleted file mode 100644 index f046c9a6f22..00000000000 --- a/tests/unit/src/com/android/settings/network/telephony/NetworkScanHelperTest.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2021 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.network.telephony; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.telephony.AccessNetworkConstants; -import android.telephony.CellInfo; -import android.telephony.ModemInfo; -import android.telephony.NetworkScan; -import android.telephony.NetworkScanRequest; -import android.telephony.PhoneCapability; -import android.telephony.RadioAccessSpecifier; -import android.telephony.TelephonyManager; -import android.telephony.TelephonyScanManager; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -@RunWith(AndroidJUnit4.class) -public class NetworkScanHelperTest { - - @Mock - private TelephonyManager mTelephonyManager; - @Mock - private List mCellInfos; - @Mock - private NetworkScanHelper.NetworkScanCallback mNetworkScanCallback; - - private static final long THREAD_EXECUTION_TIMEOUT_MS = 3000L; - - private ExecutorService mNetworkScanExecutor; - private NetworkScanHelper mNetworkScanHelper; - - private static final int SCAN_ID = 1234; - private static final int SUB_ID = 1; - - private NetworkScan mNetworkScan; - - public class NetworkScanMock extends NetworkScan { - NetworkScanMock(int scanId, int subId) { - super(scanId, subId); - } - - @Override - public void stopScan() { - return; - } - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mNetworkScanExecutor = Executors.newFixedThreadPool(1); - - mNetworkScanHelper = new NetworkScanHelper(mTelephonyManager, - mNetworkScanCallback, mNetworkScanExecutor); - - mNetworkScan = spy(new NetworkScanMock(SCAN_ID, SUB_ID)); - } - - @Test - public void startNetworkScan_incrementalAndSuccess_completionWithResult() { - when(mCellInfos.size()).thenReturn(1); - - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - TelephonyScanManager.NetworkScanCallback callback = - (TelephonyScanManager.NetworkScanCallback) - (invocation.getArguments()[2]); - callback.onResults(mCellInfos); - callback.onComplete(); - return mNetworkScan; - } - }).when(mTelephonyManager).requestNetworkScan( - any(NetworkScanRequest.class), any(Executor.class), - any(TelephonyScanManager.NetworkScanCallback.class)); - - ArgumentCaptor> argument = ArgumentCaptor.forClass(List.class); - - startNetworkScan_incremental(true); - - verify(mNetworkScanCallback, times(1)).onResults(argument.capture()); - List actualResult = argument.getValue(); - assertThat(actualResult.size()).isEqualTo(mCellInfos.size()); - verify(mNetworkScanCallback, times(1)).onComplete(); - } - - @Test - public void startNetworkScan_incrementalAndImmediateFailure_failureWithErrorCode() { - doReturn(null).when(mTelephonyManager).requestNetworkScan( - any(NetworkScanRequest.class), any(Executor.class), - any(TelephonyScanManager.NetworkScanCallback.class)); - - startNetworkScan_incremental(true); - - verify(mNetworkScanCallback, times(1)).onError(anyInt()); - } - - @Test - public void startNetworkScan_incrementalAndFailure_failureWithErrorCode() { - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - TelephonyScanManager.NetworkScanCallback callback = - (TelephonyScanManager.NetworkScanCallback) - (invocation.getArguments()[2]); - callback.onError(NetworkScan.ERROR_MODEM_ERROR); - return mNetworkScan; - } - }).when(mTelephonyManager).requestNetworkScan( - any(NetworkScanRequest.class), any(Executor.class), - any(TelephonyScanManager.NetworkScanCallback.class)); - - startNetworkScan_incremental(true); - - verify(mNetworkScanCallback, times(1)).onError(anyInt()); - } - - @Test - public void startNetworkScan_incrementalAndAbort_doStop() { - doReturn(mNetworkScan).when(mTelephonyManager).requestNetworkScan( - any(NetworkScanRequest.class), any(Executor.class), - any(TelephonyScanManager.NetworkScanCallback.class)); - - startNetworkScan_incremental(false); - - verify(mNetworkScan, times(1)).stopScan(); - } - - @Test - public void createNetworkScanForPreferredAccessNetworks_deviceNoNrSa_noNgran() { - int[] deviceNrCapabilities = new int[]{PhoneCapability.DEVICE_NR_CAPABILITY_NSA}; - PhoneCapability phoneCapability = createPhoneCapability(deviceNrCapabilities); - doReturn(TelephonyManager.NETWORK_CLASS_BITMASK_2G - | TelephonyManager.NETWORK_CLASS_BITMASK_3G - | TelephonyManager.NETWORK_CLASS_BITMASK_4G - | TelephonyManager.NETWORK_CLASS_BITMASK_5G).when( - mTelephonyManager).getPreferredNetworkTypeBitmask(); - doReturn(phoneCapability).when(mTelephonyManager).getPhoneCapability(); - List radioAccessSpecifiers = new ArrayList<>(); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN, null, - null)); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.UTRAN, null, - null)); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, null, - null)); - NetworkScanRequest expectedNetworkScanRequest = createNetworkScanRequest( - radioAccessSpecifiers); - - assertEquals(expectedNetworkScanRequest, - mNetworkScanHelper.createNetworkScanForPreferredAccessNetworks()); - } - - @Test - public void createNetworkScanForPreferredAccessNetworks_deviceHasNrSa_hasNgran() { - int[] deviceNrCapabilities = new int[]{PhoneCapability.DEVICE_NR_CAPABILITY_NSA, - PhoneCapability.DEVICE_NR_CAPABILITY_SA}; - PhoneCapability phoneCapability = createPhoneCapability(deviceNrCapabilities); - doReturn(TelephonyManager.NETWORK_CLASS_BITMASK_2G - | TelephonyManager.NETWORK_CLASS_BITMASK_3G - | TelephonyManager.NETWORK_CLASS_BITMASK_4G - | TelephonyManager.NETWORK_CLASS_BITMASK_5G).when( - mTelephonyManager).getPreferredNetworkTypeBitmask(); - doReturn(phoneCapability).when(mTelephonyManager).getPhoneCapability(); - List radioAccessSpecifiers = new ArrayList<>(); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.GERAN, null, - null)); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.UTRAN, null, - null)); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, null, - null)); - radioAccessSpecifiers.add( - new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.NGRAN, null, - null)); - NetworkScanRequest expectedNetworkScanRequest = createNetworkScanRequest( - radioAccessSpecifiers); - - assertEquals(expectedNetworkScanRequest, - mNetworkScanHelper.createNetworkScanForPreferredAccessNetworks()); - } - - private PhoneCapability createPhoneCapability(int[] deviceNrCapabilities) { - int maxActiveVoiceCalls = 1; - int maxActiveData = 2; - ModemInfo modemInfo = new ModemInfo(1, 2, true, false); - List logicalModemList = new ArrayList<>(); - logicalModemList.add(modemInfo); - return new PhoneCapability(maxActiveVoiceCalls, maxActiveData, - logicalModemList, false, deviceNrCapabilities); - } - - private NetworkScanRequest createNetworkScanRequest( - List radioAccessSpecifiers) { - return new NetworkScanRequest( - NetworkScanRequest.SCAN_TYPE_ONE_SHOT, - radioAccessSpecifiers.toArray( - new RadioAccessSpecifier[radioAccessSpecifiers.size()]), - mNetworkScanHelper.SEARCH_PERIODICITY_SEC, - mNetworkScanHelper.MAX_SEARCH_TIME_SEC, - mNetworkScanHelper.INCREMENTAL_RESULTS, - mNetworkScanHelper.INCREMENTAL_RESULTS_PERIODICITY_SEC, - null /* List of PLMN ids (MCC-MNC) */); - } - - private void startNetworkScan_incremental(boolean waitForCompletion) { - mNetworkScanHelper.startNetworkScan( - NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS); - if (!waitForCompletion) { - mNetworkScanHelper.stopNetworkQuery(); - } - } - -} diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java index 080534eeb7b..a4657cee8a0 100644 --- a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java @@ -83,7 +83,6 @@ public class NetworkSelectSettingsTest { public Context mContext; public PreferenceCategory mPreferenceCategory; - public boolean mIsAggregationEnabled = true; private TargetClass mNetworkSelectSettings; @@ -104,12 +103,13 @@ public class NetworkSelectSettingsTest { doReturn(mCellId2).when(mCellInfo2).getCellIdentity(); doReturn(mock(CellSignalStrength.class)).when(mCellInfo2).getCellSignalStrength(); doReturn(CARRIER_NAME2).when(mCellId2).getOperatorAlphaLong(); - mIsAggregationEnabled = true; mNetworkSelectSettings = spy(new TargetClass(this)); PersistableBundle config = new PersistableBundle(); config.putBoolean(CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, true); - doReturn(config).when(mCarrierConfigManager).getConfigForSubId(SUB_ID); + doReturn(config).when(mCarrierConfigManager).getConfigForSubId(SUB_ID, + CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, + CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL); doReturn(TelephonyManager.DATA_CONNECTED).when(mTelephonyManager).getDataState(); } @@ -174,11 +174,6 @@ public class NetworkSelectSettingsTest { return pref; } - @Override - protected boolean enableAggregation(Context context) { - return mTestEnv.mIsAggregationEnabled; - } - @Override protected int getSubId() { return SUB_ID; @@ -210,77 +205,7 @@ public class NetworkSelectSettingsTest { } @Test - public void doAggregation_hasDuplicateItemsDiffCellIdCase1_removeSamePlmnRatItem() { - mNetworkSelectSettings.onCreateInitialization(); - List testList = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createLteCellInfo(true, 1234, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB")); - List expected = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); - } - - @Test - public void doAggregation_hasDuplicateItemsDiffCellIdCase2_removeSamePlmnRatItem() { - mNetworkSelectSettings.onCreateInitialization(); - List testList = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB"), - createLteCellInfo(false, 1234, "123", "232", "CarrierB"), - createGsmCellInfo(false, 1234, "123", "232", "CarrierB")); - List expected = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB"), - createLteCellInfo(false, 1234, "123", "232", "CarrierB")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); - } - - @Test - public void doAggregation_hasDuplicateItemsDiffMccMncCase1_removeSamePlmnRatItem() { - mNetworkSelectSettings.onCreateInitialization(); - List testList = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createLteCellInfo(true, 123, "456", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB")); - List expected = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); - } - - @Test - public void doAggregation_hasDuplicateItemsDiffMccMncCase2_removeSamePlmnRatItem() { - mNetworkSelectSettings.onCreateInitialization(); - List testList = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB"), - createLteCellInfo(false, 1234, "123", "232", "CarrierB"), - createGsmCellInfo(false, 123, "456", "232", "CarrierB")); - List expected = Arrays.asList( - createLteCellInfo(true, 123, "123", "232", "CarrierA"), - createGsmCellInfo(false, 123, "123", "232", "CarrierB"), - createLteCellInfo(false, 1234, "123", "232", "CarrierB")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); - } - - @Test - public void doAggregation_hasDuplicateItemsDiffMccMncCase3_removeSamePlmnRatItem() { - mNetworkSelectSettings.onCreateInitialization(); - List testList = Arrays.asList( - createLteCellInfo(false, 123, "123", "232", "CarrierA"), - createLteCellInfo(false, 124, "123", "233", "CarrierA"), - createLteCellInfo(true, 125, "123", "234", "CarrierA"), - createGsmCellInfo(false, 126, "456", "232", "CarrierA")); - List expected = Arrays.asList( - createLteCellInfo(true, 125, "123", "234", "CarrierA"), - createGsmCellInfo(false, 126, "456", "232", "CarrierA")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); - } - - @Test - public void doAggregation_filterOutSatellitePlmn_whenKeyIsTrue() { + public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenKeyIsTrue() { PersistableBundle config = new PersistableBundle(); config.putBoolean( CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); @@ -304,11 +229,11 @@ public class NetworkSelectSettingsTest { List expected = Arrays.asList( createGsmCellInfo(false, 123, "123", "233", "CarrierB"), createLteCellInfo(false, 1234, "123", "234", "CarrierC")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); + assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected); } @Test - public void doAggregation_filterOutSatellitePlmn_whenNoSatellitePlmnIsAvailable() { + public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenNoSatellitePlmnIsAvailable() { PersistableBundle config = new PersistableBundle(); config.putBoolean( CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); @@ -336,17 +261,17 @@ public class NetworkSelectSettingsTest { createGsmCellInfo(false, 123, "123", "233", "CarrierB"), createLteCellInfo(false, 1234, "123", "234", "CarrierC"), createGsmCellInfo(false, 12345, "123", "235", "CarrierD")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); + assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected); // Expect no filter out when KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL is false. config.putBoolean( CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, false); mNetworkSelectSettings.onCreateInitialization(); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); + assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected); } @Test - public void doAggregation_filterOutSatellitePlmn_whenKeyIsFalse() { + public void filterOutSatellitePlmn_filterOutSatellitePlmn_whenKeyIsFalse() { PersistableBundle config = new PersistableBundle(); config.putBoolean( CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); @@ -372,7 +297,7 @@ public class NetworkSelectSettingsTest { createGsmCellInfo(false, 123, "123", "233", "CarrierB"), createLteCellInfo(false, 1234, "123", "234", "CarrierC"), createGsmCellInfo(false, 12345, "123", "235", "CarrierD")); - assertThat(mNetworkSelectSettings.doAggregation(testList)).isEqualTo(expected); + assertThat(mNetworkSelectSettings.filterOutSatellitePlmn(testList)).isEqualTo(expected); } private CellInfoLte createLteCellInfo(boolean registered, int cellId, String mcc, String mnc, From 99b09df4a8d612829df8667eb17f147fadc20cfd Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 10 May 2024 14:59:04 +0800 Subject: [PATCH 12/19] Fix empty network scan result Settings use TelephonyScanManager.NetworkScanCallback to scan, and its onComplete() could happens before onResults(). We will change Settings to fix. Fix: 338986191 Test: manual - on NetworkSelectSettings Test: unit test Change-Id: If41d957f916a99eacc1becb6b460e58722a4dca7 Merged-In: If41d957f916a99eacc1becb6b460e58722a4dca7 --- .../telephony/NetworkSelectSettings.java | 106 ++++-------------- .../telephony/scan/NetworkScanRepository.kt | 38 ++++--- .../scan/NetworkScanRepositoryTest.kt | 36 ++++-- .../telephony/NetworkSelectSettingsTest.java | 14 ++- 4 files changed, 81 insertions(+), 113 deletions(-) diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java index 33ece6be6fe..5995a13d7ef 100644 --- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java +++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java @@ -50,16 +50,13 @@ import com.android.internal.telephony.flags.Flags; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.network.telephony.scan.NetworkScanRepository; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.utils.ThreadUtils; +import com.google.common.collect.ImmutableList; + import kotlin.Unit; -import kotlin.jvm.functions.Function1; import java.util.ArrayList; import java.util.Arrays; @@ -87,7 +84,8 @@ public class NetworkSelectSettings extends DashboardFragment { private View mProgressHeader; private Preference mStatusMessagePreference; @VisibleForTesting - List mCellInfoList; + @NonNull + List mCellInfoList = ImmutableList.of(); private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private TelephonyManager mTelephonyManager; private SatelliteManager mSatelliteManager; @@ -100,7 +98,6 @@ public class NetworkSelectSettings extends DashboardFragment { private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean(); private NetworkScanRepository mNetworkScanRepository; - private boolean mUpdateScanResult = false; @Override public void onCreate(Bundle icicle) { @@ -208,40 +205,14 @@ public class NetworkSelectSettings extends DashboardFragment { } private void launchNetworkScan() { - mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), new Function1<>() { - @Override - public Unit invoke(@NonNull NetworkScanResult networkScanResult) { - if (!mUpdateScanResult) { - // Not update UI if not in scan mode. - return Unit.INSTANCE; - } - if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) { - scanResultHandler(networkScanCellInfos.getCellInfos()); - return Unit.INSTANCE; - } - if (!isPreferenceScreenEnabled()) { - clearPreferenceSummary(); - enablePreferenceScreen(true); - } else if (networkScanResult instanceof NetworkScanComplete - && mCellInfoList == null) { - // In case the scan timeout before getting any results - addMessagePreference(R.string.empty_networks_list); - } else if (networkScanResult instanceof NetworkScanError) { - addMessagePreference(R.string.network_query_error); - } - - return Unit.INSTANCE; - } - }); - } - - @Override - public void onStart() { - super.onStart(); - - updateForbiddenPlmns(); setProgressBarVisible(true); - mUpdateScanResult = true; + mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), (networkScanResult) -> { + if (isPreferenceScreenEnabled()) { + scanResultHandler(networkScanResult); + } + + return Unit.INSTANCE; + }); } /** @@ -267,8 +238,6 @@ public class NetworkSelectSettings extends DashboardFragment { return false; } - mUpdateScanResult = false; - // Refresh the last selected item in case users reselect network. clearPreferenceSummary(); if (mSelectedPreference != null) { @@ -373,27 +342,19 @@ public class NetworkSelectSettings extends DashboardFragment { } } - @Keep @VisibleForTesting - protected void scanResultHandler(List results) { - mCellInfoList = filterOutSatellitePlmn(results); + protected void scanResultHandler(NetworkScanRepository.NetworkScanResult results) { + mCellInfoList = filterOutSatellitePlmn(results.getCellInfos()); Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList)); - if (mCellInfoList != null && mCellInfoList.size() != 0) { - final NetworkOperatorPreference connectedPref = updateAllPreferenceCategory(); - if (connectedPref != null) { - // update selected preference instance into connected preference - if (mSelectedPreference != null) { - mSelectedPreference = connectedPref; - } - } else if (!isPreferenceScreenEnabled()) { - mSelectedPreference.setSummary(R.string.network_connecting); - } - enablePreferenceScreen(true); - } else if (isPreferenceScreenEnabled()) { + updateAllPreferenceCategory(); + NetworkScanRepository.NetworkScanState state = results.getState(); + if (state == NetworkScanRepository.NetworkScanState.ERROR) { + addMessagePreference(R.string.network_query_error); + } else if (mCellInfoList.isEmpty()) { addMessagePreference(R.string.empty_networks_list); - // keep showing progress bar, it will be stopped when error or completed - setProgressBarVisible(true); } + // keep showing progress bar, it will be stopped when error or completed + setProgressBarVisible(state == NetworkScanRepository.NetworkScanState.ACTIVE); } @Keep @@ -410,11 +371,8 @@ public class NetworkSelectSettings extends DashboardFragment { /** * Update the content of network operators list. - * - * @return preference which shows connected */ - @Nullable - private NetworkOperatorPreference updateAllPreferenceCategory() { + private void updateAllPreferenceCategory() { int numberOfPreferences = mPreferenceCategory.getPreferenceCount(); // remove unused preferences @@ -425,7 +383,6 @@ public class NetworkSelectSettings extends DashboardFragment { } // update the content of preference - NetworkOperatorPreference connectedPref = null; for (int index = 0; index < mCellInfoList.size(); index++) { final CellInfo cellInfo = mCellInfoList.get(index); @@ -450,23 +407,10 @@ public class NetworkSelectSettings extends DashboardFragment { if (mCellInfoList.get(index).isRegistered()) { pref.setSummary(R.string.network_connected); - connectedPref = pref; } else { pref.setSummary(null); } } - - // update selected preference instance by index - for (int index = 0; index < mCellInfoList.size(); index++) { - final CellInfo cellInfo = mCellInfoList.get(index); - - if ((mSelectedPreference != null) && mSelectedPreference.isSameCell(cellInfo)) { - mSelectedPreference = (NetworkOperatorPreference) - (mPreferenceCategory.getPreference(index)); - } - } - - return connectedPref; } /** @@ -535,13 +479,6 @@ public class NetworkSelectSettings extends DashboardFragment { } } - private boolean isProgressBarVisible() { - if (mProgressHeader == null) { - return false; - } - return (mProgressHeader.getVisibility() == View.VISIBLE); - } - protected void setProgressBarVisible(boolean visible) { if (mProgressHeader != null) { mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); @@ -549,7 +486,6 @@ public class NetworkSelectSettings extends DashboardFragment { } private void addMessagePreference(int messageId) { - setProgressBarVisible(false); mStatusMessagePreference.setTitle(messageId); mPreferenceCategory.removeAll(); mPreferenceCategory.addPreference(mStatusMessagePreference); diff --git a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt index 2067b8c3be5..0344002c48d 100644 --- a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt +++ b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt @@ -27,7 +27,6 @@ import android.telephony.TelephonyScanManager import android.util.Log import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner -import com.android.settings.network.telephony.CellInfoUtil import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.Dispatchers @@ -35,14 +34,19 @@ import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEach class NetworkScanRepository(context: Context, subId: Int) { - sealed interface NetworkScanResult + enum class NetworkScanState { + ACTIVE, COMPLETE, ERROR + } - data class NetworkScanCellInfos(val cellInfos: List) : NetworkScanResult - data object NetworkScanComplete : NetworkScanResult - data class NetworkScanError(val error: Int) : NetworkScanResult + data class NetworkScanResult( + val state: NetworkScanState, + val cellInfos: List, + ) private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId) @@ -65,23 +69,29 @@ class NetworkScanRepository(context: Context, subId: Int) { } fun networkScanFlow(): Flow = callbackFlow { + var state = NetworkScanState.ACTIVE + var cellInfos: List = emptyList() + val callback = object : TelephonyScanManager.NetworkScanCallback() { override fun onResults(results: List) { - val cellInfos = results.distinctBy { CellInfoScanKey(it) } - trySend(NetworkScanCellInfos(cellInfos)) - Log.d(TAG, "CellInfoList: ${CellInfoUtil.cellInfoListToString(cellInfos)}") + cellInfos = results.distinctBy { CellInfoScanKey(it) } + sendResult() } override fun onComplete() { - trySend(NetworkScanComplete) - close() - Log.d(TAG, "onComplete") + state = NetworkScanState.COMPLETE + sendResult() + // Don't call close() here since onComplete() could happens before onResults() } override fun onError(error: Int) { - trySend(NetworkScanError(error)) + state = NetworkScanState.ERROR + sendResult() close() - Log.d(TAG, "onError: $error") + } + + private fun sendResult() { + trySend(NetworkScanResult(state, cellInfos)) } } @@ -92,7 +102,7 @@ class NetworkScanRepository(context: Context, subId: Int) { ) awaitClose { networkScan.stopScan() } - }.flowOn(Dispatchers.Default) + }.conflate().onEach { Log.d(TAG, "networkScanFlow: $it") }.flowOn(Dispatchers.Default) /** Create network scan for allowed network types. */ private fun createNetworkScan(): NetworkScanRequest { diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt index 070c779b5ea..c0b918fcf5d 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt @@ -32,9 +32,6 @@ import android.telephony.TelephonyManager.NETWORK_CLASS_BITMASK_5G import android.telephony.TelephonyScanManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.toListWithTimeout import com.google.common.truth.Truth.assertThat @@ -88,7 +85,12 @@ class NetworkScanRepositoryTest { callback?.onResults(cellInfos) - assertThat(listDeferred.await()).containsExactly(NetworkScanCellInfos(cellInfos)) + assertThat(listDeferred.await()).containsExactly( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ACTIVE, + cellInfos = cellInfos, + ) + ) } @Test @@ -100,7 +102,12 @@ class NetworkScanRepositoryTest { callback?.onComplete() - assertThat(listDeferred.await()).containsExactly(NetworkScanComplete) + assertThat(listDeferred.await()).containsExactly( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.COMPLETE, + cellInfos = emptyList(), + ) + ) } @Test @@ -112,7 +119,12 @@ class NetworkScanRepositoryTest { callback?.onError(1) - assertThat(listDeferred.await()).containsExactly(NetworkScanError(1)) + assertThat(listDeferred.await()).containsExactly( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ERROR, + cellInfos = emptyList(), + ) + ) } @Test @@ -133,12 +145,13 @@ class NetworkScanRepositoryTest { callback?.onResults(cellInfos) assertThat(listDeferred.await()).containsExactly( - NetworkScanCellInfos( - listOf( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ACTIVE, + cellInfos = listOf( createCellInfoLte("123", false), createCellInfoLte("124", true), createCellInfoGsm("123", false), - ) + ), ) ) } @@ -162,8 +175,9 @@ class NetworkScanRepositoryTest { callback?.onResults(cellInfos) assertThat(listDeferred.await()).containsExactly( - NetworkScanCellInfos( - listOf( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ACTIVE, + cellInfos = listOf( createCellInfoLte("123", false), createCellInfoLte("123", true), createCellInfoLte("124", false), diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java index a4657cee8a0..d71af84dcf8 100644 --- a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java @@ -44,8 +44,12 @@ import androidx.preference.PreferenceManager; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.network.telephony.scan.NetworkScanRepository; +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -163,8 +167,7 @@ public class NetworkSelectSettingsTest { } @Override - protected NetworkOperatorPreference - createNetworkOperatorPreference(CellInfo cellInfo) { + protected NetworkOperatorPreference createNetworkOperatorPreference(CellInfo cellInfo) { NetworkOperatorPreference pref = super.createNetworkOperatorPreference(cellInfo); if (cellInfo == mTestEnv.mCellInfo1) { pref.updateCell(cellInfo, mTestEnv.mCellId1); @@ -183,9 +186,14 @@ public class NetworkSelectSettingsTest { @Test @UiThreadTest public void updateAllPreferenceCategory_correctOrderingPreference() { + NetworkScanResult result = new NetworkScanResult( + NetworkScanRepository.NetworkScanState.COMPLETE, + ImmutableList.of(mCellInfo1, mCellInfo2)); mNetworkSelectSettings.onCreateInitialization(); mNetworkSelectSettings.enablePreferenceScreen(true); - mNetworkSelectSettings.scanResultHandler(Arrays.asList(mCellInfo1, mCellInfo2)); + + mNetworkSelectSettings.scanResultHandler(result); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2); final NetworkOperatorPreference preference = (NetworkOperatorPreference) mPreferenceCategory.getPreference(1); From 8bcaab7cc8e566e9a800856abc126725b0f717eb Mon Sep 17 00:00:00 2001 From: Yiling Chuang Date: Thu, 9 May 2024 10:52:30 +0000 Subject: [PATCH 13/19] Update the flow of charging optimization strings. Bug: 329020954 Test: atest SettingsRoboTests Change-Id: I73738602036d79568fe4739a63534af1c64bd9d1 --- .../BatteryHeaderPreferenceController.java | 7 +- .../settings/fuelgauge/BatteryInfo.java | 53 ++++++++------- .../BatterySettingsFeatureProvider.java | 9 ++- .../BatterySettingsFeatureProviderImpl.java | 9 ++- .../TopLevelBatteryPreferenceController.java | 5 ++ ...BatteryHeaderPreferenceControllerTest.java | 66 +++++++++++++++++-- .../settings/fuelgauge/BatteryInfoTest.java | 6 +- ...atterySettingsFeatureProviderImplTest.java | 8 ++- 8 files changed, 122 insertions(+), 41 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index 06c3ceeca14..8467b2f85c1 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -86,13 +86,14 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController return mContext.getString( com.android.settingslib.R.string.battery_info_status_charging_on_hold); } + if (info.remainingLabel != null + && mBatterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) { + return info.remainingLabel; + } if (info.remainingLabel == null || info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { return info.statusLabel; } - if (mBatterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) { - return info.remainingLabel; - } if (info.pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) { final CharSequence wirelessChargingLabel = mBatterySettingsFeatureProvider.getWirelessChargingLabel(mContext, info); diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index c333a33d67f..b54801a677a 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -390,12 +390,37 @@ public class BatteryInfo { info.remainingLabel = null; int chargingLimitedResId = R.string.power_charging_limited; info.chargeLabel = context.getString(chargingLimitedResId, info.batteryPercentString); - } else if ((chargeTimeMs > 0 + return; + } + final BatterySettingsFeatureProvider featureProvider = + FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider(); + if (featureProvider.isChargingOptimizationMode(context)) { + final CharSequence chargeLabel = + featureProvider.getChargingOptimizationChargeLabel( + context, + info.batteryLevel, + info.batteryPercentString, + chargeTimeMs, + currentTimeMs); + if (chargeLabel != null) { + final CharSequence remainingLabel = + featureProvider.getChargingOptimizationRemainingLabel( + context, + info.batteryLevel, + info.pluggedStatus, + chargeTimeMs, + currentTimeMs); + if (remainingLabel != null) { + info.chargeLabel = chargeLabel; + info.remainingLabel = remainingLabel; + return; + } + } + } + if ((chargeTimeMs > 0 && status != BatteryManager.BATTERY_STATUS_FULL && dockDefenderMode == BatteryUtils.DockDefenderMode.DISABLED) || dockDefenderMode == BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED) { - final BatterySettingsFeatureProvider featureProvider = - FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider(); // Battery is charging to full info.remainingTimeUs = PowerUtil.convertMsToUs(chargeTimeMs); int resId = getChargingDurationResId(info.isFastCharging); @@ -419,8 +444,7 @@ public class BatteryInfo { info.batteryPercentString, chargeTimeMs, info.isFastCharging, - currentTimeMs, - featureProvider); + currentTimeMs); } else if (dockDefenderMode == BatteryUtils.DockDefenderMode.FUTURE_BYPASS) { // Dock defender will be triggered in the future, charging will be optimized. info.chargeLabel = @@ -447,14 +471,6 @@ public class BatteryInfo { int pluggedStatus, long currentTimeMs, BatterySettingsFeatureProvider featureProvider) { - if (featureProvider.isChargingOptimizationMode(context)) { - final CharSequence chargingOptimizationRemainingLabel = - featureProvider.getChargingOptimizationRemainingLabel( - context, chargeRemainingTimeMs, currentTimeMs); - if (chargingOptimizationRemainingLabel != null) { - return chargingOptimizationRemainingLabel; - } - } if (pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) { final CharSequence wirelessChargingRemainingLabel = featureProvider.getWirelessChargingRemainingLabel( @@ -488,16 +504,7 @@ public class BatteryInfo { String batteryPercentString, long chargeTimeMs, boolean isFastCharging, - long currentTimeMs, - BatterySettingsFeatureProvider featureProvider) { - if (featureProvider.isChargingOptimizationMode(context)) { - final CharSequence chargingOptimizationChargeLabel = - featureProvider.getChargingOptimizationChargeLabel( - context, batteryPercentString, chargeTimeMs, currentTimeMs); - if (chargingOptimizationChargeLabel != null) { - return chargingOptimizationChargeLabel; - } - } + long currentTimeMs) { if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { var timeString = PowerUtil.getTargetTimeShortString(context, chargeTimeMs, currentTimeMs); diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java index ab6d5acc1eb..454a2956861 100644 --- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java @@ -60,13 +60,18 @@ public interface BatterySettingsFeatureProvider { /** Return a charging remaining time label for charging optimization mode. */ @Nullable CharSequence getChargingOptimizationRemainingLabel( - @NonNull Context context, long chargeRemainingTimeMs, long currentTimeMs); + @NonNull Context context, + int batteryLevel, + int pluggedStatus, + long chargeRemainingTimeMs, + long currentTimeMs); /** Return a charge label for charging optimization mode. */ @Nullable CharSequence getChargingOptimizationChargeLabel( @NonNull Context context, - @NonNull String batteryPercentageString, + int batteryLevel, + String batteryPercentageString, long chargeRemainingTimeMs, long currentTimeMs); } diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java index b2db3470fa9..40cfd0cb715 100644 --- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java @@ -76,7 +76,11 @@ public class BatterySettingsFeatureProviderImpl implements BatterySettingsFeatur @Nullable @Override public CharSequence getChargingOptimizationRemainingLabel( - @NonNull Context context, long chargeRemainingTimeMs, long currentTimeMs) { + @NonNull Context context, + int batteryLevel, + int pluggedStatus, + long chargeRemainingTimeMs, + long currentTimeMs) { return null; } @@ -84,7 +88,8 @@ public class BatterySettingsFeatureProviderImpl implements BatterySettingsFeatur @Override public CharSequence getChargingOptimizationChargeLabel( @NonNull Context context, - @NonNull String batteryPercentageString, + int batteryLevel, + String batteryPercentageString, long chargeRemainingTimeMs, long currentTimeMs) { return null; diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java index 08d49f1a632..fd0d820feb1 100644 --- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java @@ -159,6 +159,11 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle com.android.settingslib.R.string.power_charging_on_hold_settings_home_page, info.batteryPercentString); } + final BatterySettingsFeatureProvider featureProvider = + FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider(); + if (info.chargeLabel != null && featureProvider.isChargingOptimizationMode(mContext)) { + return info.chargeLabel; + } if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { // Present status only if no remaining time or status anomalous return info.statusLabel; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java index bc6dadc72ac..1899eab7c51 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java @@ -286,8 +286,9 @@ public class BatteryHeaderPreferenceControllerTest { /* isFastCharging= */ true, /* isChargingStringV2= */ true); batteryInfo.pluggedStatus = BatteryManager.BATTERY_PLUGGED_WIRELESS; - when(mFactory.batterySettingsFeatureProvider.getWirelessChargingLabel(eq(mContext), - any(BatteryInfo.class))).thenReturn(label); + when(mFactory.batterySettingsFeatureProvider.getWirelessChargingLabel( + eq(mContext), any(BatteryInfo.class))) + .thenReturn(label); mController.updateBatteryStatus(/* label= */ null, batteryInfo); @@ -326,14 +327,64 @@ public class BatteryHeaderPreferenceControllerTest { verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); } + @Test + public void updateBatteryStatus_chargingOptimizationMode_remainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "Expected remaining label", + /* statusLabel= */ "Fast Charging", + /* isFastCharging= */ true, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.remainingLabel; + when(mFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) + .thenReturn(true); + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + @Test + public void updateBatteryStatus_chargingOptimizationModeNoRemainingLabel_statusLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ null, + /* statusLabel= */ "Fast Charging", + /* isFastCharging= */ true, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.statusLabel; + when(mFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) + .thenReturn(true); + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + @Test + public void updateBatteryStatus_notChargingOptimizationMode_statusWithRemainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "Full by 1:30 PM", + /* statusLabel= */ "Fast Charging", + /* isFastCharging= */ true, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.statusLabel + " • " + batteryInfo.remainingLabel; + when(mFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) + .thenReturn(false); + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + private BatteryInfo arrangeUpdateBatteryStatusTestWithRemainingLabel( String remainingLabel, String statusLabel, boolean isFastCharging, boolean isChargingStringV2) { SystemProperties.set( - BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY, - String.valueOf(isChargingStringV2)); + BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY, String.valueOf(isChargingStringV2)); mBatteryInfo.isBatteryDefender = false; mBatteryInfo.remainingLabel = remainingLabel; mBatteryInfo.statusLabel = statusLabel; @@ -389,8 +440,11 @@ public class BatteryHeaderPreferenceControllerTest { mController.updateHeaderPreference(mBatteryInfo); - verify(mBatteryUsageProgressBarPref).setBottomSummary(mContext.getString( - com.android.settingslib.R.string.battery_info_status_charging_on_hold)); + verify(mBatteryUsageProgressBarPref) + .setBottomSummary( + mContext.getString( + com.android.settingslib.R.string + .battery_info_status_charging_on_hold)); } @Test diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java index a1081f4519e..7bafc6d5198 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java @@ -724,15 +724,15 @@ public class BatteryInfoTest { Intent batteryIntent = createIntentForGetBatteryInfoTest( ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 65); - var expectedRemainingLabel = "Done charging by"; + var expectedRemainingLabel = "Expected remaining label"; var expectedChargeLabel = "65% - " + expectedRemainingLabel; when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) .thenReturn(true); when(mFeatureFactory.batterySettingsFeatureProvider.getChargingOptimizationRemainingLabel( - eq(mContext), anyLong(), anyLong())) + eq(mContext), anyInt(), anyInt(), anyLong(), anyLong())) .thenReturn(expectedRemainingLabel); when(mFeatureFactory.batterySettingsFeatureProvider.getChargingOptimizationChargeLabel( - eq(mContext), anyString(), anyLong(), anyLong())) + eq(mContext), anyInt(), anyString(), anyLong(), anyLong())) .thenReturn(expectedChargeLabel); var expectedStatusLabel = "Charging"; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java index c55a121785e..9f04892a3e5 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java @@ -19,6 +19,7 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.os.BatteryManager; import androidx.test.core.app.ApplicationProvider; @@ -87,12 +88,15 @@ public class BatterySettingsFeatureProviderImplTest { @Test public void getChargingOptimizationRemainingLabel_default_returnNull() { - assertThat(mImpl.getChargingOptimizationRemainingLabel(mContext, 1000L, 1000L)).isNull(); + assertThat( + mImpl.getChargingOptimizationRemainingLabel( + mContext, 75, BatteryManager.BATTERY_PLUGGED_AC, 1000L, 1000L)) + .isNull(); } @Test public void getChargingOptimizationChargeLabel_default_returnNull() { - assertThat(mImpl.getChargingOptimizationChargeLabel(mContext, "70%", 1000L, 1000L)) + assertThat(mImpl.getChargingOptimizationChargeLabel(mContext, 70, "70%", 1000L, 1000L)) .isNull(); } } From 7489c93347f6868065b8fbf3c51cd4b25feec0ff Mon Sep 17 00:00:00 2001 From: songferngwang Date: Wed, 15 May 2024 09:09:57 +0000 Subject: [PATCH 14/19] Fix the BasePreferenceController.createInstance's IllegalStateException When the settings search do BasePreferenceController.createInstance, it get the the IllegalStateException, and then the search ignore this controller. It causes the indexing can not filter this controller via the PreferenceController's getAvailabilityStatus. Bug: 335509130 Test: Build pass. Do search and no crash Change-Id: Icaff8ca690ca3e3a46c2f21a5fbde0bbd99fd92e --- .../CallsDefaultSubscriptionController.java | 4 ++++ .../telephony/ConvertToEsimPreferenceController.java | 8 ++++++-- .../telephony/DefaultSubscriptionController.java | 12 ++++++++---- .../telephony/MobileDataPreferenceController.java | 10 +++++++--- .../telephony/RoamingPreferenceController.java | 10 +++++++--- .../telephony/SmsDefaultSubscriptionController.java | 6 ++++++ 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java b/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java index eb833b1ae1b..b56c1b93d16 100644 --- a/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java +++ b/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java @@ -31,6 +31,10 @@ public class CallsDefaultSubscriptionController extends DefaultSubscriptionContr super(context, preferenceKey, lifecycle, lifecycleOwner); } + public CallsDefaultSubscriptionController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + @Override protected int getDefaultSubscriptionId() { int defaultCallSubId = SubscriptionManager.getDefaultVoiceSubscriptionId(); diff --git a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java index 08e993b6cde..21cceb5b68f 100644 --- a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java +++ b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java @@ -69,15 +69,19 @@ public class ConvertToEsimPreferenceController extends TelephonyBasePreferenceCo public ConvertToEsimPreferenceController(Context context, String key, Lifecycle lifecycle, LifecycleOwner lifecycleOwner, int subId) { - super(context, key); + this(context, key); mSubId = subId; - mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); mLifecycleOwner = lifecycleOwner; if (lifecycle != null) { lifecycle.addObserver(this); } } + public ConvertToEsimPreferenceController(Context context, String key) { + super(context, key); + mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); + } + public void init(int subId, SubscriptionInfoEntity subInfoEntity) { mSubId = subId; mSubscriptionInfoEntity = subInfoEntity; diff --git a/src/com/android/settings/network/telephony/DefaultSubscriptionController.java b/src/com/android/settings/network/telephony/DefaultSubscriptionController.java index 96c39f27df6..e5a4b4657af 100644 --- a/src/com/android/settings/network/telephony/DefaultSubscriptionController.java +++ b/src/com/android/settings/network/telephony/DefaultSubscriptionController.java @@ -64,16 +64,20 @@ public abstract class DefaultSubscriptionController extends TelephonyBasePrefere public DefaultSubscriptionController(Context context, String preferenceKey, Lifecycle lifecycle, LifecycleOwner lifecycleOwner) { + this(context, preferenceKey); + mLifecycleOwner = lifecycleOwner; + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + public DefaultSubscriptionController(Context context, String preferenceKey) { super(context, preferenceKey); mManager = context.getSystemService(SubscriptionManager.class); mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); mDataSubscriptionChangedReceiver = new DefaultSubscriptionReceiver(context, this); - mLifecycleOwner = lifecycleOwner; - if (lifecycle != null) { - lifecycle.addObserver(this); - } } /** @return the id of the default subscription for the service, or diff --git a/src/com/android/settings/network/telephony/MobileDataPreferenceController.java b/src/com/android/settings/network/telephony/MobileDataPreferenceController.java index bec7ee7459c..28c05c5c64e 100644 --- a/src/com/android/settings/network/telephony/MobileDataPreferenceController.java +++ b/src/com/android/settings/network/telephony/MobileDataPreferenceController.java @@ -72,16 +72,20 @@ public class MobileDataPreferenceController extends TelephonyTogglePreferenceCon public MobileDataPreferenceController(Context context, String key, Lifecycle lifecycle, LifecycleOwner lifecycleOwner, int subId) { - super(context, key); + this(context, key); mSubId = subId; - mSubscriptionManager = context.getSystemService(SubscriptionManager.class); - mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); mLifecycleOwner = lifecycleOwner; if (lifecycle != null) { lifecycle.addObserver(this); } } + public MobileDataPreferenceController(Context context, String key) { + super(context, key); + mSubscriptionManager = context.getSystemService(SubscriptionManager.class); + mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); + } + @Override public int getAvailabilityStatus(int subId) { if (Flags.isDualSimOnboardingEnabled()) { diff --git a/src/com/android/settings/network/telephony/RoamingPreferenceController.java b/src/com/android/settings/network/telephony/RoamingPreferenceController.java index fb8cd519e27..bf02308be39 100644 --- a/src/com/android/settings/network/telephony/RoamingPreferenceController.java +++ b/src/com/android/settings/network/telephony/RoamingPreferenceController.java @@ -63,16 +63,20 @@ public class RoamingPreferenceController extends TelephonyTogglePreferenceContro public RoamingPreferenceController(Context context, String key, Lifecycle lifecycle, LifecycleOwner lifecycleOwner, int subId) { - super(context, key); + this(context, key); mSubId = subId; - mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class); - mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); mLifecycleOwner = lifecycleOwner; if (lifecycle != null) { lifecycle.addObserver(this); } } + public RoamingPreferenceController(Context context, String key) { + super(context, key); + mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class); + mMobileNetworkRepository = MobileNetworkRepository.getInstance(context); + } + @Override public int getAvailabilityStatus() { final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId); diff --git a/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java b/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java index c49647dd105..c35a78c550d 100644 --- a/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java +++ b/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java @@ -35,6 +35,12 @@ public class SmsDefaultSubscriptionController extends DefaultSubscriptionControl .getBoolean(com.android.internal.R.bool.config_sms_ask_every_time_support); } + public SmsDefaultSubscriptionController(Context context, String preferenceKey) { + super(context, preferenceKey); + mIsAskEverytimeSupported = mContext.getResources() + .getBoolean(com.android.internal.R.bool.config_sms_ask_every_time_support); + } + @Override protected int getDefaultSubscriptionId() { int defaultSmsSubId = SubscriptionManager.getDefaultSmsSubscriptionId(); From 3ceffc39f9f439e2a2c5890bada0b82598b573cc Mon Sep 17 00:00:00 2001 From: Wesley Wang Date: Wed, 15 May 2024 18:46:31 +0800 Subject: [PATCH 15/19] Update battery settings page loading text - Update the loading text into empty space text to prevent layout flaky before info loaded Bug: N/A Test: atest SettingsRoboTests Change-Id: I272405f5045a682533a4e9405a9fd91f6c507f7e --- .../fuelgauge/BatteryHeaderPreferenceController.java | 5 ++--- .../fuelgauge/BatteryHeaderPreferenceControllerTest.java | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index ec63c9a9800..42379b46bf1 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -59,9 +59,8 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mBatteryUsageProgressBarPref = screen.findPreference(getPreferenceKey()); - // Set up loading text first to prevent layout flaky before info loaded. - mBatteryUsageProgressBarPref.setBottomSummary( - mContext.getString(R.string.settings_license_activity_loading)); + // Set up empty space text first to prevent layout flaky before info loaded. + mBatteryUsageProgressBarPref.setBottomSummary(" "); if (com.android.settings.Utils.isBatteryPresent(mContext)) { quickUpdateHeaderPreference(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java index 9fa965156e1..1b8d24e3e12 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java @@ -35,7 +35,6 @@ import android.text.TextUtils; import androidx.preference.PreferenceScreen; -import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip; @@ -374,11 +373,10 @@ public class BatteryHeaderPreferenceControllerTest { } @Test - public void displayPreference_init_showLoading() { + public void displayPreference_init_showEmptySpace() { mController.displayPreference(mPreferenceScreen); - verify(mBatteryUsageProgressBarPref) - .setBottomSummary(mContext.getString(R.string.settings_license_activity_loading)); + verify(mBatteryUsageProgressBarPref).setBottomSummary(" "); } private CharSequence formatBatteryPercentageText() { From 9c03ecfd9758e4334b14713f9f639276a28d6891 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 15 May 2024 11:05:10 +0000 Subject: [PATCH 16/19] Ignore test case with InitializationError See this run for example:https://android-build.corp.google.com/test_investigate/?invocationId=I49700010276869184&testResultId=TR14429303618451319 Bug: 340657656 Test: atest Change-Id: I89815f40ac8f3fedb0ddd2127e12412ff2dd7fce --- .../android/settings/datausage/DataUsageInfoControllerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/src/com/android/settings/datausage/DataUsageInfoControllerTest.java b/tests/unit/src/com/android/settings/datausage/DataUsageInfoControllerTest.java index cb5860a1806..4193b0ab002 100644 --- a/tests/unit/src/com/android/settings/datausage/DataUsageInfoControllerTest.java +++ b/tests/unit/src/com/android/settings/datausage/DataUsageInfoControllerTest.java @@ -23,10 +23,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settingslib.net.DataUsageController.DataUsageInfo; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) +@Ignore("b/340657656") public class DataUsageInfoControllerTest { private static final int ZERO = 0; From 72089c3289465b3a7ecbe220b1e2af9ef5a36540 Mon Sep 17 00:00:00 2001 From: Jigar Thakkar Date: Thu, 2 May 2024 11:18:36 +0000 Subject: [PATCH 17/19] Rename settings option for autolocking on inactivity Bug:328016823 Test: atest AutoLockPreferenceControllerTest Flag: ACONFIG android.multiuser.support_autolock_for_private_space NEXT Change-Id: Id4537efe5ac56df0fc98cef3ce6673a0673e0fba --- res/values/strings.xml | 4 ++-- .../autolock/AutoLockPreferenceControllerTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 1a83b79296e..f484bfefed6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1257,8 +1257,8 @@ You can lock your private space automatically if you haven\’t used your device for a period of time Every time device locks - - After 5 minutes of inactivity + + 5 minutes after screen timeout Only after device restarts diff --git a/tests/unit/src/com/android/settings/privatespace/autolock/AutoLockPreferenceControllerTest.java b/tests/unit/src/com/android/settings/privatespace/autolock/AutoLockPreferenceControllerTest.java index 3a605ea1727..fb2cdddd349 100644 --- a/tests/unit/src/com/android/settings/privatespace/autolock/AutoLockPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/privatespace/autolock/AutoLockPreferenceControllerTest.java @@ -127,7 +127,7 @@ public class AutoLockPreferenceControllerTest { Settings.Secure.PRIVATE_SPACE_AUTO_LOCK, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); assertThat(mAutoLockPreferenceController.getSummary().toString()) - .isEqualTo("After 5 minutes of inactivity"); + .isEqualTo("5 minutes after screen timeout"); } /** From 2fe1e08b435dda7df6b2d2a55b8f54cc0a19e141 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Wed, 15 May 2024 08:38:15 +0000 Subject: [PATCH 18/19] [ToA] Do not use spy context Test: atest TermsOfAddressFeminineControllerTest Test: atest TermsOfAddressNeutralControllerTest Test: atest TermsOfAddressNotSpecifiedControllerTest Test: atest TermsOfAddressMasculineControllerTest Bug: 339543490 Change-Id: I28c37db07b34b9da225f9d358159bc9064046b68 --- .../localepicker/TermsOfAddressFeminineControllerTest.java | 6 +----- .../localepicker/TermsOfAddressMasculineControllerTest.java | 6 +----- .../localepicker/TermsOfAddressNeutralControllerTest.java | 6 +----- .../TermsOfAddressNotSpecifiedControllerTest.java | 6 +----- 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java index 246fad62af9..215e58f6dc8 100644 --- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java +++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.spy; - import android.app.GrammaticalInflectionManager; import android.content.Context; import android.content.res.Configuration; @@ -34,7 +32,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.widget.TickButtonPreference; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -62,7 +59,7 @@ public class TermsOfAddressFeminineControllerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); + mContext = ApplicationProvider.getApplicationContext(); if (Looper.myLooper() == null) { Looper.prepare(); @@ -93,7 +90,6 @@ public class TermsOfAddressFeminineControllerTest { } @Test - @Ignore("b/339543490") public void displayPreference_setGrammaticalGenderIsFeminine_FeminineIsSelected() { TickButtonPreference selectedPreference = (TickButtonPreference) mPreferenceScreen.getPreference(2); diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java index f5ed3959b76..b4c88937d8e 100644 --- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java +++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.spy; - import android.app.GrammaticalInflectionManager; import android.content.Context; import android.content.res.Configuration; @@ -34,7 +32,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.widget.TickButtonPreference; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -62,7 +59,7 @@ public class TermsOfAddressMasculineControllerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); + mContext = ApplicationProvider.getApplicationContext(); if (Looper.myLooper() == null) { Looper.prepare(); @@ -93,7 +90,6 @@ public class TermsOfAddressMasculineControllerTest { } @Test - @Ignore("b/339543490") public void displayPreference_setGrammaticalGenderIsMasculine_MasculineIsSelected() { TickButtonPreference selectedPreference = (TickButtonPreference) mPreferenceScreen.getPreference(3); diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java index 0e53198188e..76aed0ac9ac 100644 --- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java +++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.spy; - import android.app.GrammaticalInflectionManager; import android.content.Context; import android.content.res.Configuration; @@ -34,7 +32,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.widget.TickButtonPreference; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -62,7 +59,7 @@ public class TermsOfAddressNeutralControllerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); + mContext = ApplicationProvider.getApplicationContext(); if (Looper.myLooper() == null) { Looper.prepare(); @@ -93,7 +90,6 @@ public class TermsOfAddressNeutralControllerTest { } @Test - @Ignore("b/339543490") public void displayPreference_setGrammaticalGenderIsNotSpecified_NeutralIsSelected() { TickButtonPreference selectedPreference = (TickButtonPreference) mPreferenceScreen.getPreference(4); diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNotSpecifiedControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNotSpecifiedControllerTest.java index 96bac08dde1..0f51b7dfff9 100644 --- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNotSpecifiedControllerTest.java +++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNotSpecifiedControllerTest.java @@ -18,8 +18,6 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.spy; - import android.app.GrammaticalInflectionManager; import android.content.Context; import android.content.res.Configuration; @@ -34,7 +32,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.widget.TickButtonPreference; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @@ -62,7 +59,7 @@ public class TermsOfAddressNotSpecifiedControllerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); + mContext = ApplicationProvider.getApplicationContext(); if (Looper.myLooper() == null) { Looper.prepare(); @@ -93,7 +90,6 @@ public class TermsOfAddressNotSpecifiedControllerTest { } @Test - @Ignore("b/339543490") public void displayPreference_setGrammaticalGenderIsNotSpecified_NotSpecifiedIsSelected() { TickButtonPreference selectedPreference = (TickButtonPreference) mPreferenceScreen.getPreference(1); From eff863e5a3e0ff3b372b254f208fbc12fdd52f72 Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Thu, 11 Apr 2024 17:12:24 -0400 Subject: [PATCH 19/19] Redirect to new modes page when modes_ui is on. The new modes page has essentially no content yet, but this is a rough proof of concept for the existing abstract classes and hopefully serves as a building block for future changes. It lists modes and those preferences lead to contentless pages that will be fleshed out later.. Flag: android.app.modes_ui Bug: 327260745 Test: ZenModePreferenceControllerTest Test: manual; individual pages only have skeleton functionality and should be unit tested in future CLs Change-Id: I12f48b48f761e3c9ff1a173445b15f7536d34edb --- res/values/strings.xml | 3 + res/xml/modes_list_settings.xml | 28 +++++ res/xml/modes_rule_settings.xml | 25 ++++ .../notification/modes/ZenModeFragment.java | 78 ++++++++++++ .../modes/ZenModeListPreference.java | 65 ++++++++++ .../modes/ZenModesListFragment.java | 113 ++++++++++++++++++ .../ZenModesListPreferenceController.java | 80 +++++++++++++ .../zen/ZenModePreferenceController.java | 18 ++- .../zen/ZenModePreferenceControllerTest.java | 29 +++++ 9 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 res/xml/modes_list_settings.xml create mode 100644 res/xml/modes_rule_settings.xml create mode 100644 src/com/android/settings/notification/modes/ZenModeFragment.java create mode 100644 src/com/android/settings/notification/modes/ZenModeListPreference.java create mode 100644 src/com/android/settings/notification/modes/ZenModesListFragment.java create mode 100644 src/com/android/settings/notification/modes/ZenModesListPreferenceController.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 90cc39c9fad..864719d3949 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7882,6 +7882,9 @@ Do Not Disturb + + Priority Modes + Only get notified by important people and apps diff --git a/res/xml/modes_list_settings.xml b/res/xml/modes_list_settings.xml new file mode 100644 index 00000000000..c6b6200bdcb --- /dev/null +++ b/res/xml/modes_list_settings.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml new file mode 100644 index 00000000000..d7e26946829 --- /dev/null +++ b/res/xml/modes_rule_settings.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java new file mode 100644 index 00000000000..616332e0d1f --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeFragment.java @@ -0,0 +1,78 @@ +/* + * 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.notification.modes; + +import android.app.AutomaticZenRule; +import android.app.settings.SettingsEnums; +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class ZenModeFragment extends ZenModeFragmentBase { + + @Override + protected int getPreferenceScreenResId() { + return R.xml.modes_rule_settings; + } + + @Override + protected List createPreferenceControllers(Context context) { + // TODO: fill in with all the elements of this page. Each should be an instance of + // {@link AbstractZenModePreferenceController}. + List prefControllers = new ArrayList<>(); + return prefControllers; + } + + @Override + public void onStart() { + super.onStart(); + + // Set title for the entire screen + ZenMode mode = getMode(); + AutomaticZenRule azr = getAZR(); + if (mode == null || azr == null) { + return; + } + getActivity().setTitle(azr.getName()); + + // TODO: b/308819292 - implement the real screen! + final PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + return; + } + + Preference tmpPref = screen.findPreference("zen_mode_test"); + if (tmpPref == null) { + return; + } + tmpPref.setTitle(azr.getTriggerDescription()); + tmpPref.setSummary("active?: " + mode.isActive()); + } + + @Override + public int getMetricsCategory() { + // TODO: b/332937635 - make this the correct metrics category + return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION; + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeListPreference.java b/src/com/android/settings/notification/modes/ZenModeListPreference.java new file mode 100644 index 00000000000..cb0456140bf --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeListPreference.java @@ -0,0 +1,65 @@ +/* + * 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.notification.modes; + +import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; + +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.notification.zen.ZenModeSettings; +import com.android.settingslib.RestrictedPreference; + +/** + * Preference representing a single mode item on the modes aggregator page. Clicking on this + * preference leads to an individual mode's configuration page. + */ +public class ZenModeListPreference extends RestrictedPreference { + final Context mContext; + ZenMode mZenMode; + + ZenModeListPreference(Context context, ZenMode zenMode) { + super(context); + mContext = context; + mZenMode = zenMode; + setTitle(mZenMode.getRule().getName()); + setSummary((mZenMode.isActive() ? "ACTIVE" : "inactive") + ": " + + mZenMode.getRule().getTriggerDescription()); + } + + @Override + public void onClick() { + // TODO: b/322373473 - This implementation is a hack that just leads to the old DND page + // for manual only; remove this in favor of the real implementation. + if (mZenMode.isManualDnd()) { + new SubSettingLauncher(mContext) + .setDestination(ZenModeSettings.class.getName()) + .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE) + .launch(); + } else { + Bundle bundle = new Bundle(); + bundle.putString(MODE_ID, mZenMode.getId()); + new SubSettingLauncher(mContext) + .setDestination(ZenModeFragment.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION) + .launch(); + } + + } +} diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java new file mode 100644 index 00000000000..040621e6d93 --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java @@ -0,0 +1,113 @@ +/* + * 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.notification.modes; + +import android.app.NotificationManager; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.service.notification.ConditionProviderService; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.utils.ManagedServiceSettings; +import com.android.settings.utils.ZenServiceListing; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.search.SearchIndexable; + +import java.util.ArrayList; +import java.util.List; + +@SearchIndexable +public class ZenModesListFragment extends ZenModesFragmentBase { + protected final ManagedServiceSettings.Config CONFIG = getConditionProviderConfig(); + + @Override + protected List createPreferenceControllers(Context context) { + ZenServiceListing serviceListing = new ZenServiceListing(getContext(), CONFIG); + serviceListing.reloadApprovedServices(); + return buildPreferenceControllers(context, this, serviceListing); + } + + private static List buildPreferenceControllers(Context context, + @Nullable Fragment parent, @Nullable ZenServiceListing serviceListing) { + // We need to redefine ZenModesBackend here even though mBackend exists so that this method + // can be static; it must be static to be able to be used in SEARCH_INDEX_DATA_PROVIDER. + ZenModesBackend backend = ZenModesBackend.getInstance(context); + List controllers = new ArrayList<>(); + controllers.add(new ZenModesListPreferenceController( + context, parent, backend)); + + // TODO: b/326442408 - Add controller for "Add Mode" preference/flow, which is what uses + // the ZenServiceListing. + return controllers; + } + + @Override + protected void updateZenModeState() { + // TODO: b/322373473 -- update any overall description of modes state here if necessary. + // Note the preferences linking to individual rules do not need to be updated, as + // updateState() is called on all preference controllers whenever the page is resumed. + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.modes_list_settings; + } + + @Override + public int getMetricsCategory() { + // TODO: b/332937635 - add new & set metrics categories correctly + return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION; + } + + protected static ManagedServiceSettings.Config getConditionProviderConfig() { + return new ManagedServiceSettings.Config.Builder() + .setTag(TAG) + .setIntentAction(ConditionProviderService.SERVICE_INTERFACE) + .setConfigurationIntentAction(NotificationManager.ACTION_AUTOMATIC_ZEN_RULE) + .setPermission(android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE) + .setNoun("condition provider") + .build(); + } + + // TODO: b/322373473 - Add 3-dot options menu with capability to delete modes. + + /** + * For Search. + */ + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.modes_list_settings) { + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + // TODO: b/332937523 - determine if this should be removed once the preference + // controller adds dynamic data to index + keys.add(ZenModesListPreferenceController.KEY); + return keys; + } + + @Override + public List createPreferenceControllers( + Context context) { + return buildPreferenceControllers(context, null, null); + } + }; +} diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java new file mode 100644 index 00000000000..53336c84be3 --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java @@ -0,0 +1,80 @@ +/* + * 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.notification.modes; + +import android.app.Flags; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * Controller for the PreferenceCategory on the modes aggregator page ({@link ZenModesListFragment}) + * containing links to each individual mode. This is a central controller that populates and updates + * all the preferences that then lead to a mode configuration page. + */ +public class ZenModesListPreferenceController extends AbstractPreferenceController { + protected static final String KEY = "zen_modes_list"; + + @Nullable + protected Fragment mParent; + protected ZenModesBackend mBackend; + + public ZenModesListPreferenceController(Context context, @Nullable Fragment parent, + @NonNull ZenModesBackend backend) { + super(context); + mParent = parent; + mBackend = backend; + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + return Flags.modesUi(); + } + + @Override + public void updateState(Preference preference) { + if (mBackend == null) { + return; + } + + // The preference given us is a PreferenceCategory; create one preference inside the + // category for each rule that exists. + PreferenceCategory category = (PreferenceCategory) preference; + + // TODO: b/322373473 - This is not the right way to replace these preferences; we should + // follow something similar to what + // ZenModeAutomaticRulesPreferenceController does to change rules + // only as necessary and update them. + category.removeAll(); + + for (ZenMode mode : mBackend.getModes()) { + Preference pref = new ZenModeListPreference(mContext, mode); + category.addPreference(pref); + } + } + +} diff --git a/src/com/android/settings/notification/zen/ZenModePreferenceController.java b/src/com/android/settings/notification/zen/ZenModePreferenceController.java index 24cf158c966..7c36d80f8ad 100644 --- a/src/com/android/settings/notification/zen/ZenModePreferenceController.java +++ b/src/com/android/settings/notification/zen/ZenModePreferenceController.java @@ -16,6 +16,7 @@ package com.android.settings.notification.zen; +import android.app.Flags; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -27,7 +28,9 @@ import android.provider.Settings; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settings.notification.modes.ZenModesListFragment; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; @@ -46,7 +49,9 @@ public class ZenModePreferenceController extends BasePreferenceController @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - mSettingObserver = new SettingObserver(screen.findPreference(getPreferenceKey())); + Preference preference = screen.findPreference(getPreferenceKey()); + mSettingObserver = new SettingObserver(preference); + maybeSetTitleAndDestination(preference); } @Override @@ -71,11 +76,22 @@ public class ZenModePreferenceController extends BasePreferenceController @Override public void updateState(Preference preference) { super.updateState(preference); + maybeSetTitleAndDestination(preference); if (preference.isEnabled()) { preference.setSummary(mSummaryBuilder.getSoundSummary()); } } + // Only when modes_ui is active: change title & target fragment. + private void maybeSetTitleAndDestination(Preference preference) { + if (!Flags.modesUi()) { + return; + } + + preference.setTitle(R.string.zen_modes_list_title); + preference.setFragment(ZenModesListFragment.class.getCanonicalName()); + } + class SettingObserver extends ContentObserver { private final Uri ZEN_MODE_URI = Settings.Global.getUriFor(Settings.Global.ZEN_MODE); private final Uri ZEN_MODE_CONFIG_ETAG_URI = Settings.Global.getUriFor( diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java index c2abbcdfb40..f611c9b1950 100644 --- a/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java @@ -16,10 +16,13 @@ package com.android.settings.notification.zen; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; + import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; @@ -27,13 +30,20 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Flags; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.preference.Preference; +import com.android.settings.notification.modes.ZenModesListFragment; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -46,6 +56,9 @@ import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) public class ZenModePreferenceControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @Mock private Preference mPreference; @Mock @@ -96,4 +109,20 @@ public class ZenModePreferenceControllerTest { verify(mPreference, never()).setSummary(anyString()); } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void updateState_modesUi_resetsTitleAndFragment() { + mController.updateState(mPreference); + verify(mPreference).setTitle(anyInt()); // Resource IDs are ints + verify(mPreference).setFragment(ZenModesListFragment.class.getCanonicalName()); + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + public void updateState_noModesUi_doesNotSetTitleAndFragment() { + mController.updateState(mPreference); + verify(mPreference, never()).setTitle(anyInt()); + verify(mPreference, never()).setFragment(anyString()); + } }