summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--PermissionController/Android.bp2
-rw-r--r--PermissionController/res/layout-v33/action_button_list_large_screen.xml (renamed from PermissionController/res/layout-w764dp-v33/action_button_list.xml)2
-rw-r--r--PermissionController/res/layout-v33/action_button_list_small_screen.xml (renamed from PermissionController/res/layout-v33/action_button_list.xml)2
-rw-r--r--PermissionController/res/layout-v33/preference_issue_card.xml10
-rw-r--r--PermissionController/res/layout/grant_permissions.xml2
-rw-r--r--PermissionController/res/values-night-v33/themes.xml12
-rw-r--r--PermissionController/res/values-night-v34/themes.xml5
-rw-r--r--PermissionController/res/values-v31/styles.xml8
-rw-r--r--PermissionController/res/values-v33/attrs.xml3
-rw-r--r--PermissionController/res/values-v33/dimens.xml9
-rw-r--r--PermissionController/res/values-v33/layout.xml9
-rw-r--r--PermissionController/res/values-v33/styles.xml18
-rw-r--r--PermissionController/res/values-v33/themes.xml24
-rw-r--r--PermissionController/res/values-v34/strings.xml2
-rw-r--r--PermissionController/res/values-w764dp-v33/layout.xml6
-rw-r--r--PermissionController/res/values-w764dp-v33/styles.xml14
-rw-r--r--PermissionController/res/values-w764dp-v34/dimens.xml19
-rw-r--r--PermissionController/res/values/overlayable.xml36
-rw-r--r--PermissionController/res/values/styles.xml10
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/compat/AppOpsManagerCompat.java48
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/v31/HibernationController.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt57
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt47
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingDetailsPreference.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/AllAppPermissionsViewModel.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt808
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java70
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerPregrants.kt42
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java29
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreference.java30
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreferenceMixin.java69
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppListFragment.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java (renamed from PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppPreference.java)23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java (renamed from PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSettingsPreference.java)33
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessFragment.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessListFragment.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java76
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppListPreferenceFragment.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppPreferenceFragment.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java75
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java (renamed from PermissionController/src/com/android/permissioncontroller/role/ui/handheld/SettingsButtonPreference.java)30
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessListPreferenceFragment.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessPreferenceFragment.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java74
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java33
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt28
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardAnimator.kt112
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PreferenceHighlightManager.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java46
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java24
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java1
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/LazyProperties.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryCommonViewsManager.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryGroupView.kt14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryView.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt14
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt58
-rw-r--r--PermissionController/tests/permissionui/Android.bp2
-rw-r--r--PermissionController/tests/permissionui/AndroidTest.xml4
-rw-r--r--PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/Android.bp34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/AndroidManifest.xml (renamed from PermissionController/res/values-w764dp-v33/dimens.xml)15
-rw-r--r--PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt21
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/Android.bp34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/AndroidManifest.xml34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt21
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt135
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt112
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt11
-rw-r--r--SafetyCenter/Annotations/java/com/android/safetycenter/annotations/RequiresTiramisuByDefault.java40
-rw-r--r--SafetyCenter/Resources/res/values-uk/strings.xml2
-rw-r--r--SafetyCenter/Resources/shared_res/values-af/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-am/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ar/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-as/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-az/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-be/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bg/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bs/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ca/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-cs/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-da/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-de/strings.xml3
-rw-r--r--SafetyCenter/Resources/shared_res/values-el/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml3
-rw-r--r--SafetyCenter/Resources/shared_res/values-es/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-et/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-eu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fa/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-gl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-gu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hy/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-in/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-is/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-it/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-iw/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ja/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ka/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-kk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-km/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-kn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ko/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ky/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lo/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lt/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lv/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ml/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ms/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-my/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-nb/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ne/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-nl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-or/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pa/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ro/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ru/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-si/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sq/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sv/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sw/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ta/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-te/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-th/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-tl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-tr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-uk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ur/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-uz/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-vi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values/strings.xml3
-rw-r--r--SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesApk.java353
-rw-r--r--SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java403
-rw-r--r--SafetyCenter/ResourcesLib/tests/AndroidTest.xml2
-rw-r--r--SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/safety_center_config.txt1
-rw-r--r--SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesApkTest.kt440
-rw-r--r--SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt226
-rw-r--r--SafetyCenter/TEST_MAPPING6
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterData.java12
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterIssue.java17
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterManager.java2
-rw-r--r--framework-s/java/android/safetycenter/SafetyEvent.java3
-rw-r--r--framework-s/java/android/safetycenter/SafetySourceData.java14
-rw-r--r--framework-s/java/android/safetycenter/SafetySourceIssue.java42
-rw-r--r--framework-s/java/android/safetycenter/SafetySourceStatus.java3
-rw-r--r--framework-s/java/android/safetycenter/config/SafetyCenterConfig.java3
-rw-r--r--framework-s/java/android/safetycenter/config/SafetySource.java24
-rw-r--r--framework-s/java/android/safetycenter/config/SafetySourcesGroup.java3
-rw-r--r--permissions/Android.bp6
-rw-r--r--service/java/com/android/safetycenter/ApiLock.java5
-rw-r--r--service/java/com/android/safetycenter/DevicePolicyResources.java22
-rw-r--r--service/java/com/android/safetycenter/PendingIntentFactory.java20
-rw-r--r--service/java/com/android/safetycenter/RefreshReasons.java11
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java68
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterConfigReader.java45
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterDataChangeNotifier.java5
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterDataFactory.java117
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterFlags.java33
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterListeners.java18
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java81
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java435
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java14
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterTimeouts.java5
-rw-r--r--service/java/com/android/safetycenter/SafetySourceIssueInfo.java6
-rw-r--r--service/java/com/android/safetycenter/SafetySourceKey.java6
-rw-r--r--service/java/com/android/safetycenter/SafetySources.java5
-rw-r--r--service/java/com/android/safetycenter/SafetySourcesGroups.java5
-rw-r--r--service/java/com/android/safetycenter/UserProfileGroup.java17
-rw-r--r--service/java/com/android/safetycenter/data/AndroidLockScreenFix.java26
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterDataManager.java44
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java20
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java12
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java32
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java18
-rw-r--r--service/java/com/android/safetycenter/data/SafetyEventFix.java118
-rw-r--r--service/java/com/android/safetycenter/data/SafetySourceDataRepository.java17
-rw-r--r--service/java/com/android/safetycenter/data/SafetySourceDataValidator.java18
-rw-r--r--service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java9
-rw-r--r--service/java/com/android/safetycenter/data/package-info.java2
-rw-r--r--service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java18
-rw-r--r--service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java6
-rw-r--r--service/java/com/android/safetycenter/logging/package-info.java2
-rw-r--r--service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java98
-rw-r--r--service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java116
-rw-r--r--service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java26
-rw-r--r--service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java32
-rw-r--r--service/java/com/android/safetycenter/notifications/package-info.java2
-rw-r--r--service/java/com/android/safetycenter/package-info.java2
-rw-r--r--tests/cts/safetycenter/AndroidTest.xml3
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt62
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt80
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt214
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt39
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt16
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt204
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt10
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt8
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt44
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt33
-rw-r--r--tests/functional/safetycenter/multiusers/AndroidTest.xml3
-rw-r--r--tests/functional/safetycenter/multiusers/TEST_MAPPING27
-rw-r--r--tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt428
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml3
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt111
-rw-r--r--tests/functional/safetycenter/singleuser/AndroidManifest.xml9
-rw-r--r--tests/functional/safetycenter/singleuser/AndroidTest.xml3
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt1234
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt233
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterShellCommandsTest.kt (renamed from tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt)46
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt92
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt83
-rw-r--r--tests/functional/safetycenter/subpages/Android.bp43
-rw-r--r--tests/functional/safetycenter/subpages/AndroidManifest.xml30
-rw-r--r--tests/functional/safetycenter/subpages/AndroidTest.xml64
-rw-r--r--tests/functional/safetycenter/subpages/TEST_MAPPING17
-rw-r--r--tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt (renamed from tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt)85
-rw-r--r--tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt (renamed from tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt)74
-rw-r--r--tests/hostside/safetycenter/Android.bp1
-rw-r--r--tests/hostside/safetycenter/AndroidTest.xml3
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt11
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt11
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt33
-rw-r--r--tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterInteractionLoggingHostTest.kt36
-rw-r--r--tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt24
-rw-r--r--tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetySourceStateCollectedLoggingHostTest.kt14
-rw-r--r--tests/utils/safetycenter/AndroidManifest.xml27
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt14
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt72
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt (renamed from tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/NotificationCharacteristics.kt)4
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt18
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt16
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt59
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt42
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt25
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt84
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt57
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt14
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt38
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt17
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt (renamed from tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/StatusBarNotificationWithChannel.kt)2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt29
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt50
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt34
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt (renamed from tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/TestNotificationListener.kt)82
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt75
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/WaitForBroadcasts.kt74
304 files changed, 6311 insertions, 4067 deletions
diff --git a/Android.bp b/Android.bp
index 4a436da6c..bf7437814 100644
--- a/Android.bp
+++ b/Android.bp
@@ -30,7 +30,6 @@ apex_defaults {
systemserverclasspath_fragments: ["com.android.permission-systemserverclasspath-fragment"],
prebuilts: [
"current_sdkinfo",
- "privapp_allowlist_com.android.permissioncontroller.xml",
],
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp
index 9b20819f0..892f12f2b 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -82,7 +82,7 @@ android_app {
privileged: true,
certificate: "platform",
rename_resources_package: false,
- required: ["privapp_allowlist_com.android.permissioncontroller.xml"],
+ privapp_allowlist: ":privapp_allowlist_com.android.permissioncontroller.xml",
srcs: [":permissioncontroller-sources"],
diff --git a/PermissionController/res/layout-w764dp-v33/action_button_list.xml b/PermissionController/res/layout-v33/action_button_list_large_screen.xml
index 4db6df47c..141290fa2 100644
--- a/PermissionController/res/layout-w764dp-v33/action_button_list.xml
+++ b/PermissionController/res/layout-v33/action_button_list_large_screen.xml
@@ -16,6 +16,4 @@
<com.android.permissioncontroller.safetycenter.ui.EqualWidthContainer
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/action_button_list"
style="@style/SafetyCenterIssueActionButtonList" />
diff --git a/PermissionController/res/layout-v33/action_button_list.xml b/PermissionController/res/layout-v33/action_button_list_small_screen.xml
index 3217f4c78..048ac4595 100644
--- a/PermissionController/res/layout-v33/action_button_list.xml
+++ b/PermissionController/res/layout-v33/action_button_list_small_screen.xml
@@ -15,6 +15,4 @@
-->
<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/action_button_list"
style="@style/SafetyCenterIssueActionButtonList" />
diff --git a/PermissionController/res/layout-v33/preference_issue_card.xml b/PermissionController/res/layout-v33/preference_issue_card.xml
index 571efae3b..e6d749142 100644
--- a/PermissionController/res/layout-v33/preference_issue_card.xml
+++ b/PermissionController/res/layout-v33/preference_issue_card.xml
@@ -54,7 +54,7 @@
<include
android:id="@+id/issue_card_action_button_list"
- layout="@layout/action_button_list"/>
+ layout="?attr/scActionButtonListLayout"/>
<com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView
android:id="@+id/issue_card_protected_by_android"
@@ -72,10 +72,10 @@
android:text="@string/safety_center_resolved_issue_fallback"
style="@style/SafetyCenterIssueCardResolvedTitle" />
- <!-- This group doesn't contain issue_card_dismiss_btn, issue_card_subtitle or
- issue_card_protected_by_android since the version of ConstraintLayout we're
- using doesn't allow us to override the group's visibility on individual group
- members. See b/242705351 for context. -->
+ <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn,
+ issue_card_subtitle or issue_card_protected_by_android since the version of
+ ConstraintLayout we're using doesn't allow us to override the group's visibility on
+ individual group members. See b/242705351 for context. -->
<androidx.constraintlayout.widget.Group
android:id="@+id/default_issue_content"
android:layout_width="wrap_content"
diff --git a/PermissionController/res/layout/grant_permissions.xml b/PermissionController/res/layout/grant_permissions.xml
index 9becddb7f..4b23dd959 100644
--- a/PermissionController/res/layout/grant_permissions.xml
+++ b/PermissionController/res/layout/grant_permissions.xml
@@ -184,7 +184,7 @@
<com.android.permissioncontroller.permission.ui.widget.SecureButton
android:id="@+id/permission_dont_allow_more_selected_button"
android:text="@string/grant_dialog_button_dont_select_more"
- style="@style/PermissionGrantButtonDeny" />
+ style="@style/PermissionGrantButtonDontAllowMore" />
</LinearLayout>
</LinearLayout>
diff --git a/PermissionController/res/values-night-v33/themes.xml b/PermissionController/res/values-night-v33/themes.xml
index 6374ee088..9b6f638a6 100644
--- a/PermissionController/res/values-night-v33/themes.xml
+++ b/PermissionController/res/values-night-v33/themes.xml
@@ -16,10 +16,6 @@
-->
<resources>
- <style name="Theme.SafetyCenterQs" parent="Theme.SafetyCenterQsBase">
- <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
- </style>
-
<style name="Theme.SafetyCenterBase" parent="Theme.PermissionController.Settings.FilterTouches">
<item name="colorSurface">@color/sc_surface_dark</item>
<item name="colorSurfaceVariant">@color/sc_surface_variant_dark</item>
@@ -49,8 +45,12 @@
<item name="scStatusButtonStyle">@style/SafetyCenterStatusButton.Responsive</item>
<!-- Buttons -->
- <item name="scActionButtonStyle">@style/SafetyCenterActionButton</item>
- <item name="scSecondaryActionButtonStyle">@style/SafetyCenterActionButton.Secondary</item>
+ <item name="scActionButtonListLayout">@layout/action_button_list_responsive</item>
+ <item name="scActionButtonTheme">@style/Theme.MaterialComponents.DayNight</item>
+ <item name="scActionButtonStyle">@style/SafetyCenterActionButton.Responsive</item>
+ <item name="scSecondaryActionButtonStyle">
+ @style/SecondarySafetyCenterActionButton.Responsive
+ </item>
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
diff --git a/PermissionController/res/values-night-v34/themes.xml b/PermissionController/res/values-night-v34/themes.xml
index affc57027..b6328d782 100644
--- a/PermissionController/res/values-night-v34/themes.xml
+++ b/PermissionController/res/values-night-v34/themes.xml
@@ -21,11 +21,6 @@
<item name="android:colorBackground">@color/google_grey_800</item>
</style>
- <style name="Theme.SafetyCenterQs" parent="Theme.SafetyCenterQsBase">
- <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
- <item name="colorScShieldAccent">@color/sc_shield_accent_dark</item>
- </style>
-
<style name="Theme.SafetyCenter" parent="Theme.SafetyCenterBase">
<item name="colorScShieldAccent">@color/sc_shield_accent_dark</item>
</style>
diff --git a/PermissionController/res/values-v31/styles.xml b/PermissionController/res/values-v31/styles.xml
index a05fd488b..3b42a6e5b 100644
--- a/PermissionController/res/values-v31/styles.xml
+++ b/PermissionController/res/values-v31/styles.xml
@@ -90,16 +90,16 @@
parent="@style/PermissionGrantButtonTop"></style>
<style name="PermissionGrantButtonAllowOneTimeMaterial3"
parent="@style/PermissionGrantButtonMiddle"></style>
+ <style name="PermissionGrantButtonAllowSelectedMaterial3"
+ parent="@style/PermissionGrantButtonTop"></style>
<style name="PermissionGrantButtonAllowAllMaterial3"
parent="@style/PermissionGrantButtonMiddle"></style>
- <style name="PermissionGrantButtonAllowSelectedMaterial3"
- parent="@style/PermissionGrantButtonTop"></style>
- <style name="PermissionGrantButtonDontAllowMoreMaterial3"
- parent="@style/PermissionGrantButtonBottom"></style>
<style name="PermissionGrantButtonDenyMaterial3"
parent="@style/PermissionGrantButtonBottom"></style>
<style name="PermissionGrantButtonNoUpgradeMaterial3"
parent="@style/PermissionGrantButtonBottom"></style>
+ <style name="PermissionGrantButtonDontAllowMoreMaterial3"
+ parent="@style/PermissionGrantButtonBottom"></style>
<!-- END PERMISSION GRANT DIALOG -->
diff --git a/PermissionController/res/values-v33/attrs.xml b/PermissionController/res/values-v33/attrs.xml
index b59716f6d..4a417076d 100644
--- a/PermissionController/res/values-v33/attrs.xml
+++ b/PermissionController/res/values-v33/attrs.xml
@@ -37,7 +37,10 @@
<attr name="scStatusTitleAndSummaryContainerStyle" format="reference" />
<attr name="scStatusButtonStyle" format="reference" />
+ <attr name="scActionButtonListLayout" format="reference" />
+ <attr name="scActionButtonTheme" format="reference" />
<attr name="scActionButtonStyle" format="reference"/>
<attr name="scSecondaryActionButtonStyle" format="reference"/>
+
<attr name="colorScShieldAccent" format="color" />
</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-v33/dimens.xml b/PermissionController/res/values-v33/dimens.xml
index ef70d2711..e6925bc54 100644
--- a/PermissionController/res/values-v33/dimens.xml
+++ b/PermissionController/res/values-v33/dimens.xml
@@ -24,20 +24,25 @@
<dimen name="sc_spacing_xlarge">18dp</dimen>
<dimen name="sc_spacing_xxlarge">20dp</dimen>
<dimen name="sc_spacing_xxxlarge">24dp</dimen>
- <dimen name="sc_large_screen_button_padding">56dp</dimen>
<dimen name="sc_card_margin">@dimen/sc_spacing_xxxsmall</dimen>
<dimen name="sc_list_margin">@dimen/sc_spacing_large</dimen>
<dimen name="sc_list_margin_top">@dimen/sc_spacing_small</dimen>
+
+ <!-- Only used for small screen views, but doesn't have _small_screen suffix since it's fixed in
+ the overlayable. -->
<dimen name="sc_action_button_list_margin">@dimen/sc_spacing_large</dimen>
+ <dimen name="sc_action_button_list_margin_large_screen">@dimen/sc_spacing_large</dimen>
<dimen name="sc_top_action_button_margin">@dimen/sc_spacing_xxxlarge</dimen>
+ <dimen name="sc_button_horizontal_padding_small_screen">@dimen/sc_spacing_xxxlarge</dimen>
+ <dimen name="sc_button_horizontal_padding_large_screen">56dp</dimen>
+
<dimen name="sc_entry_padding_end">@dimen/sc_spacing_xxxlarge</dimen>
<dimen name="sc_entry_group_expanded_padding_top">@dimen/sc_spacing_xxxlarge</dimen>
<dimen name="sc_entry_group_expanded_padding_bottom">@dimen/sc_spacing_xsmall</dimen>
<dimen name="sc_entry_group_collapsed_padding_top">@dimen/sc_spacing_large</dimen>
<dimen name="sc_entry_group_collapsed_padding_bottom">@dimen/sc_spacing_large</dimen>
<dimen name="sc_card_margin_bottom">@dimen/sc_spacing_xxxlarge</dimen>
- <dimen name="sc_button_horizontal_padding">@dimen/sc_spacing_xxxlarge</dimen>
<dimen name="sc_icon_button_touch_target_size">48dp</dimen>
<dimen name="sc_button_corner_radius">12dp</dimen>
diff --git a/PermissionController/res/values-v33/layout.xml b/PermissionController/res/values-v33/layout.xml
new file mode 100644
index 000000000..a963dae0d
--- /dev/null
+++ b/PermissionController/res/values-v33/layout.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item name="action_button_list_fixed" type="layout">
+ @layout/action_button_list_small_screen
+ </item>
+ <item name="action_button_list_responsive" type="layout">
+ @layout/action_button_list_small_screen
+ </item>
+</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-v33/styles.xml b/PermissionController/res/values-v33/styles.xml
index a0938665f..6b726fdb7 100644
--- a/PermissionController/res/values-v33/styles.xml
+++ b/PermissionController/res/values-v33/styles.xml
@@ -324,10 +324,10 @@
<style name="SafetyCenterActionButton"
parent="@style/Widget.MaterialComponents.Button.UnelevatedButton">
- <item name="android:theme">@style/Theme.MaterialComponents</item>
+ <item name="android:theme">?attr/scActionButtonTheme</item>
<item name="android:minHeight">56dp</item>
- <item name="android:paddingStart">@dimen/sc_button_horizontal_padding</item>
- <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding</item>
+ <item name="android:paddingStart">@dimen/sc_button_horizontal_padding_small_screen</item>
+ <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding_small_screen</item>
<item name="android:paddingTop">@dimen/sc_spacing_xlarge</item>
<item name="android:paddingBottom">@dimen/sc_spacing_xlarge</item>
<item name="android:insetTop">0dp</item>
@@ -340,13 +340,15 @@
<item name="cornerRadius">@dimen/sc_button_corner_radius</item>
<item name="rippleColor">?android:colorControlHighlight</item>
</style>
+ <style name="SafetyCenterActionButton.Fixed" />
+ <style name="SafetyCenterActionButton.Responsive" />
- <style name="SafetyCenterActionButton.Secondary"
+ <style name="SecondarySafetyCenterActionButton"
parent="@style/Widget.MaterialComponents.Button.OutlinedButton">
- <item name="android:theme">@style/Theme.MaterialComponents</item>
+ <item name="android:theme">?attr/scActionButtonTheme</item>
<item name="android:minHeight">56dp</item>
- <item name="android:paddingStart">@dimen/sc_button_horizontal_padding</item>
- <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding</item>
+ <item name="android:paddingStart">@dimen/sc_button_horizontal_padding_small_screen</item>
+ <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding_small_screen</item>
<item name="android:paddingTop">@dimen/sc_spacing_xlarge</item>
<item name="android:paddingBottom">@dimen/sc_spacing_xlarge</item>
<item name="android:insetTop">0dp</item>
@@ -361,6 +363,8 @@
<item name="cornerRadius">@dimen/sc_button_corner_radius</item>
<item name="rippleColor">?android:colorControlHighlight</item>
</style>
+ <style name="SecondarySafetyCenterActionButton.Fixed" />
+ <style name="SecondarySafetyCenterActionButton.Responsive" />
<!-- START SAFETY STATUS CARD -->
<style name="SafetyCenterCard.Status">
diff --git a/PermissionController/res/values-v33/themes.xml b/PermissionController/res/values-v33/themes.xml
index 338cbaa01..82a8ef5b6 100644
--- a/PermissionController/res/values-v33/themes.xml
+++ b/PermissionController/res/values-v33/themes.xml
@@ -47,14 +47,18 @@
<item name="scStatusButtonStyle">@style/SafetyCenterStatusButton.Fixed</item>
<!-- Buttons -->
- <item name="scActionButtonStyle">@style/SafetyCenterActionButton</item>
- <item name="scSecondaryActionButtonStyle">@style/SafetyCenterActionButton.Secondary</item>
+ <item name="scActionButtonListLayout">@layout/action_button_list_fixed</item>
+ <item name="scActionButtonTheme">
+ <!-- Dark-only theme for QS buttons -->
+ @style/Theme.MaterialComponents
+ </item>
+ <item name="scActionButtonStyle">@style/SafetyCenterActionButton.Fixed</item>
+ <item name="scSecondaryActionButtonStyle">
+ @style/SecondarySafetyCenterActionButton.Fixed
+ </item>
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
- <!-- While the rest of the text colors automatically appear correct in quick settings,
- the outline action button text color needs to be set to primary inverse in light mode
- and primary in night mode to appear correctly in QS's faux permanent night mode. -->
- <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimaryInverse</item>
+ <item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
<item name="colorScOutlineButtonInfoBase">@color/sc_outline_button_info_base_dark</item>
<item name="colorScOutlineButtonRecommendBase">
@@ -93,8 +97,12 @@
<item name="scStatusButtonStyle">@style/SafetyCenterStatusButton.Responsive</item>
<!-- Buttons -->
- <item name="scActionButtonStyle">@style/SafetyCenterActionButton</item>
- <item name="scSecondaryActionButtonStyle">@style/SafetyCenterActionButton.Secondary</item>
+ <item name="scActionButtonListLayout">@layout/action_button_list_responsive</item>
+ <item name="scActionButtonTheme">@style/Theme.MaterialComponents.DayNight</item>
+ <item name="scActionButtonStyle">@style/SafetyCenterActionButton.Responsive</item>
+ <item name="scSecondaryActionButtonStyle">
+ @style/SecondarySafetyCenterActionButton.Responsive
+ </item>
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
diff --git a/PermissionController/res/values-v34/strings.xml b/PermissionController/res/values-v34/strings.xml
index 30181aa02..26a9b4bcc 100644
--- a/PermissionController/res/values-v34/strings.xml
+++ b/PermissionController/res/values-v34/strings.xml
@@ -28,7 +28,7 @@
<!-- Title for the link to location settings [CHAR LIMIT=30] -->
<string name="location_settings">Location access</string>
- <!-- Describes what is affected by the mic toggle [CHAR LIMIT=NONE] -->
+ <!-- Describes what is affected by the mic toggle. Unlike the similar tc/9163104307990677157, there should NOT be a full stop at the end of this sentence. [CHAR LIMIT=NONE] -->
<string name="mic_toggle_description">For apps and services. If this setting is off, microphone data may still be shared when you call an emergency number</string>
<!-- Subtitle for the link to location settings [CHAR LIMIT=NONE] -->
<string name="location_settings_subtitle">For apps and services</string>
diff --git a/PermissionController/res/values-w764dp-v33/layout.xml b/PermissionController/res/values-w764dp-v33/layout.xml
new file mode 100644
index 000000000..9035c8868
--- /dev/null
+++ b/PermissionController/res/values-w764dp-v33/layout.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item name="action_button_list_responsive" type="layout">
+ @layout/action_button_list_large_screen
+ </item>
+</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-w764dp-v33/styles.xml b/PermissionController/res/values-w764dp-v33/styles.xml
index a836e4aa5..78f1bb42e 100644
--- a/PermissionController/res/values-w764dp-v33/styles.xml
+++ b/PermissionController/res/values-w764dp-v33/styles.xml
@@ -16,6 +16,16 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="SafetyCenterActionButton.Responsive">
+ <item name="android:paddingStart">@dimen/sc_button_horizontal_padding_large_screen</item>
+ <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding_large_screen</item>
+ </style>
+
+ <style name="SecondarySafetyCenterActionButton.Responsive">
+ <item name="android:paddingStart">@dimen/sc_button_horizontal_padding_large_screen</item>
+ <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding_large_screen</item>
+ </style>
+
<!-- START SAFETY STATUS CARD -->
<style name="SafetyCenterStatusTitleAndSummaryContainer.Responsive">
<item name="layout_constraintEnd_toStartOf">@id/sc_status_buttons_start_barrier</item>
@@ -30,8 +40,8 @@
<item name="layout_constraintStart_toEndOf">@id/status_title_and_summary</item>
<item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
- <item name="android:paddingStart">@dimen/sc_large_screen_button_padding</item>
- <item name="android:paddingEnd">@dimen/sc_large_screen_button_padding</item>
+ <item name="android:paddingStart">@dimen/sc_button_horizontal_padding_large_screen</item>
+ <item name="android:paddingEnd">@dimen/sc_button_horizontal_padding_large_screen</item>
<!-- Clear the base style constraints so they don't conflict with this style's constraints
on the same sides. -->
<item name="layout_constraintTop_toBottomOf">@null</item>
diff --git a/PermissionController/res/values-w764dp-v34/dimens.xml b/PermissionController/res/values-w764dp-v34/dimens.xml
deleted file mode 100644
index cb336fc3e..000000000
--- a/PermissionController/res/values-w764dp-v34/dimens.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<resources>
- <dimen name="sc_action_button_list_margin">@dimen/sc_spacing_large</dimen>
-</resources>
diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml
index 850b5df40..8f81e446b 100644
--- a/PermissionController/res/values/overlayable.xml
+++ b/PermissionController/res/values/overlayable.xml
@@ -37,12 +37,24 @@
<item type="style" name="PermissionGrantTitleMessage" />
<item type="style" name="PermissionGrantDetailMessage" />
+ <item type="style" name="PermissionGrantPermissionRationaleContent" />
+ <item type="style" name="PermissionGrantPermissionRationaleIcon" />
+ <item type="style" name="PermissionGrantPermissionRationaleMessage" />
+ <item type="style" name="PermissionGrantPermissionRationaleMoreInfoIcon" />
+
+ <item type="color" name="permission_rationale_overview_background" />
+
+ <item type="style" name="PermissionLocationAccuracyRadioGroupMaterial3" />
+ <item type="style" name="PermissionLocationAccuracyFineImageViewMaterial3" />
+ <item type="style" name="PermissionLocationAccuracyCoarseImageViewMaterial3" />
+
+ <item type="style" name="PermissionLocationAccuracyRadioFine" />
+ <item type="style" name="PermissionLocationAccuracyRadioCoarse" />
+
<item type="style" name="PermissionGrantButtonList" />
<item type="style" name="PermissionGrantButtonBarSpace" />
<item type="style" name="PermissionGrantButton" />
- <!-- IDs for V31 only -->
-
<item type="style" name="PermissionGrantButtonTop" />
<item type="style" name="PermissionGrantButtonMiddle" />
<item type="style" name="PermissionGrantButtonBottom" />
@@ -52,23 +64,27 @@
<item type="style" name="PermissionGrantButtonAllowMaterial3" />
<item type="style" name="PermissionGrantButtonAllowForegroundMaterial3" />
<item type="style" name="PermissionGrantButtonAllowOneTimeMaterial3" />
+ <item type="style" name="PermissionGrantButtonAllowSelectedMaterial3" />
+ <item type="style" name="PermissionGrantButtonAllowAllMaterial3" />
<item type="style" name="PermissionGrantButtonDenyMaterial3" />
<item type="style" name="PermissionGrantButtonNoUpgradeMaterial3" />
+ <item type="style" name="PermissionGrantButtonDontAllowMoreMaterial3" />
+
+ <!-- Used in V30 only -->
- <!-- END IDs for V31 only -->
+ <item type="style" name="PermissionLocationAccuracyFineImageView" />
+ <item type="style" name="PermissionLocationAccuracyCoarseImageView" />
<item type="style" name="PermissionGrantButtonAllow" />
<item type="style" name="PermissionGrantButtonAllowForeground" />
<item type="style" name="PermissionGrantButtonAllowOneTime" />
+ <item type="style" name="PermissionGrantButtonAllowSelected" />
+ <item type="style" name="PermissionGrantButtonAllowAll" />
<item type="style" name="PermissionGrantButtonDeny" />
<item type="style" name="PermissionGrantButtonNoUpgrade" />
+ <item type="style" name="PermissionGrantButtonDontAllowMore" />
- <item type="style" name="PermissionGrantPermissionRationaleContent" />
- <item type="style" name="PermissionGrantPermissionRationaleIcon" />
- <item type="style" name="PermissionGrantPermissionRationaleMessage" />
- <item type="style" name="PermissionGrantPermissionRationaleMoreInfoIcon" />
-
- <item type="color" name="permission_rationale_overview_background" />
+ <!-- END Used in V30 only -->
<!-- END PERMISSION GRANT DIALOG -->
@@ -526,4 +542,4 @@
</policy>
</overlayable>
-</resources> \ No newline at end of file
+</resources>
diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml
index 11b9ebec1..a859b3ce9 100644
--- a/PermissionController/res/values/styles.xml
+++ b/PermissionController/res/values/styles.xml
@@ -233,16 +233,16 @@
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonAllowOneTime"
parent="@style/PermissionGrantButton"></style>
- <style name="PermissionGrantButtonAllowAll"
- parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonAllowSelected"
parent="@style/PermissionGrantButton"></style>
- <style name="PermissionGrantButtonDontAllowMore"
+ <style name="PermissionGrantButtonAllowAll"
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonDeny"
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonNoUpgrade"
parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonDontAllowMore"
+ parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonAllowMaterial3"
parent="@style/PermissionGrantButton"></style>
@@ -256,10 +256,6 @@
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonAllowAllMaterial3"
parent="@style/PermissionGrantButton"></style>
- <style name="PermissionGrantButtonAllowSelectedPhotosMaterial3"
- parent="@style/PermissionGrantButton"></style>
- <style name="PermissionGrantButtonDontAllowMorePhotosMaterial3"
- parent="@style/PermissionGrantButton"></style>
<!-- END PERMISSION GRANT DIALOG -->
diff --git a/PermissionController/role-controller/java/com/android/role/controller/compat/AppOpsManagerCompat.java b/PermissionController/role-controller/java/com/android/role/controller/compat/AppOpsManagerCompat.java
new file mode 100644
index 000000000..8e5a768df
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/compat/AppOpsManagerCompat.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.role.controller.compat;
+
+import android.app.AppOpsManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.Objects;
+
+/** Helper for accessing features in {@link AppOpsManager}. */
+public class AppOpsManagerCompat {
+
+ private AppOpsManagerCompat() {}
+
+ /**
+ * @see AppOpsManager#permissionToOp().
+ */
+ @Nullable
+ public static String permissionToOp(@NonNull String permission) {
+ if (!SdkLevel.isAtLeastV()) {
+ // On Android U and below, PACKAGE_USAGE_STATUS is missing from the mapping
+ // in the framework.
+ if (Objects.equals(permission, android.Manifest.permission.PACKAGE_USAGE_STATS)) {
+ return AppOpsManager.OPSTR_GET_USAGE_STATS;
+ }
+ }
+ return AppOpsManager.permissionToOp(permission);
+ }
+} \ No newline at end of file
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java
index f3f9b321e..29939a1a5 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java
@@ -24,6 +24,7 @@ import android.os.Build;
import androidx.annotation.NonNull;
+import com.android.role.controller.compat.AppOpsManagerCompat;
import com.android.role.controller.util.ArrayUtils;
import com.android.role.controller.util.PackageUtils;
@@ -55,7 +56,7 @@ public class AppOpPermissions {
if (!ArrayUtils.contains(packageInfo.requestedPermissions, appOpPermission)) {
return false;
}
- String appOp = AppOpsManager.permissionToOp(appOpPermission);
+ String appOp = AppOpsManagerCompat.permissionToOp(appOpPermission);
if (!overrideNonDefaultMode) {
Integer currentMode = Permissions.getAppOpMode(packageName, appOp, context);
if (currentMode != null && currentMode != Permissions.getDefaultAppOpMode(appOp)) {
@@ -96,6 +97,7 @@ public class AppOpPermissions {
case AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS:
case AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW:
case AppOpsManager.OPSTR_WRITE_SETTINGS:
+ case AppOpsManager.OPSTR_GET_USAGE_STATS:
case AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES:
case AppOpsManager.OPSTR_START_FOREGROUND:
// This isn't an API but we are deprecating it soon anyway.
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
index 1f6b5272a..6e901fa26 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
@@ -99,6 +99,7 @@ import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.data.getUnusedPackages
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.service.revokeAppPermissions
+import com.android.permissioncontroller.permission.utils.IPC
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.StringUtils
import com.android.permissioncontroller.permission.utils.Utils
@@ -861,7 +862,7 @@ class HibernationJobService : JobService() {
return true
}
- private suspend fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) {
+ private fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) {
val notificationManager = getSystemService(NotificationManager::class.java)!!
val permissionReminderChannel = NotificationChannel(
@@ -909,8 +910,10 @@ class HibernationJobService : JobService() {
notificationManager.notify(HibernationJobService::class.java.simpleName,
Constants.UNUSED_APPS_NOTIFICATION_ID, b.build())
- // Preload the unused packages
- getUnusedPackages().getInitializedValue()
+ GlobalScope.launch(IPC) {
+ // Preload the unused packages
+ getUnusedPackages().getInitializedValue(staleOk = true)
+ }
}
override fun onStopJob(params: JobParameters?): Boolean {
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/v31/HibernationController.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/v31/HibernationController.kt
index f4dfe233a..5aa07c232 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/v31/HibernationController.kt
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/v31/HibernationController.kt
@@ -25,6 +25,7 @@ import android.os.Build
import android.os.UserHandle
import androidx.annotation.RequiresApi
import com.android.permissioncontroller.DumpableLog
+import com.android.permissioncontroller.permission.data.HibernatedPackagesLiveData
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
/**
@@ -94,6 +95,9 @@ class HibernationController(
hibernationManager.setHibernatingGlobally(pkgName, true)
globallyHibernatedApps.add(pkgName)
}
+ if (hibernatedApps.isNotEmpty()) {
+ HibernatedPackagesLiveData.update()
+ }
if (DEBUG_HIBERNATION) {
DumpableLog.i(LOG_TAG,
"Done hibernating apps $hibernatedApps \n " +
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
index b65eb6710..52f0fd1a5 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
@@ -74,5 +74,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsHibernationTestCases"
+ }
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
index b9d2d237a..7c69d6078 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
@@ -62,6 +62,7 @@ class AppPermGroupUiInfoLiveData private constructor(
private val permGroupLiveData = PermGroupLiveData[permGroupName]
private val permissionStateLiveData = PermStateLiveData[packageName, permGroupName, user]
private val isStorage = permGroupName == STORAGE
+ private val isHealth = Utils.isHealthPermissionGroup(permGroupName)
init {
isSpecialLocation = LocationUtils.isLocationGroupAndProvider(app,
@@ -127,7 +128,8 @@ class AppPermGroupUiInfoLiveData private constructor(
val shouldShow = packageInfo.enabled &&
isGrantableAndNotLegacyPlatform(packageInfo, groupInfo, requestedPermissionInfos) &&
- (!isStorage || Utils.shouldShowStorage(packageInfo))
+ (!isStorage || Utils.shouldShowStorage(packageInfo)) &&
+ (!isHealth || Utils.shouldShowHealthPermission(packageInfo, groupInfo.name))
val isSystemApp = !isUserSensitive(permissionState)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt
index ae9ccf19e..52e89e972 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt
@@ -33,6 +33,7 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_UNUSED_APP_PERMISSION_REVOKED
import com.android.permissioncontroller.hibernation.getUnusedThresholdMs
import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.data.AutoRevokedPackagesLiveData
import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
import com.android.permissioncontroller.permission.data.get
@@ -221,5 +222,8 @@ suspend fun revokeAppPermissions(
}
}
}
+ if (revokedApps.isNotEmpty()) {
+ AutoRevokedPackagesLiveData.update()
+ }
return revokedApps
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
index e21be6a05..57c828c20 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
@@ -41,6 +41,9 @@ import static android.os.UserHandle.getUserHandleForUid;
import static android.os.UserHandle.myUserId;
import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS;
import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS;
+import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID;
+import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID;
+import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_USER_HANDLE;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
@@ -1054,6 +1057,10 @@ public class LocationAccessCheck {
Intent clickIntent = null;
if (isSafetyCenterBgLocationReminderEnabled()) {
clickIntent = new Intent(ACTION_SAFETY_CENTER);
+ clickIntent.putExtra(EXTRA_SAFETY_SOURCE_ID, BG_LOCATION_SOURCE_ID);
+ clickIntent.putExtra(
+ EXTRA_SAFETY_SOURCE_ISSUE_ID, createSafetySourceIssueId(pkg, user));
+ clickIntent.putExtra(EXTRA_SAFETY_SOURCE_USER_HANDLE, user);
} else {
clickIntent = new Intent(ACTION_MANAGE_APP_PERMISSION);
clickIntent.putExtra(EXTRA_PERMISSION_GROUP_NAME, LOCATION);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt
index 3405ab014..11299def9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt
@@ -42,6 +42,7 @@ import com.android.permissioncontroller.permission.model.livedatatypes.LightPerm
import com.android.permissioncontroller.permission.utils.IPC
import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions
import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.utils.PermissionMapping.getPlatformPermissionNamesOfGroup
import com.android.permissioncontroller.permission.utils.PermissionMapping.getRuntimePlatformPermissionNames
import com.android.permissioncontroller.permission.utils.application
@@ -55,7 +56,9 @@ internal object RuntimePermissionsUpgradeController {
private val LOG_TAG = RuntimePermissionsUpgradeController::class.java.simpleName
// The latest version of the runtime permissions database
- private val LATEST_VERSION = if (SdkLevel.isAtLeastT()) {
+ private val LATEST_VERSION = if (SdkLevel.isAtLeastU()) {
+ 11
+ } else if (SdkLevel.isAtLeastT()) {
10
} else {
9
@@ -134,6 +137,7 @@ internal object RuntimePermissionsUpgradeController {
val needBackgroundAppPermGroups = sdkUpgradedFromP && currentVersion <= 6
val needAccessMediaAppPermGroups = !isNewUser && currentVersion <= 7
val needGrantedExternalStorage = currentVersion <= 9 && SdkLevel.isAtLeastT()
+ val needGrantedReadMediaVisual = currentVersion <= 10 && SdkLevel.isAtLeastU()
val isDeviceUpgrading = context.packageManager.isDeviceUpgrading
// All data needed by this method.
@@ -156,24 +160,17 @@ internal object RuntimePermissionsUpgradeController {
private val pkgInfoProvider = UserPackageInfosLiveData[myUserHandle()]
/** Provides all {@link LightAppPermGroup} this upgrade needs */
- private var permGroupProviders: MutableList<LightAppPermGroupLiveData>? = null
+ private var permGroupProviders: MutableSet<LightAppPermGroupLiveData>? = null
/** {@link #permGroupProviders} that already provided a result */
private val permGroupProvidersDone = mutableSetOf<LightAppPermGroupLiveData>()
init {
// First step: Load packages + perm infos
- // TODO ntmyren: remove once b/154796729 is fixed
- Log.i("RuntimePermissions", "observing UserPackageInfoLiveData for " +
- "${myUserHandle().identifier} in RuntimePermissionsUpgradeController")
addSource(pkgInfoProvider) { pkgInfos ->
if (pkgInfos != null) {
removeSource(pkgInfoProvider)
- // TODO ntmyren: remove once b/154796729 is fixed
- Log.i("RuntimePermissions", "observing " +
- "PreinstalledUserPackageInfoLiveData for ${myUserHandle().identifier}" +
- " in RuntimePermissionsUpgradeController")
addSource(preinstalledPkgInfoProvider) { preinstalledPkgInfos ->
if (preinstalledPkgInfos != null) {
removeSource(preinstalledPkgInfoProvider)
@@ -203,15 +200,16 @@ internal object RuntimePermissionsUpgradeController {
if (permGroupProviders == null && pkgInfoProvider.value != null) {
// Second step: Trigger load of app-perm-groups
- permGroupProviders = mutableListOf()
+ permGroupProviders = mutableSetOf()
// Only load app-perm-groups needed for this upgrade
if (needBackgroundAppPermGroups || needAccessMediaAppPermGroups ||
- needGrantedExternalStorage) {
+ needGrantedExternalStorage || needGrantedReadMediaVisual) {
for ((pkgName, _, requestedPerms, requestedPermFlags) in
pkgInfoProvider.value!!) {
var requestsAccessMediaLocation = false
var hasGrantedExternalStorage = false
+ var hasGrantedReadMediaVisual = false
for ((perm, flags) in requestedPerms.zip(requestedPermFlags)) {
if (needBackgroundAppPermGroups &&
@@ -220,17 +218,22 @@ internal object RuntimePermissionsUpgradeController {
permission_group.LOCATION, myUserHandle()])
}
- if (needAccessMediaAppPermGroups || needGrantedExternalStorage) {
+ if (needAccessMediaAppPermGroups || needGrantedExternalStorage ||
+ needGrantedReadMediaVisual) {
if (needAccessMediaAppPermGroups &&
perm == permission.ACCESS_MEDIA_LOCATION) {
requestsAccessMediaLocation = true
}
- if (perm == permission.READ_EXTERNAL_STORAGE &&
- flags and PackageInfo.REQUESTED_PERMISSION_GRANTED
- != 0) {
+ val isGranted =
+ flags and PackageInfo.REQUESTED_PERMISSION_GRANTED != 0
+ if (perm == permission.READ_EXTERNAL_STORAGE && isGranted) {
hasGrantedExternalStorage = true
}
+ if (PermissionMapping.getGroupOfPlatformPermission(perm)
+ == permission_group.READ_MEDIA_VISUAL && isGranted) {
+ hasGrantedReadMediaVisual = true
+ }
}
}
@@ -255,6 +258,10 @@ internal object RuntimePermissionsUpgradeController {
accessMediaLocationPermGroup, myUserHandle()])
}
}
+ if (hasGrantedReadMediaVisual && needGrantedReadMediaVisual) {
+ permGroupProviders!!.add(LightAppPermGroupLiveData[pkgName,
+ permission_group.READ_MEDIA_VISUAL, myUserHandle()])
+ }
}
}
@@ -536,6 +543,24 @@ internal object RuntimePermissionsUpgradeController {
currentVersion = 10
}
+ if (currentVersion == 10 && SdkLevel.isAtLeastU()) {
+ // On U, if the app is granted READ_MEDIA_VISUAL, expand the grant to
+ // READ_MEDIA_VISUAL_USER_SELECTED
+ if (isDeviceUpgrading && !isNewUser) {
+ Log.i(LOG_TAG, "Grandfathering READ_MEDIA_VISUAL_USER_SELECTED to apps already " +
+ "granted visual permissions")
+ val visualAppPermGroups = storageAndMediaAppPermGroups.filter {
+ it.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU &&
+ it.permGroupInfo.name == permission_group.READ_MEDIA_VISUAL &&
+ it.isGranted && it.isUserSet
+ }
+ visualAppPermGroups.forEach {
+ grants.add(Grant(false, it))
+ }
+ }
+ currentVersion = 11
+ }
+
// XXX: Add new upgrade steps above this point.
return Triple(currentVersion, exemptions, grants)
@@ -591,7 +616,7 @@ internal object RuntimePermissionsUpgradeController {
private val isBackground: Boolean,
/** Group to be granted */
private val group: LightAppPermGroup,
- /** Which of th permissions in the group should be granted */
+ /** Which of the permissions in the group should be granted */
private val permissions: List<String> = group.permissions.keys.toList()
) {
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index ff63cdae5..c173146eb 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -172,7 +172,6 @@ public class GrantPermissionsActivity extends SettingsActivity
private String mTargetPackage;
/** A key representing this activity, defined by the target package and task ID */
private Pair<String, Integer> mKey;
- private int mCurrentRequestIdx = 0;
private float mOriginalDimAmount;
private View mRootView;
private int mStoragePermGroupIcon = R.drawable.ic_empty_icon;
@@ -507,8 +506,6 @@ public class GrantPermissionsActivity extends SettingsActivity
Log.e(LOG_TAG, "Cannot load icon for group" + info.getGroupName(), e);
}
- boolean showingNewGroup = message == null || !message.equals(getTitle());
-
// Set the permission message as the title so it can be announced. Skip on Wear
// because the dialog title is already announced, as is the default selection which
// is a text view containing the title.
@@ -542,12 +539,10 @@ public class GrantPermissionsActivity extends SettingsActivity
mRequestCounts = mRequestInfos.size();
}
- mViewHandler.updateUi(info.getGroupName(), mRequestCounts, mCurrentRequestIdx, icon,
+ int pageIdx = mRequestCounts - mRequestInfos.size();
+ mViewHandler.updateUi(info.getGroupName(), mRequestCounts, pageIdx, icon,
message, detailMessage, permissionRationaleMessage, mButtonVisibilities,
locationVisibilities);
- if (showingNewGroup) {
- mCurrentRequestIdx++;
- }
getWindow().setDimAmount(mOriginalDimAmount);
if (mRootView.getVisibility() == View.GONE) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index afeb19aa9..81af33294 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -31,7 +31,6 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.PERM
import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__OPEN;
import android.Manifest;
-import android.app.ActionBar;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -549,15 +548,6 @@ public final class ManagePermissionsActivity extends SettingsActivity {
}
@Override
- public ActionBar getActionBar() {
- ActionBar ab = super.getActionBar();
- if (ab != null) {
- ab.setHomeActionContentDescription(R.string.back);
- }
- return ab;
- }
-
- @Override
public boolean onOptionsItemSelected(MenuItem item) {
// in automotive mode, there's no system wide back button, so need to add that
if (DeviceUtils.isAuto(this)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
index a16bc1ce4..481543eb6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
@@ -112,8 +112,10 @@ class AutoPermissionUsageDetailsFragment :
activity?.finish()
return
}
- if (!requireArguments().containsKey(Intent.EXTRA_PERMISSION_GROUP_NAME) or
- (requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null)) {
+ if (
+ !requireArguments().containsKey(Intent.EXTRA_PERMISSION_GROUP_NAME) or
+ (requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null)
+ ) {
DumpableLog.e(LOG_TAG, "Missing argument ${Intent.EXTRA_USER}")
activity?.finish()
return
@@ -128,14 +130,19 @@ class AutoPermissionUsageDetailsFragment :
headerLabel =
resources.getString(
R.string.permission_group_usage_title,
- getPermGroupLabel(requireContext(), filterGroup))
+ getPermGroupLabel(requireContext(), filterGroup)
+ )
val context = preferenceManager.getContext()
permissionUsages = PermissionUsages(context)
roleManager = Utils.getSystemServiceSafe(context, RoleManager::class.java)
val usageViewModelFactory =
PermissionUsageDetailsViewModelFactoryLegacy(
- PermissionControllerApplication.get(), roleManager, filterGroup, sessionId)
+ PermissionControllerApplication.get(),
+ roleManager,
+ filterGroup,
+ sessionId
+ )
usageViewModel =
ViewModelProvider(this, usageViewModelFactory)[
PermissionUsageDetailsViewModelLegacy::class.java]
@@ -157,7 +164,11 @@ class AutoPermissionUsageDetailsFragment :
/** Reloads the data to show. */
private fun reloadData() {
usageViewModel.loadPermissionUsages(
- requireActivity().getLoaderManager(), permissionUsages, this, FILTER_24_HOURS)
+ requireActivity().getLoaderManager(),
+ permissionUsages,
+ this,
+ FILTER_24_HOURS
+ )
if (finishedInitialLoad) {
setLoading(true)
}
@@ -176,7 +187,8 @@ class AutoPermissionUsageDetailsFragment :
PermissionControllerStatsLog.write(
PERMISSION_USAGE_FRAGMENT_INTERACTION,
sessionId,
- PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED)
+ PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
+ )
}
showSystem = !showSystem
updateAction()
@@ -206,19 +218,25 @@ class AutoPermissionUsageDetailsFragment :
val uiData =
usageViewModel.buildPermissionUsageDetailsUiData(
- appPermissionUsages, showSystem, SHOW_7_DAYS)
+ appPermissionUsages,
+ showSystem,
+ SHOW_7_DAYS
+ )
if (hasSystemApps != uiData.shouldDisplayShowSystemToggle) {
hasSystemApps = uiData.shouldDisplayShowSystemToggle
updateAction()
}
- val category = AtomicReference(PreferenceCategory(context))
+ val category = AtomicReference(PreferenceCategory(requireContext()))
preferenceScreen.addPreference(category.get())
AppDataLoader(context) {
renderHistoryPreferences(
- uiData.getHistoryPreferenceDataList(), category, preferenceScreen)
+ uiData.getHistoryPreferenceDataList(),
+ category,
+ preferenceScreen
+ )
setLoading(false)
finishedInitialLoad = true
@@ -239,7 +257,8 @@ class AutoPermissionUsageDetailsFragment :
summary =
getString(
R.string.permission_group_usage_subtitle_24h,
- getPermGroupLabel(requireContext(), filterGroup))
+ getPermGroupLabel(requireContext(), filterGroup)
+ )
isSelectable = false
}
preferenceScreen.addPreference(preference)
@@ -252,7 +271,8 @@ class AutoPermissionUsageDetailsFragment :
summary =
getString(
R.string.manage_permission_summary,
- getPermGroupLabel(requireContext(), filterGroup))
+ getPermGroupLabel(requireContext(), filterGroup)
+ )
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent =
@@ -279,12 +299,13 @@ class AutoPermissionUsageDetailsFragment :
val currentDateMs =
ZonedDateTime.ofInstant(
Instant.ofEpochMilli(usageTimestamp),
- Clock.system(ZoneId.systemDefault()).zone)
+ Clock.system(ZoneId.systemDefault()).zone
+ )
.truncatedTo(ChronoUnit.DAYS)
.toEpochSecond() * 1000L
if (currentDateMs != previousDateMs) {
if (previousDateMs != 0L) {
- category.set(PreferenceCategory(context))
+ category.set(PreferenceCategory(requireContext()))
preferenceScreen.addPreference(category.get())
}
if (usageTimestamp > MIDNIGHT_TODAY) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingDetailsPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingDetailsPreference.kt
index 014627c3c..70d0632f9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingDetailsPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingDetailsPreference.kt
@@ -45,7 +45,7 @@ class AppDataSharingDetailsPreference : Preference {
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
- val noUpdatesMessage = holder.findViewById(R.id.no_updates_message)
+ val noUpdatesMessage = holder.findViewById(R.id.no_updates_message)!!
noUpdatesMessage.isVisible = showNoUpdates
super.onBindViewHolder(holder)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AllAppPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AllAppPermissionsViewModel.kt
index d789f4e1e..226208041 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AllAppPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AllAppPermissionsViewModel.kt
@@ -81,6 +81,8 @@ class AllAppPermissionsViewModel(
.filter { filterGroup == null || it.key == filterGroup }
.filter { (it.key != Manifest.permission_group.STORAGE ||
Utils.shouldShowStorage(packageInfo)) }
+ .filter { (!Utils.isHealthPermissionGroup(it.key) ||
+ Utils.shouldShowHealthPermission(packageInfo, it.key))}
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index 0680ffcd2..d773b5ff7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -43,7 +43,6 @@ import android.os.UserManager
import android.permission.PermissionManager
import android.provider.MediaStore
import android.util.Log
-import androidx.annotation.ChecksSdkIntAtLeast
import androidx.core.util.Consumer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
@@ -516,8 +515,8 @@ class GrantPermissionsViewModel(
// Show location permission dialogs based on location permissions
val locationVisibilities = MutableList(NEXT_LOCATION_DIALOG) { false }
- if (groupState.group.permGroupName == LOCATION && isLocationAccuracyEnabled() &&
- packageInfo.targetSdkVersion >= Build.VERSION_CODES.S) {
+ if (groupState.group.permGroupName == LOCATION &&
+ isLocationAccuracyEnabledForApp(groupState.group)) {
if (needFgPermissions) {
locationVisibilities[LOCATION_ACCURACY_LAYOUT] = true
if (fgState != null &&
@@ -619,7 +618,7 @@ class GrantPermissionsViewModel(
if (rhsHasOneTime && !lhsHasOneTime) {
-1
} else if ((!rhsHasOneTime && lhsHasOneTime) ||
- isHealthPermissionGroup(rhs.groupName)
+ Utils.isHealthPermissionGroup(rhs.groupName)
) {
1
} else {
@@ -764,7 +763,7 @@ class GrantPermissionsViewModel(
// Skip showing groups that we know cannot be granted.
return false
} else if (subGroup.isUserFixed) {
- if (perm == ACCESS_COARSE_LOCATION) {
+ if (perm == ACCESS_COARSE_LOCATION && isLocationAccuracyEnabledForApp(group)) {
val coarsePerm = group.permissions[perm]
if (coarsePerm != null && !coarsePerm.isUserFixed) {
// If the location group is user fixed but ACCESS_COARSE_LOCATION is not, then
@@ -868,7 +867,7 @@ class GrantPermissionsViewModel(
private fun canAutoGrantWholeGroup(group: LightAppPermGroup): Boolean {
// If FINE location is not granted, do not grant it automatically when COARSE
// location is already granted.
- if (group.permGroupName == LOCATION &&
+ if (group.permGroupName == LOCATION && isLocationAccuracyEnabledForApp(group) &&
group.allPermissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp == false) {
return false
}
@@ -1280,11 +1279,6 @@ class GrantPermissionsViewModel(
}
}
- @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- private fun isHealthPermissionGroup(permGroupName: String): Boolean {
- return SdkLevel.isAtLeastU() && HEALTH_PERMISSION_GROUP.equals(permGroupName)
- }
-
fun handleHealthConnectPermissions(activity: Activity) {
if (activityResultCallback == null) {
activityResultCallback = Consumer {
@@ -1475,6 +1469,11 @@ class GrantPermissionsViewModel(
}
}
+ private fun isLocationAccuracyEnabledForApp(group: LightAppPermGroup): Boolean {
+ return isLocationAccuracyEnabled() &&
+ group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.S
+ }
+
/**
* Log all permission groups which were requested
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index f9345ef58..49a35cadf 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -86,7 +86,7 @@ import com.android.permissioncontroller.permission.model.livedatatypes.LightPerm
import com.android.permissioncontroller.permission.model.livedatatypes.PermState
import com.android.permissioncontroller.permission.service.LocationAccessCheck
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import java.time.Duration
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.Continuation
@@ -106,13 +106,14 @@ object KotlinUtils {
private const val LOG_TAG = "PermissionController Utils"
- private const val PERMISSION_CONTROLLER_CHANGED_FLAG_MASK = FLAG_PERMISSION_USER_SET or
- FLAG_PERMISSION_USER_FIXED or
- FLAG_PERMISSION_ONE_TIME or
- FLAG_PERMISSION_REVOKED_COMPAT or
- FLAG_PERMISSION_ONE_TIME or
- FLAG_PERMISSION_REVIEW_REQUIRED or
- FLAG_PERMISSION_AUTO_REVOKED
+ private const val PERMISSION_CONTROLLER_CHANGED_FLAG_MASK =
+ FLAG_PERMISSION_USER_SET or
+ FLAG_PERMISSION_USER_FIXED or
+ FLAG_PERMISSION_ONE_TIME or
+ FLAG_PERMISSION_REVOKED_COMPAT or
+ FLAG_PERMISSION_ONE_TIME or
+ FLAG_PERMISSION_REVIEW_REQUIRED or
+ FLAG_PERMISSION_AUTO_REVOKED
private const val KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"
private const val SAFETY_PROTECTION_RESOURCES_ENABLED = "safety_protection_enabled"
@@ -131,10 +132,10 @@ object KotlinUtils {
private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
- /** Whether to show the Permissions Hub. */
+ /** Whether to show the Permissions Hub. */
private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
- /** Whether to show the mic and camera icons. */
+ /** Whether to show the mic and camera icons. */
private const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"
/** Whether to show the location indicators. */
@@ -143,18 +144,18 @@ object KotlinUtils {
/** Whether location accuracy feature is enabled */
private const val PROPERTY_LOCATION_ACCURACY_ENABLED = "location_accuracy_enabled"
- /** Whether to show 7-day toggle in privacy hub. */
+ /** Whether to show 7-day toggle in privacy hub. */
private const val PRIVACY_DASHBOARD_7_DAY_TOGGLE = "privacy_dashboard_7_day_toggle"
/** Default location precision */
private const val PROPERTY_LOCATION_PRECISION = "location_precision"
- /** Whether to show the photo picker option in permission prompts. */
+ /** Whether to show the photo picker option in permission prompts. */
private const val PROPERTY_PHOTO_PICKER_PROMPT_ENABLED = "photo_picker_prompt_enabled"
/**
- * The minimum amount of time to wait, after scheduling the safety label changes job, before
- * the job actually runs for the first time.
+ * The minimum amount of time to wait, after scheduling the safety label changes job, before the
+ * job actually runs for the first time.
*/
private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS =
"safety_label_changes_job_delay_millis"
@@ -178,8 +179,12 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
fun isPermissionsHub2FlagEnabled(): Boolean {
- return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
+ return SdkLevel.isAtLeastS() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_2_ENABLED,
+ false
+ )
}
/**
* Whether to show the Permissions Dashboard
@@ -198,13 +203,17 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
fun isCameraMicIconsFlagEnabled(): Boolean {
- return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_CAMERA_MIC_ICONS_ENABLED, true)
+ return SdkLevel.isAtLeastS() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_CAMERA_MIC_ICONS_ENABLED,
+ true
+ )
}
/**
- * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons
- * specifically, are enabled.
+ * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the
+ * icons specifically, are enabled.
*
* @return whether to show the icons.
*/
@@ -220,36 +229,42 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
fun isLocationIndicatorsFlagEnabled(): Boolean {
- return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_INDICATORS_ENABLED, false)
+ return SdkLevel.isAtLeastS() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_INDICATORS_ENABLED,
+ false
+ )
}
/**
- * Whether to show the location indicators. The location indicators are enable if the
- * permission hub, or location indicator specifically are enabled.
+ * Whether to show the location indicators. The location indicators are enable if the permission
+ * hub, or location indicator specifically are enabled.
*/
@ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
fun shouldShowLocationIndicators(): Boolean {
return isLocationIndicatorsFlagEnabled() || isPermissionsHub2FlagEnabled()
}
- /**
- * Whether the location accuracy feature is enabled
- */
+ /** Whether the location accuracy feature is enabled */
@ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
fun isLocationAccuracyEnabled(): Boolean {
- return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_ACCURACY_ENABLED, true)
+ return SdkLevel.isAtLeastS() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCURACY_ENABLED,
+ true
+ )
}
- /**
- * Default state of location precision
- * true: default is FINE.
- * false: default is COARSE.
- */
+ /** Default state of location precision true: default is FINE. false: default is COARSE. */
fun getDefaultPrecision(): Boolean {
- return !SdkLevel.isAtLeastS() || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_PRECISION, true)
+ return !SdkLevel.isAtLeastS() ||
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_PRECISION,
+ true
+ )
}
/**
@@ -259,8 +274,12 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
fun is7DayToggleEnabled(): Boolean {
- return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PRIVACY_DASHBOARD_7_DAY_TOGGLE, false)
+ return SdkLevel.isAtLeastS() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PRIVACY_DASHBOARD_7_DAY_TOGGLE,
+ false
+ )
}
/**
@@ -271,10 +290,15 @@ object KotlinUtils {
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
fun isPhotoPickerPromptEnabled(): Boolean {
val app = PermissionControllerApplication.get()
- return SdkLevel.isAtLeastU() && !DeviceUtils.isAuto(app) &&
- !DeviceUtils.isTelevision(app) && !DeviceUtils.isWear(app) &&
- DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PHOTO_PICKER_PROMPT_ENABLED, true)
+ return SdkLevel.isAtLeastU() &&
+ !DeviceUtils.isAuto(app) &&
+ !DeviceUtils.isTelevision(app) &&
+ !DeviceUtils.isWear(app) &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PHOTO_PICKER_PROMPT_ENABLED,
+ true
+ )
}
/*
@@ -284,8 +308,12 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
fun isPermissionRationaleEnabled(): Boolean {
- return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PERMISSION_RATIONALE_ENABLED, true)
+ return SdkLevel.isAtLeastU() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PERMISSION_RATIONALE_ENABLED,
+ true
+ )
}
/**
@@ -293,8 +321,12 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
fun isSafetyLabelChangeNotificationsEnabled(context: Context): Boolean {
- return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true) &&
+ return SdkLevel.isAtLeastU() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED,
+ true
+ ) &&
!DeviceUtils.isAuto(context) &&
!DeviceUtils.isTelevision(context) &&
!DeviceUtils.isWear(context)
@@ -306,8 +338,12 @@ object KotlinUtils {
*/
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
fun safetyLabelChangesJobServiceKillSwitch(): Boolean {
- return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH, false)
+ return SdkLevel.isAtLeastU() &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH,
+ false
+ )
}
/** How often the safety label changes job will run. */
@@ -316,7 +352,8 @@ object KotlinUtils {
return DeviceConfig.getLong(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS,
- Duration.ofDays(30).toMillis())
+ Duration.ofDays(30).toMillis()
+ )
}
/** Whether the safety label changes job should only be run when the device is idle. */
@@ -325,19 +362,19 @@ object KotlinUtils {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE,
- true)
+ true
+ )
}
/**
- * Given a Map, and a List, determines which elements are in the list, but not the map, and
- * vice versa. Used primarily for determining which liveDatas are already being watched, and
- * which need to be removed or added
+ * Given a Map, and a List, determines which elements are in the list, but not the map, and vice
+ * versa. Used primarily for determining which liveDatas are already being watched, and which
+ * need to be removed or added
*
* @param oldValues A map of key type K, with any value type
* @param newValues A list of type K
- *
* @return A pair, where the first value is all items in the list, but not the map, and the
- * second is all keys in the map, but not the list
+ * second is all keys in the map, but not the list
*/
fun <K> getMapAndListDifferences(
newValues: Collection<K>,
@@ -359,7 +396,7 @@ object KotlinUtils {
*
* @param compare The function comparing two preferences, which will be used to sort
* @param hasHeader Whether the group contains a LargeHeaderPreference, which will be kept at
- * the top of the list
+ * the top of the list
*/
fun sortPreferenceGroup(
group: PreferenceGroup,
@@ -372,15 +409,17 @@ object KotlinUtils {
}
if (hasHeader) {
- preferences.sortWith(Comparator { lhs, rhs ->
- if (lhs is SettingsWithLargeHeader.LargeHeaderPreference) {
- -1
- } else if (rhs is SettingsWithLargeHeader.LargeHeaderPreference) {
- 1
- } else {
- compare(lhs, rhs)
+ preferences.sortWith(
+ Comparator { lhs, rhs ->
+ if (lhs is SettingsWithLargeHeader.LargeHeaderPreference) {
+ -1
+ } else if (rhs is SettingsWithLargeHeader.LargeHeaderPreference) {
+ 1
+ } else {
+ compare(lhs, rhs)
+ }
}
- })
+ )
} else {
preferences.sortWith(Comparator(compare))
}
@@ -395,17 +434,15 @@ object KotlinUtils {
*
* @param context The context from which to get the icon
* @param groupName The name of the permission group whose icon we want
- *
* @return The permission group's icon, the ic_perm_device_info icon if the group has no icon,
- * or the group does not exist
+ * or the group does not exist
*/
@JvmOverloads
fun getPermGroupIcon(context: Context, groupName: String, tint: Int? = null): Drawable? {
val groupInfo = Utils.getGroupInfo(groupName, context)
var icon: Drawable? = null
if (groupInfo != null && groupInfo.icon != 0) {
- icon = Utils.loadDrawable(context.packageManager, groupInfo.packageName,
- groupInfo.icon)
+ icon = Utils.loadDrawable(context.packageManager, groupInfo.packageName, groupInfo.icon)
}
if (icon == null) {
@@ -425,13 +462,15 @@ object KotlinUtils {
*
* @param context The context from which to get the label
* @param groupName The name of the permission group whose label we want
- *
* @return The permission group's label, or the group name, if the group is invalid
*/
fun getPermGroupLabel(context: Context, groupName: String): CharSequence {
val groupInfo = Utils.getGroupInfo(groupName, context) ?: return groupName
- return groupInfo.loadSafeLabel(context.packageManager, 0f,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM)
+ return groupInfo.loadSafeLabel(
+ context.packageManager,
+ 0f,
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+ )
}
/**
@@ -439,9 +478,8 @@ object KotlinUtils {
*
* @param context The context from which to get the description
* @param groupName The name of the permission group whose description we want
- *
* @return The permission group's description, or an empty string, if the group is invalid, or
- * its description does not exist
+ * its description does not exist
*/
fun getPermGroupDescription(context: Context, groupName: String): CharSequence {
val groupInfo = Utils.getGroupInfo(groupName, context)
@@ -457,15 +495,20 @@ object KotlinUtils {
/**
* Gets a permission's label from the system.
+ *
* @param context The context from which to get the label
* @param permName The name of the permission whose label we want
- *
* @return The permission's label, or the permission name, if the permission is invalid
*/
fun getPermInfoLabel(context: Context, permName: String): CharSequence {
return try {
- context.packageManager.getPermissionInfo(permName, 0).loadSafeLabel(
- context.packageManager, 20000.toFloat(), TextUtils.SAFE_STRING_FLAG_TRIM)
+ context.packageManager
+ .getPermissionInfo(permName, 0)
+ .loadSafeLabel(
+ context.packageManager,
+ 20000.toFloat(),
+ TextUtils.SAFE_STRING_FLAG_TRIM
+ )
} catch (e: PackageManager.NameNotFoundException) {
permName
}
@@ -473,19 +516,23 @@ object KotlinUtils {
/**
* Gets a permission's icon from the system.
+ *
* @param context The context from which to get the icon
* @param permName The name of the permission whose icon we want
- *
- * @return The permission's icon, or the permission's group icon if the icon isn't set, or
- * the ic_perm_device_info icon if the permission is invalid.
+ * @return The permission's icon, or the permission's group icon if the icon isn't set, or the
+ * ic_perm_device_info icon if the permission is invalid.
*/
fun getPermInfoIcon(context: Context, permName: String): Drawable? {
return try {
val permInfo = context.packageManager.getPermissionInfo(permName, 0)
var icon: Drawable? = null
if (permInfo.icon != 0) {
- icon = Utils.applyTint(context, permInfo.loadUnbadgedIcon(context.packageManager),
- android.R.attr.colorControlNormal)
+ icon =
+ Utils.applyTint(
+ context,
+ permInfo.loadUnbadgedIcon(context.packageManager),
+ android.R.attr.colorControlNormal
+ )
}
if (icon == null) {
@@ -495,8 +542,11 @@ object KotlinUtils {
icon
} catch (e: PackageManager.NameNotFoundException) {
- Utils.applyTint(context, context.getDrawable(R.drawable.ic_perm_device_info),
- android.R.attr.colorControlNormal)
+ Utils.applyTint(
+ context,
+ context.getDrawable(R.drawable.ic_perm_device_info),
+ android.R.attr.colorControlNormal
+ )
}
}
@@ -505,9 +555,8 @@ object KotlinUtils {
*
* @param context The context from which to get the description
* @param permName The name of the permission whose description we want
- *
- * @return The permission's description, or an empty string, if the group is invalid, or
- * its description does not exist
+ * @return The permission's description, or an empty string, if the group is invalid, or its
+ * description does not exist
*/
fun getPermInfoDescription(context: Context, permName: String): CharSequence {
return try {
@@ -524,14 +573,9 @@ object KotlinUtils {
* @param app The current application
* @param packageName The name of the package whose icon we want
* @param user The user for whom we want the package icon
- *
* @return The package's icon, or null, if the package does not exist
*/
- fun getBadgedPackageIcon(
- app: Application,
- packageName: String,
- user: UserHandle
- ): Drawable? {
+ fun getBadgedPackageIcon(app: Application, packageName: String, user: UserHandle): Drawable? {
return try {
val userContext = Utils.getUserContext(app, user)
val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
@@ -547,7 +591,6 @@ object KotlinUtils {
* @param app The current application
* @param packageName The name of the package whose label we want
* @param user The user for whom we want the package label
- *
* @return The package's label
*/
fun getPackageLabel(app: Application, packageName: String, user: UserHandle): String {
@@ -561,8 +604,12 @@ object KotlinUtils {
}
fun convertToBitmap(pkgIcon: Drawable): Bitmap {
- val pkgIconBmp = Bitmap.createBitmap(pkgIcon.intrinsicWidth, pkgIcon.intrinsicHeight,
- Bitmap.Config.ARGB_8888)
+ val pkgIconBmp =
+ Bitmap.createBitmap(
+ pkgIcon.intrinsicWidth,
+ pkgIcon.intrinsicHeight,
+ Bitmap.Config.ARGB_8888
+ )
// Draw the icon so it can be displayed.
val canvas = Canvas(pkgIconBmp)
pkgIcon.setBounds(0, 0, pkgIcon.intrinsicWidth, pkgIcon.intrinsicHeight)
@@ -577,13 +624,13 @@ object KotlinUtils {
* @param app The current application
* @param packageName The name of the package whose uid we want
* @param user The user we want the package uid for
- *
* @return The package's UID, or null if the package or user is invalid
*/
fun getPackageUid(app: Application, packageName: String, user: UserHandle): Int? {
val liveData = LightPackageInfoLiveData[packageName, user]
val liveDataUid = liveData.value?.uid
- return if (liveDataUid != null && liveData.hasActiveObservers()) liveDataUid else {
+ return if (liveDataUid != null && liveData.hasActiveObservers()) liveDataUid
+ else {
val userContext = Utils.getUserContext(app, user)
try {
val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
@@ -594,9 +641,7 @@ object KotlinUtils {
}
}
- /**
- * Return a specific MIME type, if a set of permissions is associated with one
- */
+ /** Return a specific MIME type, if a set of permissions is associated with one */
fun getMimeTypeForPermissions(permissions: List<String>): String? {
if (permissions.contains(READ_MEDIA_IMAGES) && !permissions.contains(READ_MEDIA_VIDEO)) {
return "image/*"
@@ -614,30 +659,33 @@ object KotlinUtils {
* @param app The currenct application
* @param packageName The package name to check
* @param user The user whose package we want to check
- *
* @return true if the package is R+ (and not a work profile) or has auto revoke enabled
*/
fun isROrAutoRevokeEnabled(app: Application, packageName: String, user: UserHandle): Boolean {
val userContext = Utils.getUserContext(app, user)
val liveDataValue = LightPackageInfoLiveData[packageName, user].value
- val (targetSdk, uid) = if (liveDataValue != null) {
- liveDataValue.targetSdkVersion to liveDataValue.uid
- } else {
- val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
- appInfo.targetSdkVersion to appInfo.uid
- }
+ val (targetSdk, uid) =
+ if (liveDataValue != null) {
+ liveDataValue.targetSdkVersion to liveDataValue.uid
+ } else {
+ val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
+ appInfo.targetSdkVersion to appInfo.uid
+ }
if (targetSdk <= Build.VERSION_CODES.Q) {
val opsManager = app.getSystemService(AppOpsManager::class.java)!!
- return opsManager.unsafeCheckOpNoThrow(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid,
- packageName) == MODE_ALLOWED
+ return opsManager.unsafeCheckOpNoThrow(
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+ uid,
+ packageName
+ ) == MODE_ALLOWED
}
return true
}
/**
- * Determine if the given permission should be treated as split from a
- * non-runtime permission for an application targeting the given SDK level.
+ * Determine if the given permission should be treated as split from a non-runtime permission
+ * for an application targeting the given SDK level.
*/
private fun isPermissionSplitFromNonRuntime(
app: Application,
@@ -664,8 +712,7 @@ object KotlinUtils {
* @param group: The LightAppPermGroup whose permission flags we wish to set
* @param flags: Pairs of <FlagInt, ShouldSetFlag>
* @param filterPermissions: A list of permissions to filter by. Only the filtered permissions
- * will be set
- *
+ * will be set
* @return A new LightAppPermGroup with the flags set.
*/
fun setGroupFlags(
@@ -690,14 +737,29 @@ object KotlinUtils {
}
// Check if flags need to be updated
if (flagMask and (perm.flags xor flagsToSet) != 0) {
- app.packageManager.updatePermissionFlags(permName, group.packageName,
- group.userHandle, *flags)
+ app.packageManager.updatePermissionFlags(
+ permName,
+ group.packageName,
+ group.userHandle,
+ *flags
+ )
}
- newPerms[permName] = LightPermission(group.packageInfo, perm.permInfo,
- perm.isGrantedIncludingAppOp, perm.flags or flagsToSet, perm.foregroundPerms)
+ newPerms[permName] =
+ LightPermission(
+ group.packageInfo,
+ perm.permInfo,
+ perm.isGrantedIncludingAppOp,
+ perm.flags or flagsToSet,
+ perm.foregroundPerms
+ )
}
- return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
- group.hasInstallToRuntimeSplit, group.specialLocationGrant)
+ return LightAppPermGroup(
+ group.packageInfo,
+ group.permGroupInfo,
+ newPerms,
+ group.hasInstallToRuntimeSplit,
+ group.specialLocationGrant
+ )
}
/**
@@ -708,9 +770,7 @@ object KotlinUtils {
* @param app The current application
* @param group The group whose permissions should be granted
* @param filterPermissions If not specified, all permissions of the group will be granted.
- * Otherwise only permissions in {@code filterPermissions} will be
- * granted.
- *
+ * Otherwise only permissions in {@code filterPermissions} will be granted.
* @return a new LightAppPermGroup, reflecting the new state
*/
@JvmOverloads
@@ -722,8 +782,15 @@ object KotlinUtils {
userFixed: Boolean = false,
withoutAppOps: Boolean = false,
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, false, isOneTime, userFixed,
- withoutAppOps, filterPermissions)
+ return grantRuntimePermissions(
+ app,
+ group,
+ false,
+ isOneTime,
+ userFixed,
+ withoutAppOps,
+ filterPermissions
+ )
}
/**
@@ -734,9 +801,7 @@ object KotlinUtils {
* @param app The current application
* @param group The group whose permissions should be granted
* @param filterPermissions If not specified, all permissions of the group will be granted.
- * Otherwise only permissions in {@code filterPermissions} will be
- * granted.
- *
+ * Otherwise only permissions in {@code filterPermissions} will be granted.
* @return a new LightAppPermGroup, reflecting the new state
*/
@JvmOverloads
@@ -745,8 +810,7 @@ object KotlinUtils {
group: LightAppPermGroup,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, true, false, false, false,
- filterPermissions)
+ return grantRuntimePermissions(app, group, true, false, false, false, filterPermissions)
}
private fun grantRuntimePermissions(
@@ -764,8 +828,8 @@ object KotlinUtils {
val perm = group.permissions[permName] ?: continue
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == grantBackground) {
- val (newPerm, shouldKill) = grantRuntimePermission(app, perm, group, isOneTime,
- userFixed, withoutAppOps)
+ val (newPerm, shouldKill) =
+ grantRuntimePermission(app, perm, group, isOneTime, userFixed, withoutAppOps)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -776,32 +840,48 @@ object KotlinUtils {
var permFlags = groupPerm.flags
permFlags = permFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
if (groupPerm.flags != permFlags) {
- app.packageManager.updatePermissionFlags(groupPerm.name,
- group.packageInfo.packageName, PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
- permFlags, user)
+ app.packageManager.updatePermissionFlags(
+ groupPerm.name,
+ group.packageInfo.packageName,
+ PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
+ permFlags,
+ user
+ )
}
}
}
if (shouldKillForAnyPermission) {
(app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
- group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
+ group.packageInfo.uid,
+ KILL_REASON_APP_OP_CHANGE
+ )
}
- val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
- group.hasInstallToRuntimeSplit, group.specialLocationGrant)
+ val newGroup =
+ LightAppPermGroup(
+ group.packageInfo,
+ group.permGroupInfo,
+ newPerms,
+ group.hasInstallToRuntimeSplit,
+ group.specialLocationGrant
+ )
// If any permission in the group is one time granted, start one time permission session.
if (newGroup.permissions.any { it.value.isOneTime && it.value.isGrantedIncludingAppOp }) {
if (SdkLevel.isAtLeastT()) {
app.getSystemService(PermissionManager::class.java)!!.startOneTimePermissionSession(
- group.packageName, Utils.getOneTimePermissionsTimeout(),
- Utils.getOneTimePermissionsKilledDelay(false),
- ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
- ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE)
+ group.packageName,
+ Utils.getOneTimePermissionsTimeout(),
+ Utils.getOneTimePermissionsKilledDelay(false),
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE
+ )
} else {
app.getSystemService(PermissionManager::class.java)!!.startOneTimePermissionSession(
- group.packageName, Utils.getOneTimePermissionsTimeout(),
- ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
- ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE)
+ group.packageName,
+ Utils.getOneTimePermissionsTimeout(),
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE
+ )
}
}
return newGroup
@@ -813,14 +893,13 @@ object KotlinUtils {
* @param app The current application
* @param perm The permission which should be granted.
* @param group An app permission group in which to look for background or foreground
- * @param isOneTime Whether this is a one-time permission grant
- * permissions
+ * @param isOneTime Whether this is a one-time permission grant permissions
* @param userFixed Whether to mark the permissions as user fixed when granted
* @param withoutAppOps If these permission have app ops associated, and this value is true,
- * then do not grant the app op when the permission is granted, and add the REVOKED_COMPAT flag.
- *
+ * then do not grant the app op when the permission is granted, and add the REVOKED_COMPAT
+ * flag.
* @return a LightPermission and boolean pair <permission with updated state (or the original
- * state, if it wasn't changed), should kill app>
+ * state, if it wasn't changed), should kill app>
*/
private fun grantRuntimePermission(
app: Application,
@@ -833,8 +912,9 @@ object KotlinUtils {
val pkgInfo = group.packageInfo
val user = UserHandle.getUserHandleForUid(pkgInfo.uid)
val supportsRuntime = pkgInfo.targetSdkVersion >= Build.VERSION_CODES.M
- val isGrantingAllowed = (!pkgInfo.isInstantApp || perm.isInstantPerm) &&
- (supportsRuntime || !perm.isRuntimeOnly)
+ val isGrantingAllowed =
+ (!pkgInfo.isInstantApp || perm.isInstantPerm) &&
+ (supportsRuntime || !perm.isRuntimeOnly)
// Do not touch permissions fixed by the system, or permissions that cannot be granted
if (!isGrantingAllowed || perm.isSystemFixed) {
return perm to false
@@ -855,8 +935,13 @@ object KotlinUtils {
// flag, so that the PermissionPolicyService doesn't reset the app op state
if (affectsAppOp && withoutAppOps) {
oldFlags = oldFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
- app.packageManager.updatePermissionFlags(perm.name, group.packageName,
- PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, oldFlags, user)
+ app.packageManager.updatePermissionFlags(
+ perm.name,
+ group.packageName,
+ PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
+ oldFlags,
+ user
+ )
disallowAppOp(app, perm, group)
}
app.packageManager.grantRuntimePermission(group.packageName, perm.name, user)
@@ -869,11 +954,12 @@ object KotlinUtils {
shouldKill = true
isGranted = true
}
- newFlags = if (affectsAppOp && withoutAppOps) {
- newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
- } else {
- newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
- }
+ newFlags =
+ if (affectsAppOp && withoutAppOps) {
+ newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ } else {
+ newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ }
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
// If this permission affects an app op, ensure the permission app op is enabled
@@ -898,11 +984,12 @@ object KotlinUtils {
}
newFlags = newFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
- newFlags = if (isOneTime) {
- newFlags.setFlag(FLAG_PERMISSION_ONE_TIME)
- } else {
- newFlags.clearFlag(FLAG_PERMISSION_ONE_TIME)
- }
+ newFlags =
+ if (isOneTime) {
+ newFlags.setFlag(FLAG_PERMISSION_ONE_TIME)
+ } else {
+ newFlags.clearFlag(FLAG_PERMISSION_ONE_TIME)
+ }
// If we newly grant background access to the fine location, double-guess the user some
// time later if this was really the right choice.
@@ -922,13 +1009,18 @@ object KotlinUtils {
}
if (oldFlags != newFlags) {
- app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
- PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
+ app.packageManager.updatePermissionFlags(
+ perm.name,
+ group.packageInfo.packageName,
+ PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
+ newFlags,
+ user
+ )
}
val newState = PermState(newFlags, isGranted)
- return LightPermission(perm.pkgInfo, perm.permInfo, newState,
- perm.foregroundPerms) to shouldKill
+ return LightPermission(perm.pkgInfo, perm.permInfo, newState, perm.foregroundPerms) to
+ shouldKill
}
/**
@@ -941,9 +1033,7 @@ object KotlinUtils {
* @param userFixed If the user requested that they do not want to be asked again
* @param oneTime If the permission should be mark as one-time
* @param filterPermissions If not specified, all permissions of the group will be revoked.
- * Otherwise only permissions in {@code filterPermissions} will be
- * revoked.
- *
+ * Otherwise only permissions in {@code filterPermissions} will be revoked.
* @return a LightAppPermGroup representing the new state
*/
@JvmOverloads
@@ -955,8 +1045,15 @@ object KotlinUtils {
forceRemoveRevokedCompat: Boolean = false,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return revokeRuntimePermissions(app, group, false, userFixed, oneTime,
- forceRemoveRevokedCompat, filterPermissions)
+ return revokeRuntimePermissions(
+ app,
+ group,
+ false,
+ userFixed,
+ oneTime,
+ forceRemoveRevokedCompat,
+ filterPermissions
+ )
}
/**
@@ -968,9 +1065,7 @@ object KotlinUtils {
* @param group The group whose permissions should be revoked
* @param userFixed If the user requested that they do not want to be asked again
* @param filterPermissions If not specified, all permissions of the group will be revoked.
- * Otherwise only permissions in {@code filterPermissions} will be
- * revoked.
- *
+ * Otherwise only permissions in {@code filterPermissions} will be revoked.
* @return a LightAppPermGroup representing the new state
*/
@JvmOverloads
@@ -982,8 +1077,15 @@ object KotlinUtils {
forceRemoveRevokedCompat: Boolean = false,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return revokeRuntimePermissions(app, group, true, userFixed, oneTime,
- forceRemoveRevokedCompat, filterPermissions)
+ return revokeRuntimePermissions(
+ app,
+ group,
+ true,
+ userFixed,
+ oneTime,
+ forceRemoveRevokedCompat,
+ filterPermissions
+ )
}
private fun revokeRuntimePermissions(
@@ -1003,8 +1105,14 @@ object KotlinUtils {
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == revokeBackground) {
val (newPerm, shouldKill) =
- revokeRuntimePermission(app, perm, userFixed, oneTime, forceRemoveRevokedCompat,
- group)
+ revokeRuntimePermission(
+ app,
+ perm,
+ userFixed,
+ oneTime,
+ forceRemoveRevokedCompat,
+ group
+ )
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -1012,15 +1120,24 @@ object KotlinUtils {
if (shouldKillForAnyPermission && !shouldSkipKillForGroup(app, group)) {
(app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
- group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
+ group.packageInfo.uid,
+ KILL_REASON_APP_OP_CHANGE
+ )
}
- val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
- group.hasInstallToRuntimeSplit, group.specialLocationGrant)
+ val newGroup =
+ LightAppPermGroup(
+ group.packageInfo,
+ group.permGroupInfo,
+ newPerms,
+ group.hasInstallToRuntimeSplit,
+ group.specialLocationGrant
+ )
if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup)) {
app.getSystemService(PermissionManager::class.java)!!.stopOneTimePermissionSession(
- group.packageName)
+ group.packageName
+ )
}
return newGroup
}
@@ -1042,8 +1159,9 @@ object KotlinUtils {
postRevokeHandler: Runnable?
) {
GlobalScope.launch(Dispatchers.Main) {
- val group = LightAppPermGroupLiveData[packageName, permissionGroupName, user]
- .getInitializedValue()
+ val group =
+ LightAppPermGroupLiveData[packageName, permissionGroupName, user]
+ .getInitializedValue()
if (group != null) {
revokeBackgroundRuntimePermissions(context.application, group)
}
@@ -1059,7 +1177,6 @@ object KotlinUtils {
* @param app The current application
* @param packageInfo The packageInfo we wish to examine
* @param group Optional, the current app permission group we are examining
- *
* @return true if any permission in the package is granted for one time, false otherwise
*/
private fun anyPermsOfPackageOneTimeGranted(
@@ -1075,11 +1192,12 @@ object KotlinUtils {
if (permName in group?.permissions ?: emptyMap()) {
continue
}
- val flags = app.packageManager.getPermissionFlags(permName, packageInfo.packageName,
- user) and FLAG_PERMISSION_ONE_TIME
- val granted = packageInfo.requestedPermissionsFlags[idx] ==
- PackageManager.PERMISSION_GRANTED &&
- (flags and FLAG_PERMISSION_REVOKED_COMPAT) == 0
+ val flags =
+ app.packageManager.getPermissionFlags(permName, packageInfo.packageName, user) and
+ FLAG_PERMISSION_ONE_TIME
+ val granted =
+ packageInfo.requestedPermissionsFlags[idx] == PackageManager.PERMISSION_GRANTED &&
+ (flags and FLAG_PERMISSION_REVOKED_COMPAT) == 0
if (granted && (flags and FLAG_PERMISSION_ONE_TIME) != 0) {
return true
}
@@ -1093,10 +1211,9 @@ object KotlinUtils {
* @param perm The permission which should be revoked.
* @param userFixed If the user requested that they do not want to be asked again
* @param group An optional app permission group in which to look for background or foreground
- * permissions
- *
+ * permissions
* @return a LightPermission and boolean pair <permission with updated state (or the original
- * state, if it wasn't changed), should kill app>
+ * state, if it wasn't changed), should kill app>
*/
private fun revokeRuntimePermission(
app: Application,
@@ -1120,11 +1237,20 @@ object KotlinUtils {
val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission
if (perm.isGrantedIncludingAppOp || (perm.isCompatRevoked && forceRemoveRevokedCompat)) {
- if (supportsRuntime && !isPermissionSplitFromNonRuntime(app, perm.name,
- group.packageInfo.targetSdkVersion)) {
+ if (
+ supportsRuntime &&
+ !isPermissionSplitFromNonRuntime(
+ app,
+ perm.name,
+ group.packageInfo.targetSdkVersion
+ )
+ ) {
// Revoke the permission if needed.
- app.packageManager.revokeRuntimePermission(group.packageInfo.packageName,
- perm.name, user)
+ app.packageManager.revokeRuntimePermission(
+ group.packageInfo.packageName,
+ perm.name,
+ user
+ )
isGranted = false
if (forceRemoveRevokedCompat) {
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
@@ -1149,18 +1275,26 @@ object KotlinUtils {
// Update the permission flags.
// Take a note that the user fixed the permission, if applicable.
- newFlags = if (userFixed) newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
- else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
- newFlags = if (oneTime) newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_SET)
- else newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
- newFlags = if (oneTime) newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
- else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ newFlags =
+ if (userFixed) newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
+ else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
+ newFlags =
+ if (oneTime) newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_SET)
+ else newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
+ newFlags =
+ if (oneTime) newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
if (perm.flags != newFlags) {
- app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
- PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
+ app.packageManager.updatePermissionFlags(
+ perm.name,
+ group.packageInfo.packageName,
+ PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
+ newFlags,
+ user
+ )
}
// If we revoke background access to the fine location, we trigger a check to remove
@@ -1176,17 +1310,18 @@ object KotlinUtils {
}
if (cancelLocationAccessWarning) {
// cancel location access warning notification
- LocationAccessCheck(app, null).cancelBackgroundAccessWarningNotification(
- group.packageInfo.packageName,
- user,
- true
- )
+ LocationAccessCheck(app, null)
+ .cancelBackgroundAccessWarningNotification(
+ group.packageInfo.packageName,
+ user,
+ true
+ )
}
}
val newState = PermState(newFlags, isGranted)
- return LightPermission(perm.pkgInfo, perm.permInfo, newState,
- perm.foregroundPerms) to shouldKill
+ return LightPermission(perm.pkgInfo, perm.permInfo, newState, perm.foregroundPerms) to
+ shouldKill
}
private fun Int.setFlag(flagToSet: Int): Int {
@@ -1200,27 +1335,20 @@ object KotlinUtils {
/**
* Allow the app op for a permission/uid.
*
- * <p>There are three cases:
- * <dl>
- * <dt>The permission is not split into foreground/background</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
- * <dt>The permission is a foreground permission:</dt>
- * <dd><dl><dt>The background permission permission is granted</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
- * <dt>The background permission permission is <u>not</u> granted</dt>
- * <dd>The app op matching the permission will be set to
- * {@link AppOpsManager#MODE_FOREGROUND}</dd>
- * </dl></dd>
- * <dt>The permission is a background permission:</dt>
- * <dd>All granted foreground permissions for this background permission will be set to
- * {@link AppOpsManager#MODE_ALLOWED}</dd>
- * </dl>
+ * <p>There are three cases: <dl> <dt>The permission is not split into
+ * foreground/background</dt> <dd>The app op matching the permission will be set to {@link
+ * AppOpsManager#MODE_ALLOWED}</dd> <dt>The permission is a foreground permission:</dt>
+ * <dd><dl><dt>The background permission permission is granted</dt> <dd>The app op matching the
+ * permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd> <dt>The background
+ * permission permission is <u>not</u> granted</dt> <dd>The app op matching the permission will
+ * be set to {@link AppOpsManager#MODE_FOREGROUND}</dd> </dl></dd> <dt>The permission is a
+ * background permission:</dt> <dd>All granted foreground permissions for this background
+ * permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd> </dl>
*
* @param app The current application
* @param perm The LightPermission whose app op should be allowed
- * @param group The LightAppPermGroup which will be looked in for foreground or
- * background LightPermission objects
- *
+ * @param group The LightAppPermGroup which will be looked in for foreground or background
+ * LightPermission objects
* @return {@code true} iff app-op was changed
*/
private fun allowAppOp(
@@ -1239,25 +1367,29 @@ object KotlinUtils {
val appOpName = permissionToOp(foregroundPermName) ?: continue
if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) {
- wasChanged = setOpMode(appOpName, uid, packageName, MODE_ALLOWED,
- appOpsManager) || wasChanged
+ wasChanged =
+ setOpMode(appOpName, uid, packageName, MODE_ALLOWED, appOpsManager) ||
+ wasChanged
}
}
} else {
val appOpName = permissionToOp(perm.name) ?: return false
if (perm.backgroundPermission != null) {
- wasChanged = if (group.permissions.containsKey(perm.backgroundPermission)) {
- val bgPerm = group.permissions[perm.backgroundPermission]
- val mode = if (bgPerm != null && bgPerm.isGrantedIncludingAppOp) MODE_ALLOWED
- else MODE_FOREGROUND
-
- setOpMode(appOpName, uid, packageName, mode, appOpsManager)
- } else {
- // The app requested a permission that has a background permission but it did
- // not request the background permission, hence it can never get background
- // access
- setOpMode(appOpName, uid, packageName, MODE_FOREGROUND, appOpsManager)
- }
+ wasChanged =
+ if (group.permissions.containsKey(perm.backgroundPermission)) {
+ val bgPerm = group.permissions[perm.backgroundPermission]
+ val mode =
+ if (bgPerm != null && bgPerm.isGrantedIncludingAppOp) MODE_ALLOWED
+ else MODE_FOREGROUND
+
+ setOpMode(appOpName, uid, packageName, mode, appOpsManager)
+ } else {
+ // The app requested a permission that has a background permission but it
+ // did
+ // not request the background permission, hence it can never get background
+ // access
+ setOpMode(appOpName, uid, packageName, MODE_FOREGROUND, appOpsManager)
+ }
} else {
wasChanged = setOpMode(appOpName, uid, packageName, MODE_ALLOWED, appOpsManager)
}
@@ -1268,22 +1400,17 @@ object KotlinUtils {
/**
* Disallow the app op for a permission/uid.
*
- * <p>There are three cases:
- * <dl>
- * <dt>The permission is not split into foreground/background</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
- * <dt>The permission is a foreground permission:</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
- * <dt>The permission is a background permission:</dt>
- * <dd>All granted foreground permissions for this background permission will be set to
- * {@link AppOpsManager#MODE_FOREGROUND}</dd>
- * </dl>
+ * <p>There are three cases: <dl> <dt>The permission is not split into
+ * foreground/background</dt> <dd>The app op matching the permission will be set to {@link
+ * AppOpsManager#MODE_IGNORED}</dd> <dt>The permission is a foreground permission:</dt> <dd>The
+ * app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd> <dt>The
+ * permission is a background permission:</dt> <dd>All granted foreground permissions for this
+ * background permission will be set to {@link AppOpsManager#MODE_FOREGROUND}</dd> </dl>
*
* @param app The current application
* @param perm The LightPermission whose app op should be allowed
- * @param group The LightAppPermGroup which will be looked in for foreground or
- * background LightPermission objects
- *
+ * @param group The LightAppPermGroup which will be looked in for foreground or background
+ * LightPermission objects
* @return {@code true} iff app-op was changed
*/
private fun disallowAppOp(
@@ -1301,8 +1428,9 @@ object KotlinUtils {
val fgPerm = group.permissions[foregroundPermName]
if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) {
val appOpName = permissionToOp(foregroundPermName) ?: return false
- wasChanged = wasChanged || setOpMode(appOpName, uid, packageName,
- MODE_FOREGROUND, appOpsManager)
+ wasChanged =
+ wasChanged ||
+ setOpMode(appOpName, uid, packageName, MODE_FOREGROUND, appOpsManager)
}
}
} else {
@@ -1320,7 +1448,6 @@ object KotlinUtils {
* @param packageName The package the app-op belongs to
* @param mode The new mode
* @param manager The app ops manager to use to change the app op
- *
* @return {@code true} iff app-op was changed
*/
private fun setOpMode(
@@ -1343,22 +1470,25 @@ object KotlinUtils {
return false
}
- return shouldSkipKillOnPermDeny(app, POST_NOTIFICATIONS, group.packageName,
- group.userHandle)
+ return shouldSkipKillOnPermDeny(
+ app,
+ POST_NOTIFICATIONS,
+ group.packageName,
+ group.userHandle
+ )
}
/**
* Determine if the usual "kill app on permission denial" should be skipped. It should be
- * skipped if the permission is POST_NOTIFICATIONS, the app holds the BACKUP permission, and
- * a backup restore is currently in progress.
+ * skipped if the permission is POST_NOTIFICATIONS, the app holds the BACKUP permission, and a
+ * backup restore is currently in progress.
*
* @param app the current application
* @param permission the permission being denied
* @param packageName the package the permission was denied for
* @param user the user whose package the permission was denied for
- *
* @return true if the permission denied was POST_NOTIFICATIONS, the app is a backup app, and a
- * backup restore is in progress, false otherwise
+ * backup restore is in progress, false otherwise
*/
fun shouldSkipKillOnPermDeny(
app: Application,
@@ -1367,17 +1497,27 @@ object KotlinUtils {
user: UserHandle
): Boolean {
val userContext: Context = Utils.getUserContext(app, user)
- if (permission != POST_NOTIFICATIONS || userContext.packageManager
- .checkPermission(BACKUP, packageName) != PackageManager.PERMISSION_GRANTED) {
+ if (
+ permission != POST_NOTIFICATIONS ||
+ userContext.packageManager.checkPermission(BACKUP, packageName) !=
+ PackageManager.PERMISSION_GRANTED
+ ) {
return false
}
return try {
- val isInSetup = Settings.Secure.getInt(userContext.contentResolver,
- Settings.Secure.USER_SETUP_COMPLETE, user.identifier) == 0
- val isInDeferredSetup = Settings.Secure.getInt(userContext.contentResolver,
- Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, user.identifier) ==
- Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
+ val isInSetup =
+ Settings.Secure.getInt(
+ userContext.contentResolver,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ user.identifier
+ ) == 0
+ val isInDeferredSetup =
+ Settings.Secure.getInt(
+ userContext.contentResolver,
+ Settings.Secure.USER_SETUP_PERSONALIZATION_STATE,
+ user.identifier
+ ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
isInSetup || isInDeferredSetup
} catch (e: Settings.SettingNotFoundException) {
Log.w(LOG_TAG, "Failed to check if the user is in restore: $e")
@@ -1391,22 +1531,27 @@ object KotlinUtils {
*
* @param context: The context from which to retrieve the package
* @param packageName: The package name to check
- *
* @return whether or not the given package has a launch intent
*/
fun packageHasLaunchIntent(context: Context, packageName: String): Boolean {
val intentToResolve = Intent(ACTION_MAIN)
intentToResolve.addCategory(CATEGORY_INFO)
intentToResolve.setPackage(packageName)
- var resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
- MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
+ var resolveInfos =
+ context.packageManager.queryIntentActivities(
+ intentToResolve,
+ MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE
+ )
if (resolveInfos.size <= 0) {
intentToResolve.removeCategory(CATEGORY_INFO)
intentToResolve.addCategory(CATEGORY_LAUNCHER)
intentToResolve.setPackage(packageName)
- resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
- MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
+ resolveInfos =
+ context.packageManager.queryIntentActivities(
+ intentToResolve,
+ MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE
+ )
}
return resolveInfos.size > 0
}
@@ -1424,39 +1569,55 @@ object KotlinUtils {
isFineSelected: Boolean
) {
if (isFineSelected) {
- setGroupFlags(app, group,
+ setGroupFlags(
+ app,
+ group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to true,
- filterPermissions = listOf(ACCESS_FINE_LOCATION))
- setGroupFlags(app, group,
+ filterPermissions = listOf(ACCESS_FINE_LOCATION)
+ )
+ setGroupFlags(
+ app,
+ group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to false,
- filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION))
+ filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION)
+ )
} else {
- setGroupFlags(app, group,
+ setGroupFlags(
+ app,
+ group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to false,
- filterPermissions = listOf(ACCESS_FINE_LOCATION))
- setGroupFlags(app, group,
+ filterPermissions = listOf(ACCESS_FINE_LOCATION)
+ )
+ setGroupFlags(
+ app,
+ group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to true,
- filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION))
+ filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION)
+ )
}
}
/**
- * Determines whether we should show the safety protection resources.
- * We show the resources only if
- * (1) the build version is T or after and
- * (2) the feature flag safety_protection_enabled is enabled and
- * (3) the config value config_safetyProtectionEnabled is enabled/true and
- * (4) the resources exist (currently the resources only exist on GMS devices)
+ * Determines whether we should show the safety protection resources. We show the resources only
+ * if (1) the build version is T or after and (2) the feature flag safety_protection_enabled is
+ * enabled and (3) the config value config_safetyProtectionEnabled is enabled/true and (4) the
+ * resources exist (currently the resources only exist on GMS devices)
*/
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
fun shouldShowSafetyProtectionResources(context: Context): Boolean {
return try {
SdkLevel.isAtLeastT() &&
DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PRIVACY, SAFETY_PROTECTION_RESOURCES_ENABLED, false) &&
- context.getResources().getBoolean(
- Resources.getSystem()
- .getIdentifier("config_safetyProtectionEnabled", "bool", "android")) &&
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_PROTECTION_RESOURCES_ENABLED,
+ false
+ ) &&
+ context
+ .getResources()
+ .getBoolean(
+ Resources.getSystem()
+ .getIdentifier("config_safetyProtectionEnabled", "bool", "android")
+ ) &&
context.getDrawable(android.R.drawable.ic_safety_protection) != null &&
!context.getString(android.R.string.safety_protection_display_text).isNullOrEmpty()
} catch (e: Resources.NotFoundException) {
@@ -1480,8 +1641,7 @@ object KotlinUtils {
installerPackageName: String?,
packageName: String?
): Intent? {
- val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO)
- .setPackage(installerPackageName)
+ val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName)
val result: Intent? = resolveActivityForIntent(context, intent)
if (result != null) {
result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
@@ -1512,9 +1672,10 @@ object KotlinUtils {
val color: Int
// If U resources are available, and this is a U+ device, use those
if (SdkLevel.isAtLeastU()) {
- val scContext = SafetyCenterResourcesContext(context)
- val uIcon = scContext.getIconByDrawableName("ic_notification_badge_general")
- val uColor = scContext.getColorByName("notification_tint_normal")
+ val safetyCenterResourcesApk = SafetyCenterResourcesApk(context)
+ val uIcon =
+ safetyCenterResourcesApk.getIconByDrawableName("ic_notification_badge_general")
+ val uColor = safetyCenterResourcesApk.getColorByName("notification_tint_normal")
if (uIcon != null && uColor != null) {
appLabel = context.getString(R.string.safety_privacy_qs_tile_title)
return NotificationResources(appLabel, uIcon, uColor)
@@ -1523,24 +1684,21 @@ object KotlinUtils {
// Use PbA branding if available, otherwise default to more generic branding
if (shouldShowSafetyProtectionResources(context)) {
- appLabel = Html.fromHtml(context.getString(
- android.R.string.safety_protection_display_text), 0).toString()
- smallIcon =
- Icon.createWithResource(context, android.R.drawable.ic_safety_protection)
+ appLabel =
+ Html.fromHtml(context.getString(android.R.string.safety_protection_display_text), 0)
+ .toString()
+ smallIcon = Icon.createWithResource(context, android.R.drawable.ic_safety_protection)
color = context.getColor(R.color.safety_center_info)
} else {
appLabel = context.getString(R.string.safety_center_notification_app_label)
- smallIcon =
- Icon.createWithResource(context, R.drawable.ic_settings_notification)
+ smallIcon = Icon.createWithResource(context, R.drawable.ic_settings_notification)
color = context.getColor(android.R.color.system_notification_accent_color)
}
return NotificationResources(appLabel, smallIcon, color)
}
}
-/**
- * Get the [value][LiveData.getValue], suspending until [isInitialized] if not yet so
- */
+/** Get the [value][LiveData.getValue], suspending until [isInitialized] if not yet so */
suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
observe: LD.(Observer<T>) -> Unit = { observeForever(it) },
isInitialized: LD.() -> Boolean = { value != null }
@@ -1550,20 +1708,20 @@ suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
} else {
suspendCoroutine { continuation: Continuation<T> ->
val observer = AtomicReference<Observer<T>>()
- observer.set(Observer { newValue ->
- if (isInitialized()) {
- GlobalScope.launch(Dispatchers.Main) {
- observer.getAndSet(null)?.let { observerSnapshot ->
- removeObserver(observerSnapshot)
- continuation.resume(newValue)
+ observer.set(
+ Observer { newValue ->
+ if (isInitialized()) {
+ GlobalScope.launch(Dispatchers.Main) {
+ observer.getAndSet(null)?.let { observerSnapshot ->
+ removeObserver(observerSnapshot)
+ continuation.resume(newValue)
+ }
}
}
}
- })
+ )
- GlobalScope.launch(Dispatchers.Main) {
- observe(observer.get())
- }
+ GlobalScope.launch(Dispatchers.Main) { observe(observer.get()) }
}
}
}
@@ -1571,8 +1729,8 @@ suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
/**
* A parallel equivalent of [map]
*
- * Starts the given suspending function for each item in the collection without waiting for
- * previous ones to complete, then suspends until all the started operations finish.
+ * Starts the given suspending function for each item in the collection without waiting for previous
+ * ones to complete, then suspends until all the started operations finish.
*/
suspend inline fun <T, R> Iterable<T>.mapInParallel(
context: CoroutineContext,
@@ -1594,8 +1752,8 @@ suspend inline fun <T> Iterable<T>.forEachInParallel(
}
/**
- * Check that we haven't already started transitioning to a given destination. If we haven't,
- * start navigating to that destination.
+ * Check that we haven't already started transitioning to a given destination. If we haven't, start
+ * navigating to that destination.
*
* @param destResId The ID of the desired destination
* @param args The optional bundle of args to be passed to the destination
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index d4354bd72..6b3dc98ad 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_W
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.health.connect.HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS;
+import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
import static android.os.UserHandle.myUserId;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
@@ -73,6 +74,7 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
+import android.health.connect.HealthConnectManager;
import android.os.Binder;
import android.os.Build;
import android.os.Parcelable;
@@ -109,6 +111,8 @@ import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup;
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo;
+import kotlin.Triple;
+
import java.lang.annotation.Retention;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
@@ -120,8 +124,6 @@ import java.util.Locale;
import java.util.Random;
import java.util.Set;
-import kotlin.Triple;
-
public final class Utils {
@Retention(SOURCE)
@@ -943,6 +945,70 @@ public final class Utils {
}
/**
+ * Returns true if the group name passed is that of the Platform health group.
+ * @param permGroupName name of the group that needs to be checked.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static Boolean isHealthPermissionGroup(String permGroupName) {
+ return SdkLevel.isAtLeastU() && HEALTH_PERMISSION_GROUP.equals(permGroupName);
+ }
+
+ /**
+ * Return whether health permission setting entry should be shown or not
+ *
+ * Should not show Health permissions preference if the package doesn't handle
+ * VIEW_PERMISSION_USAGE_INTENT.
+ *
+ * Will show if above is true AND permission is already granted.
+ *
+ * @param packageInfo the {@link PackageInfo} app which uses the permission
+ * @param permGroupName the health permission group name to show
+ * @return {@code TRUE} iff health permission should be shown
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static Boolean shouldShowHealthPermission(LightPackageInfo packageInfo,
+ String permGroupName) {
+ if (!isHealthPermissionGroup(permGroupName)) {
+ return false;
+ }
+
+ PermissionControllerApplication app = PermissionControllerApplication.get();
+ PackageManager pm = app.getPackageManager();
+ Context context = getUserContext(app, UserHandle.getUserHandleForUid(packageInfo.getUid()));
+
+ List<PermissionInfo> permissions = new ArrayList<>();
+ try {
+ permissions.addAll(getPermissionInfosForGroup(pm, permGroupName));
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "No permissions found for permission group " + permGroupName);
+ return false;
+ }
+
+ // Check in permission is already granted as we should not hide it in the UX at that point.
+ List<String> grantedPermissions = packageInfo.getGrantedPermissions();
+ for (PermissionInfo permission : permissions) {
+ boolean isCurrentlyGranted = grantedPermissions.contains(permission.name);
+ if (isCurrentlyGranted) {
+ Log.d(LOG_TAG, "At least one Health permission group permission is granted, "
+ + "show permission group entry");
+ return true;
+ }
+ }
+
+ Intent viewUsageIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE);
+ viewUsageIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS);
+ viewUsageIntent.setPackage(packageInfo.getPackageName());
+
+ ResolveInfo resolveInfo = pm.resolveActivity(viewUsageIntent, PackageManager.MATCH_ALL);
+ if (resolveInfo == null) {
+ Log.e(LOG_TAG, "Package that asks for Health permission must also handle "
+ + "VIEW_PERMISSION_USAGE_INTENT.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
*
* @param context the context to get the shared preferences
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
index 1d5c9c9fa..33acd8285 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
@@ -35,6 +35,8 @@ import android.os.Bundle
import android.provider.DeviceConfig
import android.provider.Settings
import android.safetycenter.SafetyCenterManager
+import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID
+import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceIssue
@@ -239,7 +241,9 @@ class AccessibilitySourceService(
PendingIntent.FLAG_IMMUTABLE
)
)
- .setContentIntent(getSafetyCenterActivityIntent(context, uid, sessionId))
+ .setContentIntent(
+ getSafetyCenterActivityIntent(context, uid, sessionId, componentName)
+ )
val appNameExtras = Bundle()
appNameExtras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appLabel)
@@ -290,7 +294,7 @@ class AccessibilitySourceService(
sessionId: Long
): SafetySourceIssue {
val componentName = ComponentName.unflattenFromString(a11yService.id)!!
- val safetySourceIssueId = "accessibility_${componentName.flattenToString()}"
+ val safetySourceIssueId = getSafetySourceIssueId(componentName)
val pkgLabel = a11yService.resolveInfo.loadLabel(packageManager).toString()
val uid = a11yService.resolveInfo.serviceInfo.applicationInfo.uid
@@ -368,7 +372,7 @@ class AccessibilitySourceService(
val intent =
Intent(parentUserContext, AccessibilityRemoveAccessHandler::class.java).apply {
putExtra(Intent.EXTRA_COMPONENT_NAME, serviceComponentName)
- putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID, safetySourceIssueId)
+ putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, safetySourceIssueId)
putExtra(Constants.EXTRA_SESSION_ID, sessionId)
putExtra(Intent.EXTRA_UID, uid)
flags = Intent.FLAG_RECEIVER_FOREGROUND
@@ -414,12 +418,15 @@ class AccessibilitySourceService(
private fun getSafetyCenterActivityIntent(
context: Context,
uid: Int,
- sessionId: Long
+ sessionId: Long,
+ componentName: ComponentName
): PendingIntent {
val intent = Intent(Intent.ACTION_SAFETY_CENTER)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra(Constants.EXTRA_SESSION_ID, sessionId)
intent.putExtra(Intent.EXTRA_UID, uid)
+ intent.putExtra(EXTRA_SAFETY_SOURCE_ID, SC_ACCESSIBILITY_SOURCE_ID)
+ intent.putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, getSafetySourceIssueId(componentName))
intent.putExtra(
Constants.EXTRA_PRIVACY_SOURCE,
PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__PRIVACY_SOURCE__A11Y_SERVICE
@@ -432,6 +439,10 @@ class AccessibilitySourceService(
)
}
+ private fun getSafetySourceIssueId(componentName: ComponentName): String {
+ return "accessibility_${componentName.flattenToString()}"
+ }
+
private fun sendIssuesToSafetyCenter(
a11yServiceList: List<AccessibilityServiceInfo>,
sessionId: Long,
@@ -788,7 +799,7 @@ class AccessibilityRemoveAccessHandler : BroadcastReceiver() {
)
}
val safetySourceIssueId = intent.getStringExtra(
- SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
+ EXTRA_SAFETY_SOURCE_ISSUE_ID
)
val safetyEvent = builder.setSafetySourceIssueId(safetySourceIssueId)
.setSafetySourceIssueActionId(SC_ACCESSIBILITY_REMOVE_ACCESS_ACTION_ID)
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerPregrants.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerPregrants.kt
index aaf2d32e6..4063ec8b2 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerPregrants.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerPregrants.kt
@@ -18,32 +18,34 @@ package com.android.permissioncontroller.privacysources
import android.content.Context
import androidx.annotation.VisibleForTesting
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
// (TODO:b/242573074) Remove for Android U.
class NotificationListenerPregrants(private val context: Context) {
@VisibleForTesting
val pregrantedPackagesDelegate = lazy {
hashSetOf(
- "android",
- "com.android.cellbroadcastreceiver",
- "com.android.server.telecom",
- "com.android.settings",
- "com.android.systemui",
- "com.android.launcher3",
- "com.android.dynsystem",
- "com.android.providers.settings",
- "com.android.inputdevices",
- "com.android.keychain",
- "com.android.localtransport",
- "com.android.wallpaperbackup",
- "com.android.location.fused"
- ).also {
- it.addAll(
- SafetyCenterResourcesContext(context)
- .getStringByName("config_NotificationListenerServicePregrants")
- .split(","))
- }
+ "android",
+ "com.android.cellbroadcastreceiver",
+ "com.android.server.telecom",
+ "com.android.settings",
+ "com.android.systemui",
+ "com.android.launcher3",
+ "com.android.dynsystem",
+ "com.android.providers.settings",
+ "com.android.inputdevices",
+ "com.android.keychain",
+ "com.android.localtransport",
+ "com.android.wallpaperbackup",
+ "com.android.location.fused"
+ )
+ .also {
+ it.addAll(
+ SafetyCenterResourcesApk(context)
+ .getStringByName("config_NotificationListenerServicePregrants")
+ .split(",")
+ )
+ }
}
val pregrantedPackages: Set<String> by pregrantedPackagesDelegate
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
index 06e5ed264..a8b16c521 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
@@ -196,20 +196,25 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
@NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo,
@NonNull ArrayMap<String, Preference> oldPreferences,
@NonNull PreferenceScreen preferenceScreen, @NonNull Context context) {
- TwoStatePreference preference = (TwoStatePreference) oldPreferences.get(key);
- if (preference == null) {
- preference = requirePreferenceFragment().createApplicationPreference();
+ RoleApplicationPreference roleApplicationPreference =
+ (RoleApplicationPreference) oldPreferences.get(key);
+ TwoStatePreference preference;
+ if (roleApplicationPreference == null) {
+ roleApplicationPreference = requirePreferenceFragment().createApplicationPreference();
+ preference = roleApplicationPreference.asTwoStatePreference();
preference.setKey(key);
preference.setIcon(icon);
preference.setTitle(title);
preference.setPersistent(false);
preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
preference.setOnPreferenceClickListener(this);
+ } else {
+ preference = roleApplicationPreference.asTwoStatePreference();
}
preference.setChecked(checked);
if (applicationInfo != null) {
- RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, preference,
+ RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
applicationInfo, mUser, context);
}
@@ -281,7 +286,7 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
* @return a new preference for an application
*/
@NonNull
- TwoStatePreference createApplicationPreference();
+ RoleApplicationPreference createApplicationPreference();
/**
* Create a new preference for the footer.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
index 3c8af1136..f9a0193bd 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
@@ -167,15 +167,19 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
RoleItem roleItem = roleItems.get(i);
Role role = roleItem.getRole();
- Preference preference = oldPreferences.get(role.getName());
- if (preference == null) {
- preference = (Preference) preferenceFragment.createPreference();
+ RolePreference rolePreference = (RolePreference) oldPreferences.get(role.getName());
+ Preference preference;
+ if (rolePreference == null) {
+ rolePreference = preferenceFragment.createPreference();
+ preference = rolePreference.asPreference();
preference.setKey(role.getName());
preference.setIconSpaceReserved(true);
preference.setTitle(role.getShortLabelResource());
preference.setPersistent(false);
preference.setOnPreferenceClickListener(listener);
preference.getExtras().putParcelable(Intent.EXTRA_USER, user);
+ } else {
+ preference = rolePreference.asPreference();
}
List<ApplicationInfo> holderApplicationInfos = roleItem.getHolderApplicationInfos();
@@ -187,8 +191,7 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
preference.setIcon(Utils.getBadgedIcon(context, holderApplicationInfo));
preference.setSummary(Utils.getAppLabel(holderApplicationInfo, context));
}
- RoleUiBehaviorUtils.preparePreferenceAsUser(role, (TwoTargetPreference) preference,
- user, context);
+ RoleUiBehaviorUtils.preparePreferenceAsUser(role, rolePreference, user, context);
preferenceGroup.addPreference(preference);
}
}
@@ -283,7 +286,7 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
* @return a new preference for a default app
*/
@NonNull
- TwoTargetPreference createPreference();
+ RolePreference createPreference();
/**
* Callback when changes have been made to the {@link PreferenceScreen} of the parent
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
index 3a4312c00..827d42643 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Process;
+import android.os.UserManager;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -154,6 +155,16 @@ public class RequestRoleActivity extends FragmentActivity {
return;
}
+ UserManager userManager = getSystemService(UserManager.class);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DEFAULT_APPS)) {
+ Log.w(LOG_TAG, "Cannot request role due to user restriction"
+ + " DISALLOW_CONFIG_DEFAULT_APPS, role: " + mRoleName);
+ reportRequestResult(PermissionControllerStatsLog
+ .ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_RESTRICTION);
+ finish();
+ return;
+ }
+
if (!role.isPackageQualified(mPackageName, this)) {
Log.w(LOG_TAG, "Application doesn't qualify for role, role: " + mRoleName
+ ", package: " + mPackageName);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java
new file mode 100644
index 000000000..1b5b27971
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui;
+
+import androidx.annotation.NonNull;
+import androidx.preference.TwoStatePreference;
+
+/**
+ * Preference for application being a candidate or holding a role.
+ */
+public interface RoleApplicationPreference extends UserRestrictionAwarePreference {
+
+ /**
+ * Get instance of {@code this} as {@link TwoStatePreference}.
+ */
+ @NonNull
+ TwoStatePreference asTwoStatePreference();
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java
new file mode 100644
index 000000000..442963ce6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui;
+
+import androidx.preference.Preference;
+
+/**
+ * Preference used by the default apps list UI.
+ */
+public interface RolePreference extends TwoTargetPreference, UserRestrictionAwarePreference {
+ /**
+ * Return this preference as {@link Preference}.
+ */
+ Preference asPreference();
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java
index 23044b833..3a8cd55d3 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java
@@ -18,6 +18,7 @@ package com.android.permissioncontroller.role.ui;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.preference.Preference;
/**
* {@link androidx.preference.Preference} with the widget layout as a separate target.
@@ -38,6 +39,11 @@ public interface TwoTargetPreference {
void setOnSecondTargetClickListener(@Nullable OnSecondTargetClickListener listener);
/**
+ * Return this preference as {@link Preference}.
+ */
+ Preference asPreference();
+
+ /**
* Listener for second target click.
*/
interface OnSecondTargetClickListener {
@@ -49,7 +55,4 @@ public interface TwoTargetPreference {
*/
void onSecondTargetClick(@NonNull TwoTargetPreference preference);
}
-
- /** @see androidx.preference.Preference#setEnabled(boolean) */
- void setEnabled(boolean enabled);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreference.java
new file mode 100644
index 000000000..e6bc9bab6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreference.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Preference that is aware of user restrictions that can block them.
+ */
+public interface UserRestrictionAwarePreference {
+
+ /**
+ * Specifies user restriction that blocks this preference.
+ */
+ void setUserRestriction(@Nullable String userRestriction);
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreferenceMixin.java b/PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreferenceMixin.java
new file mode 100644
index 000000000..033507991
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/UserRestrictionAwarePreferenceMixin.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * Mixin for implementing {@link UserRestrictionAwarePreference}.
+ */
+public class UserRestrictionAwarePreferenceMixin {
+
+ @NonNull
+ private final Preference mPreference;
+ @Nullable
+ private String mUserRestriction = null;
+
+ public UserRestrictionAwarePreferenceMixin(@NonNull Preference preference) {
+ mPreference = preference;
+ }
+
+ /**
+ * Implementation for {@link UserRestrictionAwarePreference#setUserRestriction}.
+ */
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mUserRestriction = userRestriction;
+ mPreference.setEnabled(mUserRestriction == null);
+ }
+
+ /**
+ * Call after {@link Preference#onBindViewHolder} to apply blocking effects.
+ */
+ public void onAfterBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ if (mUserRestriction != null) {
+ // We set the item view to enabled to make the preference row clickable.
+ // Normal disabled preferences have the whole view hierarchy disabled, so by making only
+ // the top-level itemView enabled, we don't change the fact that the whole preference
+ // still "looks" disabled (see Preference.onBindViewHolder).
+ // Preference.onBindViewHolder sets the onClickListener as well on each preference, so
+ // we don't need to unset the listener here (we wouldn't know the correct one anyway).
+ // This approach is used already by com.android.settingslib.RestrictedPreferenceHelper.
+ holder.itemView.setEnabled(true);
+ Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)
+ .putExtra(DevicePolicyManager.EXTRA_RESTRICTION, mUserRestriction);
+ holder.itemView.setOnClickListener(
+ (view) -> holder.itemView.getContext().startActivity(intent));
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java
index dbd4c7c03..dc6c03d09 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java
@@ -23,11 +23,11 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
-import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
import com.android.permissioncontroller.role.ui.DefaultAppChildFragment;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.role.controller.model.Role;
/** Screen to pick a default app for a particular {@link Role}. */
@@ -90,8 +90,8 @@ public class AutoDefaultAppFragment extends AutoSettingsFrameFragment implements
@NonNull
@Override
- public TwoStatePreference createApplicationPreference() {
- return new AutoDefaultAppPreference(requireContext());
+ public RoleApplicationPreference createApplicationPreference() {
+ return new AutoRadioPreference(requireContext());
}
@NonNull
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppListFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppListFragment.java
index b74caa7ed..081e3153b 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppListFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppListFragment.java
@@ -24,7 +24,7 @@ import androidx.annotation.Nullable;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
import com.android.permissioncontroller.role.ui.DefaultAppListChildFragment;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
/** Shows various roles for which a default app can be picked. */
public class AutoDefaultAppListFragment extends AutoSettingsFrameFragment implements
@@ -57,8 +57,8 @@ public class AutoDefaultAppListFragment extends AutoSettingsFrameFragment implem
@NonNull
@Override
- public TwoTargetPreference createPreference() {
- return new AutoSettingsPreference(requireContext());
+ public RolePreference createPreference() {
+ return new AutoRolePreference(requireContext());
}
@Override
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java
index c91e40561..97a3dad26 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java
@@ -19,16 +19,23 @@ package com.android.permissioncontroller.role.ui.auto;
import android.content.Context;
import android.widget.RadioButton;
+import androidx.annotation.Nullable;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreferenceMixin;
/** Preference used to represent apps that can be picked as a default app. */
-public class AutoDefaultAppPreference extends TwoStatePreference {
+public class AutoRadioPreference extends TwoStatePreference implements
+ RoleApplicationPreference {
- public AutoDefaultAppPreference(Context context) {
+ private final UserRestrictionAwarePreferenceMixin mUserRestrictionAwarePreferenceMixin =
+ new UserRestrictionAwarePreferenceMixin(this);
+
+ public AutoRadioPreference(Context context) {
super(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
android.R.attr.preferenceStyle));
init();
@@ -45,6 +52,18 @@ public class AutoDefaultAppPreference extends TwoStatePreference {
RadioButton radioButton = (RadioButton) holder.findViewById(R.id.radio_button);
radioButton.setChecked(isChecked());
+
+ mUserRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
+ }
+
+ @Override
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mUserRestrictionAwarePreferenceMixin.setUserRestriction(userRestriction);
+ }
+
+ @Override
+ public AutoRadioPreference asTwoStatePreference() {
+ return this;
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSettingsPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java
index ba5b17be9..d2f1b6cde 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSettingsPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java
@@ -22,34 +22,57 @@ import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+import com.android.permissioncontroller.role.ui.RolePreference;
import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreferenceMixin;
/**
* Preference for use in auto lists. Extends {@link TwoTargetPreference} in order to make sure of
* shared logic between phone and auto settings UI.
*/
-public class AutoSettingsPreference extends Preference implements TwoTargetPreference {
+public class AutoRolePreference extends Preference implements RolePreference {
- public AutoSettingsPreference(@NonNull Context context,
+ private UserRestrictionAwarePreferenceMixin mUserRestrictionAwarePreferenceMixin =
+ new UserRestrictionAwarePreferenceMixin(this);
+
+ public AutoRolePreference(@NonNull Context context,
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
- public AutoSettingsPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ public AutoRolePreference(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public AutoSettingsPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ public AutoRolePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
- public AutoSettingsPreference(@NonNull Context context) {
+ public AutoRolePreference(@NonNull Context context) {
super(context);
}
@Override
public void setOnSecondTargetClickListener(@Nullable OnSecondTargetClickListener listener) {
}
+
+ @Override
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mUserRestrictionAwarePreferenceMixin.setUserRestriction(userRestriction);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mUserRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
+ }
+
+ @Override
+ public AutoRolePreference asPreference() {
+ return this;
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessFragment.java
index c561420da..c37735427 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessFragment.java
@@ -22,11 +22,10 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
-import androidx.preference.SwitchPreference;
-import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.permissioncontroller.role.ui.specialappaccess.SpecialAppAccessChildFragment;
/** Automotive fragment for displaying special app access for a role. */
@@ -81,8 +80,8 @@ public class AutoSpecialAppAccessFragment extends AutoSettingsFrameFragment impl
@NonNull
@Override
- public TwoStatePreference createApplicationPreference() {
- return new SwitchPreference(requireContext());
+ public RoleApplicationPreference createApplicationPreference() {
+ return new AutoSwitchPreference(requireContext());
}
@NonNull
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessListFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessListFragment.java
index e2dce4a94..59e6766cc 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessListFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSpecialAppAccessListFragment.java
@@ -24,7 +24,7 @@ import androidx.annotation.Nullable;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
import com.android.permissioncontroller.role.ui.specialappaccess.SpecialAppAccessListChildFragment;
/** Automotive fragment for the list of role related special app accesses. */
@@ -59,8 +59,8 @@ public class AutoSpecialAppAccessListFragment extends AutoSettingsFrameFragment
@NonNull
@Override
- public TwoTargetPreference createPreference(@NonNull Context context) {
- return new AutoSettingsPreference(context);
+ public RolePreference createPreference(@NonNull Context context) {
+ return new AutoRolePreference(context);
}
@Override
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java
new file mode 100644
index 000000000..900e58551
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui.auto;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreferenceMixin;
+
+/**
+ * Role application preference represented as a switch.
+ */
+public class AutoSwitchPreference extends SwitchPreference
+ implements RoleApplicationPreference {
+
+ private UserRestrictionAwarePreferenceMixin mUserRestrictionAwarePreferenceMixin =
+ new UserRestrictionAwarePreferenceMixin(this);
+
+ public AutoSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public AutoSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ @StyleRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AutoSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoSwitchPreference(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mUserRestrictionAwarePreferenceMixin.setUserRestriction(userRestriction);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mUserRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
+ }
+
+ @NonNull
+ @Override
+ public AutoSwitchPreference asTwoStatePreference() {
+ return this;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java
index 8a5c8bdc7..e6b8dabe1 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java
@@ -40,9 +40,6 @@ public class DialerRoleUiBehavior implements RoleUiBehavior {
public void prepareApplicationPreferenceAsUser(@NonNull Role role,
@NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
@NonNull UserHandle user, @NonNull Context context) {
- RoleUiBehavior.super.prepareApplicationPreferenceAsUser(
- role, preference, applicationInfo, user, context);
-
TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
String systemPackageName = telecomManager.getSystemDialerPackage();
if (Objects.equals(applicationInfo.packageName, systemPackageName)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java
index 36bbd1cb1..d0e7c0eef 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java
@@ -56,8 +56,6 @@ public class HomeRoleUiBehavior implements RoleUiBehavior {
@Override
public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference,
@NonNull UserHandle user, @NonNull Context context) {
- RoleUiBehavior.super.preparePreferenceAsUser(role, preference, user, context);
-
TwoTargetPreference.OnSecondTargetClickListener listener = null;
RoleManager roleManager = context.getSystemService(RoleManager.class);
String packageName = CollectionUtils.firstOrNull(roleManager.getRoleHoldersAsUser(
@@ -95,9 +93,6 @@ public class HomeRoleUiBehavior implements RoleUiBehavior {
public void prepareApplicationPreferenceAsUser(@NonNull Role role,
@NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
@NonNull UserHandle user, @NonNull Context context) {
- RoleUiBehavior.super.prepareApplicationPreferenceAsUser(
- role, preference, applicationInfo, user, context);
-
boolean missingWorkProfileSupport = isMissingWorkProfileSupport(applicationInfo, context);
if (preference.isEnabled()) {
preference.setEnabled(!missingWorkProfileSupport);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
index 13343e926..6e3b47fba 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
@@ -20,13 +20,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.UserHandle;
-import android.os.UserManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
-import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.role.ui.TwoTargetPreference;
import com.android.role.controller.model.Role;
@@ -75,13 +73,7 @@ public interface RoleUiBehavior {
default void preparePreferenceAsUser(@NonNull Role role,
@NonNull TwoTargetPreference preference,
@NonNull UserHandle user,
- @NonNull Context context) {
- if (SdkLevel.isAtLeastU() && role.isExclusive()) {
- final UserManager userManager = context.getSystemService(UserManager.class);
- preference.setEnabled(!userManager.hasUserRestrictionForUser(
- UserManager.DISALLOW_CONFIG_DEFAULT_APPS, user));
- }
- }
+ @NonNull Context context) {}
/**
* Check whether a qualifying application should be visible to user.
@@ -108,13 +100,7 @@ public interface RoleUiBehavior {
*/
default void prepareApplicationPreferenceAsUser(@NonNull Role role,
@NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
- @NonNull UserHandle user, @NonNull Context context) {
- if (SdkLevel.isAtLeastU() && role.isExclusive()) {
- final UserManager userManager = context.getSystemService(UserManager.class);
- preference.setEnabled(!userManager.hasUserRestrictionForUser(
- UserManager.DISALLOW_CONFIG_DEFAULT_APPS, user));
- }
- }
+ @NonNull UserHandle user, @NonNull Context context) {}
/**
* Get the confirmation message for adding an application as a holder of this role.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppListPreferenceFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppListPreferenceFragment.java
index b76b8c561..da920ea7f 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppListPreferenceFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppListPreferenceFragment.java
@@ -23,7 +23,7 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceFragmentCompat;
import com.android.permissioncontroller.role.ui.DefaultAppListChildFragment;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
/**
* Handheld preference fragment for the list of default apps.
@@ -62,8 +62,8 @@ public class HandheldDefaultAppListPreferenceFragment extends PreferenceFragment
@NonNull
@Override
- public TwoTargetPreference createPreference() {
- return new SettingsButtonPreference(requireContext());
+ public RolePreference createPreference() {
+ return new HandheldRolePreference(requireContext());
}
@Override
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppPreferenceFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppPreferenceFragment.java
index 94c07ef67..b8156590a 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppPreferenceFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldDefaultAppPreferenceFragment.java
@@ -24,11 +24,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.role.ui.DefaultAppChildFragment;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.settingslib.widget.FooterPreference;
-import com.android.settingslib.widget.SelectorWithWidgetPreference;
/**
* Handheld preference fragment for a default app.
@@ -96,8 +95,8 @@ public class HandheldDefaultAppPreferenceFragment extends PreferenceFragmentComp
@NonNull
@Override
- public TwoStatePreference createApplicationPreference() {
- return new SelectorWithWidgetPreference(requireContext());
+ public RoleApplicationPreference createApplicationPreference() {
+ return new HandheldRadioPreference(requireContext());
}
@NonNull
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java
new file mode 100644
index 000000000..d9ef047d6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui.handheld;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreferenceMixin;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+/**
+ * Preference used to represent apps that can be picked as a default app.
+ */
+public class HandheldRadioPreference extends SelectorWithWidgetPreference implements
+ RoleApplicationPreference {
+
+ private final UserRestrictionAwarePreferenceMixin mUserRestrictionAwarePreferenceMixin =
+ new UserRestrictionAwarePreferenceMixin(this);
+
+ public HandheldRadioPreference(@NonNull Context context,
+ @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public HandheldRadioPreference(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public HandheldRadioPreference(@NonNull Context context, boolean isCheckbox) {
+ super(context, isCheckbox);
+ }
+
+ public HandheldRadioPreference(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setUserRestriction(
+ @Nullable String userRestriction) {
+ mUserRestrictionAwarePreferenceMixin.setUserRestriction(userRestriction);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mUserRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
+ }
+
+ @NonNull
+ @Override
+ public HandheldRadioPreference asTwoStatePreference() {
+ return this;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/SettingsButtonPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java
index f54c9d95d..978fe7d5a 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/SettingsButtonPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java
@@ -28,7 +28,9 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreferenceMixin;
+import com.android.settingslib.widget.TwoTargetPreference;
/**
* {@link Preference} with a settings button.
@@ -36,33 +38,35 @@ import com.android.permissioncontroller.role.ui.TwoTargetPreference;
* @see com.android.settings.widget.GearPreference
*/
// Made public for com.android.permissioncontroller.role.ui.specialappaccess.handheld
-public class SettingsButtonPreference extends com.android.settingslib.widget.TwoTargetPreference
- implements TwoTargetPreference {
+public class HandheldRolePreference extends TwoTargetPreference implements RolePreference {
+
+ private final UserRestrictionAwarePreferenceMixin mUserRestrictionAwarePreferenceMixin =
+ new UserRestrictionAwarePreferenceMixin(this);
@Nullable
private OnSecondTargetClickListener mOnSecondTargetClickListener;
- public SettingsButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ public HandheldRolePreference(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
- public SettingsButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ public HandheldRolePreference(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
- public SettingsButtonPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ public HandheldRolePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
- public SettingsButtonPreference(@NonNull Context context) {
+ public HandheldRolePreference(@NonNull Context context) {
super(context);
init();
@@ -89,6 +93,11 @@ public class SettingsButtonPreference extends com.android.settingslib.widget.Two
}
@Override
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mUserRestrictionAwarePreferenceMixin.setUserRestriction(userRestriction);
+ }
+
+ @Override
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
@@ -103,5 +112,12 @@ public class SettingsButtonPreference extends com.android.settingslib.widget.Two
}
// Make the settings button enabled even if the preference itself is disabled.
settingsButton.setEnabled(true);
+
+ mUserRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
+ }
+
+ @Override
+ public HandheldRolePreference asPreference() {
+ return this;
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
index d75747b52..b95440bbd 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
@@ -37,6 +37,7 @@ import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.Roles;
@@ -143,9 +144,12 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
String key = qualifyingApplicationInfo.packageName + '_'
+ qualifyingApplicationInfo.uid;
- TwoStatePreference preference = (TwoStatePreference) oldPreferences.get(key);
- if (preference == null) {
- preference = preferenceFragment.createApplicationPreference();
+ RoleApplicationPreference roleApplicationPreference =
+ (RoleApplicationPreference) oldPreferences.get(key);
+ TwoStatePreference preference;
+ if (roleApplicationPreference == null) {
+ roleApplicationPreference = preferenceFragment.createApplicationPreference();
+ preference = roleApplicationPreference.asTwoStatePreference();
preference.setKey(key);
preference.setIcon(Utils.getBadgedIcon(context, qualifyingApplicationInfo));
preference.setTitle(Utils.getFullAppLabel(qualifyingApplicationInfo, context));
@@ -154,11 +158,13 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
preference.setOnPreferenceClickListener(this);
preference.getExtras().putParcelable(PREFERENCE_EXTRA_APPLICATION_INFO,
qualifyingApplicationInfo);
+ } else {
+ preference = roleApplicationPreference.asTwoStatePreference();
}
preference.setChecked(isHolderPackage);
UserHandle user = UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid);
- RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, preference,
+ RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
qualifyingApplicationInfo, user, context);
preferenceScreen.addPreference(preference);
}
@@ -228,7 +234,7 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
* @return a new preference for an application
*/
@NonNull
- TwoStatePreference createApplicationPreference();
+ RoleApplicationPreference createApplicationPreference();
/**
* Create a new preference for the footer.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java
index ec4de84e1..4b256cef0 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java
@@ -33,7 +33,7 @@ import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.permissioncontroller.role.ui.RoleItem;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.Roles;
@@ -102,16 +102,20 @@ public class SpecialAppAccessListChildFragment<PF extends PreferenceFragmentComp
RoleItem roleItem = roleItems.get(i);
Role role = roleItem.getRole();
- Preference preference = oldPreferences.get(role.getName());
- if (preference == null) {
- preference = (Preference) preferenceFragment.createPreference(context);
+ RolePreference rolePreference = (RolePreference) oldPreferences.get(role.getName());
+ Preference preference;
+ if (rolePreference == null) {
+ rolePreference = preferenceFragment.createPreference(context);
+ preference = rolePreference.asPreference();
preference.setKey(role.getName());
preference.setIconSpaceReserved(true);
preference.setTitle(role.getShortLabelResource());
preference.setPersistent(false);
preference.setOnPreferenceClickListener(this);
+ } else {
+ preference = rolePreference.asPreference();
}
- RoleUiBehaviorUtils.preparePreferenceAsUser(role, (TwoTargetPreference) preference,
+ RoleUiBehaviorUtils.preparePreferenceAsUser(role, rolePreference,
Process.myUserHandle(),
context);
preferenceScreen.addPreference(preference);
@@ -153,7 +157,7 @@ public class SpecialAppAccessListChildFragment<PF extends PreferenceFragmentComp
* @return a new preference for a special app access
*/
@NonNull
- TwoTargetPreference createPreference(@NonNull Context context);
+ RolePreference createPreference(@NonNull Context context);
/**
* Callback when changes have been made to the {@link PreferenceScreen} of the parent
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessListPreferenceFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessListPreferenceFragment.java
index e0d7884a1..26d858d72 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessListPreferenceFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessListPreferenceFragment.java
@@ -23,8 +23,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceFragmentCompat;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
-import com.android.permissioncontroller.role.ui.handheld.SettingsButtonPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
+import com.android.permissioncontroller.role.ui.handheld.HandheldRolePreference;
import com.android.permissioncontroller.role.ui.specialappaccess.SpecialAppAccessListChildFragment;
/**
@@ -65,8 +65,8 @@ public class HandheldSpecialAppAccessListPreferenceFragment extends PreferenceFr
@NonNull
@Override
- public TwoTargetPreference createPreference(@NonNull Context context) {
- return new SettingsButtonPreference(context);
+ public RolePreference createPreference(@NonNull Context context) {
+ return new HandheldRolePreference(context);
}
@Override
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessPreferenceFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessPreferenceFragment.java
index c1bb0fb23..bfcbefdca 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessPreferenceFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSpecialAppAccessPreferenceFragment.java
@@ -23,10 +23,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.TwoStatePreference;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.permissioncontroller.role.ui.specialappaccess.SpecialAppAccessChildFragment;
-import com.android.settingslib.widget.AppSwitchPreference;
import com.android.settingslib.widget.FooterPreference;
/**
@@ -90,8 +89,8 @@ public class HandheldSpecialAppAccessPreferenceFragment extends PreferenceFragme
@NonNull
@Override
- public TwoStatePreference createApplicationPreference() {
- return new AppSwitchPreference(requireContext());
+ public RoleApplicationPreference createApplicationPreference() {
+ return new HandheldSwitchPreference(requireContext());
}
@NonNull
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java
new file mode 100644
index 000000000..1b4dd78a4
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.role.ui.specialappaccess.handheld;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreferenceMixin;
+import com.android.settingslib.widget.AppSwitchPreference;
+
+/** {@link AppSwitchPreference} that is a role application preference. */
+public class HandheldSwitchPreference extends AppSwitchPreference
+ implements RoleApplicationPreference {
+
+ private UserRestrictionAwarePreferenceMixin mUserRestrictionAwarePreferenceMixin =
+ new UserRestrictionAwarePreferenceMixin(this);
+
+ public HandheldSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public HandheldSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public HandheldSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public HandheldSwitchPreference(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setUserRestriction(@Nullable String userRestriction) {
+ mUserRestrictionAwarePreferenceMixin.setUserRestriction(userRestriction);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mUserRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
+ }
+
+ @NonNull
+ @Override
+ public HandheldSwitchPreference asTwoStatePreference() {
+ return this;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
index e60bc6d76..6081695b5 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
@@ -21,13 +21,16 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import com.android.permissioncontroller.role.ui.RolePreference;
+import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreference;
import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior;
import com.android.role.controller.model.Role;
@@ -100,8 +103,10 @@ public final class RoleUiBehaviorUtils {
* @see RoleUiBehavior#preparePreferenceAsUser
*/
public static void preparePreferenceAsUser(@NonNull Role role,
- @NonNull TwoTargetPreference preference, @NonNull UserHandle user,
+ @NonNull RolePreference preference, @NonNull UserHandle user,
@NonNull Context context) {
+ prepareUserRestrictionAwarePreferenceAsUser(role, preference, user, context);
+
RoleUiBehavior uiBehavior = getUiBehavior(role);
if (uiBehavior == null) {
return;
@@ -126,16 +131,32 @@ public final class RoleUiBehaviorUtils {
* @see RoleUiBehavior#prepareApplicationPreferenceAsUser
*/
public static void prepareApplicationPreferenceAsUser(@NonNull Role role,
- @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
- @NonNull UserHandle user, @NonNull Context context) {
+ @NonNull RoleApplicationPreference preference,
+ @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
+ @NonNull Context context) {
+ prepareUserRestrictionAwarePreferenceAsUser(role, preference, user, context);
+
RoleUiBehavior uiBehavior = getUiBehavior(role);
if (uiBehavior == null) {
return;
}
- uiBehavior.prepareApplicationPreferenceAsUser(role, preference, applicationInfo, user,
+ uiBehavior.prepareApplicationPreferenceAsUser(
+ role, preference.asTwoStatePreference(), applicationInfo, user,
context);
}
+ private static void prepareUserRestrictionAwarePreferenceAsUser(@NonNull Role role,
+ @NonNull UserRestrictionAwarePreference preference, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (SdkLevel.isAtLeastU() && role.isExclusive()) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ boolean hasDisallowConfigDefaultApps = userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS, user);
+ preference.setUserRestriction(hasDisallowConfigDefaultApps
+ ? UserManager.DISALLOW_CONFIG_DEFAULT_APPS : null);
+ }
+ }
+
/**
* @see RoleUiBehavior#getConfirmationMessage
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java
index e959d20be..d97b5034f 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java
@@ -39,6 +39,8 @@ import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
+import java.time.Duration;
+
/**
* Uses {@link android.app.job.JobScheduler} to schedule periodic calls to {@link
* SafetyCenterManager#refreshSafetySources} after boot completed if safety center is already
@@ -46,7 +48,6 @@ import com.android.modules.utils.build.SdkLevel;
*
* <p>The job waits until the device is in idle mode to minimize impact on system health.
*/
-// TODO(b/243493200): Add tests
public final class SafetyCenterBackgroundRefreshJobService extends JobService {
private static final String TAG = "SafetyCenterBackgroundR";
@@ -76,7 +77,7 @@ public final class SafetyCenterBackgroundRefreshJobService extends JobService {
Context context, @Nullable String actionString) {
if (!isActionStringValid(actionString)) {
- Log.v(TAG, "Ignoring a " + actionString + " broadcast.");
+ Log.i(TAG, "Ignoring a " + actionString + " broadcast.");
return;
}
@@ -104,30 +105,26 @@ public final class SafetyCenterBackgroundRefreshJobService extends JobService {
return;
}
+ Duration periodicBackgroundRefreshInterval =
+ SafetyCenterJobServiceFlags.getPeriodicBackgroundRefreshInterval();
JobInfo jobInfo =
new JobInfo.Builder(
SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID,
new ComponentName(
context, SafetyCenterBackgroundRefreshJobService.class))
.setRequiresDeviceIdle(true)
- .setRequiresCharging(
- SafetyCenterJobServiceFlags.getBackgroundRefreshRequiresCharging())
- .setPeriodic(
- SafetyCenterJobServiceFlags.getPeriodicBackgroundRefreshInterval()
- .toMillis())
+ .setRequiresCharging(true)
+ .setPeriodic(periodicBackgroundRefreshInterval.toMillis())
.build();
Log.v(
TAG,
- "Scheduling a periodic background refresh with "
- + ", interval="
- + jobInfo.getIntervalMillis()
- + "requires charging="
- + jobInfo.isRequireCharging());
+ "Scheduling a periodic background refresh with interval="
+ + periodicBackgroundRefreshInterval);
int scheduleResult = jobScheduler.schedule(jobInfo);
if (scheduleResult != RESULT_SUCCESS) {
- Log.e(
+ Log.w(
TAG,
"Could not schedule the background refresh job, scheduleResult="
+ scheduleResult);
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java
index bdca4d77d..23e048e7b 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java
@@ -26,8 +26,6 @@ public class SafetyCenterJobServiceFlags {
private static final Duration DEFAULT_PERIODIC_BACKGROUND_REFRESH_INTERVAL = Duration.ofDays(1);
private static final String PROPERTY_BACKGROUND_REFRESH_IS_ENABLED =
"safety_center_background_refresh_is_enabled";
- private static final String PROPERTY_BACKGROUND_REFRESH_REQUIRES_CHARGING =
- "safety_center_background_requires_charging";
private static final String PROPERTY_PERIODIC_BACKGROUND_REFRESH_INTERVAL_MILLIS =
"safety_center_periodic_background_interval_millis";
@@ -47,15 +45,4 @@ public class SafetyCenterJobServiceFlags {
PROPERTY_PERIODIC_BACKGROUND_REFRESH_INTERVAL_MILLIS,
DEFAULT_PERIODIC_BACKGROUND_REFRESH_INTERVAL.toMillis()));
}
-
- /**
- * Returns whether we should constrain background refresh jobs to only run when the device is
- * charging.
- */
- static boolean getBackgroundRefreshRequiresCharging() {
- return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_BACKGROUND_REFRESH_REQUIRES_CHARGING,
- true);
- }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt
index 8e5e63452..88dd8a6e3 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt
@@ -52,7 +52,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsVie
import com.android.safetycenter.internaldata.SafetyCenterBundles
import com.android.safetycenter.internaldata.SafetyCenterEntryId
import com.android.safetycenter.internaldata.SafetyCenterIds
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
/** [android.provider.SearchIndexablesProvider] for Safety Center. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@@ -67,7 +67,7 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
val context = requireContext()
val safetyCenterManager =
context.getSystemService(SafetyCenterManager::class.java) ?: return cursor
- val resourcesContext = SafetyCenterResourcesContext(context)
+ val safetyCenterResourcesApk = SafetyCenterResourcesApk(context)
val screenTitle = context.getString(R.string.safety_center_dashboard_page_title)
@@ -76,7 +76,11 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
SdkLevel.isAtLeastU() &&
safetySourcesGroup.type == SAFETY_SOURCES_GROUP_TYPE_STATEFUL
) {
- cursor.addSafetySourcesGroupRow(safetySourcesGroup, resourcesContext, screenTitle)
+ cursor.addSafetySourcesGroupRow(
+ safetySourcesGroup,
+ safetyCenterResourcesApk,
+ screenTitle
+ )
}
safetySourcesGroup.safetySources
.asSequence()
@@ -85,7 +89,7 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
cursor.addSafetySourceRow(
context,
safetySource,
- resourcesContext,
+ safetyCenterResourcesApk,
safetyCenterManager,
screenTitle
)
@@ -139,11 +143,12 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
private fun MatrixCursor.addSafetySourcesGroupRow(
safetySourcesGroups: SafetySourcesGroup,
- resourcesContext: SafetyCenterResourcesContext,
+ safetyCenterResourcesApk: SafetyCenterResourcesApk,
screenTitle: String,
) {
val groupTitle =
- resourcesContext.getNotEmptyStringOrNull(safetySourcesGroups.titleResId) ?: return
+ safetyCenterResourcesApk.getNotEmptyStringOrNull(safetySourcesGroups.titleResId)
+ ?: return
newRow()
.add(COLUMN_RANK, 0)
@@ -157,11 +162,12 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
private fun MatrixCursor.addSafetySourceRow(
context: Context,
safetySource: SafetySource,
- resourcesContext: SafetyCenterResourcesContext,
+ safetyCenterResourcesApk: SafetyCenterResourcesApk,
safetyCenterManager: SafetyCenterManager,
screenTitle: String,
) {
- val searchTerms = resourcesContext.getNotEmptyStringOrNull(safetySource.searchTermsResId)
+ val searchTerms =
+ safetyCenterResourcesApk.getNotEmptyStringOrNull(safetySource.searchTermsResId)
var isPersonalEntryAdded = false
var isWorkEntryAdded = false
@@ -194,19 +200,19 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
}
if (!isPersonalEntryAdded) {
- resourcesContext.getNotEmptyStringOrNull(safetySource.titleResId)?.let {
+ safetyCenterResourcesApk.getNotEmptyStringOrNull(safetySource.titleResId)?.let {
addIndexableRow(title = it, isWorkProfile = false)
}
}
if (!isWorkEntryAdded && safetySource.profile == SafetySource.PROFILE_ALL) {
- resourcesContext.getNotEmptyStringOrNull(safetySource.titleForWorkResId)?.let {
+ safetyCenterResourcesApk.getNotEmptyStringOrNull(safetySource.titleForWorkResId)?.let {
addIndexableRow(title = it, isWorkProfile = true)
}
}
}
- private fun Context.getNotEmptyStringOrNull(resId: Int): String? =
+ private fun SafetyCenterResourcesApk.getNotEmptyStringOrNull(resId: Int): String? =
if (resId != Resources.ID_NULL) {
getString(resId).takeIf { it.isNotEmpty() }
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardAnimator.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardAnimator.kt
index a0c7c01b9..c83737649 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardAnimator.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardAnimator.kt
@@ -55,44 +55,47 @@ class IssueCardAnimator(val callback: AnimationCallback) {
// Ensure AVD is reset before transition starts
(resolvedImageView.drawable as AnimatedVectorDrawable).reset()
- val defaultIssueContentGroup = holder.findViewById(R.id.default_issue_content)
- val resolvedIssueContentGroup = holder.findViewById(R.id.resolved_issue_content)
-
- val transitionSet = TransitionSet()
- .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
- .setInterpolator(linearInterpolator)
- .addTransition(hideIssueContentTransition)
- .addTransition(
- showResolvedImageTransition
- .clone()
- .addListener(
- object : TransitionListenerAdapter() {
- override fun onTransitionEnd(
- transition: Transition
- ) {
- super.onTransitionEnd(transition)
- startIssueResolvedAnimation(
- resolvedIssueContentGroup,
- resolvedImageView
- )
+ val defaultIssueContentGroup = holder.findViewById(R.id.default_issue_content)!!
+ val resolvedIssueContentGroup = holder.findViewById(R.id.resolved_issue_content)!!
+
+ val transitionSet =
+ TransitionSet()
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
+ .setInterpolator(linearInterpolator)
+ .addTransition(hideIssueContentTransition)
+ .addTransition(
+ showResolvedImageTransition
+ .clone()
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ startIssueResolvedAnimation(
+ resolvedIssueContentGroup,
+ resolvedImageView
+ )
+ }
}
- })
- )
- .addTransition(showResolvedTextTransition)
+ )
+ )
+ .addTransition(showResolvedTextTransition)
// Defer transition so that it's called after the root ViewGroup has been laid out.
holder.itemView.post {
TransitionManager.beginDelayedTransition(
- defaultIssueContentGroup.parent as ViewGroup?, transitionSet
+ defaultIssueContentGroup.parent as ViewGroup?,
+ transitionSet
)
// Setting INVISIBLE rather than GONE to ensure consistent card height between
// view groups.
defaultIssueContentGroup.visibility = View.INVISIBLE
- // These two views are outside of the group since their visibility must be set
+ // These views are outside of the group since their visibility must be set
// independently of the rest of the group, and some frustrating constraints of
// constraint layout's behavior. See b/242705351 for context.
+ makeInvisibleIfVisible(holder.findViewById(R.id.issue_card_attribution_title))
+ makeInvisibleIfVisible(holder.findViewById(R.id.issue_card_dismiss_btn))
makeInvisibleIfVisible(holder.findViewById(R.id.issue_card_subtitle))
makeInvisibleIfVisible(holder.findViewById(R.id.issue_card_protected_by_android))
@@ -111,7 +114,8 @@ class IssueCardAnimator(val callback: AnimationCallback) {
resolvedImageView
)
}
- })
+ }
+ )
}
private fun makeInvisibleIfVisible(view: View?) {
@@ -133,23 +137,27 @@ class IssueCardAnimator(val callback: AnimationCallback) {
super.onAnimationEnd(drawable)
transitionResolvedIssueUiToHiddenAndMarkComplete(resolvedIssueContentGroup)
}
- })
+ }
+ )
animatedDrawable.start()
}
private fun transitionResolvedIssueUiToHiddenAndMarkComplete(resolvedIssueContentGroup: View) {
- val hideTransition = hideResolvedUiTransition
- .clone()
- .setInterpolator(linearInterpolator)
- .addListener(
- object : TransitionListenerAdapter() {
- override fun onTransitionEnd(transition: Transition) {
- super.onTransitionEnd(transition)
- callback.markIssueResolvedUiCompleted()
+ val hideTransition =
+ hideResolvedUiTransition
+ .clone()
+ .setInterpolator(linearInterpolator)
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ callback.markIssueResolvedUiCompleted()
+ }
}
- })
+ )
TransitionManager.beginDelayedTransition(
- resolvedIssueContentGroup.parent as ViewGroup, hideTransition
+ resolvedIssueContentGroup.parent as ViewGroup,
+ hideTransition
)
resolvedIssueContentGroup.visibility = View.GONE
}
@@ -191,10 +199,14 @@ class IssueCardAnimator(val callback: AnimationCallback) {
// Using getter due to reliance on DeviceConfig property modification in tests
private val hideResolvedUiTransitionDelay
- get() = Duration.ofMillis(
- DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_HIDE_RESOLVED_UI_TRANSITION_DELAY_MILLIS,
- 400))
+ get() =
+ Duration.ofMillis(
+ DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_HIDE_RESOLVED_UI_TRANSITION_DELAY_MILLIS,
+ 400
+ )
+ )
private val linearInterpolator = LinearInterpolator()
@@ -207,14 +219,16 @@ class IssueCardAnimator(val callback: AnimationCallback) {
.setDuration(0)
.addTarget(R.id.resolved_issue_image)
- private val showResolvedTextTransition = Fade(Fade.IN)
- .setStartDelay(SHOW_RESOLVED_TEXT_TRANSITION_DELAY.toMillis())
- .setDuration(SHOW_RESOLVED_TEXT_TRANSITION_DURATION.toMillis())
- .addTarget(R.id.resolved_issue_text)
+ private val showResolvedTextTransition =
+ Fade(Fade.IN)
+ .setStartDelay(SHOW_RESOLVED_TEXT_TRANSITION_DELAY.toMillis())
+ .setDuration(SHOW_RESOLVED_TEXT_TRANSITION_DURATION.toMillis())
+ .addTarget(R.id.resolved_issue_text)
private val hideResolvedUiTransition
- get() = Fade(Fade.OUT)
- .setStartDelay(hideResolvedUiTransitionDelay.toMillis())
- .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis())
+ get() =
+ Fade(Fade.OUT)
+ .setStartDelay(hideResolvedUiTransitionDelay.toMillis())
+ .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis())
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
index c56cce0a5..7622270b9 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.safetycenter.SafetyCenterIssue;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
@@ -134,9 +135,7 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
configureSafetyProtectionView(holder);
maybeStartResolutionAnimation(holder);
- mSafetyCenterViewModel
- .getInteractionLogger()
- .recordIssueViewed(mIssue, mIsDismissed);
+ mSafetyCenterViewModel.getInteractionLogger().recordIssueViewed(mIssue, mIsDismissed);
}
private void maybeDisplayText(@Nullable CharSequence maybeText, TextView textView) {
@@ -424,8 +423,14 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
ActionButtonBuilder(SafetyCenterIssue.Action action, Context context) {
mAction = action;
mContext = context;
- mContextThemeWrapper =
- new ContextThemeWrapper(context, R.style.Theme_MaterialComponents_DayNight);
+
+ TypedValue buttonThemeValue = new TypedValue();
+ mContext.getTheme()
+ .resolveAttribute(
+ R.attr.scActionButtonTheme,
+ buttonThemeValue,
+ /* resolveRefs= */ false);
+ mContextThemeWrapper = new ContextThemeWrapper(context, buttonThemeValue.data);
}
public ActionButtonBuilder setIndex(int index) {
@@ -540,9 +545,11 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
return;
}
- int margin =
- mContext.getResources()
- .getDimensionPixelSize(R.dimen.sc_action_button_list_margin);
+ int marginRes =
+ mIsLargeScreen
+ ? R.dimen.sc_action_button_list_margin_large_screen
+ : R.dimen.sc_action_button_list_margin;
+ int margin = mContext.getResources().getDimensionPixelSize(marginRes);
Space space = new Space(mContext);
space.setLayoutParams(new ViewGroup.LayoutParams(margin, margin));
buttonList.addView(space);
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PreferenceHighlightManager.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PreferenceHighlightManager.kt
index 2acd6b5a3..613493f82 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PreferenceHighlightManager.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PreferenceHighlightManager.kt
@@ -59,7 +59,7 @@ internal class PreferenceHighlightManager(private val fragment: PreferenceFragme
/** Creates a new [HighlightablePreferenceGroupAdapter] instance */
fun createAdapter(
- preferenceScreen: PreferenceScreen?,
+ preferenceScreen: PreferenceScreen,
): RecyclerView.Adapter<RecyclerView.ViewHolder> {
val intent = fragment.getActivity()?.getIntent()
preferenceGroupAdapter =
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
index bf2d0565c..57e4175ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
@@ -42,7 +42,7 @@ internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet)
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
- val brandChipButton = holder.findViewById(R.id.brand_chip)
+ val brandChipButton = holder.findViewById(R.id.brand_chip)!!
brandChipButton.setOnClickListener(brandChipClickListener)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
index 8f31da828..5a5245068 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
@@ -30,12 +30,14 @@ import static com.android.permissioncontroller.safetycenter.SafetyCenterConstant
import android.app.ActionBar;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.provider.Settings;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
+import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -107,7 +109,30 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
.commitNow();
}
+ configureHomeButton();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ maybeRedirectIfDisabled();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // We don't set configChanges, but small screen size changes may still be delivered here.
+ super.onConfigurationChanged(newConfig);
+ configureHomeButton();
+ }
+
+ /** Decide whether a home/back button should be shown or not. */
+ private void configureHomeButton() {
ActionBar actionBar = getActionBar();
+ Fragment frag = getSupportFragmentManager().findFragmentById(R.id.content_frame);
+ if (actionBar == null || frag == null) {
+ return;
+ }
+
// Only the homepage can be considered a "second layer" page as it's the only one that
// can be reached from the Settings menu. The other pages are only reachable using
// a direct intent (e.g. notification, "first layer") and/or by navigating within Safety
@@ -115,30 +140,33 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
// Note that the homepage can also be a "first layer" page, but that would only happen
// if the activity is not embedded.
boolean isSecondLayerPage = frag instanceof SafetyCenterScrollWrapperFragment;
- if (actionBar != null
- && ActivityEmbeddingUtils.shouldHideNavigateUpButton(this, isSecondLayerPage)) {
+ if (ActivityEmbeddingUtils.shouldHideNavigateUpButton(this, isSecondLayerPage)) {
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setHomeButtonEnabled(false);
}
}
- @Override
- protected void onStart() {
- super.onStart();
- maybeRedirectIfDisabled();
- }
-
private boolean maybeRedirectIfDisabled() {
if (mSafetyCenterManager == null || !mSafetyCenterManager.isSafetyCenterEnabled()) {
Log.w(TAG, "Safety Center disabled, redirecting to settings page");
startActivity(
- new Intent(Settings.ACTION_SETTINGS).addFlags(FLAG_ACTIVITY_FORWARD_RESULT));
+ new Intent(getActionToRedirectWhenDisabled())
+ .addFlags(FLAG_ACTIVITY_FORWARD_RESULT));
finish();
return true;
}
return false;
}
+ private String getActionToRedirectWhenDisabled() {
+ boolean isPrivacyControls =
+ TextUtils.equals(getIntent().getAction(), PRIVACY_CONTROLS_ACTION);
+ if (isPrivacyControls) {
+ return Settings.ACTION_PRIVACY_SETTINGS;
+ }
+ return Settings.ACTION_SETTINGS;
+ }
+
private boolean maybeRedirectIntoTwoPaneSettings() {
return shouldUseTwoPaneSettings() && tryRedirectTwoPaneSettings();
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index 940cb2f69..57462c20e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -38,18 +38,22 @@ import android.safetycenter.SafetyCenterIssue;
import android.safetycenter.SafetyCenterStaticEntry;
import android.safetycenter.SafetyCenterStaticEntryGroup;
import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
+import androidx.recyclerview.widget.RecyclerView;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData;
import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.safetycenter.internaldata.SafetyCenterBundles;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import kotlin.Unit;
@@ -126,6 +130,18 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
prerenderCurrentSafetyCenterData();
}
+ @Override
+ public RecyclerView onCreateRecyclerView(
+ LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+ RecyclerView recyclerView =
+ super.onCreateRecyclerView(inflater, parent, savedInstanceState);
+
+ if (mIsQuickSettingsFragment) {
+ recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+ recyclerView.setVerticalScrollBarEnabled(false);
+ }
+ return recyclerView;
+ }
// Set the default divider line between preferences to be transparent
@Override
public void setDivider(Drawable divider) {
@@ -162,10 +178,10 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
private void updateStatus(StatusUiData statusUiData) {
if (mIsQuickSettingsFragment) {
- SafetyCenterResourcesContext safetyCenterResourcesContext =
- new SafetyCenterResourcesContext(requireContext());
+ SafetyCenterResourcesApk safetyCenterResourcesApk =
+ new SafetyCenterResourcesApk(requireContext());
boolean hasPendingActions =
- safetyCenterResourcesContext
+ safetyCenterResourcesApk
.getStringByName("overall_severity_level_ok_review_summary")
.equals(statusUiData.getOriginalSummary().toString());
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
index 7d5dbb3cb..6f146e48c 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
@@ -32,7 +32,7 @@ import com.android.permissioncontroller.safetycenter.ui.ParsedSafetyCenterIntent
import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterViewModelFactory
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
/** A base fragment that represents a page in Safety Center. */
@RequiresApi(TIRAMISU)
@@ -50,7 +50,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
}
override fun onCreateAdapter(
- preferenceScreen: PreferenceScreen?
+ preferenceScreen: PreferenceScreen
): RecyclerView.Adapter<RecyclerView.ViewHolder> {
/* The scroll-to-result functionality for settings search is currently implemented only for
* subpages i.e. non expand-and-collapse type entries. Hence, we check that the flag is
@@ -72,7 +72,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
sameTaskSourceIds =
- SafetyCenterResourcesContext(requireContext())
+ SafetyCenterResourcesApk(requireContext())
.getStringByName("config_same_task_safety_source_ids")
.split(",")
safetyCenterSessionId = requireArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID)
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java
index f69746e39..8804b3c29 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java
@@ -149,6 +149,7 @@ public class SafetyCenterQsFragment extends Fragment {
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.safety_center_qs, container, false);
root.setVisibility(View.GONE);
+ root.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS);
View closeButton = root.findViewById(R.id.close_button);
closeButton.setOnClickListener((v) -> requireActivity().finish());
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
index 5e45d2b3c..02bf6278e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
@@ -26,7 +26,7 @@ import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
import com.android.permissioncontroller.R
import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreference.Companion.closeSubpage
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.settingslib.widget.FooterPreference
/** A fragment that represents a generic subpage in Safety Center. */
@@ -88,8 +88,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
private fun setupIllustration() {
val resName = "illustration_${SnakeCaseConverter.fromCamelCase(sourceGroupId)}"
val context = requireContext()
- val drawable =
- SafetyCenterResourcesContext(context).getDrawableByName(resName, context.theme)
+ val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme)
if (drawable == null) {
Log.w(TAG, "$sourceGroupId doesn't have any matching illustration")
subpageIllustration.setVisible(false)
@@ -100,7 +99,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
private fun setupFooter() {
val resName = "${SnakeCaseConverter.fromCamelCase(sourceGroupId)}_footer"
- val footerText = SafetyCenterResourcesContext(requireContext()).getStringByName(resName)
+ val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName)
if (footerText.isEmpty()) {
Log.w(TAG, "$sourceGroupId doesn't have any matching footer")
subpageFooter.setVisible(false)
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt
index 2a9f6f780..46590448e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt
@@ -45,10 +45,10 @@ class SafetyGroupPreference(
layoutResource = R.layout.preference_group
}
- override fun onBindViewHolder(holder: PreferenceViewHolder?) {
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
- (holder?.itemView as? SafetyEntryGroupView)?.showGroup(
+ (holder.itemView as? SafetyEntryGroupView)?.showGroup(
group,
isExpanded,
isFirstCard,
@@ -56,7 +56,8 @@ class SafetyGroupPreference(
getTaskIdForEntry,
viewModel,
onExpandedListener,
- onCollapsedListener)
+ onCollapsedListener
+ )
}
override fun isSameItem(preference: Preference): Boolean =
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/LazyProperties.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/LazyProperties.kt
new file mode 100644
index 000000000..542ab1e03
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/LazyProperties.kt
@@ -0,0 +1,12 @@
+package com.android.permissioncontroller.safetycenter.ui.view
+
+import android.view.View
+
+/** Returns a lazy property wrapping a view with a given ID. */
+fun <T> View.lazyView(childViewId: Int): Lazy<T> = lazyView { findViewById(childViewId) }
+
+/** Returns a lazy property wrapping a view produced by the given function. */
+fun <T> lazyView(viewProducer: () -> T): Lazy<T> =
+ // Lazy by default uses synchronization to ensure a variable is only initialized once. This
+ // is unnecessary and expensive for view properties, so we don't use synchronization here.
+ lazy(LazyThreadSafetyMode.NONE) { viewProducer() }
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt
index 264aa488e..00cfd141a 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt
@@ -43,13 +43,13 @@ constructor(
}
private val moreIssuesCardAnimator = MoreIssuesCardAnimator()
- private val statusIconView: ImageView by lazy { findViewById(R.id.status_icon) }
- private val titleView: TextView by lazy { findViewById(R.id.title) }
- private val expandCollapseLayout: View by lazy { findViewById(android.R.id.widget_frame) }
- private val counterView: TextView by lazy {
+ private val statusIconView: ImageView by lazyView(R.id.status_icon)
+ private val titleView: TextView by lazyView(R.id.title)
+ private val expandCollapseLayout: View by lazyView(android.R.id.widget_frame)
+ private val counterView: TextView by lazyView {
expandCollapseLayout.findViewById(R.id.widget_title)
}
- private val expandCollapseIcon: ImageView by lazy {
+ private val expandCollapseIcon: ImageView by lazyView {
expandCollapseLayout.findViewById(R.id.widget_icon)
}
private var cornerAnimator: ValueAnimator? = null
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryCommonViewsManager.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryCommonViewsManager.kt
index 4be327285..34565421a 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryCommonViewsManager.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryCommonViewsManager.kt
@@ -29,11 +29,11 @@ import com.android.permissioncontroller.safetycenter.ui.SeverityIconPicker
internal class SafetyEntryCommonViewsManager(rootEntryView: ViewGroup?) {
- val titleView: TextView? by lazy { rootEntryView?.findViewById(R.id.title) }
- val summaryView: TextView? by lazy { rootEntryView?.findViewById(R.id.summary) }
- private val iconView: ImageView? by lazy { rootEntryView?.findViewById(R.id.icon) }
- private val iconFrame: View? by lazy { rootEntryView?.findViewById(R.id.icon_frame) }
- private val emptySpace: View? by lazy { rootEntryView?.findViewById(R.id.empty_space) }
+ val titleView: TextView? by lazyView { rootEntryView?.findViewById(R.id.title) }
+ val summaryView: TextView? by lazyView { rootEntryView?.findViewById(R.id.summary) }
+ private val iconView: ImageView? by lazyView { rootEntryView?.findViewById(R.id.icon) }
+ private val iconFrame: View? by lazyView { rootEntryView?.findViewById(R.id.icon_frame) }
+ private val emptySpace: View? by lazyView { rootEntryView?.findViewById(R.id.empty_space) }
fun showDetails(
id: String,
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryGroupView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryGroupView.kt
index 318ade5cb..7e4223298 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryGroupView.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryGroupView.kt
@@ -59,20 +59,20 @@ constructor(
inflate(context, R.layout.safety_center_group, this)
}
- private val groupHeaderView: LinearLayout? by lazy { findViewById(R.id.group_header) }
+ private val groupHeaderView: LinearLayout? by lazyView(R.id.group_header)
- private val expandedHeaderView: ViewGroup? by lazy { findViewById(R.id.expanded_header) }
- private val expandedTitleView: TextView? by lazy {
+ private val expandedHeaderView: ViewGroup? by lazyView(R.id.expanded_header)
+ private val expandedTitleView: TextView? by lazyView {
expandedHeaderView?.findViewById(R.id.title)
}
- private val collapsedHeaderView: ViewGroup? by lazy { findViewById(R.id.collapsed_header) }
- private val commonEntryView: SafetyEntryCommonViewsManager? by lazy {
+ private val collapsedHeaderView: ViewGroup? by lazyView(R.id.collapsed_header)
+ private val commonEntryView: SafetyEntryCommonViewsManager? by lazyView {
SafetyEntryCommonViewsManager(collapsedHeaderView)
}
- private val chevronIconView: ImageView? by lazy { findViewById(R.id.chevron_icon) }
- private val entriesContainerView: LinearLayout? by lazy { findViewById(R.id.entries_container) }
+ private val chevronIconView: ImageView? by lazyView(R.id.chevron_icon)
+ private val entriesContainerView: LinearLayout? by lazyView(R.id.entries_container)
private var isExpanded: Boolean? = null
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryView.kt
index ff7233686..7d7214ab0 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryView.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/SafetyEntryView.kt
@@ -52,10 +52,10 @@ constructor(
inflate(context, R.layout.view_entry, this)
}
- private val commonEntryView: SafetyEntryCommonViewsManager? by lazy {
+ private val commonEntryView: SafetyEntryCommonViewsManager? by lazyView {
SafetyEntryCommonViewsManager(this)
}
- private val widgetFrame: ViewGroup? by lazy { findViewById(R.id.widget_frame) }
+ private val widgetFrame: ViewGroup? by lazyView(R.id.widget_frame)
fun showEntry(
entry: SafetyCenterEntry,
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt
index 013f32c85..6a415c563 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt
@@ -42,14 +42,12 @@ constructor(
inflate(context, R.layout.view_status_card, this)
}
- val statusImageView: ImageView by lazy { findViewById(R.id.status_image) }
- val titleAndSummaryContainerView: LinearLayout by lazy {
- findViewById(R.id.status_title_and_summary)
- }
- val titleView: TextView by lazy { findViewById(R.id.status_title) }
- val summaryView: TextView by lazy { findViewById(R.id.status_summary) }
- val reviewSettingsButton: MaterialButton by lazy { findViewById(R.id.review_settings_button) }
- val rescanButton: MaterialButton by lazy { findViewById(R.id.rescan_button) }
+ val statusImageView: ImageView by lazyView(R.id.status_image)
+ val titleAndSummaryContainerView: LinearLayout by lazyView(R.id.status_title_and_summary)
+ val titleView: TextView by lazyView(R.id.status_title)
+ val summaryView: TextView by lazyView(R.id.status_summary)
+ val reviewSettingsButton: MaterialButton by lazyView(R.id.review_settings_button)
+ val rescanButton: MaterialButton by lazyView(R.id.rescan_button)
fun showButtons(statusUiData: StatusUiData) {
rescanButton.isEnabled = !statusUiData.isRefreshInProgress
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt
index d576f2924..86aab9b60 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt
@@ -24,6 +24,7 @@ import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.READ_MEDIA_AUDIO
import android.Manifest.permission.READ_MEDIA_IMAGES
import android.Manifest.permission.READ_MEDIA_VIDEO
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
import android.Manifest.permission.SEND_SMS
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.ActivityManager
@@ -547,6 +548,63 @@ class RuntimePermissionsUpgradeControllerTest {
verifyGranted(TEST_PKG_NAME, READ_MEDIA_IMAGES)
}
+ @Test
+ fun userSelectedGrantedIfReadMediaVisualGrantedWhenVersionIs10() {
+ Assume.assumeTrue(SdkLevel.isAtLeastU())
+ whenever(packageManager.isDeviceUpgrading).thenReturn(true)
+ setInitialDatabaseVersion(10)
+ setPackages(
+ Package(TEST_PKG_NAME,
+ Permission(READ_MEDIA_VIDEO, isGranted = true, flags = FLAG_PERMISSION_USER_SET),
+ Permission(READ_MEDIA_IMAGES, isGranted = true, flags = FLAG_PERMISSION_USER_SET),
+ Permission(READ_MEDIA_VISUAL_USER_SELECTED, isGranted = false),
+ targetSdkVersion = 33
+ )
+ )
+
+ upgradeIfNeeded()
+
+ verifyGranted(TEST_PKG_NAME, READ_MEDIA_VISUAL_USER_SELECTED)
+ }
+
+ @Test
+ fun userSelectedNotGrantedIfDeviceNotUpgradingWhenVersionIs10() {
+ Assume.assumeTrue(SdkLevel.isAtLeastU())
+ whenever(packageManager.isDeviceUpgrading).thenReturn(false)
+ setInitialDatabaseVersion(10)
+ setPackages(
+ Package(TEST_PKG_NAME,
+ Permission(READ_MEDIA_VIDEO, isGranted = true, flags = FLAG_PERMISSION_USER_SET),
+ Permission(READ_MEDIA_IMAGES, isGranted = true, flags = FLAG_PERMISSION_USER_SET),
+ Permission(READ_MEDIA_VISUAL_USER_SELECTED, isGranted = false),
+ targetSdkVersion = 33
+ )
+ )
+
+ upgradeIfNeeded()
+
+ verifyNotGranted(TEST_PKG_NAME, READ_MEDIA_VISUAL_USER_SELECTED)
+ }
+
+ @Test
+ fun userSelectedNotGrantedIfReadMediaVisualNotGrantedWhenVersionIs10() {
+ Assume.assumeTrue(SdkLevel.isAtLeastU())
+ whenever(packageManager.isDeviceUpgrading).thenReturn(false)
+ setInitialDatabaseVersion(10)
+ setPackages(
+ Package(TEST_PKG_NAME,
+ Permission(READ_MEDIA_VIDEO, isGranted = false, flags = FLAG_PERMISSION_USER_SET),
+ Permission(READ_MEDIA_IMAGES, isGranted = false, flags = FLAG_PERMISSION_USER_SET),
+ Permission(READ_MEDIA_VISUAL_USER_SELECTED, isGranted = false),
+ targetSdkVersion = 33
+ )
+ )
+
+ upgradeIfNeeded()
+
+ verifyNotGranted(TEST_PKG_NAME, READ_MEDIA_VISUAL_USER_SELECTED)
+ }
+
@After
fun resetSystem() {
// Send low memory notifications for all data repositories which will clear cached data
diff --git a/PermissionController/tests/permissionui/Android.bp b/PermissionController/tests/permissionui/Android.bp
index 704307c1f..9a898d88b 100644
--- a/PermissionController/tests/permissionui/Android.bp
+++ b/PermissionController/tests/permissionui/Android.bp
@@ -60,6 +60,8 @@ android_test {
":CtsAppThatRequestsLocationPermission29",
":PermissionUiUseStoragePermissionApp",
":PermissionUiUseCameraPermissionApp",
+ ":PermissionUiUseHealthConnectPermissionApp",
+ ":PermissionUiInvalidUseHealthConnectPermissionApp",
":PermissionUiDefineAdditionalPermissionApp",
":PermissionUiUseAdditionalPermissionApp",
":PermissionUiUseTwoAdditionalPermissionsApp",
diff --git a/PermissionController/tests/permissionui/AndroidTest.xml b/PermissionController/tests/permissionui/AndroidTest.xml
index 62fc6ed3f..9cea3b243 100644
--- a/PermissionController/tests/permissionui/AndroidTest.xml
+++ b/PermissionController/tests/permissionui/AndroidTest.xml
@@ -43,6 +43,10 @@
value="/data/local/tmp/permissioncontroller/tests/permissionui/PermissionUiUseStoragePermissionApp.apk" />
<option name="push-file" key="PermissionUiUseCameraPermissionApp.apk"
value="/data/local/tmp/permissioncontroller/tests/permissionui/PermissionUiUseCameraPermissionApp.apk" />
+ <option name="push-file" key="PermissionUiUseHealthConnectPermissionApp.apk"
+ value="/data/local/tmp/permissioncontroller/tests/permissionui/PermissionUiUseHealthConnectPermissionApp.apk" />
+ <option name="push-file" key="PermissionUiInvalidUseHealthConnectPermissionApp.apk"
+ value="/data/local/tmp/permissioncontroller/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp.apk" />
<option name="push-file" key="PermissionUiDefineAdditionalPermissionApp.apk"
value="/data/local/tmp/permissioncontroller/tests/permissionui/PermissionUiDefineAdditionalPermissionApp.apk" />
<option name="push-file" key="PermissionUiUseAdditionalPermissionApp.apk"
diff --git a/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/Android.bp b/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/Android.bp
new file mode 100644
index 000000000..c366f9a0c
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2023 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Permission_PermissionController_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: [
+ "packages_modules_Permission_PermissionController_license",
+ ],
+}
+
+android_test_helper_app {
+ name: "PermissionUiInvalidUseHealthConnectPermissionApp",
+
+ srcs: ["src/**/*.kt"],
+
+ sdk_version: "34",
+}
diff --git a/PermissionController/res/values-w764dp-v33/dimens.xml b/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/AndroidManifest.xml
index 78b4675f2..c5e0f4906 100644
--- a/PermissionController/res/values-w764dp-v33/dimens.xml
+++ b/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/AndroidManifest.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2020 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.
@@ -14,6 +15,12 @@
~ limitations under the License.
-->
-<resources>
- <dimen name="sc_button_horizontal_padding">@dimen/sc_large_screen_button_padding</dimen>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permissioncontroller.tests.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
+ <uses-permission android:name="android.permission.health.READ_STEPS" />
+
+ <application android:label="HealthConnectRequestApp" />
+</manifest>
+
diff --git a/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt b/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt
new file mode 100644
index 000000000..98089208a
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiInvalidUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.tests.appthatrequestpermission
+
+import android.app.Activity
+
+class DummyActivity : Activity()
diff --git a/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/Android.bp b/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/Android.bp
new file mode 100644
index 000000000..10788a116
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2023 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Permission_PermissionController_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: [
+ "packages_modules_Permission_PermissionController_license",
+ ],
+}
+
+android_test_helper_app {
+ name: "PermissionUiUseHealthConnectPermissionApp",
+
+ srcs: ["src/**/*.kt"],
+
+ sdk_version: "34",
+}
diff --git a/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/AndroidManifest.xml b/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/AndroidManifest.xml
new file mode 100644
index 000000000..d5903a543
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permissioncontroller.tests.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED" />
+ <uses-permission android:name="android.permission.health.READ_STEPS" />
+
+ <application android:label="HealthConnectRequestApp">
+ <activity android:name=".DummyActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
+ <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt b/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt
new file mode 100644
index 000000000..98089208a
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseHealthConnectPermissionApp/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.tests.appthatrequestpermission
+
+import android.app.Activity
+
+class DummyActivity : Activity()
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
new file mode 100644
index 000000000..c7a33e857
--- /dev/null
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.permissionui.ui
+
+import android.content.Intent
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
+import com.android.permissioncontroller.permissionui.wakeUpScreen
+import org.junit.After
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Simple tests for {@link AllAppPermissionsFragment} for Health Connect behaviors
+ * Currently, does NOT run on TV.
+ * TODO(b/178576541): Adapt and run on TV.
+ * Run with:
+ * atest HealthConnectAllAppPermissionFragmentTest
+ */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class HealthConnectAllAppPermissionFragmentTest : BasePermissionUiTest() {
+ @Before
+ fun assumeNotTelevision() = assumeFalse(isTelevision)
+
+ @Before
+ fun wakeScreenUp() {
+ wakeUpScreen()
+ }
+
+ @After
+ fun uninstallTestApp() {
+ uninstallTestApps()
+ }
+ @Test
+ fun usedHealthConnectPermissionsAreListed() {
+ installTestAppThatUsesHealthConnectPermission()
+
+ startManageAppPermissionsActivity()
+
+ eventually {
+ waitFindObject(By.text(HEALTH_CONNECT_LABEL))
+ waitFindObject(By.text(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED_LABEL))
+ waitFindObject(By.text(HEALTH_CONNECT_PERMISSION_READ_STEPS_LABEL))
+ }
+ }
+
+ @Test
+ fun invalidUngrantedUsedHealthConnectPermissionsAreNotListed() {
+ installInvalidTestAppThatUsesHealthConnectPermission()
+
+ startManageAppPermissionsActivity()
+
+ eventually {
+ assertNull(waitFindObjectOrNull(By.text(HEALTH_CONNECT_LABEL), TIMEOUT_SHORT))
+ assertNull(waitFindObjectOrNull(
+ By.text(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED_LABEL), TIMEOUT_SHORT))
+ assertNull(waitFindObjectOrNull(
+ By.text(HEALTH_CONNECT_PERMISSION_READ_STEPS_LABEL), TIMEOUT_SHORT))
+ }
+ }
+
+ @Test
+ fun invalidGrantedUsedHealthConnectPermissionsAreListed() {
+ installInvalidTestAppThatUsesHealthConnectPermission()
+ grantTestAppPermission(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED)
+
+ startManageAppPermissionsActivity()
+
+ // Ensure that Health Connect permission group permissions are present if a single one is
+ // already granted, regardless of whether the intent filters are incorrectly or not setup
+ // for the app
+ eventually {
+ waitFindObject(By.text(HEALTH_CONNECT_LABEL))
+ waitFindObject(By.text(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED_LABEL))
+
+ // READ_STEPS is not granted, but should still be present due to READ_FLOORS_CLIMBED
+ // being granted
+ waitFindObject(By.text(HEALTH_CONNECT_PERMISSION_READ_STEPS_LABEL))
+ }
+ }
+
+ private fun startManageAppPermissionsActivity() {
+ runWithShellPermissionIdentity {
+ instrumentationContext.startActivity(Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
+ .apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra(Intent.EXTRA_PACKAGE_NAME, PERM_USER_PACKAGE)
+ })
+ }
+
+ waitFindObject(By.descContains(MORE_OPTIONS)).click()
+ waitFindObject(By.text(ALL_PERMISSIONS)).click()
+ }
+
+ companion object {
+ // Health connect label uses a non breaking space
+ private const val HEALTH_CONNECT_LABEL = "Health\u00A0Connect"
+ private const val HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED =
+ "android.permission.health.READ_FLOORS_CLIMBED"
+ private const val HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED_LABEL =
+ "Read floors climbed"
+ private const val HEALTH_CONNECT_PERMISSION_READ_STEPS_LABEL =
+ "Read steps"
+
+ private const val MORE_OPTIONS = "More options"
+ private const val ALL_PERMISSIONS = "All permissions"
+
+ private val TIMEOUT_SHORT = 500L
+ }
+} \ No newline at end of file
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
new file mode 100644
index 000000000..4f3e281cd
--- /dev/null
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 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.permissioncontroller.permissionui.ui
+
+import android.content.Intent
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
+import com.android.permissioncontroller.permissionui.wakeUpScreen
+import org.junit.After
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Simple tests for {@link AppPermissionsFragment} for Health Connect behaviors
+ * Currently, does NOT run on TV.
+ * TODO(b/178576541): Adapt and run on TV.
+ * Run with:
+ * atest HealthConnectAppPermissionFragmentTest
+ */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
+ @Before
+ fun assumeNotTelevision() = assumeFalse(isTelevision)
+
+ @Before
+ fun wakeScreenUp() {
+ wakeUpScreen()
+ }
+
+ @After
+ fun uninstallTestApp() {
+ uninstallTestApps()
+ }
+ @Test
+ fun usedHealthConnectPermissionsAreListed() {
+ installTestAppThatUsesHealthConnectPermission()
+
+ startManageAppPermissionsActivity()
+
+ eventually {
+ waitFindObject(By.text(HEALTH_CONNECT_LABEL))
+ }
+ }
+
+ @Test
+ fun invalidUngrantedUsedHealthConnectPermissionsAreNotListed() {
+ installInvalidTestAppThatUsesHealthConnectPermission()
+
+ startManageAppPermissionsActivity()
+
+ // TODO(b/288286032): update to use waitUntilObjectGone
+ eventually {
+ assertNull(waitFindObjectOrNull(By.text(HEALTH_CONNECT_LABEL), TIMEOUT_SHORT))
+ }
+ }
+
+ @Test
+ fun invalidGrantedUsedHealthConnectPermissionsAreListed() {
+ installInvalidTestAppThatUsesHealthConnectPermission()
+ grantTestAppPermission(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED)
+
+ startManageAppPermissionsActivity()
+
+ eventually {
+ waitFindObject(By.text(HEALTH_CONNECT_LABEL))
+ }
+ }
+
+ private fun startManageAppPermissionsActivity() {
+ runWithShellPermissionIdentity {
+ instrumentationContext.startActivity(Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
+ .apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra(Intent.EXTRA_PACKAGE_NAME, PERM_USER_PACKAGE)
+ })
+ }
+ }
+
+ companion object {
+ // Health connect label uses a non breaking space
+ private const val HEALTH_CONNECT_LABEL = "Health\u00A0Connect"
+ private const val HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED =
+ "android.permission.health.READ_FLOORS_CLIMBED"
+
+ private val TIMEOUT_SHORT = 500L
+ }
+} \ No newline at end of file
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
index 06e25485e..523ce6dc0 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
@@ -24,6 +24,10 @@ import android.permission.cts.PermissionUtils.uninstallApp
private const val APK_DIRECTORY = "/data/local/tmp/permissioncontroller/tests/permissionui/"
private const val LOCATION_PERM_USER_APK = "$APK_DIRECTORY/AppThatRequestsLocation.apk"
private const val CAMERA_PERM_USER_APK = "$APK_DIRECTORY/PermissionUiUseCameraPermissionApp.apk"
+private const val HEALTH_CONNECT_PERMISSION_USER_APK =
+ "$APK_DIRECTORY/PermissionUiUseHealthConnectPermissionApp.apk"
+private const val INVALID_HEALTH_CONNECT_PERMISSION_USER_APK =
+ "$APK_DIRECTORY/PermissionUiInvalidUseHealthConnectPermissionApp.apk"
private const val ADDITIONAL_PERM_USER_APK =
"$APK_DIRECTORY/PermissionUiUseAdditionalPermissionApp.apk"
private const val TWO_ADDITIONAL_PERM_USER_APK =
@@ -32,10 +36,10 @@ private const val ADDITIONAL_PERM_DEFINER_APK =
"$APK_DIRECTORY/PermissionUiDefineAdditionalPermissionApp.apk"
// All 4 of the AppThatUses_X_Permission(s) applications share the same package name.
-private const val PERM_USER_PACKAGE =
- "com.android.permissioncontroller.tests.appthatrequestpermission"
private const val PERM_DEFINER_PACKAGE =
"com.android.permissioncontroller.tests.appthatdefinespermission"
+const val PERM_USER_PACKAGE =
+ "com.android.permissioncontroller.tests.appthatrequestpermission"
const val CAMERA_TEST_APP_LABEL = "CameraRequestApp"
@@ -49,6 +53,9 @@ const val TEST_APP_DEFINED_PERMISSION_C_LABEL = "Permission C"
fun installTestAppThatRequestsLocation() = install(LOCATION_PERM_USER_APK)
fun installTestAppThatUsesCameraPermission() = install(CAMERA_PERM_USER_APK)
+fun installTestAppThatUsesHealthConnectPermission() = install(HEALTH_CONNECT_PERMISSION_USER_APK)
+fun installInvalidTestAppThatUsesHealthConnectPermission() =
+ install(INVALID_HEALTH_CONNECT_PERMISSION_USER_APK)
fun installTestAppThatUsesAdditionalPermission() = install(ADDITIONAL_PERM_USER_APK)
fun installTestAppThatUsesTwoAdditionalPermissions() = install(TWO_ADDITIONAL_PERM_USER_APK)
fun installTestAppThatDefinesAdditionalPermissions() = install(ADDITIONAL_PERM_DEFINER_APK)
diff --git a/SafetyCenter/Annotations/java/com/android/safetycenter/annotations/RequiresTiramisuByDefault.java b/SafetyCenter/Annotations/java/com/android/safetycenter/annotations/RequiresTiramisuByDefault.java
new file mode 100644
index 000000000..6e1f4c3b7
--- /dev/null
+++ b/SafetyCenter/Annotations/java/com/android/safetycenter/annotations/RequiresTiramisuByDefault.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.annotations;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.RequiresApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.annotation.meta.TypeQualifierDefault;
+
+/**
+ * Specifies that all types are {@code RequiresApi(TIRAMISU)} within the annotated package, unless
+ * tagged another {@code @RequiresApi} annotation.
+ */
+@Retention(CLASS)
+@Target(PACKAGE)
+@TypeQualifierDefault({TYPE})
+@RequiresApi(TIRAMISU)
+public @interface RequiresTiramisuByDefault {}
diff --git a/SafetyCenter/Resources/res/values-uk/strings.xml b/SafetyCenter/Resources/res/values-uk/strings.xml
index 8a3c33755..0c1e109a3 100644
--- a/SafetyCenter/Resources/res/values-uk/strings.xml
+++ b/SafetyCenter/Resources/res/values-uk/strings.xml
@@ -24,7 +24,7 @@
<string name="lock_screen_summary_disabled" msgid="354071230916616692">"Поки немає інформації"</string>
<string name="lock_screen_search_terms" msgid="2678486357779794826">"блокування пристрою, блокування екрана, блокувати екран, заблокований екран, пароль, PIN-код, ключ"</string>
<string name="biometrics_title" msgid="5859504610285212938">"Біометрія"</string>
- <string name="biometrics_search_terms" msgid="6040319118762671981">"відбиток пальця, палець, додати відбиток пальця, фейсконтроль, обличчя"</string>
+ <string name="biometrics_search_terms" msgid="6040319118762671981">"відбиток пальця, палець, додати відбиток пальця, фейс-контроль, обличчя"</string>
<string name="privacy_sources_title" msgid="4061110826457365957">"Конфіденційність"</string>
<string name="privacy_sources_summary" msgid="4089719981155120864">"Панель керування, дозволи, параметри"</string>
<string name="permission_usage_title" msgid="3633779688945350407">"Панель керування дозволами"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-af/strings.xml b/SafetyCenter/Resources/shared_res/values-af/strings.xml
index 9ab860ecf..3c20c6428 100644
--- a/SafetyCenter/Resources/shared_res/values-af/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-af/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Sien waarskuwing}other{Sien waarskuwings}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kon nie bladsy oopmaak nie"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kon nie opletberig afhandel nie"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Kon nie instellings herlaai nie"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kon nie instelling nagaan nie}other{Kon nie instellings nagaan nie}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Werkprofiel is onderbreek"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nog geen inligting nie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-am/strings.xml b/SafetyCenter/Resources/shared_res/values-am/strings.xml
index 093dda2f2..d1080ebf7 100644
--- a/SafetyCenter/Resources/shared_res/values-am/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-am/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ማንቂያ ይመልከቱ}one{ማንቂያ ይመልከቱ}other{ማንቂያዎች ይመልከቱ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ገጹን መከፈት አልተቻለም"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ማንቂያን መፍታት አልተቻለም"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ቅንብሮችን ማደስ አልተቻለም"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ቅንብርን መፈተሽ አልተቻለም}one{ቅንብርን መፈተሽ አልተቻለም}other{ቅንብሮችን መፈተሽ አልተቻለም}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"የስራ መገለጫ ባለበት ቆሟል"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ገና ምንም መረጃ የለም"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ar/strings.xml b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
index ce0b1c10d..49e16c39c 100644
--- a/SafetyCenter/Resources/shared_res/values-ar/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{الاطّلاع على التنبيه}zero{الاطّلاع على التنبيهات}two{الاطّلاع على التنبيهَين}few{الاطّلاع على التنبيهات}many{الاطّلاع على التنبيهات}other{الاطّلاع على التنبيهات}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"تعذَّر فتح الصفحة"</string>
<string name="resolving_action_error" msgid="371968886143262375">"تعذَّر التعامل بشكل نهائي مع التنبيه"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"تعذّر تحديث الإعدادات"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{تعذّر التحقّق من الإعداد.}zero{تعذّر التحقّق من الإعدادات.}two{تعذّر التحقّق من الإعدادَين.}few{تعذّر التحقّق من الإعدادات.}many{تعذّر التحقّق من الإعدادات.}other{تعذّر التحقّق من الإعدادات.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"تم إيقاف الملف الشخصي للعمل مؤقتًا"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ما مِن معلومات بعد."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-as/strings.xml b/SafetyCenter/Resources/shared_res/values-as/strings.xml
index 473003f59..4105c26d0 100644
--- a/SafetyCenter/Resources/shared_res/values-as/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-as/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{সতৰ্কবাৰ্তা চাওক}one{সতৰ্কবাৰ্তাসমূহ চাওক}other{সতৰ্কবাৰ্তাসমূহ চাওক}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"পৃষ্ঠাখন খুলিব পৰা নগ’ল"</string>
<string name="resolving_action_error" msgid="371968886143262375">"সতৰ্কবাৰ্তা সমাধান কৰিব পৰা নগ’ল"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ছেটিং ৰিফ্ৰেশ্ব কৰিব পৰা নগ’ল"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}one{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}other{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"কৰ্মস্থানৰ প্ৰ’ফাইলটো পজ কৰা আছে"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"এতিয়ালৈকে কোনো তথ্য নাই"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-az/strings.xml b/SafetyCenter/Resources/shared_res/values-az/strings.xml
index e3c31a178..7e2e7c140 100644
--- a/SafetyCenter/Resources/shared_res/values-az/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-az/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Xəbərdarlığa baxın}other{Xəbərdarlıqlara baxın}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Səhifəni açmaq mümkün olmadı"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Siqnalı həll etmək mümkün olmadı"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ayarları yeniləmək mümkün olmadı"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayarı yoxlamaq alınmadı}other{Ayarları yoxlamaq alınmadı}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"İş profili durdurulub"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Hələ ki, məlumat yoxdur"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
index 5233edc91..19660cb06 100644
--- a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Prikaži obaveštenje}one{Prikaži obaveštenja}few{Prikaži obaveštenja}other{Prikaži obaveštenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Rešavanje obaveštenja nije uspelo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Osvežavanje podešavanja nije uspelo"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Provera podešavanja nije uspela}one{Provera podešavanja nije uspela}few{Provera podešavanja nije uspela}other{Provera podešavanja nije uspela}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Poslovni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još nema informacija"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-be/strings.xml b/SafetyCenter/Resources/shared_res/values-be/strings.xml
index 2b386f3e6..2c27670e4 100644
--- a/SafetyCenter/Resources/shared_res/values-be/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-be/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Паглядзець абвестку}one{Паглядзець абвесткі}few{Паглядзець абвесткі}many{Паглядзець абвесткі}other{Паглядзець абвесткі}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не ўдалося адкрыць старонку"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не ўдалося вырашыць праблему"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не ўдалося абнавіць налады"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не ўдалося праверыць наладу}one{Не ўдалося праверыць налады}few{Не ўдалося праверыць налады}many{Не ўдалося праверыць налады}other{Не ўдалося праверыць налады}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Працоўны профіль прыпынены"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Пакуль няма інфармацыі"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bg/strings.xml b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
index 85833f145..e3495d4d5 100644
--- a/SafetyCenter/Resources/shared_res/values-bg/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Преглед на сигнала}other{Преглед на сигналите}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Страницата не се отвори"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Сигналът не се отстрани"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Настройките не бяха опреснени"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Настройката не бе проверена}other{Настройките не бяха проверени}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Служебният потребителски профил е поставен на пауза"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Още няма информация"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bn/strings.xml b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
index 30904c97e..3f24f05a4 100644
--- a/SafetyCenter/Resources/shared_res/values-bn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{বিজ্ঞপ্তি দেখুন}one{বিজ্ঞপ্তি দেখুন}other{বিজ্ঞপ্তি দেখুন}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"পৃষ্ঠা খোলা যায়নি"</string>
<string name="resolving_action_error" msgid="371968886143262375">"সতর্কতার সমাধান করা যায়নি"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"সেটিংস রিফ্রেশ করা যায়নি"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{সেটিং চেক করা যায়নি}one{সেটিংস চেক করা যায়নি}other{সেটিংস চেক করা যায়নি}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"অফিস প্রোফাইল পজ করা আছে"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"এখনও কোনও তথ্য নেই"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bs/strings.xml b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
index 93cfcf8fa..a877f8b36 100644
--- a/SafetyCenter/Resources/shared_res/values-bs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Pogledajte upozorenje}one{Pogledajte upozorenja}few{Pogledajte upozorenja}other{Pogledajte upozorenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspjelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Rješavanje upozorenja nije uspjelo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Osvježavanje postavki nije uspjelo"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Provjera postavke nije uspjela}one{Provjera postavki nije uspjela}few{Provjera postavki nije uspjela}other{Provjera postavki nije uspjela}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Radni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još uvijek nema informacija"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ca/strings.xml b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
index dbec15679..a2d8b0f35 100644
--- a/SafetyCenter/Resources/shared_res/values-ca/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostra l\'alerta}many{Mostra les alertes}other{Mostra les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No s\'ha pogut obrir la pàgina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No s\'ha pogut resoldre l\'alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"No s\'ha pogut actualitzar la configuració"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No s\'ha pogut comprovar la configuració}many{No s\'ha pogut comprovar la configuració}other{No s\'ha pogut comprovar la configuració}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de treball s\'ha posat en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Encara no hi ha informació"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-cs/strings.xml b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
index 9d90b05aa..28aabd4e8 100644
--- a/SafetyCenter/Resources/shared_res/values-cs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobrazit upozornění}few{Zobrazit upozornění}many{Zobrazit upozornění}other{Zobrazit upozornění}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Stránku nelze otevřít"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Upozornění se nepodařilo vyřešit"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nastavení se nepodařilo obnovit"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavení nelze zkontrolovat}few{Nastavení nelze zkontrolovat}many{Nastavení nelze zkontrolovat}other{Nastavení nelze zkontrolovat}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Pracovní profil je pozastaven"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Zatím žádné údaje"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-da/strings.xml b/SafetyCenter/Resources/shared_res/values-da/strings.xml
index bce5898fa..e4d28aaf1 100644
--- a/SafetyCenter/Resources/shared_res/values-da/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-da/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se underretning}one{Se underretning}other{Se underretninger}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Siden kunne ikke åbnes"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Underretningen kunne ikke behandles"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Indstillingerne kunne ikke opdateres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Indstillingen kunne ikke tjekkes}one{Indstillingen kunne ikke tjekkes}other{Indstillingerne kunne ikke tjekkes}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Arbejdsprofilen er sat på pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Der er ingen oplysninger endnu"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-de/strings.xml b/SafetyCenter/Resources/shared_res/values-de/strings.xml
index cbaf373d1..a5b1758ad 100644
--- a/SafetyCenter/Resources/shared_res/values-de/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-de/strings.xml
@@ -36,11 +36,10 @@
<string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Mögliche Sicherheitsrisiken gefunden"</string>
<string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Sicherheitsrisiken gefunden"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Konto eventuell gefährdet"</string>
- <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Konto gefährdet"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Konto ist gefährdet"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Benachrichtigung ansehen}other{Benachrichtigungen ansehen}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Seite konnte nicht geöffnet werden"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ursache konnte nicht behoben werden"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Einstellungen konnten nicht aktualisiert werden"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Einstellung konnte nicht überprüft werden}other{Einstellungen konnten nicht überprüft werden}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Arbeitsprofil pausiert"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Noch keine Angaben vorhanden"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-el/strings.xml b/SafetyCenter/Resources/shared_res/values-el/strings.xml
index ac93dedd8..c6f005d5f 100644
--- a/SafetyCenter/Resources/shared_res/values-el/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-el/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Εμφάνιση ειδοποίησης}other{Εμφάνιση ειδοποιήσεων}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Δεν ήταν δυνατό το άνοιγμα της σελίδας"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Δεν ήταν δυνατή η επίλυση της ειδοποίησης"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Δεν ήταν δυνατή η ανανέωση των ρυθμίσεων"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Δεν ήταν δυνατός ο έλεγχος της ρύθμισης}other{Δεν ήταν δυνατός ο έλεγχος των ρυθμίσεων}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Το προφίλ εργασίας έχει τεθεί σε παύση"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Δεν υπάρχουν ακόμα πληροφορίες"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
index 7e0312660..f389be966 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
index f63228247..00234beca 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldnt open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldnt resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldnt refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldnt check setting}other{Couldnt check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
index 7e0312660..f389be966 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
index 7e0312660..f389be966 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
index d1cd45d43..811e6e7fd 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎See alert‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎See alerts‎‏‎‎‏‎}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎Couldnt open page‎‏‎‎‏‎"</string>
<string name="resolving_action_error" msgid="371968886143262375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎Couldnt resolve alert‎‏‎‎‏‎"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎Couldnt refresh settings‎‏‎‎‏‎"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Couldnt check setting‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Couldnt check settings‎‏‎‎‏‎}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‎Work profile is paused‎‏‎‎‏‎"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎No info yet‎‏‎‎‏‎"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
index 787838a20..29613fc69 100644
--- a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
@@ -36,11 +36,10 @@
<string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Se detectaron riesgos potenciales"</string>
<string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Se detectaron riesgos"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"La cuenta podría estar en riesgo"</string>
- <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Cuenta en peligro"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"La cuenta está en riesgo"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ver alerta}many{Ver alertas}other{Ver alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No se pudo abrir la página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No se pudo resolver la alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"No se pudo actualizar la configuración"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No se pudo revisar el parámetro de configuración}many{No se pudieron revisar los parámetros de configuración}other{No se pudieron revisar los parámetros de configuración}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de trabajo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aún no hay información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es/strings.xml b/SafetyCenter/Resources/shared_res/values-es/strings.xml
index 245d3394c..a13a68d8f 100644
--- a/SafetyCenter/Resources/shared_res/values-es/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ver alerta}many{Ver alertas}other{Ver alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No se ha podido abrir la página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No se ha podido resolver la alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"No se han podido actualizar los ajustes"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No se ha podido comprobar el ajuste}many{No se han podido comprobar los ajustes}other{No se han podido comprobar los ajustes}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de trabajo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aún no hay información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-et/strings.xml b/SafetyCenter/Resources/shared_res/values-et/strings.xml
index 0aab37815..cfe0541d2 100644
--- a/SafetyCenter/Resources/shared_res/values-et/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-et/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Vaadake hoiatust}other{Vaadake hoiatusi}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Lehte ei saanud avada"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hoiatusega seotud probleemi ei saanud lahendada"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Seadeid ei saanud värskendada"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Seadet ei õnnestunud kontrollida}other{Seadeid ei õnnestunud kontrollida}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Tööprofiil on peatatud"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Teavet ei ole veel"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-eu/strings.xml b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
index 88942a47b..c00cb5827 100644
--- a/SafetyCenter/Resources/shared_res/values-eu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ikusi alerta}other{Ikusi alertak}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ezin da ireki orria"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ezin izan da ebatzi alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ezin izan dira freskatu ezarpenak"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ezin izan da egiaztatu ezarpena}other{Ezin izan dira egiaztatu ezarpenak}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Laneko profila pausatuta dago"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ez dago informaziorik oraindik"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fa/strings.xml b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
index df11d470f..744ac950c 100644
--- a/SafetyCenter/Resources/shared_res/values-fa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{دیدن هشدار}one{دیدن هشدار}other{دیدن هشدارها}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"صفحه باز نشد"</string>
<string name="resolving_action_error" msgid="371968886143262375">"هشدار رفع نشد"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"تنظیمات بازآوری نشد"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{تنظیم بررسی نشد}one{تنظیم بررسی نشد}other{تنظیمات بررسی نشدند}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"نمایه کاری موقتاً متوقف شده است"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"هنوز اطلاعاتی دردسترس نیست"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fi/strings.xml b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
index bad542e6d..801b40081 100644
--- a/SafetyCenter/Resources/shared_res/values-fi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Näytä ilmoitus}other{Näytä ilmoitukset}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sivun avaaminen epäonnistui"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hälytyksen ratkaiseminen epäonnistui"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Asetuksia ei voitu päivittää"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Asetuksen tarkistaminen ei onnistunut}other{Asetusten tarkistaminen ei onnistunut}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Työprofiilin käyttö on keskeytetty"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ei vielä tietoa"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
index c1530b2cb..3956c61f5 100644
--- a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Afficher l\'alerte}one{Afficher l\'alerte}many{Afficher les alertes}other{Afficher les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossible d\'ouvrir la page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossible de résoudre l\'alerte"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Impossible d\'actualiser les paramètres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossible de vérifier le paramètre}one{Impossible de vérifier le paramètre}many{Impossible de vérifier les paramètres}other{Impossible de vérifier les paramètres}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Le profil professionnel est interrompu"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aucune donnée pour le moment"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr/strings.xml b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
index d7bf215a0..b05f99a15 100644
--- a/SafetyCenter/Resources/shared_res/values-fr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Voir l\'alerte}one{Voir l\'alerte}many{Voir les alertes}other{Voir les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossible d\'accéder à la page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossible de résoudre l\'alerte"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Impossible d\'actualiser les paramètres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossible de vérifier le paramètre}one{Impossible de vérifier le paramètre}many{Impossible de vérifier les paramètres}other{Impossible de vérifier les paramètres}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil professionnel en pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aucune info pour l\'instant"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gl/strings.xml b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
index ea934a37b..3a0251dd2 100644
--- a/SafetyCenter/Resources/shared_res/values-gl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Consulta a alerta}other{Consulta as alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Non se puido abrir a páxina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Non se puido resolver a alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Non se puido actualizar a configuración"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Non se puido comprobar a opción de configuración}other{Non se puideron comprobar as opcións de configuración}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de traballo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aínda non hai información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gu/strings.xml b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
index 0894b304b..ded82b3e5 100644
--- a/SafetyCenter/Resources/shared_res/values-gu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{અલર્ટ જુઓ}one{અલર્ટ જુઓ}other{અલર્ટ જુઓ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"પેજ ખોલી શક્યા નથી"</string>
<string name="resolving_action_error" msgid="371968886143262375">"અલર્ટનું નિરાકરણ લાવી શક્યા નથી"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"સેટિંગ રિફ્રેશ કરી શકાયા નથી"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{સેટિંગ ચેક કરી શકાયું નથી}one{સેટિંગ ચેક કરી શકાયું નથી}other{સેટિંગ ચેક કરી શકાયા નથી}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ઑફિસની પ્રોફાઇલ થોભાવી છે"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"હજી સુધી કોઈ માહિતી નથી"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hi/strings.xml b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
index c9a80e7f0..6fe62b414 100644
--- a/SafetyCenter/Resources/shared_res/values-hi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{चेतावनी देखें}one{चेतावनी देखें}other{चेतावनियां देखें}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज को खोला नहीं जा सका"</string>
<string name="resolving_action_error" msgid="371968886143262375">"चेतावनी में बताई गई समस्या को ठीक नहीं किया जा सका"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"सेटिंग को रीफ़्रेश नहीं किया जा सका"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिंग की जांच नहीं की जा सकी}one{सेटिंग की जांच नहीं की जा सकी}other{सेटिंग की जांच नहीं की जा सकी}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"वर्क प्रोफ़ाइल रोक दी गई है"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"कोई जानकारी मौजूद नहीं है"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hr/strings.xml b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
index ed02bc991..a644d6126 100644
--- a/SafetyCenter/Resources/shared_res/values-hr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Pogledajte upozorenje}one{Pogledajte upozorenja}few{Pogledajte upozorenja}other{Pogledajte upozorenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspjelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Razrješavanje upozorenja nije uspjelo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nije bilo moglo osvježiti postavke"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nije moguće provjeriti postavku}one{Nije moguće provjeriti postavke}few{Nije moguće provjeriti postavke}other{Nije moguće provjeriti postavke}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Poslovni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još nema podataka"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hu/strings.xml b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
index 5f97845bc..b8a8540ef 100644
--- a/SafetyCenter/Resources/shared_res/values-hu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Értesítés megtekintése}other{Értesítések megtekintése}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nem sikerült megnyitni az oldalt"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nem sikerült feloldani a figyelmeztetést"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nem sikerült a beállítások frissítése"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nem sikerült a beállítás ellenőrzése}other{Nem sikerült a beállítások ellenőrzése}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"A munkaprofil használata szünetel"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Még nincsenek adatok"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hy/strings.xml b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
index befaeb8e5..e6b05056b 100644
--- a/SafetyCenter/Resources/shared_res/values-hy/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Տեսնել ծանուցումը}one{Տեսնել ծանուցումները}other{Տեսնել ծանուցումները}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Չհաջողվեց բացել էջը"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Չհաջողվեց լուծել ծանուցումը"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Չհաջողվեց թարմացնել կարգավորումները"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Չհաջողվեց ստուգել կարգավորումը}one{Չհաջողվեց ստուգել կարգավորումը}other{Չհաջողվեց ստուգել կարգավորումները}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Աշխատանքային պրոֆիլը դադարեցված է"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Տեղեկություններ դեռ չկան"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-in/strings.xml b/SafetyCenter/Resources/shared_res/values-in/strings.xml
index cd584dbab..8d25d7340 100644
--- a/SafetyCenter/Resources/shared_res/values-in/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-in/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Lihat peringatan}other{Lihat peringatan}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Tidak dapat membuka halaman"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Tidak dapat menyelesaikan peringatan"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Tidak dapat merefresh setelan"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Tidak dapat memeriksa setelan}other{Tidak dapat memeriksa setelan}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil kerja dijeda"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Belum ada info"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-is/strings.xml b/SafetyCenter/Resources/shared_res/values-is/strings.xml
index 3e36f81f1..faa4c2e1f 100644
--- a/SafetyCenter/Resources/shared_res/values-is/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-is/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Sjá viðvörun}one{Sjá viðvaranir}other{Sjá viðvaranir}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ekki tókst að opna síðuna"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ekki tókst að leysa úr viðvöruninni"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ekki tókst að endurnýja stillingar"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ekki tókst að athuga stillingu}one{Ekki tókst að athuga stillingar}other{Ekki tókst að athuga stillingar}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Hlé gert á vinnusniði"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Engar upplýsingar ennþá"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-it/strings.xml b/SafetyCenter/Resources/shared_res/values-it/strings.xml
index 68aa4eab3..b2661e84a 100644
--- a/SafetyCenter/Resources/shared_res/values-it/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-it/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Visualizza l\'avviso}many{Visualizza gli avvisi}other{Visualizza gli avvisi}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossibile aprire la pagina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossibile risolvere l\'avviso"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Impossibile aggiornare le impostazioni"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossibile controllare l\'impostazione}many{Impossibile controllare le impostazioni}other{Impossibile controllare le impostazioni}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profilo di lavoro in pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ancora nessuna informazione"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-iw/strings.xml b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
index 05cb0cd18..8a0794632 100644
--- a/SafetyCenter/Resources/shared_res/values-iw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{הצגת ההתראה}one{הצגת ההתראות}two{הצגת ההתראות}other{הצגת ההתראות}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"לא ניתן היה לפתוח את הדף"</string>
<string name="resolving_action_error" msgid="371968886143262375">"לא ניתן היה לפתור את הבעיה בהתראה"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"לא ניתן היה לרענן את ההגדרות"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{לא ניתן היה לבדוק את ההגדרה}one{לא ניתן היה לבדוק את ההגדרות}two{לא ניתן היה לבדוק את ההגדרות}other{לא ניתן היה לבדוק את ההגדרות}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"פרופיל העבודה מושהה"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"אין עדיין פרטים"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ja/strings.xml b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
index aa760e5d7..393ca271c 100644
--- a/SafetyCenter/Resources/shared_res/values-ja/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{アラートを確認する}other{アラートを確認する}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ページを開けませんでした"</string>
<string name="resolving_action_error" msgid="371968886143262375">"アラートを解決できませんでした"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"設定を更新できませんでした"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{設定を確認できませんでした}other{設定を確認できませんでした}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"仕事用プロファイルが一時停止しています"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"まだ情報がありません"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ka/strings.xml b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
index 4dda15927..de8890dec 100644
--- a/SafetyCenter/Resources/shared_res/values-ka/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{გაფრთხილების ნახვა}other{გაფრთხილებების ნახვა}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"გვერდის გახსნა ვერ მოხერხდა"</string>
<string name="resolving_action_error" msgid="371968886143262375">"გაფრთხილება ვერ გადაიჭრა"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"პარამეტრები ვერ განახლდა"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{პარამეტრის შემოწმება ვერ მოხერხდა}other{პარამეტრების Შემოწმება ვერ მოხერხდა}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"სამსახურის პროფილი დაპაუზებულია"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ინფო ჯერ არ არის"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kk/strings.xml b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
index 5ea3fa3e1..9226d4fcb 100644
--- a/SafetyCenter/Resources/shared_res/values-kk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Хабарландыруды көру}other{Хабарландыруларды көру}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Бет ашылмады."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Хабарландыруда көрсетілген мәселе шешілмеді."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Параметрлер жаңартылмады."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметрді тексеру мүмкін болмады.}other{Параметрлерді тексеру мүмкін болмады.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Жұмыс профилі кідіртілді."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Әзірге мәлімет жоқ."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-km/strings.xml b/SafetyCenter/Resources/shared_res/values-km/strings.xml
index d2bf8fc48..e1af9019d 100644
--- a/SafetyCenter/Resources/shared_res/values-km/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-km/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{មើលការជូន​ដំណឹង}other{មើលការជូនដំណឹង}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"មិនអាច​បើកទំព័រ​បានទេ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"មិនអាច​ដោះស្រាយការជូនដំណឹងនេះ​បានទេ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"មិនអាចផ្ទុកការកំណត់ឡើងវិញបានទេ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{មិន​អាច​ពិនិត្យ​មើល​ការកំណត់​បាន​ទេ}other{មិន​អាច​ពិនិត្យ​មើល​ការកំណត់​បាន​ទេ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"កម្រងព័ត៌មានការងារត្រូវបាន​ផ្អាក"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"មិន​ទាន់​មាន​ព័ត៌មាន​នៅ​ឡើយ​ទេ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kn/strings.xml b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
index 9c3cb9dca..46eedaac5 100644
--- a/SafetyCenter/Resources/shared_res/values-kn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ಎಚ್ಚರಿಕೆಯನ್ನು ನೋಡಿ}one{ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}other{ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ಪುಟವನ್ನು ತೆರೆಯಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ಅಲರ್ಟ್ ಅನ್ನು ಬಗೆಹರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}one{ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}other{ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಅನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ಇನ್ನೂ ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ko/strings.xml b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
index 295fe4992..1a5938c9a 100644
--- a/SafetyCenter/Resources/shared_res/values-ko/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{알림 보기}other{알림 보기}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"페이지를 열 수 없습니다."</string>
<string name="resolving_action_error" msgid="371968886143262375">"알림을 해결할 수 없습니다."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"설정을 새로고침할 수 없습니다."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{설정을 확인할 수 없습니다.}other{설정을 확인할 수 없습니다.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"직장 프로필이 일시중지됨"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"아직 정보 없음"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ky/strings.xml b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
index 6fbc42ea6..474377b87 100644
--- a/SafetyCenter/Resources/shared_res/values-ky/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Эскетүүнү көрүү}other{Эскертүүлөрдү көрүү}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Барак ачылган жок"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Эскертүү чечилген жок"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Параметрлер жаңыртылган жок"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметр текшерилген жок}other{Параметрлер текшерилген жок}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Жумуш профили тындырылды"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Азырынча маалымат жок"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lo/strings.xml b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
index 724411879..0abf76b30 100644
--- a/SafetyCenter/Resources/shared_res/values-lo/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ເບິ່ງແຈ້ງເຕືອນ}other{ເບິ່ງແຈ້ງເຕືອນ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ບໍ່ສາມາດເປີດໜ້າໄດ້"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ບໍ່ສາມາດແກ້ໄຂແຈ້ງເຕືອນໄດ້"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ບໍ່ສາມາດໂຫຼດການຕັ້ງຄ່າຄືນໃໝ່"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ກວດສອບການຕັ້ງຄ່າບໍ່ໄດ້}other{ກວດສອບການຕັ້ງຄ່າບໍ່ໄດ້}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ຢຸດໂປຣໄຟລ໌ວຽກໄວ້ຊົ່ວຄາວແລ້ວ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ບໍ່ມີຂໍ້ມູນເທື່ອ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lt/strings.xml b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
index fc1704c13..832ef32f1 100644
--- a/SafetyCenter/Resources/shared_res/values-lt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Žr. įspėjimą}one{Žr. įspėjimus}few{Žr. įspėjimus}many{Žr. įspėjimus}other{Žr. įspėjimus}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nepavyko atidaryti puslapio"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nepavyko pašalinti įspėjimo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nepavyko atnaujinti nustatymų"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nepavyko patikrinti nustatymo}one{Nepavyko patikrinti nustatymų}few{Nepavyko patikrinti nustatymų}many{Nepavyko patikrinti nustatymų}other{Nepavyko patikrinti nustatymų}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Darbo profilis pristabdytas"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Kol kas informacijos nėra"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lv/strings.xml b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
index c27624603..eb544b15f 100644
--- a/SafetyCenter/Resources/shared_res/values-lv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Skatiet brīdinājumu}zero{Skatiet brīdinājumus}one{Skatiet brīdinājumus}other{Skatiet brīdinājumus}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nevarēja atvērt lapu"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nevarēja atrisināt ieteikumu vai brīdinājumu"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nevarēja atsvaidzināt iestatījumus"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nevarēja pārbaudīt iestatījumu.}zero{Nevarēja pārbaudīt iestatījumus.}one{Nevarēja pārbaudīt iestatījumus.}other{Nevarēja pārbaudīt iestatījumus.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Darba profila darbība ir apturēta"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Vēl nav informācijas"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mk/strings.xml b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
index 2ba7a1976..30b405c67 100644
--- a/SafetyCenter/Resources/shared_res/values-mk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Видете го предупредувањето}one{Видете ги предупредувањата}other{Видете ги предупредувањата}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не можеше да се отвори страницата"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не можеше да се реши предупредувањето"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не можеше да се освежат поставките"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не може да се провери поставката}one{Не може да се проверат поставките}other{Не може да се проверат поставките}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Работниот профил е паузиран"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Сѐ уште нема податоци"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ml/strings.xml b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
index a05a7eed1..4a77469da 100644
--- a/SafetyCenter/Resources/shared_res/values-ml/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{മുന്നറിയിപ്പ് കാണുക}other{മുന്നറിയിപ്പുകൾ കാണുക}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"പേജ് തുറക്കാനായില്ല"</string>
<string name="resolving_action_error" msgid="371968886143262375">"മുന്നറിയിപ്പ് പരിഹരിക്കാനായില്ല"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ക്രമീകരണം റീഫ്രഷ് ചെയ്യാനായില്ല"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ക്രമീകരണം പരിശോധിക്കാനായില്ല}other{ക്രമീകരണം പരിശോധിക്കാനായില്ല}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ഔദ്യോഗിക പ്രൊഫൈൽ തൽക്കാലം നിർത്തിയിരിക്കുന്നു"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ഇതുവരെ വിവരങ്ങളൊന്നുമില്ല"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mn/strings.xml b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
index 8b2f17f37..88f75f648 100644
--- a/SafetyCenter/Resources/shared_res/values-mn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Сэрэмжлүүлгийг харах}other{Сэрэмжлүүлгүүдийг харах}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Хуудсыг нээж чадсангүй"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Сэрэмжлүүлгийг шийдвэрлэж чадсангүй"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Тохиргоог сэргээж чадсангүй"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Тохиргоог шалгаж чадсангүй}other{Тохиргоог шалгаж чадсангүй}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Ажлын профайлыг түр зогсоосон"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Мэдээлэл хараахан алга"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mr/strings.xml b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
index f406a2931..0b47053cd 100644
--- a/SafetyCenter/Resources/shared_res/values-mr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{सूचना पहा}other{सूचना पहा}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज उघडता आले नाही"</string>
<string name="resolving_action_error" msgid="371968886143262375">"इशाऱ्याचे निराकरण करता आले नाही"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"सेटिंग्ज रिफ्रेश करता आली नाही"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिंग तपासता आले नाही}other{सेटिंग्ज तपासता आली नाहीत}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"कार्य प्रोफाइल थांबवली आहे"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"अद्याप कोणतीही माहिती नाही"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ms/strings.xml b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
index bb98c69d0..f8c3e2c32 100644
--- a/SafetyCenter/Resources/shared_res/values-ms/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Lihat makluman}other{Lihat makluman}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Tidak dapat membuka halaman"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Tidak dapat menyelesaikan amaran"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Tidak dapat menyegar semula tetapan"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Tidak dapat menyemak tetapan}other{Tidak dapat menyemak tetapan}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil kerja dijeda"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Belum ada maklumat lagi"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-my/strings.xml b/SafetyCenter/Resources/shared_res/values-my/strings.xml
index af7f71a34..76e53c8dc 100644
--- a/SafetyCenter/Resources/shared_res/values-my/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-my/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{သတိပေးချက် ကြည့်ရန်}other{သတိပေးချက်များ ကြည့်ရန်}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"စာမျက်နှာကို ဖွင့်၍မရပါ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"သတိပေးချက်ကို ဖြေရှင်း၍မရပါ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ဆက်တင်များကို ပြန်လည် စတင်၍မရပါ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ဆက်တင်ကြည့်၍မရပါ}other{ဆက်တင်များ ကြည့်၍မရပါ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"အလုပ်ပရိုဖိုင် ခဏရပ်ထားသည်"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"အချက်အလက် မရှိသေးပါ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nb/strings.xml b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
index 8365aaea8..61338a390 100644
--- a/SafetyCenter/Resources/shared_res/values-nb/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se varselet}other{Se varslene}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kunne ikke åpne siden"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kunne ikke løse varselet"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Kunne ikke laste inn innstillingene på nytt"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kunne ikke sjekke innstillingen}other{Kunne ikke sjekke innstillingene}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Jobbprofilen er satt på pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ingen informasjon ennå"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ne/strings.xml b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
index 4e55d23f1..e7c392a3b 100644
--- a/SafetyCenter/Resources/shared_res/values-ne/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{अलर्ट हेर्नुहोस्}other{अलर्टहरू हेर्नुहोस्}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज खोल्न सकिएन"</string>
<string name="resolving_action_error" msgid="371968886143262375">"अलर्ट समाधान गर्न सकिएन"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"सेटिङ रिफ्रेस गर्न सकिएन"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिङ जाँच गर्न सकिएन}other{सेटिङहरू जाँच गर्न सकिएन}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"कार्य प्रोफाइल पज गरिएको छ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"कुनै जानकारी उपलब्ध छैन"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nl/strings.xml b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
index b916f37bc..ab8edda5d 100644
--- a/SafetyCenter/Resources/shared_res/values-nl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Melding bekijken}other{Meldingen bekijken}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kan de pagina niet openen"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kan melding niet oplossen"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Kan instellingen niet vernieuwen"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kan instelling niet checken}other{Kan instellingen niet checken}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Werkprofiel is onderbroken"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nog geen informatie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-or/strings.xml b/SafetyCenter/Resources/shared_res/values-or/strings.xml
index cf550456f..341149120 100644
--- a/SafetyCenter/Resources/shared_res/values-or/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-or/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ଆଲର୍ଟ ଦେଖନ୍ତୁ}other{ଆଲର୍ଟଗୁଡ଼ିକ ଦେଖନ୍ତୁ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ପୃଷ୍ଠାକୁ ଖୋଲା ଯାଇପାରିଲା ନାହିଁ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ଆଲର୍ଟର ସମାଧାନ କରାଯାଇପାରିଲା ନାହିଁ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ସେଟିଂସ ରିଫ୍ରେସ କରାଯାଇପାରିଲା ନାହିଁ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ସେଟିଂ ଯାଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ}other{ସେଟିଂସ ଯାଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ୱାର୍କ ପ୍ରୋଫାଇଲକୁ ବିରତ କରାଯାଇଛି"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ସୂଚନା ନାହିଁ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pa/strings.xml b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
index e13d6327f..54aad5cbb 100644
--- a/SafetyCenter/Resources/shared_res/values-pa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ਅਲਰਟ ਦੇਖੋ}one{ਅਲਰਟ ਦੇਖੋ}other{ਅਲਰਟ ਦੇਖੋ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ਪੰਨਾ ਖੋਲ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ਸੁਚੇਤਨਾ ਦਾ ਹੱਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ਸੈਟਿੰਗਾਂ ਨੂੰ ਰਿਫ੍ਰੈਸ਼ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ਸੈਟਿੰਗ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}one{ਸੈਟਿੰਗ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}other{ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ਅਜੇ ਕੋਈ ਜਾਣਕਾਰੀ ਨਹੀਂ ਹੈ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pl/strings.xml b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
index 23a331b7f..89a242e7f 100644
--- a/SafetyCenter/Resources/shared_res/values-pl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobacz alert}few{Zobacz alerty}many{Zobacz alerty}other{Zobacz alerty}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nie udało się otworzyć strony"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nie udało się rozwiązać problemu z alertu"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nie udało się odświeżyć ustawień"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nie udało się sprawdzić ustawienia}few{Nie udało się sprawdzić ustawień}many{Nie udało się sprawdzić ustawień}other{Nie udało się sprawdzić ustawień}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Wstrzymano profil służbowy"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nie ma jeszcze informacji"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
index b9d887f78..0599f1c24 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostrar alerta}one{Mostrar alerta}many{Mostrar alertas}other{Mostrar alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as configurações"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a configuração}one{Não foi possível verificar a configuração}many{Não foi possível verificar as configurações}other{Não foi possível verificar as configurações}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de trabalho está pausado"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda não há informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
index 592e1b60a..835b8a14f 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Veja o alerta}many{Veja os alertas}other{Veja os alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as definições"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a definição}many{Não foi possível verificar as definições}other{Não foi possível verificar as definições}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Perfil de trabalho em pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda sem informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt/strings.xml b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
index b9d887f78..0599f1c24 100644
--- a/SafetyCenter/Resources/shared_res/values-pt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostrar alerta}one{Mostrar alerta}many{Mostrar alertas}other{Mostrar alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as configurações"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a configuração}one{Não foi possível verificar a configuração}many{Não foi possível verificar as configurações}other{Não foi possível verificar as configurações}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de trabalho está pausado"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda não há informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ro/strings.xml b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
index e64b8b497..8fa57dcb4 100644
--- a/SafetyCenter/Resources/shared_res/values-ro/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Vezi alerta}few{Vezi alertele}other{Vezi alertele}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Pagina nu s-a putut deschide"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nu s-a putut rezolva alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nu s-au putut actualiza setările"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nu s-a putut verifica setarea}few{Nu s-au putut verifica setările}other{Nu s-au putut verifica setările}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profilul de serviciu este întrerupt"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nu există informații încă"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ru/strings.xml b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
index 832dd396c..b72d9b0f0 100644
--- a/SafetyCenter/Resources/shared_res/values-ru/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Посмотрите оповещение}one{Посмотрите оповещения}few{Посмотрите оповещения}many{Посмотрите оповещения}other{Посмотрите оповещения}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не удалось открыть страницу."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не удалось устранить проблему."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не удалось обновить настройки"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не удалось проверить параметр}one{Не удалось проверить параметры}few{Не удалось проверить параметры}many{Не удалось проверить параметры}other{Не удалось проверить параметры}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Действие рабочего профиля приостановлено."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Данных пока нет"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-si/strings.xml b/SafetyCenter/Resources/shared_res/values-si/strings.xml
index 08e6787cc..1ee63199f 100644
--- a/SafetyCenter/Resources/shared_res/values-si/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-si/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ඇඟවීම බලන්න}one{ඇඟවීම් බලන්න}other{ඇඟවීම් බලන්න}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"පිටුව විවෘත කළ නොහැකි විය"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ඇඟවීම විසඳිය නොහැකි විය"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"සැකසීම් නැවුම් කිරීමට නොහැකි විය"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{සැකසීම පරීක්ෂා කිරීමට නොහැකි විය}one{සැකසීම් පරීක්ෂා කිරීමට නොහැකි විය}other{සැකසීම් පරීක්ෂා කිරීමට නොහැකි විය}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"කාර්යාල පැතිකඩ විරාම කර ඇත"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"තවම තතු නැත"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sk/strings.xml b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
index 0c6da9c5b..905c3ffe5 100644
--- a/SafetyCenter/Resources/shared_res/values-sk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobraziť upozornenie}few{Zobraziť upozornenia}many{See alerts}other{Zobraziť upozornenia}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Stránku sa nepodarilo otvoriť"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Upozornenie sa nepodarilo vyriešiť"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nastavenia sa nepodarilo obnoviť"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavenie sa nepodarilo skontrolovať}few{Nastavenia sa nepodarilo skontrolovať}many{Nastavenia sa nepodarilo skontrolovať}other{Nastavenia sa nepodarilo skontrolovať}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Pracovný profil je pozastavený"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Zatiaľ žiadne informácie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sl/strings.xml b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
index c8b0d7d00..4502b63fd 100644
--- a/SafetyCenter/Resources/shared_res/values-sl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ogled opozorila}one{Ogled opozoril}two{Ogled opozoril}few{Ogled opozoril}other{Ogled opozoril}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Strani ni bilo mogoče odpreti."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Opozorila ni bilo mogoče odpraviti."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nastavitev ni bilo mogoče osvežiti."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavitve ni bilo mogoče preveriti.}one{Nastavitve ni bilo mogoče preveriti.}two{Nastavitev ni bilo mogoče preveriti.}few{Nastavitev ni bilo mogoče preveriti.}other{Nastavitev ni bilo mogoče preveriti.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Delovni profil je začasno zaustavljen."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ni še nobenega podatka."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sq/strings.xml b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
index 68a9d3e1b..0e4981d2f 100644
--- a/SafetyCenter/Resources/shared_res/values-sq/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Shiko sinjalizimin}other{Shiko sinjalizimet}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Faqja nuk mund të hapej"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Sinjalizimi nuk mund të zgjidhej"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Cilësimet nuk mund të rifreskoheshin"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Cilësimi nuk mund të kontrollohej}other{Cilësimet nuk mund të kontrolloheshin}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profili i punës është në pauzë"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nuk ka ende informacione"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sr/strings.xml b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
index e258d138b..e7a5db675 100644
--- a/SafetyCenter/Resources/shared_res/values-sr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Прикажи обавештење}one{Прикажи обавештења}few{Прикажи обавештења}other{Прикажи обавештења}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Отварање странице није успело"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Решавање обавештења није успело"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Освежавање подешавања није успело"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Провера подешавања није успела}one{Провера подешавања није успела}few{Провера подешавања није успела}other{Провера подешавања није успела}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Пословни профил је паузиран"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Још нема информација"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sv/strings.xml b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
index a14fd7c8b..e7773a565 100644
--- a/SafetyCenter/Resources/shared_res/values-sv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se varning}other{Se varningar}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Det gick inte att öppna sidan"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Det gick inte att åtgärda varningen"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Det gick inte att uppdatera inställningarna"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Det gick inte att kontrollera inställningen}other{Det gick inte att kontrollera inställningarna}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Jobbprofilen är pausad"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ingen information än"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sw/strings.xml b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
index aaea33e79..b78abb739 100644
--- a/SafetyCenter/Resources/shared_res/values-sw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Angalia tahadhari}other{Angalia tahadhari}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Imeshindwa kufungua ukurasa"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Imeshindwa kutia alama kuwa arifa imeshughulikiwa"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Tumeshindwa kuonyesha upya mipangilio"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Imeshindwa kukagua mipangilio}other{Imeshindwa kukagua mipangilio}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Wasifu wa kazini umesimamishwa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Bado hakuna maelezo"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ta/strings.xml b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
index 05eb793e9..3acc13e92 100644
--- a/SafetyCenter/Resources/shared_res/values-ta/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{விழிப்பூட்டலைப் பாருங்கள்}other{விழிப்பூட்டல்களைப் பாருங்கள்}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"பக்கத்தைத் திறக்க முடியவில்லை"</string>
<string name="resolving_action_error" msgid="371968886143262375">"எச்சரிக்கையைத் தீர்க்க முடியவில்லை"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"அமைப்புகளைப் புதுப்பிக்க முடியவில்லை"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{அமைப்பைச் சரிபார்க்க முடியவில்லை}other{அமைப்புகளைச் சரிபார்க்க முடியவில்லை}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"பணிக் கணக்கு இடைநிறுத்தப்பட்டது"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"தகவல்கள் எதுவுமில்லை"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-te/strings.xml b/SafetyCenter/Resources/shared_res/values-te/strings.xml
index 1fc82228d..c9ecc0f23 100644
--- a/SafetyCenter/Resources/shared_res/values-te/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-te/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{అలర్ట్‌ను చూడండి}other{అలర్ట్‌లను చూడండి}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"పేజీని తెరవడం సాధ్యపడలేదు"</string>
<string name="resolving_action_error" msgid="371968886143262375">"అలర్ట్‌ను పరిష్కరించడం సాధ్యపడలేదు"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"సెట్టింగ్‌లను రిఫ్రెష్ చేయడం సాధ్యపడలేదు"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{సెట్టింగ్‌ను చెక్ చేయడం సాధ్యపడలేదు}other{సెట్టింగ్‌లను చెక్ చేయడం సాధ్యపడలేదు}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"వర్క్ ప్రొఫైల్ పాజ్ చేయబడింది"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ఇంకా ఏ సమాచారం లేదు"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-th/strings.xml b/SafetyCenter/Resources/shared_res/values-th/strings.xml
index 37f345214..f879c3ae4 100644
--- a/SafetyCenter/Resources/shared_res/values-th/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-th/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ดูการแจ้งเตือน}other{ดูการแจ้งเตือน}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"เปิดหน้าไม่ได้"</string>
<string name="resolving_action_error" msgid="371968886143262375">"แก้ไขการแจ้งเตือนไม่ได้"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"รีเฟรชการตั้งค่าไม่ได้"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ไม่สามารถตรวจสอบการตั้งค่า}other{ไม่สามารถตรวจสอบการตั้งค่า}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"โปรไฟล์งานหยุดชั่วคราว"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ยังไม่มีข้อมูล"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tl/strings.xml b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
index ee7210dc2..d2449116f 100644
--- a/SafetyCenter/Resources/shared_res/values-tl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Tingnan ang alerto}one{Tingnan ang mga alerto}other{Tingnan ang mga alerto}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Hindi mabuksan ang page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hindi ma-resolve ang alerto"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Hindi ma-refresh ang mga setting"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Hindi masuri ang setting}one{Hindi masuri ang mga setting}other{Hindi masuri ang mga setting}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Naka-pause ang profile sa trabaho"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Wala pang impormasyon"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tr/strings.xml b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
index 5a32bcfac..69bf874dd 100644
--- a/SafetyCenter/Resources/shared_res/values-tr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Uyarıya göz atın}other{Uyarılara göz atın}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sayfa açılamadı"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Uyarı sonlandırılamadı"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ayarlar yenilenemedi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayar kontrol edilemedi}other{Ayarlar kontrol edilemedi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"İş profili duraklatıldı"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Henüz bilgi yok"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uk/strings.xml b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
index f103d5a4f..44b304f80 100644
--- a/SafetyCenter/Resources/shared_res/values-uk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Перегляньте сповіщення}one{Перегляньте сповіщення}few{Перегляньте сповіщення}many{Перегляньте сповіщення}other{Перегляньте сповіщення}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не вдалося відкрити сторінку"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не вдалося закрити сповіщення"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не вдалось оновити налаштування"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не вдалося перевірити налаштування}one{Не вдалося перевірити налаштування}few{Не вдалося перевірити налаштування}many{Не вдалося перевірити налаштування}other{Не вдалося перевірити налаштування}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Робочий профіль призупинено"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Поки немає інформації"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ur/strings.xml b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
index df25d9ac2..f0d791f1e 100644
--- a/SafetyCenter/Resources/shared_res/values-ur/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{الرٹ دیکھیں}other{الرٹس دیکھیں}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"صفحہ نہیں کھل سکا"</string>
<string name="resolving_action_error" msgid="371968886143262375">"الرٹ حل نہیں ہو سکا"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ترتیبات ریفریش نہیں کی جا سکی"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ترتیب کی جانچ نہیں کی جا سکی}other{ترتیبات کی جانچ نہیں کی جا سکی}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"دفتری پروفائل روک دی گئی ہے"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ابھی تک کوئی معلومات نہیں ہے"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uz/strings.xml b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
index 5965fb760..a4b933eb9 100644
--- a/SafetyCenter/Resources/shared_res/values-uz/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ogohlantirish}other{Ogohlantirishlar}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sahifa ochilmadi"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ogohlantirish hal qilinmadi"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Sozlamalar yangilanmadi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Sozlama tekshirilmadi}other{Sozlamalar tekshirilmadi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Ish profili pauzada"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Hali axborot olinmadi"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-vi/strings.xml b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
index b697186e0..8bbffeb1b 100644
--- a/SafetyCenter/Resources/shared_res/values-vi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Xem cảnh báo}other{Xem cảnh báo}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Không thể mở trang"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Không thể giải quyết vấn đề cảnh báo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Không thể làm mới cài đặt"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Không kiểm tra được chế độ cài đặt}other{Không kiểm tra được các chế độ cài đặt}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Hồ sơ công việc của bạn đã bị tạm dừng"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Chưa có thông tin"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
index 1ed1355cf..401c82a5f 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看提醒}other{查看提醒}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"无法打开页面"</string>
<string name="resolving_action_error" msgid="371968886143262375">"无法解决提醒事项"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"无法刷新设置"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{无法检查设置}other{无法检查设置}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作资料已被暂停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"尚无任何信息"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
index 0222a0acc..a2f8f75a8 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看警示}other{查看警示}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"無法開啟頁面"</string>
<string name="resolving_action_error" msgid="371968886143262375">"無法解除警示"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"無法重新整理設定"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{無法檢查設定}other{無法檢查設定}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作設定檔已暫停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"暫時沒有資料"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
index 1be6fa621..beb5af4a2 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看警示}other{查看警示}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"無法開啟網頁"</string>
<string name="resolving_action_error" msgid="371968886143262375">"無法解決警示"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"無法重新整理設定"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{無法檢查設定}other{無法檢查設定}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作資料夾已暫停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"目前還沒有任何資訊"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zu/strings.xml b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
index 61096c234..21c358f15 100644
--- a/SafetyCenter/Resources/shared_res/values-zu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Bona isixwayiso}one{Bona izixwayiso}other{Bona izixwayiso}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ayikwazanga ukuvula ikhasi"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ayikwazanga ukuxazulula isexwayiso"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ayikwazanga ukuvuselela amasethingi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayikwazanga ukuhlola isethingi}one{Ayikwazanga ukuhlola amasethingi}other{Ayikwazanga ukuhlola amasethingi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Iphrofayela yomsebenzi iphunyuziwe"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Alukho ulwazi okwamanje"</string>
diff --git a/SafetyCenter/Resources/shared_res/values/strings.xml b/SafetyCenter/Resources/shared_res/values/strings.xml
index f4f1b0201..2b231b55f 100644
--- a/SafetyCenter/Resources/shared_res/values/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values/strings.xml
@@ -94,9 +94,6 @@
<!-- An error shown to the user when we failed to resolve an alert that they attempted to resolve directly from the Safety Center screen [CHAR LIMIT=50] -->
<string name="resolving_action_error">Couldn\’t resolve alert</string>
- <!-- An error shown to the user when we failed to refresh the overall Safety Center status. This happens when at least one safety signal did not get back to Safety Center within an arbitrary timeout [CHAR LIMIT=50] -->
- <string name="refresh_timeout">Couldn\’t refresh settings</string>
-
<!-- An error shown to the user when we failed to refresh the status of one or more Safety Center entries. The number of entries is used as a parameter to format the error [CHAR LIMIT=60] -->
<string name="refresh_error">{count, plural,
=1 {Couldn\’t check setting}
diff --git a/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesApk.java b/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesApk.java
new file mode 100644
index 000000000..fe0908ea0
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesApk.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2022 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.safetycenter.resources;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.util.Log;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * A class to access Safety Center resources that need to be fetched from a dedicated APK.
+ *
+ * <p>You must check whether Safety Center is enabled or the value returned by {@link #init()} prior
+ * to interacting with this class. Failure to do so may cause an {@link IllegalStateException} if
+ * the resources APK cannot be accessed.
+ *
+ * <p>This class isn't thread safe. Thread safety must be handled by the caller, or this may cause
+ * the resources APK {@link Context} to be initialized multiple times.
+ */
+public final class SafetyCenterResourcesApk {
+
+ private static final String TAG = "SafetyCenterResApk";
+
+ /** Intent action that is used to identify the Safety Center resources APK */
+ private static final String RESOURCES_APK_ACTION =
+ "com.android.safetycenter.intent.action.SAFETY_CENTER_RESOURCES_APK";
+
+ /** Permission APEX name */
+ private static final String APEX_MODULE_NAME = "com.android.permission";
+
+ /**
+ * The path where the Permission apex is mounted. Current value = "/apex/com.android.permission"
+ */
+ private static final String APEX_MODULE_PATH =
+ new File("/apex", APEX_MODULE_NAME).getAbsolutePath();
+
+ /** Raw XML config resource name */
+ private static final String CONFIG_NAME = "safety_center_config";
+
+ private final Context mContext;
+
+ /** Intent action that is used to identify the Safety Center resources APK */
+ private final String mResourcesApkAction;
+
+ /** The path where the Safety Center resources APK is expected to be installed */
+ private final String mResourcesApkPath;
+
+ /** Specific flags used for retrieving resolve info. */
+ private final int mFlags;
+
+ /**
+ * Whether we should fallback with an empty string / null values when calling the methods of
+ * this class for a resource that does not exist.
+ */
+ private final boolean mShouldFallbackIfNamedResourceNotFound;
+
+ // Cached context from the resources APK.
+ @Nullable private Context mResourcesApkContext;
+
+ public SafetyCenterResourcesApk(Context context) {
+ this(context, /* shouldFallbackIfNamedResourceNotFound */ true);
+ }
+
+ private SafetyCenterResourcesApk(
+ Context context, boolean shouldFallbackIfNamedResourceNotFound) {
+ this(
+ context,
+ RESOURCES_APK_ACTION,
+ APEX_MODULE_PATH,
+ PackageManager.MATCH_SYSTEM_ONLY,
+ shouldFallbackIfNamedResourceNotFound);
+ }
+
+ @VisibleForTesting
+ SafetyCenterResourcesApk(
+ Context context,
+ String resourcesApkAction,
+ String resourcesApkPath,
+ int flags,
+ boolean shouldFallbackIfNamedResourceNotFound) {
+ mContext = requireNonNull(context);
+ mResourcesApkAction = requireNonNull(resourcesApkAction);
+ mResourcesApkPath = requireNonNull(resourcesApkPath);
+ mFlags = flags;
+ mShouldFallbackIfNamedResourceNotFound = shouldFallbackIfNamedResourceNotFound;
+ }
+
+ /** Creates a new {@link SafetyCenterResourcesApk} for testing. */
+ @VisibleForTesting
+ public static SafetyCenterResourcesApk forTests(Context context) {
+ return new SafetyCenterResourcesApk(
+ context, /* shouldFallbackIfNamedResourceNotFound */ false);
+ }
+
+ /**
+ * Initializes the resources APK {@link Context}, and returns whether this was successful.
+ *
+ * <p>This call is optional as this can also be lazily instantiated. It can be used to ensure
+ * that the resources APK context is loaded prior to interacting with this class. This
+ * initialization code needs to run in the same user as the provided base {@link Context}. This
+ * may not be the case with a binder call, which is why it can be more appropriate to do this
+ * explicitly.
+ */
+ public boolean init() {
+ mResourcesApkContext = loadResourcesApkContext();
+ return mResourcesApkContext != null;
+ }
+
+ /**
+ * Returns the {@link Context} of the Safety Center resources APK.
+ *
+ * <p>Throws an {@link IllegalStateException} if the resources APK is not available
+ */
+ public Context getContext() {
+ if (mResourcesApkContext != null) {
+ return mResourcesApkContext;
+ }
+
+ mResourcesApkContext = loadResourcesApkContext();
+ if (mResourcesApkContext == null) {
+ throw new IllegalStateException("Resources APK context not found");
+ }
+
+ return mResourcesApkContext;
+ }
+
+ @Nullable
+ private Context loadResourcesApkContext() {
+ List<ResolveInfo> resolveInfos =
+ mContext.getPackageManager()
+ .queryIntentActivities(new Intent(mResourcesApkAction), mFlags);
+
+ if (resolveInfos.size() > 1) {
+ // multiple apps found, log a warning, but continue
+ Log.w(TAG, "Found > 1 APK that can resolve Safety Center resources APK intent:");
+ final int resolveInfosSize = resolveInfos.size();
+ for (int i = 0; i < resolveInfosSize; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ Log.w(
+ TAG,
+ String.format(
+ "- pkg:%s at:%s",
+ resolveInfo.activityInfo.applicationInfo.packageName,
+ resolveInfo.activityInfo.applicationInfo.sourceDir));
+ }
+ }
+
+ ResolveInfo info = null;
+ // Assume the first good ResolveInfo is the one we're looking for
+ final int resolveInfosSize = resolveInfos.size();
+ for (int i = 0; i < resolveInfosSize; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ if (!resolveInfo.activityInfo.applicationInfo.sourceDir.startsWith(mResourcesApkPath)) {
+ // skip apps that don't live in the Permission apex
+ continue;
+ }
+ info = resolveInfo;
+ break;
+ }
+
+ if (info == null) {
+ // Resource APK not loaded yet, print a stack trace to see where this is called from
+ Log.e(TAG, "Could not find Safety Center resources APK", new IllegalStateException());
+ return null;
+ }
+
+ String resourcesApkPkgName = info.activityInfo.applicationInfo.packageName;
+ Log.i(TAG, "Found Safety Center resources APK at: " + resourcesApkPkgName);
+ return getPackageContext(resourcesApkPkgName);
+ }
+
+ @Nullable
+ private Context getPackageContext(String packageName) {
+ try {
+ return mContext.createPackageContext(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to load package context for: " + packageName, e);
+ }
+ return null;
+ }
+
+ /** Calls {@link Context#getResources()} for the resources APK {@link Context}. */
+ public Resources getResources() {
+ return getContext().getResources();
+ }
+
+ /**
+ * Returns the raw XML resource representing the Safety Center configuration file from the
+ * Safety Center resources APK.
+ */
+ @Nullable
+ public InputStream getSafetyCenterConfig() {
+ return getSafetyCenterConfig(CONFIG_NAME);
+ }
+
+ @VisibleForTesting
+ @Nullable
+ InputStream getSafetyCenterConfig(String configName) {
+ int resId = getResIdAndMaybeThrowIfNull(configName, "raw");
+ if (resId == Resources.ID_NULL) {
+ return null;
+ }
+ return getResources().openRawResource(resId);
+ }
+
+ /** Calls {@link Context#getString(int)} for the resources APK {@link Context}. */
+ public String getString(@StringRes int stringId) {
+ return getContext().getString(stringId);
+ }
+
+ /** Same as {@link #getString(int)} but with the given {@code formatArgs}. */
+ public String getString(@StringRes int stringId, Object... formatArgs) {
+ return getContext().getString(stringId, formatArgs);
+ }
+
+ /**
+ * Returns the {@link String} with the given resource name.
+ *
+ * <p>If the {@link String} cannot be accessed, returns {@code ""} or throws {@link
+ * Resources.NotFoundException} depending on {@link #mShouldFallbackIfNamedResourceNotFound}.
+ */
+ public String getStringByName(String name) {
+ int resId = getResIdAndMaybeThrowIfNull(name, "string");
+ if (resId == Resources.ID_NULL) {
+ return "";
+ }
+ return getString(resId);
+ }
+
+ /** Same as {@link #getStringByName(String)} but with the given {@code formatArgs}. */
+ public String getStringByName(String name, Object... formatArgs) {
+ int resId = getResIdAndMaybeThrowIfNull(name, "string");
+ if (resId == Resources.ID_NULL) {
+ return "";
+ }
+ return getString(resId, formatArgs);
+ }
+
+ /**
+ * Returns an optional {@link String} resource with the given {@code stringId}.
+ *
+ * <p>Returns {@code null} if {@code stringId} is equal to {@link Resources#ID_NULL}. Otherwise,
+ * throws a {@link Resources.NotFoundException}.
+ */
+ @Nullable
+ public String getOptionalString(@StringRes int stringId) {
+ if (stringId == Resources.ID_NULL) {
+ return null;
+ }
+ return getString(stringId);
+ }
+
+ /** Same as {@link #getOptionalString(int)} but with the given resource name rather than ID. */
+ @Nullable
+ public String getOptionalStringByName(String name) {
+ return getOptionalString(getResId(name, "string"));
+ }
+
+ /**
+ * Returns the {@link Drawable} with the given resource name.
+ *
+ * <p>If the {@link Drawable} cannot be accessed, returns {@code null} or throws {@link
+ * Resources.NotFoundException} depending on {@link #mShouldFallbackIfNamedResourceNotFound}.
+ *
+ * @param theme the theme used to style the drawable attributes, may be {@code null}
+ */
+ @Nullable
+ public Drawable getDrawableByName(String name, @Nullable Resources.Theme theme) {
+ int resId = getResIdAndMaybeThrowIfNull(name, "drawable");
+ if (resId == Resources.ID_NULL) {
+ return null;
+ }
+ return getResources().getDrawable(resId, theme);
+ }
+
+ /**
+ * Returns an {@link Icon} containing the {@link Drawable} with the given resource name.
+ *
+ * <p>If the {@link Drawable} cannot be accessed, returns {@code null} or throws {@link
+ * Resources.NotFoundException} depending on {@link #mShouldFallbackIfNamedResourceNotFound}.
+ */
+ @Nullable
+ public Icon getIconByDrawableName(String name) {
+ int resId = getResIdAndMaybeThrowIfNull(name, "drawable");
+ if (resId == Resources.ID_NULL) {
+ return null;
+ }
+ return Icon.createWithResource(getContext().getPackageName(), resId);
+ }
+
+ /**
+ * Returns the {@link ColorInt} with the given resource name.
+ *
+ * <p>If the {@link ColorInt} cannot be accessed, returns {@code null} or throws {@link
+ * Resources.NotFoundException} depending on {@link #mShouldFallbackIfNamedResourceNotFound}.
+ */
+ @ColorInt
+ @Nullable
+ public Integer getColorByName(String name) {
+ int resId = getResIdAndMaybeThrowIfNull(name, "color");
+ if (resId == Resources.ID_NULL) {
+ return null;
+ }
+ return getResources().getColor(resId, getContext().getTheme());
+ }
+
+ private int getResIdAndMaybeThrowIfNull(String name, String type) {
+ int resId = getResId(name, type);
+ if (resId != Resources.ID_NULL) {
+ return resId;
+ }
+ if (!mShouldFallbackIfNamedResourceNotFound) {
+ throw new Resources.NotFoundException();
+ }
+ Log.w(TAG, "Named " + type + " resource: " + name + " not found");
+ return resId;
+ }
+
+ private int getResId(String name, String type) {
+ // TODO(b/227738283): profile the performance of this operation and consider adding caching
+ // or finding some alternative solution.
+ return getResources().getIdentifier(name, type, getContext().getPackageName());
+ }
+}
diff --git a/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java b/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java
deleted file mode 100644
index 9a77296e2..000000000
--- a/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2022 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.safetycenter.resources;
-
-import static java.util.Objects.requireNonNull;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.util.Log;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.annotation.VisibleForTesting;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Wrapper for context to override getResources method. Resources for the Safety Center that need to
- * be fetched from the dedicated resources APK.
- */
-public class SafetyCenterResourcesContext extends ContextWrapper {
- private static final String TAG = "SafetyCenterResContext";
-
- /** Intent action that is used to identify the Safety Center resources APK */
- private static final String RESOURCES_APK_ACTION =
- "com.android.safetycenter.intent.action.SAFETY_CENTER_RESOURCES_APK";
-
- /** Permission APEX name */
- private static final String APEX_MODULE_NAME = "com.android.permission";
-
- /**
- * The path where the Permission apex is mounted. Current value = "/apex/com.android.permission"
- */
- private static final String APEX_MODULE_PATH =
- new File("/apex", APEX_MODULE_NAME).getAbsolutePath();
-
- /** Raw XML config resource name */
- private static final String CONFIG_NAME = "safety_center_config";
-
- /** Intent action that is used to identify the Safety Center resources APK */
- private final String mResourcesApkAction;
-
- /** The path where the Safety Center resources APK is expected to be installed */
- @Nullable private final String mResourcesApkPath;
-
- /** Raw XML config resource name */
- private final String mConfigName;
-
- /** Specific flags used for retrieving resolve info */
- private final int mFlags;
-
- /**
- * Whether we should fallback with an empty string when calling {@link #getStringByName} for a
- * string resource that does not exist.
- */
- private final boolean mShouldFallbackIfNamedResourceNotFound;
-
- // Cached package name and resources from the resources APK
- @Nullable private String mResourcesApkPkgName;
- @Nullable private AssetManager mAssetsFromApk;
- @Nullable private Resources mResourcesFromApk;
- @Nullable private Resources.Theme mThemeFromApk;
-
- public SafetyCenterResourcesContext(Context contextBase) {
- this(contextBase, /* shouldFallbackIfNamedResourceNotFound */ true);
- }
-
- private SafetyCenterResourcesContext(
- Context contextBase, boolean shouldFallbackIfNamedResourceNotFound) {
- this(
- contextBase,
- RESOURCES_APK_ACTION,
- APEX_MODULE_PATH,
- CONFIG_NAME,
- PackageManager.MATCH_SYSTEM_ONLY,
- shouldFallbackIfNamedResourceNotFound);
- }
-
- @VisibleForTesting
- SafetyCenterResourcesContext(
- Context contextBase,
- String resourcesApkAction,
- @Nullable String resourcesApkPath,
- String configName,
- int flags,
- boolean shouldFallbackIfNamedResourceNotFound) {
- super(contextBase);
- mResourcesApkAction = requireNonNull(resourcesApkAction);
- mResourcesApkPath = resourcesApkPath;
- mConfigName = requireNonNull(configName);
- mFlags = flags;
- mShouldFallbackIfNamedResourceNotFound = shouldFallbackIfNamedResourceNotFound;
- }
-
- /** Creates a new {@link SafetyCenterResourcesContext} for testing. */
- @VisibleForTesting
- public static SafetyCenterResourcesContext forTests(Context contextBase) {
- return new SafetyCenterResourcesContext(
- contextBase, /* shouldFallbackIfNamedResourceNotFound */ false);
- }
-
- /**
- * Initializes the {@link Context}'s {@link AssetManager}, {@link Resources} and {@link
- * Resources.Theme}.
- *
- * <p>This call is optional as this can also be lazily instantiated. This is useful to ensure
- * that resources are loaded prior to interacting with the {@link SafetyCenterResourcesContext},
- * as this code needs to run for the same user as the provided base {@link Context}; which may
- * not be the case with a binder call.
- */
- public void init() {
- mAssetsFromApk = getAssets();
- mResourcesFromApk = getResources();
- mThemeFromApk = getTheme();
- }
-
- /** Get the package name of the Safety Center resources APK. */
- @VisibleForTesting
- @Nullable
- String getResourcesApkPkgName() {
- if (mResourcesApkPkgName != null) {
- return mResourcesApkPkgName;
- }
-
- List<ResolveInfo> resolveInfos =
- getPackageManager().queryIntentActivities(new Intent(mResourcesApkAction), mFlags);
-
- if (resolveInfos.size() > 1) {
- // multiple apps found, log a warning, but continue
- Log.w(TAG, "Found > 1 APK that can resolve Safety Center resources APK intent:");
- final int resolveInfosSize = resolveInfos.size();
- for (int i = 0; i < resolveInfosSize; i++) {
- ResolveInfo resolveInfo = resolveInfos.get(i);
- Log.w(
- TAG,
- String.format(
- "- pkg:%s at:%s",
- resolveInfo.activityInfo.applicationInfo.packageName,
- resolveInfo.activityInfo.applicationInfo.sourceDir));
- }
- }
-
- ResolveInfo info = null;
- // Assume the first good ResolveInfo is the one we're looking for
- final int resolveInfosSize = resolveInfos.size();
- for (int i = 0; i < resolveInfosSize; i++) {
- ResolveInfo resolveInfo = resolveInfos.get(i);
- if (mResourcesApkPath != null
- && !resolveInfo.activityInfo.applicationInfo.sourceDir.startsWith(
- mResourcesApkPath)) {
- // skip apps that don't live in the Permission apex
- continue;
- }
- info = resolveInfo;
- break;
- }
-
- if (info == null) {
- // Resource APK not loaded yet, print a stack trace to see where this is called from
- Log.e(
- TAG,
- "Attempted to fetch resources before Safety Center resources APK is loaded!",
- new IllegalStateException());
- return null;
- }
-
- mResourcesApkPkgName = info.activityInfo.applicationInfo.packageName;
- Log.i(TAG, "Found Safety Center resources APK at: " + mResourcesApkPkgName);
- return mResourcesApkPkgName;
- }
-
- /**
- * Gets the raw XML resource representing the Safety Center configuration from the Safety Center
- * resources APK.
- */
- @Nullable
- public InputStream getSafetyCenterConfig() {
- String resourcePkgName = getResourcesApkPkgName();
- if (resourcePkgName == null) {
- return null;
- }
- Resources resources = getResources();
- if (resources == null) {
- return null;
- }
- int id = resources.getIdentifier(mConfigName, "raw", resourcePkgName);
- if (id == Resources.ID_NULL) {
- return null;
- }
- return resources.openRawResource(id);
- }
-
- /**
- * Returns an optional {@link String} resource from the given {@code stringId}.
- *
- * <p>Returns {@code null} if {@code stringId} is equal to {@link Resources#ID_NULL}. Otherwise,
- * throws a {@link Resources.NotFoundException} if the resource cannot be accessed.
- */
- @Nullable
- public String getOptionalString(@StringRes int stringId) {
- if (stringId == Resources.ID_NULL) {
- return null;
- }
- return getString(stringId);
- }
-
- /** Same as {@link #getOptionalString(int)} but with the given {@code formatArgs}. */
- @Nullable
- public String getOptionalString(@StringRes int stringId, Object... formatArgs) {
- if (stringId == Resources.ID_NULL) {
- return null;
- }
- return getString(stringId, formatArgs);
- }
-
- /** Same as {@link #getOptionalString(int)} but using the string name rather than ID. */
- @Nullable
- public String getOptionalStringByName(String name) {
- return getOptionalString(getStringRes(name));
- }
-
- /**
- * Gets a string resource by name from the Safety Center resources APK, and returns an empty
- * string if the resource does not exist (or throws a {@link Resources.NotFoundException} if
- * {@link #mShouldFallbackIfNamedResourceNotFound} is {@code false}).
- */
- public String getStringByName(String name) {
- int id = getStringRes(name);
- return maybeFallbackIfNamedResourceIsNull(name, getOptionalString(id));
- }
-
- /** Same as {@link #getStringByName(String)} but with the given {@code formatArgs}. */
- public String getStringByName(String name, Object... formatArgs) {
- int id = getStringRes(name);
- return maybeFallbackIfNamedResourceIsNull(name, getOptionalString(id, formatArgs));
- }
-
- private String maybeFallbackIfNamedResourceIsNull(String name, @Nullable String value) {
- if (value != null) {
- return value;
- }
- if (!mShouldFallbackIfNamedResourceNotFound) {
- throw new Resources.NotFoundException();
- }
- Log.w(TAG, "String resource " + name + " not found");
- return "";
- }
-
- @StringRes
- private int getStringRes(String name) {
- return getResId(name, "string");
- }
-
- private int getResId(String name, String type) {
- String resourcePkgName = getResourcesApkPkgName();
- if (resourcePkgName == null) {
- return Resources.ID_NULL;
- }
- Resources resources = getResources();
- if (resources == null) {
- return Resources.ID_NULL;
- }
- // TODO(b/227738283): profile the performance of this operation and consider adding caching
- // or finding some alternative solution.
- return resources.getIdentifier(name, type, resourcePkgName);
- }
-
- @Nullable
- private Context getResourcesApkContext() {
- String name = getResourcesApkPkgName();
- if (name == null) {
- return null;
- }
- try {
- return createPackageContext(name, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.wtf(TAG, "Failed to load resources", e);
- }
- return null;
- }
-
- /** Retrieve assets held in the Safety Center resources APK. */
- @Override
- @Nullable
- public AssetManager getAssets() {
- if (mAssetsFromApk == null) {
- Context resourcesApkContext = getResourcesApkContext();
- if (resourcesApkContext != null) {
- mAssetsFromApk = resourcesApkContext.getAssets();
- }
- }
- return mAssetsFromApk;
- }
-
- /** Retrieve resources held in the Safety Center resources APK. */
- @Override
- @Nullable
- public Resources getResources() {
- if (mResourcesFromApk == null) {
- Context resourcesApkContext = getResourcesApkContext();
- if (resourcesApkContext != null) {
- mResourcesFromApk = resourcesApkContext.getResources();
- }
- }
- return mResourcesFromApk;
- }
-
- /** Retrieve theme held in the Safety Center resources APK. */
- @Override
- @Nullable
- public Resources.Theme getTheme() {
- if (mThemeFromApk == null) {
- Context resourcesApkContext = getResourcesApkContext();
- if (resourcesApkContext != null) {
- mThemeFromApk = resourcesApkContext.getTheme();
- }
- }
- return mThemeFromApk;
- }
-
- /**
- * Gets a drawable resource by name from the Safety Center resources APK. Returns a null
- * drawable if the resource does not exist (or throws a {@link Resources.NotFoundException} if
- * {@link #mShouldFallbackIfNamedResourceNotFound} is {@code false}).
- *
- * @param name the identifier for this drawable resource
- * @param theme the theme used to style the drawable attributes, may be {@code null}
- */
- @Nullable
- public Drawable getDrawableByName(String name, @Nullable Resources.Theme theme) {
- int resId = getResId(name, "drawable");
- if (resId != Resources.ID_NULL) {
- return getResources().getDrawable(resId, theme);
- }
-
- if (!mShouldFallbackIfNamedResourceNotFound) {
- throw new Resources.NotFoundException();
- }
-
- Log.w(TAG, "Drawable resource " + name + " not found");
- return null;
- }
-
- /**
- * Returns an {@link Icon} instance containing a drawable with the given name. If no such
- * drawable exists, returns {@code null} or throws {@link Resources.NotFoundException}.
- */
- @Nullable
- public Icon getIconByDrawableName(String drawableResName) {
- int resId = getResId(drawableResName, "drawable");
- if (resId != Resources.ID_NULL) {
- return Icon.createWithResource(getResourcesApkPkgName(), resId);
- }
-
- if (!mShouldFallbackIfNamedResourceNotFound) {
- throw new Resources.NotFoundException();
- }
-
- Log.w(TAG, "Drawable resource " + drawableResName + " not found");
- return null;
- }
-
- /** Gets a color by resource name */
- @ColorInt
- @Nullable
- public Integer getColorByName(String name) {
- int resId = getResId(name, "color");
- if (resId != Resources.ID_NULL) {
- return getResources().getColor(resId, getTheme());
- }
-
- if (!mShouldFallbackIfNamedResourceNotFound) {
- throw new Resources.NotFoundException();
- }
-
- Log.w(TAG, "Color resource " + name + " not found");
- return null;
- }
-}
diff --git a/SafetyCenter/ResourcesLib/tests/AndroidTest.xml b/SafetyCenter/ResourcesLib/tests/AndroidTest.xml
index 6dcfb0362..e15ecf22a 100644
--- a/SafetyCenter/ResourcesLib/tests/AndroidTest.xml
+++ b/SafetyCenter/ResourcesLib/tests/AndroidTest.xml
@@ -19,7 +19,7 @@
<configuration description="Runs unit tests for the Safety Center ResourcesLib library.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
- <option name="test-tag" value="SafetyCenterConfigTests" />
+ <option name="test-tag" value="SafetyCenterResourcesLibTests" />
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
<option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
diff --git a/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/safety_center_config.txt b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/safety_center_config.txt
new file mode 100644
index 000000000..3b1246497
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/safety_center_config.txt
@@ -0,0 +1 @@
+TEST \ No newline at end of file
diff --git a/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesApkTest.kt b/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesApkTest.kt
new file mode 100644
index 000000000..b52595b13
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesApkTest.kt
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2022 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.safetycenter.resources
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterResourcesApkTest {
+ private val context: Context = getApplicationContext()
+
+ @Test
+ fun init_withValidInputs_returnsTrue() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val initialized = resourcesApk.init()
+
+ assertThat(initialized).isTrue()
+ }
+
+ @Test
+ fun init_withWrongAction_returnsFalse() {
+ val resourcesApk = newSafetyCenterResourcesApk(resourcesApkAction = "wrong")
+
+ val initialized = resourcesApk.init()
+
+ assertThat(initialized).isFalse()
+ }
+
+ @Test
+ fun init_withWrongPath_returnsFalse() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkPath = "/apex/com.android.permission")
+
+ val initialized = resourcesApk.init()
+
+ assertThat(initialized).isFalse()
+ }
+
+ @Test
+ fun init_withWrongFlags_returnsFalse() {
+ val resourcesApk = newSafetyCenterResourcesApk(flags = PackageManager.MATCH_SYSTEM_ONLY)
+
+ val initialized = resourcesApk.init()
+
+ assertThat(initialized).isFalse()
+ }
+
+ @Test
+ fun getContext_withValidInputs_returnsResourcesApkContext() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val resourcesApkContext = resourcesApk.context
+
+ assertThat(resourcesApkContext.packageName).isEqualTo(RESOURCES_APK_PKG_NAME)
+ }
+
+ @Test
+ fun getContext_withWrongAction_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(resourcesApkAction = "wrong")
+
+ assertFailsWith(IllegalStateException::class) { resourcesApk.context }
+ }
+
+ @Test
+ fun getContext_withWrongPath_throws() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkPath = "/apex/com.android.permission")
+
+ assertFailsWith(IllegalStateException::class) { resourcesApk.context }
+ }
+
+ @Test
+ fun getContext_withWrongFlags_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(flags = PackageManager.MATCH_SYSTEM_ONLY)
+
+ assertFailsWith(IllegalStateException::class) { resourcesApk.context }
+ }
+
+ @Test
+ fun getResources_withValidInputs_returnsResourcesApkContextResources() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val resources = resourcesApk.resources
+
+ assertThat(resources).isEqualTo(resourcesApk.context.resources)
+ }
+
+ @Test
+ fun getResources_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) { resourcesApk.resources }
+ }
+
+ @Test
+ fun getSafetyCenterConfig_withValidInputs_returnsConfigContent() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val config = resourcesApk.safetyCenterConfig
+ val configContent = config?.bufferedReader().use { it?.readText() }
+
+ assertThat(config).isNotNull()
+ assertThat(configContent).isEqualTo(CONFIG_CONTENT)
+ }
+
+ @Test
+ fun getSafetyCenterConfig_anotherValidConfigName_returnsConfigContent() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val config = resourcesApk.getSafetyCenterConfig(CONFIG_NAME)
+ val configContent = config?.bufferedReader().use { it?.readText() }
+
+ assertThat(config).isNotNull()
+ assertThat(configContent).isEqualTo(CONFIG_CONTENT)
+ }
+
+ @Test
+ fun getSafetyCenterConfig_invalidConfigNameWithFallback_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getSafetyCenterConfig("wrong")).isNull()
+ }
+
+ @Test
+ fun getSafetyCenterConfig_invalidConfigNameWithoutFallback_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertFailsWith(Resources.NotFoundException::class) {
+ resourcesApk.getSafetyCenterConfig("wrong")
+ }
+ }
+
+ @Test
+ fun getSafetyCenterConfig_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) { resourcesApk.safetyCenterConfig }
+ }
+
+ @Test
+ fun getString_validString_returnsString() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val ok = resourcesApk.getString(android.R.string.ok)
+
+ assertThat(ok).isEqualTo("OK")
+ }
+
+ @Test
+ fun getString_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getString(android.R.string.ok)
+ }
+ }
+
+ @Test
+ fun getStringWithFormatArgs_validString_returnsString() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val ok = resourcesApk.getString(android.R.string.ok, "")
+
+ assertThat(ok).isEqualTo("OK")
+ }
+
+ @Test
+ fun getStringWithFormatArgs_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getString(android.R.string.ok, "")
+ }
+ }
+
+ @Test
+ fun getStringByName_validString_returnsString() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ assertThat(resourcesApk.getStringByName("valid_string")).isEqualTo("I exist!")
+ }
+
+ @Test
+ fun getStringByName_invalidStringWithFallback_returnsEmptyString() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getStringByName("invalid_string")).isEqualTo("")
+ }
+
+ @Test
+ fun getStringByName_invalidStringWithoutFallback_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertFailsWith(Resources.NotFoundException::class) {
+ resourcesApk.getStringByName("invalid_string")
+ }
+ }
+
+ @Test
+ fun getStringByName_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getStringByName("valid_string")
+ }
+ }
+
+ @Test
+ fun getStringByNameWithFormatArgs_validString_returnsString() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ assertThat(resourcesApk.getStringByName("valid_string", "")).isEqualTo("I exist!")
+ }
+
+ @Test
+ fun getStringByNameWithFormatArgs_invalidStringWithFallback_returnsEmptyString() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getStringByName("invalid_string", "")).isEqualTo("")
+ }
+
+ @Test
+ fun getStringByNameWithFormatArgs_invalidStringWithoutFallback_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertFailsWith(Resources.NotFoundException::class) {
+ resourcesApk.getStringByName("invalid_string", "")
+ }
+ }
+
+ @Test
+ fun getStringByNameWithFormatArgs_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getStringByName("valid_string", "")
+ }
+ }
+
+ @Test
+ fun getOptionalString_validString_returnsString() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val ok = resourcesApk.getOptionalString(android.R.string.ok)
+
+ assertThat(ok).isEqualTo("OK")
+ }
+
+ @Test
+ fun getOptionalString_resourceIdNull_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ val string = resourcesApk.getOptionalString(Resources.ID_NULL)
+
+ assertThat(string).isNull()
+ }
+
+ @Test
+ fun getOptionalString_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getOptionalString(android.R.string.ok)
+ }
+ }
+
+ @Test
+ fun getOptionalStringByName_validString_returnsString() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ assertThat(resourcesApk.getOptionalStringByName("valid_string")).isEqualTo("I exist!")
+ }
+
+ @Test
+ fun getOptionalStringByName_invalidStringWithFallback_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getOptionalStringByName("invalid_string")).isNull()
+ }
+
+ @Test
+ fun getOptionalStringByName_invalidStringWithoutFallback_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertThat(resourcesApk.getOptionalStringByName("invalid_string")).isNull()
+ }
+
+ @Test
+ fun getOptionalStringByName_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getOptionalStringByName("valid_string")
+ }
+ }
+
+ @Test
+ fun getDrawableByName_validDrawable_returnsDrawable() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ assertThat(resourcesApk.getDrawableByName("valid_drawable", context.theme)).isNotNull()
+ }
+
+ @Test
+ fun getDrawableByName_invalidDrawableWithFallback_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getDrawableByName("invalid_drawable", context.theme)).isNull()
+ }
+
+ @Test
+ fun getDrawableByName_invalidDrawableWithoutFallback_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertFailsWith(Resources.NotFoundException::class) {
+ resourcesApk.getDrawableByName("invalid_drawable", context.theme)
+ }
+ }
+
+ @Test
+ fun getDrawableByName_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getDrawableByName("valid_drawable", context.theme)
+ }
+ }
+
+ @Test
+ fun getIconByDrawableName_validDrawable_returnsIcon() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ assertThat(resourcesApk.getIconByDrawableName("valid_drawable")).isNotNull()
+ }
+
+ @Test
+ fun getIconByDrawableName_invalidDrawableWithFallback_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getIconByDrawableName("invalid_drawable")).isNull()
+ }
+
+ @Test
+ fun getIconByDrawableName_invalidDrawableWithoutFallback_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertFailsWith(Resources.NotFoundException::class) {
+ resourcesApk.getIconByDrawableName("invalid_drawable")
+ }
+ }
+
+ @Test
+ fun getIconByDrawableByName_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) {
+ resourcesApk.getIconByDrawableName("valid_drawable")
+ }
+ }
+
+ @Test
+ fun getColorByName_validColor_returnsColor() {
+ val resourcesApk = newSafetyCenterResourcesApk()
+
+ assertThat(resourcesApk.getColorByName("valid_color")).isNotNull()
+ }
+
+ @Test
+ fun getColorByName_invalidColorWithFallback_returnsNull() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = true)
+
+ assertThat(resourcesApk.getColorByName("invalid_color")).isNull()
+ }
+
+ @Test
+ fun getColorByName_invalidColorWithoutFallback_throws() {
+ val resourcesApk = newSafetyCenterResourcesApk(fallback = false)
+
+ assertFailsWith(Resources.NotFoundException::class) {
+ resourcesApk.getColorByName("invalid_color")
+ }
+ }
+
+ @Test
+ fun getColorByName_nullContext_throwsRegardlessOfFallback() {
+ val resourcesApk =
+ newSafetyCenterResourcesApk(resourcesApkAction = "wrong", fallback = true)
+
+ assertFailsWith(IllegalStateException::class) { resourcesApk.getColorByName("valid_color") }
+ }
+
+ private fun newSafetyCenterResourcesApk(
+ resourcesApkAction: String = RESOURCES_APK_ACTION,
+ resourcesApkPath: String = "",
+ flags: Int = 0,
+ fallback: Boolean = false
+ ) = SafetyCenterResourcesApk(context, resourcesApkAction, resourcesApkPath, flags, fallback)
+
+ companion object {
+ const val RESOURCES_APK_ACTION =
+ "com.android.safetycenter.tests.intent.action.SAFETY_CENTER_TEST_RESOURCES_APK"
+ const val RESOURCES_APK_PKG_NAME =
+ "com.android.safetycenter.tests.config.safetycenterresourceslibtestresources"
+ const val CONFIG_NAME = "test"
+ const val CONFIG_CONTENT = "TEST"
+ }
+}
diff --git a/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt b/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt
deleted file mode 100644
index 1a82460d2..000000000
--- a/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2022 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.safetycenter.resources
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.content.res.Resources
-import androidx.test.core.app.ApplicationProvider.getApplicationContext
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertFailsWith
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SafetyCenterResourcesContextTest {
- private val context: Context = getApplicationContext()
- private val theme: Resources.Theme? = context.theme
-
- @Test
- fun validDataWithValidInputs() {
- val resourcesContext =
- SafetyCenterResourcesContext(context, RESOURCES_APK_ACTION, null, CONFIG_NAME, 0, false)
-
- assertThat(resourcesContext.resourcesApkPkgName).isEqualTo(RESOURCES_APK_PKG_NAME)
-
- val configContent =
- resourcesContext.safetyCenterConfig?.bufferedReader().use { it?.readText() }
-
- assertThat(configContent).isEqualTo(CONFIG_CONTENT)
- assertThat(resourcesContext.assets).isNotNull()
- assertThat(resourcesContext.resources).isNotNull()
- assertThat(resourcesContext.theme).isNotNull()
- }
-
- @Test
- fun nullDataWithWrongAction() {
- val resourcesContext = createNewResourcesContext(resourcesApkAction = "wrong")
-
- assertThat(resourcesContext.resourcesApkPkgName).isNull()
- assertThat(resourcesContext.safetyCenterConfig).isNull()
- assertThat(resourcesContext.assets).isNull()
- assertThat(resourcesContext.resources).isNull()
- assertThat(resourcesContext.theme).isNull()
- }
-
- @Test
- fun nullDataWithWrongPath() {
- val resourcesContext =
- createNewResourcesContext(resourcesApkPath = "/apex/com.android.permission")
-
- assertThat(resourcesContext.resourcesApkPkgName).isNull()
- assertThat(resourcesContext.safetyCenterConfig).isNull()
- assertThat(resourcesContext.assets).isNull()
- assertThat(resourcesContext.resources).isNull()
- assertThat(resourcesContext.theme).isNull()
- }
-
- @Test
- fun nullDataWithWrongFlag() {
- val resourcesContext = createNewResourcesContext(flags = PackageManager.MATCH_SYSTEM_ONLY)
-
- assertThat(resourcesContext.resourcesApkPkgName).isNull()
- assertThat(resourcesContext.safetyCenterConfig).isNull()
- assertThat(resourcesContext.assets).isNull()
- assertThat(resourcesContext.resources).isNull()
- assertThat(resourcesContext.theme).isNull()
- }
-
- @Test
- fun nullConfigWithWrongConfigName() {
- val resourcesContext = createNewResourcesContext(configName = "wrong")
-
- assertThat(resourcesContext.resourcesApkPkgName).isNotNull()
- assertThat(resourcesContext.safetyCenterConfig).isNull()
- assertThat(resourcesContext.assets).isNotNull()
- assertThat(resourcesContext.resources).isNotNull()
- assertThat(resourcesContext.theme).isNotNull()
- }
-
- @Test
- fun getStringByName_validString_returnsString() {
- val resourcesContext = createNewResourcesContext()
-
- assertThat(resourcesContext.getStringByName("valid_string")).isEqualTo("I exist!")
- }
-
- @Test
- fun getStringByName_invalidStringWithFallback_returnsEmptyString() {
- val resourcesContext = createNewResourcesContext(fallback = true)
-
- assertThat(resourcesContext.getStringByName("invalid_string")).isEqualTo("")
- }
-
- @Test
- fun getStringByName_invalidStringWithoutFallback_throws() {
- val resourcesContext = createNewResourcesContext(fallback = false)
-
- assertFailsWith(Resources.NotFoundException::class) {
- resourcesContext.getStringByName("invalid_string")
- }
- }
-
- @Test
- fun getOptionalStringByName_validString_returnsString() {
- val resourcesContext = createNewResourcesContext()
-
- assertThat(resourcesContext.getOptionalStringByName("valid_string")).isEqualTo("I exist!")
- }
-
- @Test
- fun getOptionalStringByName_invalidStringWithFallback_returnsNull() {
- val resourcesContext = createNewResourcesContext(fallback = true)
-
- assertThat(resourcesContext.getOptionalStringByName("invalid_string")).isNull()
- }
-
- @Test
- fun getOptionalStringByName_invalidStringWithoutFallback_returnsNull() {
- val resourcesContext = createNewResourcesContext(fallback = false)
-
- assertThat(resourcesContext.getOptionalStringByName("invalid_string")).isNull()
- }
-
- @Test
- fun getDrawableByName_validDrawable_returnsDrawable() {
- val resourcesContext = createNewResourcesContext()
-
- assertThat(resourcesContext.getDrawableByName("valid_drawable", theme)).isNotNull()
- }
-
- @Test
- fun getDrawableByName_invalidDrawableWithFallback_returnsNull() {
- val resourcesContext = createNewResourcesContext(fallback = true)
-
- assertThat(resourcesContext.getDrawableByName("invalid_drawable", theme)).isNull()
- }
-
- @Test
- fun getDrawableByName_invalidDrawableWithoutFallback_throws() {
- val resourcesContext = createNewResourcesContext(fallback = false)
-
- assertFailsWith(Resources.NotFoundException::class) {
- resourcesContext.getDrawableByName("invalid_drawable", theme)
- }
- }
-
- @Test
- fun getIconByDrawableName_validDrawable_returnsIcon() {
- val resourcesContext = createNewResourcesContext()
-
- assertThat(resourcesContext.getIconByDrawableName("valid_drawable")).isNotNull()
- }
-
- @Test
- fun getIconByDrawableName_invalidDrawableWithFallback_returnsNull() {
- val resourcesContext = createNewResourcesContext(fallback = true)
-
- assertThat(resourcesContext.getIconByDrawableName("invalid_drawable")).isNull()
- }
-
- @Test
- fun getIconByDrawableName_invalidDrawableWithoutFallback_throws() {
- val resourcesContext = createNewResourcesContext(fallback = false)
-
- assertFailsWith(Resources.NotFoundException::class) {
- resourcesContext.getIconByDrawableName("invalid_drawable")
- }
- }
-
- @Test
- fun getColorByName_validColor_returnsColor() {
- val resourcesContext = createNewResourcesContext()
-
- assertThat(resourcesContext.getColorByName("valid_color")).isNotNull()
- }
-
- @Test
- fun getColorByName_invalidColorWithFallback_returnsNull() {
- val resourcesContext = createNewResourcesContext(fallback = true)
-
- assertThat(resourcesContext.getColorByName("invalid_color")).isNull()
- }
-
- @Test
- fun getColorByName_invalidColorWithoutFallback_throws() {
- val resourcesContext = createNewResourcesContext(fallback = false)
-
- assertFailsWith(Resources.NotFoundException::class) {
- resourcesContext.getColorByName("invalid_color")
- }
- }
-
- private fun createNewResourcesContext(
- resourcesApkAction: String = RESOURCES_APK_ACTION,
- resourcesApkPath: String? = null,
- configName: String = CONFIG_NAME,
- flags: Int = 0,
- fallback: Boolean = false
- ) =
- SafetyCenterResourcesContext(
- context, resourcesApkAction, resourcesApkPath, configName, flags, fallback)
-
- companion object {
- const val RESOURCES_APK_ACTION =
- "com.android.safetycenter.tests.intent.action.SAFETY_CENTER_TEST_RESOURCES_APK"
- const val RESOURCES_APK_PKG_NAME =
- "com.android.safetycenter.tests.config.safetycenterresourceslibtestresources"
- const val CONFIG_NAME = "test"
- const val CONFIG_CONTENT = "TEST"
- }
-}
diff --git a/SafetyCenter/TEST_MAPPING b/SafetyCenter/TEST_MAPPING
index 5bc6abbc7..92397fe71 100644
--- a/SafetyCenter/TEST_MAPPING
+++ b/SafetyCenter/TEST_MAPPING
@@ -10,7 +10,13 @@
"path": "packages/modules/Permission/tests/functional/safetycenter/safetycenteractivity/"
},
{
+ "path": "packages/modules/Permission/tests/functional/safetycenter/subpages/"
+ },
+ {
"path": "packages/modules/Permission/tests/functional/safetycenter/singleuser/"
+ },
+ {
+ "path": "packages/modules/Permission/tests/hostside/safetycenter/"
}
]
}
diff --git a/framework-s/java/android/safetycenter/SafetyCenterData.java b/framework-s/java/android/safetycenter/SafetyCenterData.java
index 720fb4997..7cae061a6 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterData.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterData.java
@@ -176,7 +176,8 @@ public final class SafetyCenterData implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public List<SafetyCenterIssue> getDismissedIssues() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mDismissedIssues;
}
@@ -191,7 +192,8 @@ public final class SafetyCenterData implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Bundle getExtras() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mExtras;
}
@@ -371,7 +373,8 @@ public final class SafetyCenterData implements Parcelable {
public Builder(@NonNull SafetyCenterStatus status) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mStatus = requireNonNull(status);
}
@@ -379,7 +382,8 @@ public final class SafetyCenterData implements Parcelable {
/** Creates a {@link Builder} with the values from the given {@link SafetyCenterData}. */
public Builder(@NonNull SafetyCenterData safetyCenterData) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetyCenterData);
mStatus = safetyCenterData.mStatus;
diff --git a/framework-s/java/android/safetycenter/SafetyCenterIssue.java b/framework-s/java/android/safetycenter/SafetyCenterIssue.java
index cee872a3a..f10b56b83 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterIssue.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterIssue.java
@@ -190,7 +190,7 @@ public final class SafetyCenterIssue implements Parcelable {
public CharSequence getAttributionTitle() {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException(
- "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mAttributionTitle;
}
@@ -237,7 +237,7 @@ public final class SafetyCenterIssue implements Parcelable {
public String getGroupId() {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException(
- "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mGroupId;
}
@@ -404,7 +404,7 @@ public final class SafetyCenterIssue implements Parcelable {
public Builder setAttributionTitle(@Nullable CharSequence attributionTitle) {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException(
- "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mAttributionTitle = attributionTitle;
return this;
@@ -461,7 +461,7 @@ public final class SafetyCenterIssue implements Parcelable {
public Builder setGroupId(@Nullable String groupId) {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException(
- "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mGroupId = groupId;
return this;
@@ -599,7 +599,8 @@ public final class SafetyCenterIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public ConfirmationDialogDetails getConfirmationDialogDetails() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mConfirmationDialogDetails;
}
@@ -811,7 +812,8 @@ public final class SafetyCenterIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull Action action) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(action);
mId = action.mId;
@@ -890,7 +892,8 @@ public final class SafetyCenterIssue implements Parcelable {
public Builder setConfirmationDialogDetails(
@Nullable ConfirmationDialogDetails confirmationDialogDetails) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mConfirmationDialogDetails = confirmationDialogDetails;
return this;
diff --git a/framework-s/java/android/safetycenter/SafetyCenterManager.java b/framework-s/java/android/safetycenter/SafetyCenterManager.java
index bb67f578f..35d09cce3 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterManager.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterManager.java
@@ -471,7 +471,7 @@ public final class SafetyCenterManager {
@RefreshReason int refreshReason, @NonNull List<String> safetySourceIds) {
if (!SdkLevel.isAtLeastU()) {
throw new UnsupportedOperationException(
- "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetySourceIds, "safetySourceIds cannot be null");
diff --git a/framework-s/java/android/safetycenter/SafetyEvent.java b/framework-s/java/android/safetycenter/SafetyEvent.java
index 72e8defaa..2a0d07032 100644
--- a/framework-s/java/android/safetycenter/SafetyEvent.java
+++ b/framework-s/java/android/safetycenter/SafetyEvent.java
@@ -240,7 +240,8 @@ public final class SafetyEvent implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetyEvent safetyEvent) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetyEvent);
mType = safetyEvent.mType;
diff --git a/framework-s/java/android/safetycenter/SafetySourceData.java b/framework-s/java/android/safetycenter/SafetySourceData.java
index 2e80621a2..04846dd75 100644
--- a/framework-s/java/android/safetycenter/SafetySourceData.java
+++ b/framework-s/java/android/safetycenter/SafetySourceData.java
@@ -216,7 +216,8 @@ public final class SafetySourceData implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Bundle getExtras() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mExtras;
}
@@ -274,7 +275,8 @@ public final class SafetySourceData implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetySourceData safetySourceData) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetySourceData);
mIssues.addAll(safetySourceData.mIssues);
@@ -299,13 +301,14 @@ public final class SafetySourceData implements Parcelable {
/**
* Sets additional information for the {@link SafetySourceData}.
*
- * If not set, the default value is {@link Bundle#EMPTY}.
+ * <p>If not set, the default value is {@link Bundle#EMPTY}.
*/
@NonNull
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setExtras(@NonNull Bundle extras) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mExtras = requireNonNull(extras);
return this;
@@ -319,7 +322,8 @@ public final class SafetySourceData implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder clearExtras() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mExtras = Bundle.EMPTY;
return this;
diff --git a/framework-s/java/android/safetycenter/SafetySourceIssue.java b/framework-s/java/android/safetycenter/SafetySourceIssue.java
index 985131764..de75aa298 100644
--- a/framework-s/java/android/safetycenter/SafetySourceIssue.java
+++ b/framework-s/java/android/safetycenter/SafetySourceIssue.java
@@ -337,7 +337,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public CharSequence getAttributionTitle() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mAttributionTitle;
}
@@ -418,7 +419,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Notification getCustomNotification() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mCustomNotification;
}
@@ -449,7 +451,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public int getNotificationBehavior() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mNotificationBehavior;
}
@@ -482,7 +485,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public String getDeduplicationId() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mDeduplicationId;
}
@@ -505,7 +509,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public int getIssueActionability() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mIssueActionability;
}
@@ -739,7 +744,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public ConfirmationDialogDetails getConfirmationDialogDetails() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mConfirmationDialogDetails;
}
@@ -938,7 +944,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull Action action) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(action);
mId = action.mId;
@@ -984,7 +991,8 @@ public final class SafetySourceIssue implements Parcelable {
public Builder setConfirmationDialogDetails(
@Nullable ConfirmationDialogDetails confirmationDialogDetails) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mConfirmationDialogDetails = confirmationDialogDetails;
return this;
@@ -1229,7 +1237,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetySourceIssue safetySourceIssue) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetySourceIssue);
mId = safetySourceIssue.mId;
@@ -1266,7 +1275,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public Builder setAttributionTitle(@Nullable CharSequence attributionTitle) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mAttributionTitle = attributionTitle;
return this;
@@ -1335,7 +1345,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setCustomNotification(@Nullable Notification customNotification) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mCustomNotification = customNotification;
return this;
@@ -1355,7 +1366,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setNotificationBehavior(@NotificationBehavior int notificationBehavior) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mNotificationBehavior = validateNotificationBehavior(notificationBehavior);
return this;
@@ -1370,7 +1382,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setDeduplicationId(@Nullable String deduplicationId) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mDeduplicationId = deduplicationId;
return this;
@@ -1388,7 +1401,8 @@ public final class SafetySourceIssue implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setIssueActionability(@IssueActionability int issueActionability) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mIssueActionability = validateIssueActionability(issueActionability);
return this;
diff --git a/framework-s/java/android/safetycenter/SafetySourceStatus.java b/framework-s/java/android/safetycenter/SafetySourceStatus.java
index 37095eb59..d8900f8d9 100644
--- a/framework-s/java/android/safetycenter/SafetySourceStatus.java
+++ b/framework-s/java/android/safetycenter/SafetySourceStatus.java
@@ -348,7 +348,8 @@ public final class SafetySourceStatus implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetySourceStatus safetySourceStatus) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetySourceStatus);
mTitle = safetySourceStatus.mTitle;
diff --git a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
index c94cedbd6..f6c6c2156 100644
--- a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
+++ b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
@@ -122,7 +122,8 @@ public final class SafetyCenterConfig implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetyCenterConfig safetyCenterConfig) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetyCenterConfig);
mSafetySourcesGroups.addAll(safetyCenterConfig.mSafetySourcesGroups);
diff --git a/framework-s/java/android/safetycenter/config/SafetySource.java b/framework-s/java/android/safetycenter/config/SafetySource.java
index fddb8b622..8aa897850 100644
--- a/framework-s/java/android/safetycenter/config/SafetySource.java
+++ b/framework-s/java/android/safetycenter/config/SafetySource.java
@@ -297,7 +297,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public String getOptionalPackageName() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mPackageName;
}
@@ -488,7 +489,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public boolean areNotificationsAllowed() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mNotificationsAllowed;
}
@@ -503,7 +505,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public String getDeduplicationGroup() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mDeduplicationGroup;
}
@@ -525,7 +528,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Set<String> getPackageCertificateHashes() {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
return mPackageCertificateHashes;
}
@@ -668,7 +672,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetySource safetySource) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(safetySource);
mType = safetySource.mType;
@@ -884,7 +889,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setNotificationsAllowed(boolean notificationsAllowed) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mNotificationsAllowed = notificationsAllowed;
return this;
@@ -903,7 +909,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder setDeduplicationGroup(@Nullable String deduplicationGroup) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mDeduplicationGroup = deduplicationGroup;
return this;
@@ -919,7 +926,8 @@ public final class SafetySource implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder addPackageCertificateHash(@NonNull String packageCertificateHash) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
mPackageCertificateHashes.add(packageCertificateHash);
return this;
diff --git a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
index 1bbe25bbb..9f9ff96cd 100644
--- a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
+++ b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
@@ -314,7 +314,8 @@ public final class SafetySourcesGroup implements Parcelable {
@RequiresApi(UPSIDE_DOWN_CAKE)
public Builder(@NonNull SafetySourcesGroup original) {
if (!SdkLevel.isAtLeastU()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(
+ "Method not supported on versions lower than UPSIDE_DOWN_CAKE");
}
requireNonNull(original);
mSafetySources.addAll(original.mSafetySources);
diff --git a/permissions/Android.bp b/permissions/Android.bp
index 8c7dab40b..6c1fdb8f8 100644
--- a/permissions/Android.bp
+++ b/permissions/Android.bp
@@ -19,9 +19,7 @@ package {
default_visibility: ["//packages/modules/Permission:__subpackages__"],
}
-prebuilt_etc {
+filegroup {
name: "privapp_allowlist_com.android.permissioncontroller.xml",
- sub_dir: "permissions",
- src: "com.android.permissioncontroller.xml",
- installable: false,
+ srcs: ["com.android.permissioncontroller.xml"],
}
diff --git a/service/java/com/android/safetycenter/ApiLock.java b/service/java/com/android/safetycenter/ApiLock.java
index 91466d3d5..f80e2ea32 100644
--- a/service/java/com/android/safetycenter/ApiLock.java
+++ b/service/java/com/android/safetycenter/ApiLock.java
@@ -16,10 +16,6 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import androidx.annotation.RequiresApi;
-
/**
* A class that is used to strongly type the {@link Object} used to synchronize the Safety Center
* APIs.
@@ -29,7 +25,6 @@ import androidx.annotation.RequiresApi;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class ApiLock {
ApiLock() {}
}
diff --git a/service/java/com/android/safetycenter/DevicePolicyResources.java b/service/java/com/android/safetycenter/DevicePolicyResources.java
index 25cab343f..8d31c254b 100644
--- a/service/java/com/android/safetycenter/DevicePolicyResources.java
+++ b/service/java/com/android/safetycenter/DevicePolicyResources.java
@@ -16,8 +16,6 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static java.util.Objects.requireNonNull;
import android.annotation.StringRes;
@@ -26,14 +24,11 @@ import android.app.admin.DevicePolicyResourcesManager;
import android.content.Context;
import android.os.Binder;
-import androidx.annotation.RequiresApi;
-
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.util.function.Supplier;
/** A class that handles dynamically updating enterprise-related resources. */
-@RequiresApi(TIRAMISU)
final class DevicePolicyResources {
private static final String SAFETY_CENTER_PREFIX = "SafetyCenter.";
@@ -46,31 +41,30 @@ final class DevicePolicyResources {
* DevicePolicyResourcesManager#getString}.
*/
static String getSafetySourceWorkString(
- SafetyCenterResourcesContext safetyCenterResourcesContext,
+ SafetyCenterResourcesApk safetyCenterResourcesApk,
String safetySourceId,
@StringRes int workResId) {
return getEnterpriseString(
- safetyCenterResourcesContext,
+ safetyCenterResourcesApk.getContext(),
safetySourceId,
- () -> safetyCenterResourcesContext.getString(workResId));
+ () -> safetyCenterResourcesApk.getString(workResId));
}
/**
* Returns the updated string for the {@code work_profile_paused} string by calling {@link
* DevicePolicyResourcesManager#getString}.
*/
- static String getWorkProfilePausedString(
- SafetyCenterResourcesContext safetyCenterResourcesContext) {
+ static String getWorkProfilePausedString(SafetyCenterResourcesApk safetyCenterResourcesApk) {
return getEnterpriseString(
- safetyCenterResourcesContext,
+ safetyCenterResourcesApk.getContext(),
WORK_PROFILE_PAUSED_TITLE,
- () -> safetyCenterResourcesContext.getStringByName("work_profile_paused"));
+ () -> safetyCenterResourcesApk.getStringByName("work_profile_paused"));
}
private static String getEnterpriseString(
Context context, String devicePolicyIdentifier, Supplier<String> defaultValueLoader) {
// This call requires the caller’s identity to match the package name of the given context.
- // However, the SafetyCenterResourcesContext’s has package name "android", which does not
+ // However, the SafetyCenterResourceApk Context's has package name "android", which does not
// necessarily match the caller’s package when making Binder calls, so the calling identity
// has to be cleared.
final long callingId = Binder.clearCallingIdentity();
diff --git a/service/java/com/android/safetycenter/PendingIntentFactory.java b/service/java/com/android/safetycenter/PendingIntentFactory.java
index 8c447c477..b52365c38 100644
--- a/service/java/com/android/safetycenter/PendingIntentFactory.java
+++ b/service/java/com/android/safetycenter/PendingIntentFactory.java
@@ -16,11 +16,8 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static java.util.Objects.requireNonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.Context;
@@ -32,9 +29,9 @@ import android.os.Binder;
import android.os.UserHandle;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.util.Arrays;
@@ -43,7 +40,6 @@ import java.util.Arrays;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class PendingIntentFactory {
private static final String TAG = "PendingIntentFactory";
@@ -53,12 +49,11 @@ public final class PendingIntentFactory {
private static final String IS_SETTINGS_HOMEPAGE = "is_from_settings_homepage";
private final Context mContext;
- private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
+ private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
- PendingIntentFactory(
- Context context, SafetyCenterResourcesContext safetyCenterResourcesContext) {
+ PendingIntentFactory(Context context, SafetyCenterResourcesApk safetyCenterResourcesApk) {
mContext = context;
- mSafetyCenterResourcesContext = safetyCenterResourcesContext;
+ mSafetyCenterResourcesApk = safetyCenterResourcesApk;
}
/**
@@ -146,7 +141,7 @@ public final class PendingIntentFactory {
private boolean shouldAddSettingsHomepageExtra(String sourceId) {
return Arrays.asList(
- mSafetyCenterResourcesContext
+ mSafetyCenterResourcesApk
.getStringByName("config_useSettingsHomepageIntentExtra")
.split(","))
.contains(sourceId);
@@ -235,7 +230,8 @@ public final class PendingIntentFactory {
// This call requires the INTERACT_ACROSS_USERS permission.
final long callingId = Binder.clearCallingIdentity();
try {
- return context.createPackageContextAsUser(packageName, 0, UserHandle.of(userId));
+ return context.createPackageContextAsUser(
+ packageName, /* flags= */ 0, UserHandle.of(userId));
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package name " + packageName + " not found", e);
return null;
diff --git a/service/java/com/android/safetycenter/RefreshReasons.java b/service/java/com/android/safetycenter/RefreshReasons.java
index ee318c7fd..f62d4cddb 100644
--- a/service/java/com/android/safetycenter/RefreshReasons.java
+++ b/service/java/com/android/safetycenter/RefreshReasons.java
@@ -16,7 +16,6 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA;
@@ -33,12 +32,7 @@ import android.safetycenter.SafetyCenterManager.RefreshReason;
import android.safetycenter.SafetyCenterManager.RefreshRequestType;
import android.util.Log;
-import androidx.annotation.RequiresApi;
-
-import com.android.modules.utils.build.SdkLevel;
-
/** Helpers to do with {@link RefreshReason}. */
-@RequiresApi(TIRAMISU)
final class RefreshReasons {
private static final String TAG = "RefreshReasons";
@@ -49,6 +43,7 @@ final class RefreshReasons {
* Validates the given {@link RefreshReason}, and throws an {@link IllegalArgumentException} in
* case of unexpected value.
*/
+ @TargetApi(UPSIDE_DOWN_CAKE)
static void validate(@RefreshReason int refreshReason) {
switch (refreshReason) {
case REFRESH_REASON_RESCAN_BUTTON_CLICK:
@@ -57,11 +52,9 @@ final class RefreshReasons {
case REFRESH_REASON_DEVICE_LOCALE_CHANGE:
case REFRESH_REASON_SAFETY_CENTER_ENABLED:
case REFRESH_REASON_OTHER:
+ case REFRESH_REASON_PERIODIC:
return;
}
- if (SdkLevel.isAtLeastU() && refreshReason == REFRESH_REASON_PERIODIC) {
- return;
- }
throw new IllegalArgumentException("Unexpected refresh reason: " + refreshReason);
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java
index e8e6befe5..a36beb2d3 100644
--- a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java
+++ b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java
@@ -18,8 +18,8 @@ package com.android.safetycenter;
import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS;
import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE;
+import static android.content.Intent.FLAG_INCLUDE_STOPPED_PACKAGES;
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.PowerExemptionManager.REASON_REFRESH_SAFETY_SOURCES;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
@@ -32,8 +32,6 @@ import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CEN
import static java.util.Collections.unmodifiableList;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.BroadcastOptions;
import android.content.Context;
@@ -42,13 +40,12 @@ import android.os.Binder;
import android.os.UserHandle;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.SafetyCenterManager.RefreshReason;
-import android.safetycenter.SafetyCenterManager.RefreshRequestType;
import android.safetycenter.SafetySourceData;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.util.PackageUtils;
import com.android.safetycenter.SafetyCenterConfigReader.Broadcast;
@@ -67,7 +64,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterBroadcastDispatcher {
private static final String TAG = "SafetyCenterBroadcastDi";
@@ -139,7 +135,6 @@ final class SafetyCenterBroadcastDispatcher {
String broadcastId,
@Nullable List<String> requiredSourceIds) {
boolean hasSentAtLeastOneBroadcast = false;
- int requestType = RefreshReasons.toRefreshRequestType(refreshReason);
String packageName = broadcast.getPackageName();
Set<String> deniedSourceIds = getRefreshDeniedSourceIds(refreshReason);
SparseArray<List<String>> userIdsToSourceIds =
@@ -163,7 +158,7 @@ final class SafetyCenterBroadcastDispatcher {
continue;
}
- Intent intent = createRefreshIntent(requestType, packageName, sourceIds, broadcastId);
+ Intent intent = createRefreshIntent(refreshReason, packageName, sourceIds, broadcastId);
boolean broadcastWasSent =
sendBroadcastIfResolves(intent, UserHandle.of(userId), broadcastOptions);
if (broadcastWasSent) {
@@ -198,7 +193,11 @@ final class SafetyCenterBroadcastDispatcher {
}
Intent implicitIntent = createImplicitEnabledChangedIntent();
- sendBroadcast(implicitIntent, UserHandle.SYSTEM, READ_SAFETY_CENTER_STATUS, null);
+ sendBroadcast(
+ implicitIntent,
+ UserHandle.SYSTEM,
+ READ_SAFETY_CENTER_STATUS,
+ /* broadcastOptions= */ null);
}
private void sendEnabledChangedBroadcast(
@@ -206,15 +205,17 @@ final class SafetyCenterBroadcastDispatcher {
BroadcastOptions broadcastOptions,
List<UserProfileGroup> userProfileGroups) {
Intent intent = createExplicitEnabledChangedIntent(broadcast.getPackageName());
- // The same ENABLED reason is used here for both enable and disable events. It is not sent
- // externally and is only used internally to filter safety sources in the methods of the
- // Broadcast class.
- int refreshReason = REFRESH_REASON_SAFETY_CENTER_ENABLED;
for (int i = 0; i < userProfileGroups.size(); i++) {
UserProfileGroup userProfileGroup = userProfileGroups.get(i);
SparseArray<List<String>> userIdsToSourceIds =
- getUserIdsToSourceIds(broadcast, userProfileGroup, refreshReason);
+ getUserIdsToSourceIds(
+ broadcast,
+ userProfileGroup,
+ // The same ENABLED reason is used here for both enable and disable
+ // events. It is not sent externally and is only used internally to
+ // filter safety sources in the methods of the Broadcast class.
+ REFRESH_REASON_SAFETY_CENTER_ENABLED);
for (int j = 0; j < userIdsToSourceIds.size(); j++) {
int userId = userIdsToSourceIds.keyAt(j);
@@ -229,24 +230,22 @@ final class SafetyCenterBroadcastDispatcher {
if (!doesBroadcastResolve(intent, userHandle)) {
Log.w(
TAG,
- "No receiver for intent targeting "
+ "No receiver for intent targeting: "
+ intent.getPackage()
- + " and user "
- + userHandle);
+ + ", and user id: "
+ + userHandle.getIdentifier());
return false;
}
Log.v(
TAG,
- "Found receiver for intent targeting "
+ "Found receiver for intent targeting: "
+ intent.getPackage()
- + " and user "
- + userHandle);
+ + ", and user id: "
+ + userHandle.getIdentifier());
sendBroadcast(intent, userHandle, SEND_SAFETY_CENTER_UPDATE, broadcastOptions);
return true;
}
- // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
- @SuppressLint("NewApi")
private void sendBroadcast(
Intent intent,
UserHandle userHandle,
@@ -267,7 +266,7 @@ final class SafetyCenterBroadcastDispatcher {
private boolean doesBroadcastResolve(Intent broadcastIntent, UserHandle userHandle) {
return !PackageUtils.queryUnfilteredBroadcastReceiversAsUser(
- broadcastIntent, 0, userHandle.getIdentifier(), mContext)
+ broadcastIntent, /* flags= */ 0, userHandle.getIdentifier(), mContext)
.isEmpty();
}
@@ -280,24 +279,29 @@ final class SafetyCenterBroadcastDispatcher {
}
private static Intent createRefreshIntent(
- @RefreshRequestType int requestType,
+ @RefreshReason int refreshReason,
String packageName,
List<String> sourceIdsToRefresh,
String broadcastId) {
String[] sourceIdsArray = sourceIdsToRefresh.toArray(new String[0]);
- return createBroadcastIntent(ACTION_REFRESH_SAFETY_SOURCES)
- .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE, requestType)
- .putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, sourceIdsArray)
- .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, broadcastId)
- .setPackage(packageName);
+ int requestType = RefreshReasons.toRefreshRequestType(refreshReason);
+ Intent refreshIntent =
+ createBroadcastIntent(ACTION_REFRESH_SAFETY_SOURCES)
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE, requestType)
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, sourceIdsArray)
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, broadcastId)
+ .setPackage(packageName);
+ boolean isUserInitiated = !RefreshReasons.isBackgroundRefresh(refreshReason);
+ if (isUserInitiated) {
+ return refreshIntent.addFlags(FLAG_INCLUDE_STOPPED_PACKAGES);
+ }
+ return refreshIntent;
}
private static Intent createBroadcastIntent(String intentAction) {
- return new Intent(intentAction).setFlags(FLAG_RECEIVER_FOREGROUND);
+ return new Intent(intentAction).addFlags(FLAG_RECEIVER_FOREGROUND);
}
- // TODO(b/193460475): Remove when tooling supports SystemApi to public API.
- @SuppressLint("NewApi")
private static BroadcastOptions createBroadcastOptions() {
BroadcastOptions broadcastOptions = BroadcastOptions.makeBasic();
Duration allowListDuration = SafetyCenterFlags.getFgsAllowlistDuration();
diff --git a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java
index 92959a47d..e3e39cc9a 100644
--- a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java
+++ b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java
@@ -16,12 +16,9 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
-import android.annotation.Nullable;
import android.content.res.Resources;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
@@ -29,11 +26,11 @@ import android.safetycenter.config.SafetySourcesGroup;
import android.util.ArrayMap;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.safetycenter.config.ParseException;
import com.android.safetycenter.config.SafetyCenterConfigParser;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -50,21 +47,20 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterConfigReader {
private static final String TAG = "SafetyCenterConfigReade";
- private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
+ private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
@Nullable private SafetyCenterConfigInternal mConfigInternalFromXml;
@Nullable private SafetyCenterConfigInternal mConfigInternalOverrideForTests;
- /** Creates a {@link SafetyCenterConfigReader} from a {@link SafetyCenterResourcesContext}. */
- SafetyCenterConfigReader(SafetyCenterResourcesContext safetyCenterResourcesContext) {
- mSafetyCenterResourcesContext = safetyCenterResourcesContext;
+ /** Creates a {@link SafetyCenterConfigReader} from a {@link SafetyCenterResourcesApk}. */
+ SafetyCenterConfigReader(SafetyCenterResourcesApk safetyCenterResourcesApk) {
+ mSafetyCenterResourcesApk = safetyCenterResourcesApk;
}
/**
@@ -76,7 +72,7 @@ public final class SafetyCenterConfigReader {
* this method was {@code true}.
*/
boolean loadConfig() {
- SafetyCenterConfig safetyCenterConfig = readSafetyCenterConfig();
+ SafetyCenterConfig safetyCenterConfig = loadSafetyCenterConfig();
if (safetyCenterConfig == null) {
return false;
}
@@ -114,7 +110,7 @@ public final class SafetyCenterConfigReader {
/**
* Returns the groups of {@link SafetySource}, filtering out any sources where {@link
- * SafetySources#isLoggable(SafetySource)} is false (and any resultingly empty groups).
+ * SafetySources#isLoggable(SafetySource)} is {@code false} (and any resulting empty groups).
*/
public List<SafetySourcesGroup> getLoggableSafetySourcesGroups() {
return getCurrentConfigInternal().getLoggableSourcesGroups();
@@ -137,15 +133,15 @@ public final class SafetyCenterConfigReader {
@Nullable
public ExternalSafetySource getExternalSafetySource(
String safetySourceId, String callingPackageName) {
- SafetyCenterConfigInternal currentConfig = getCurrentConfigInternal();
+ SafetyCenterConfigInternal testConfig = mConfigInternalOverrideForTests;
SafetyCenterConfigInternal xmlConfig = requireNonNull(mConfigInternalFromXml);
- if (currentConfig == xmlConfig) {
+ if (testConfig == null) {
// No override, access source directly.
- return currentConfig.getExternalSafetySources().get(safetySourceId);
+ return xmlConfig.getExternalSafetySources().get(safetySourceId);
}
ExternalSafetySource externalSafetySourceInTestConfig =
- currentConfig.getExternalSafetySources().get(safetySourceId);
+ testConfig.getExternalSafetySources().get(safetySourceId);
ExternalSafetySource externalSafetySourceInRealConfig =
xmlConfig.getExternalSafetySources().get(safetySourceId);
@@ -225,26 +221,21 @@ public final class SafetyCenterConfigReader {
}
@Nullable
- private SafetyCenterConfig readSafetyCenterConfig() {
- InputStream in = mSafetyCenterResourcesContext.getSafetyCenterConfig();
+ private SafetyCenterConfig loadSafetyCenterConfig() {
+ InputStream in = mSafetyCenterResourcesApk.getSafetyCenterConfig();
if (in == null) {
- Log.e(TAG, "Cannot get safety center config file, safety center will be disabled.");
- return null;
- }
-
- Resources resources = mSafetyCenterResourcesContext.getResources();
- if (resources == null) {
- Log.e(TAG, "Cannot get safety center resources, safety center will be disabled.");
+ Log.e(TAG, "Cannot access Safety Center config file");
return null;
}
+ Resources resources = mSafetyCenterResourcesApk.getResources();
try {
SafetyCenterConfig safetyCenterConfig =
SafetyCenterConfigParser.parseXmlResource(in, resources);
- Log.i(TAG, "SafetyCenterConfig read successfully");
+ Log.d(TAG, "SafetyCenterConfig loaded successfully");
return safetyCenterConfig;
} catch (ParseException e) {
- Log.e(TAG, "Cannot read SafetyCenterConfig, safety center will be disabled.", e);
+ Log.e(TAG, "Cannot parse SafetyCenterConfig", e);
return null;
}
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterDataChangeNotifier.java b/service/java/com/android/safetycenter/SafetyCenterDataChangeNotifier.java
index e8bf2626a..0e5617c8d 100644
--- a/service/java/com/android/safetycenter/SafetyCenterDataChangeNotifier.java
+++ b/service/java/com/android/safetycenter/SafetyCenterDataChangeNotifier.java
@@ -16,12 +16,8 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import android.annotation.UserIdInt;
-import androidx.annotation.RequiresApi;
-
import com.android.safetycenter.notifications.SafetyCenterNotificationSender;
import java.util.List;
@@ -34,7 +30,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterDataChangeNotifier {
diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
index fe4a1ee43..d74d160f4 100644
--- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
+++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
@@ -16,14 +16,14 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY;
import static com.android.safetycenter.internaldata.SafetyCenterBundles.STATIC_ENTRIES_TO_IDS_BUNDLE_KEY;
import static java.util.Collections.emptyList;
-import android.annotation.Nullable;
+import android.annotation.TargetApi;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.Context;
@@ -50,7 +50,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
@@ -61,7 +61,7 @@ import com.android.safetycenter.internaldata.SafetyCenterIds;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.util.ArrayList;
import java.util.List;
@@ -78,7 +78,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterDataFactory {
@@ -87,7 +86,7 @@ public final class SafetyCenterDataFactory {
private static final String ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID = "AndroidLockScreenSources";
private final Context mContext;
- private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
+ private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
private final SafetyCenterConfigReader mSafetyCenterConfigReader;
private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
private final PendingIntentFactory mPendingIntentFactory;
@@ -96,13 +95,13 @@ public final class SafetyCenterDataFactory {
SafetyCenterDataFactory(
Context context,
- SafetyCenterResourcesContext safetyCenterResourcesContext,
+ SafetyCenterResourcesApk safetyCenterResourcesApk,
SafetyCenterConfigReader safetyCenterConfigReader,
SafetyCenterRefreshTracker safetyCenterRefreshTracker,
PendingIntentFactory pendingIntentFactory,
SafetyCenterDataManager safetyCenterDataManager) {
mContext = context;
- mSafetyCenterResourcesContext = safetyCenterResourcesContext;
+ mSafetyCenterResourcesApk = safetyCenterResourcesApk;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
mPendingIntentFactory = pendingIntentFactory;
@@ -334,7 +333,7 @@ public final class SafetyCenterDataFactory {
if (SdkLevel.isAtLeastU()) {
CharSequence issueAttributionTitle =
TextUtils.isEmpty(safetySourceIssue.getAttributionTitle())
- ? mSafetyCenterResourcesContext.getOptionalString(
+ ? mSafetyCenterResourcesApk.getOptionalString(
safetySourcesGroup.getTitleResId())
: safetySourceIssue.getAttributionTitle();
safetyCenterIssueBuilder.setAttributionTitle(issueAttributionTitle);
@@ -400,8 +399,8 @@ public final class SafetyCenterDataFactory {
safetySource,
defaultPackageName,
userProfileGroup.getProfileParentUserId(),
- false,
- false));
+ /* isUserManaged= */ false,
+ /* isManagedUserRunning= */ false));
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
@@ -422,7 +421,7 @@ public final class SafetyCenterDataFactory {
safetySource,
defaultPackageName,
managedProfileUserId,
- true,
+ /* isUserManaged= */ true,
isManagedUserRunning));
}
}
@@ -443,7 +442,7 @@ public final class SafetyCenterDataFactory {
new SafetyCenterEntryOrGroup(
new SafetyCenterEntryGroup.Builder(
safetySourcesGroup.getId(),
- mSafetyCenterResourcesContext.getString(
+ mSafetyCenterResourcesApk.getString(
safetySourcesGroup.getTitleResId()))
.setSeverityLevel(groupSafetyCenterEntryLevel)
.setSummary(groupSummary)
@@ -509,13 +508,10 @@ public final class SafetyCenterDataFactory {
for (int i = 0; i < entries.size(); i++) {
SafetySourceKey key = toSafetySourceKey(entries.get(i).getId());
if (mSafetyCenterDataManager.sourceHasError(key)) {
- // We always use the singular form of the error string for groups because
- // they appear as single entries in the UI and this ensures consistency,
- // especially when subpages are enabled.
- return getRefreshErrorString(1);
+ return getRefreshErrorString();
}
}
- return mSafetyCenterResourcesContext.getStringByName("group_unknown_summary");
+ return mSafetyCenterResourcesApk.getStringByName("group_unknown_summary");
}
Log.w(
@@ -529,8 +525,7 @@ public final class SafetyCenterDataFactory {
private CharSequence getDefaultGroupSummary(
SafetySourcesGroup safetySourcesGroup, List<SafetyCenterEntry> entries) {
CharSequence groupSummary =
- mSafetyCenterResourcesContext.getOptionalString(
- safetySourcesGroup.getSummaryResId());
+ mSafetyCenterResourcesApk.getOptionalString(safetySourcesGroup.getSummaryResId());
if (safetySourcesGroup.getId().equals(ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID)
&& TextUtils.isEmpty(groupSummary)) {
@@ -649,7 +644,7 @@ public final class SafetyCenterDataFactory {
.setSummary(
inQuietMode
? DevicePolicyResources.getWorkProfilePausedString(
- mSafetyCenterResourcesContext)
+ mSafetyCenterResourcesApk)
: safetySourceStatus.getSummary())
.setEnabled(enabled)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
@@ -710,20 +705,19 @@ public final class SafetyCenterDataFactory {
CharSequence title =
isUserManaged
? DevicePolicyResources.getSafetySourceWorkString(
- mSafetyCenterResourcesContext,
+ mSafetyCenterResourcesApk,
safetySource.getId(),
safetySource.getTitleForWorkResId())
- : mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
+ : mSafetyCenterResourcesApk.getString(safetySource.getTitleResId());
CharSequence summary =
mSafetyCenterDataManager.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId))
- ? getRefreshErrorString(1)
- : mSafetyCenterResourcesContext.getOptionalString(
+ ? getRefreshErrorString()
+ : mSafetyCenterResourcesApk.getOptionalString(
safetySource.getSummaryResId());
if (isQuietModeEnabled) {
enabled = false;
- summary =
- DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesContext);
+ summary = DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesApk);
}
return new SafetyCenterEntry.Builder(
SafetyCenterIds.encodeToString(safetyCenterEntryId), title)
@@ -754,8 +748,8 @@ public final class SafetyCenterDataFactory {
safetySource,
defaultPackageName,
userProfileGroup.getProfileParentUserId(),
- false,
- false);
+ /* isUserManaged= */ false,
+ /* isManagedUserRunning= */ false);
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
@@ -774,7 +768,7 @@ public final class SafetyCenterDataFactory {
safetySource,
defaultPackageName,
managedProfileUserId,
- true,
+ /* isUserManaged= */ true,
isManagedUserRunning);
}
}
@@ -785,7 +779,7 @@ public final class SafetyCenterDataFactory {
safetyCenterStaticEntryGroups.add(
new SafetyCenterStaticEntryGroup(
- mSafetyCenterResourcesContext.getString(safetySourcesGroup.getTitleResId()),
+ mSafetyCenterResourcesApk.getString(safetySourcesGroup.getTitleResId()),
staticEntries));
}
@@ -864,7 +858,7 @@ public final class SafetyCenterDataFactory {
.setSummary(
inQuietMode
? DevicePolicyResources.getWorkProfilePausedString(
- mSafetyCenterResourcesContext)
+ mSafetyCenterResourcesApk)
: safetySourceStatus.getSummary())
.setPendingIntent(entryPendingIntent)
.build();
@@ -914,19 +908,18 @@ public final class SafetyCenterDataFactory {
CharSequence title =
isUserManaged
? DevicePolicyResources.getSafetySourceWorkString(
- mSafetyCenterResourcesContext,
+ mSafetyCenterResourcesApk,
safetySource.getId(),
safetySource.getTitleForWorkResId())
- : mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
+ : mSafetyCenterResourcesApk.getString(safetySource.getTitleResId());
CharSequence summary =
mSafetyCenterDataManager.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId))
- ? getRefreshErrorString(1)
- : mSafetyCenterResourcesContext.getOptionalString(
+ ? getRefreshErrorString()
+ : mSafetyCenterResourcesApk.getOptionalString(
safetySource.getSummaryResId());
if (isQuietModeEnabled) {
- summary =
- DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesContext);
+ summary = DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesApk);
}
return new SafetyCenterStaticEntry.Builder(title)
.setSummary(summary)
@@ -1088,11 +1081,10 @@ public final class SafetyCenterDataFactory {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
if (hasSettingsToReview) {
- return mSafetyCenterResourcesContext.getStringByName(
+ return mSafetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_title");
}
- return mSafetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_title");
+ return mSafetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
return getStatusTitleFromIssueCategories(
topNonDismissedIssueInfo,
@@ -1117,6 +1109,7 @@ public final class SafetyCenterDataFactory {
return "";
}
+ @TargetApi(UPSIDE_DOWN_CAKE)
private String getStatusTitleFromIssueCategories(
@Nullable SafetySourceIssueInfo topNonDismissedIssueInfo,
String deviceResourceName,
@@ -1125,7 +1118,7 @@ public final class SafetyCenterDataFactory {
String dataResourceName,
String passwordsResourceName,
String personalSafetyResourceName) {
- String generalString = mSafetyCenterResourcesContext.getStringByName(generalResourceName);
+ String generalString = mSafetyCenterResourcesApk.getStringByName(generalResourceName);
if (topNonDismissedIssueInfo == null) {
Log.w(TAG, "No safety center issues found in a non-green status");
return generalString;
@@ -1133,22 +1126,17 @@ public final class SafetyCenterDataFactory {
int issueCategory = topNonDismissedIssueInfo.getSafetySourceIssue().getIssueCategory();
switch (issueCategory) {
case SafetySourceIssue.ISSUE_CATEGORY_DEVICE:
- return mSafetyCenterResourcesContext.getStringByName(deviceResourceName);
+ return mSafetyCenterResourcesApk.getStringByName(deviceResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT:
- return mSafetyCenterResourcesContext.getStringByName(accountResourceName);
+ return mSafetyCenterResourcesApk.getStringByName(accountResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_GENERAL:
return generalString;
- }
- if (SdkLevel.isAtLeastU()) {
- switch (issueCategory) {
- case SafetySourceIssue.ISSUE_CATEGORY_DATA:
- return mSafetyCenterResourcesContext.getStringByName(dataResourceName);
- case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS:
- return mSafetyCenterResourcesContext.getStringByName(passwordsResourceName);
- case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY:
- return mSafetyCenterResourcesContext.getStringByName(
- personalSafetyResourceName);
- }
+ case SafetySourceIssue.ISSUE_CATEGORY_DATA:
+ return mSafetyCenterResourcesApk.getStringByName(dataResourceName);
+ case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS:
+ return mSafetyCenterResourcesApk.getStringByName(passwordsResourceName);
+ case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY:
+ return mSafetyCenterResourcesApk.getStringByName(personalSafetyResourceName);
}
Log.w(TAG, "Unexpected SafetySourceIssue.IssueCategory: " + issueCategory);
@@ -1172,17 +1160,16 @@ public final class SafetyCenterDataFactory {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
if (topNonDismissedIssue == null) {
if (safetyCenterOverallState.hasSettingsToReview()) {
- return mSafetyCenterResourcesContext.getStringByName(
+ return mSafetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_summary");
}
- return mSafetyCenterResourcesContext.getStringByName(
+ return mSafetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_summary");
} else if (isTip(topNonDismissedIssue.getSafetySourceIssue())) {
- return mSafetyCenterResourcesContext.getStringByName(
- "overall_severity_level_tip_summary", numTipIssues);
+ return getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues);
} else if (isAutomatic(topNonDismissedIssue.getSafetySourceIssue())) {
- return mSafetyCenterResourcesContext.getStringByName(
+ return getIcuPluralsString(
"overall_severity_level_action_taken_summary", numAutomaticIssues);
}
// Fall through.
@@ -1207,14 +1194,14 @@ public final class SafetyCenterDataFactory {
== SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC;
}
- private String getRefreshErrorString(int numberOfErrorEntries) {
- return getIcuPluralsString("refresh_error", numberOfErrorEntries);
+ private String getRefreshErrorString() {
+ return getIcuPluralsString("refresh_error", /* count= */ 1);
}
private String getIcuPluralsString(String name, int count, Object... formatArgs) {
MessageFormat messageFormat =
new MessageFormat(
- mSafetyCenterResourcesContext.getStringByName(name, formatArgs),
+ mSafetyCenterResourcesApk.getStringByName(name, formatArgs),
Locale.getDefault());
ArrayMap<String, Object> arguments = new ArrayMap<>();
arguments.put("count", count);
@@ -1233,7 +1220,7 @@ public final class SafetyCenterDataFactory {
}
// Fall through.
case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
- return mSafetyCenterResourcesContext.getStringByName("scanning_title");
+ return mSafetyCenterResourcesApk.getStringByName("scanning_title");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
@@ -1248,7 +1235,7 @@ public final class SafetyCenterDataFactory {
return null;
case SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS:
case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
- return mSafetyCenterResourcesContext.getStringByName("loading_summary");
+ return mSafetyCenterResourcesApk.getStringByName("loading_summary");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java
index 1fc88d4b0..fcb016160 100644
--- a/service/java/com/android/safetycenter/SafetyCenterFlags.java
+++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java
@@ -16,10 +16,8 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.safetycenter.SafetyCenterManager.RefreshReason;
-import android.annotation.Nullable;
import android.os.Binder;
import android.provider.DeviceConfig;
import android.safetycenter.SafetySourceData;
@@ -27,10 +25,10 @@ import android.safetycenter.SafetySourceIssue;
import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.io.PrintWriter;
import java.time.Duration;
@@ -40,7 +38,6 @@ import java.time.Duration;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetyCenterFlags {
private static final String TAG = "SafetyCenterFlags";
@@ -63,9 +60,6 @@ public final class SafetyCenterFlags {
private static final String PROPERTY_NOTIFICATION_RESURFACE_INTERVAL =
"safety_center_notification_resurface_interval";
- private static final String PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT =
- "safety_center_show_error_entries_on_timeout";
-
private static final String PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION =
"safety_center_replace_lock_screen_icon_action";
@@ -134,24 +128,27 @@ public final class SafetyCenterFlags {
private static volatile String sRefreshOnPageOpenSourcesDefault =
"AndroidBiometrics,AndroidLockScreen";
- static void init(SafetyCenterResourcesContext resourceContext) {
+ static void init(SafetyCenterResourcesApk safetyCenterResourcesApk) {
String untrackedSourcesDefault =
- resourceContext.getOptionalStringByName("config_defaultUntrackedSources");
+ safetyCenterResourcesApk.getOptionalStringByName("config_defaultUntrackedSources");
if (untrackedSourcesDefault != null) {
sUntrackedSourcesDefault = untrackedSourcesDefault;
}
String backgroundRefreshDenyDefault =
- resourceContext.getOptionalStringByName("config_defaultBackgroundRefreshDeny");
+ safetyCenterResourcesApk.getOptionalStringByName(
+ "config_defaultBackgroundRefreshDeny");
if (backgroundRefreshDenyDefault != null) {
sBackgroundRefreshDenyDefault = backgroundRefreshDenyDefault;
}
String issueCategoryAllowlistDefault =
- resourceContext.getOptionalStringByName("config_defaultIssueCategoryAllowlist");
+ safetyCenterResourcesApk.getOptionalStringByName(
+ "config_defaultIssueCategoryAllowlist");
if (issueCategoryAllowlistDefault != null) {
sIssueCategoryAllowlistDefault = issueCategoryAllowlistDefault;
}
String refreshOnPageOpenSourcesDefault =
- resourceContext.getOptionalStringByName("config_defaultRefreshOnPageOpenSources");
+ safetyCenterResourcesApk.getOptionalStringByName(
+ "config_defaultRefreshOnPageOpenSources");
if (refreshOnPageOpenSourcesDefault != null) {
sRefreshOnPageOpenSourcesDefault = refreshOnPageOpenSourcesDefault;
}
@@ -173,7 +170,6 @@ public final class SafetyCenterFlags {
getImmediateNotificationBehaviorIssues());
printFlag(
fout, PROPERTY_NOTIFICATION_RESURFACE_INTERVAL, getNotificationResurfaceInterval());
- printFlag(fout, PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT, getShowErrorEntriesOnTimeout());
printFlag(fout, PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION, getReplaceLockScreenIconAction());
printFlag(fout, PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS, getResolvingActionTimeout());
printFlag(fout, PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS, getFgsAllowlistDuration());
@@ -284,13 +280,6 @@ public final class SafetyCenterFlags {
}
/**
- * Returns whether we should show error entries for sources that timeout when refreshing them.
- */
- static boolean getShowErrorEntriesOnTimeout() {
- return getBoolean(PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT, true);
- }
-
- /**
* Returns whether we should replace the lock screen source's {@link
* android.safetycenter.SafetySourceStatus.IconAction}.
*/
@@ -442,7 +431,7 @@ public final class SafetyCenterFlags {
if (allowlistedCertString == null) {
return new ArraySet<>();
}
- return new ArraySet<String>(allowlistedCertString.split("\\|"));
+ return new ArraySet<>(allowlistedCertString.split("\\|"));
}
/**
diff --git a/service/java/com/android/safetycenter/SafetyCenterListeners.java b/service/java/com/android/safetycenter/SafetyCenterListeners.java
index 9e07c3d17..5f89f46ff 100644
--- a/service/java/com/android/safetycenter/SafetyCenterListeners.java
+++ b/service/java/com/android/safetycenter/SafetyCenterListeners.java
@@ -16,9 +16,6 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.IBinder;
import android.os.RemoteCallbackList;
@@ -30,7 +27,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicReference;
@@ -43,7 +40,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterListeners {
@@ -67,7 +63,7 @@ final class SafetyCenterListeners {
try {
listener.onSafetyCenterDataChanged(safetyCenterData);
} catch (RemoteException e) {
- Log.e(TAG, "Error delivering SafetyCenterData to listener", e);
+ Log.w(TAG, "Error delivering SafetyCenterData to listener", e);
}
}
@@ -81,7 +77,7 @@ final class SafetyCenterListeners {
try {
listener.onError(safetyCenterErrorDetails);
} catch (RemoteException e) {
- Log.e(TAG, "Error delivering SafetyCenterErrorDetails to listener", e);
+ Log.w(TAG, "Error delivering SafetyCenterErrorDetails to listener", e);
}
}
@@ -94,7 +90,11 @@ final class SafetyCenterListeners {
int[] relevantUserIds = userProfileGroup.getProfileParentAndManagedRunningProfilesUserIds();
for (int i = 0; i < relevantUserIds.length; i++) {
deliverUpdateForUser(
- relevantUserIds[i], userProfileGroup, safetyCenterDataCache, true, null);
+ relevantUserIds[i],
+ userProfileGroup,
+ safetyCenterDataCache,
+ /* updateSafetyCenterData= */ true,
+ /* safetyCenterErrorDetails= */ null);
}
}
@@ -111,7 +111,7 @@ final class SafetyCenterListeners {
relevantUserIds[i],
userProfileGroup,
safetyCenterDataCache,
- false,
+ /* updateSafetyCenterData= */ false,
safetyCenterErrorDetails);
}
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java
index a28ce7d22..17688063a 100644
--- a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java
+++ b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java
@@ -16,14 +16,12 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK;
import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_SYSTEM_EVENT_REPORTED__RESULT__TIMEOUT;
import static com.android.safetycenter.logging.SafetyCenterStatsdLogger.toSystemEventResult;
import android.annotation.ElapsedRealtimeLong;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.SystemClock;
@@ -34,7 +32,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
@@ -53,7 +51,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterRefreshTracker {
private static final String TAG = "SafetyCenterRefreshTrac";
@@ -78,15 +75,15 @@ public final class SafetyCenterRefreshTracker {
String reportRefreshInProgress(
@RefreshReason int refreshReason, UserProfileGroup userProfileGroup) {
if (mRefreshInProgress != null) {
- Log.w(TAG, "Replacing an ongoing refresh");
+ Log.i(TAG, "Replacing ongoing refresh with id: " + mRefreshInProgress.getId());
}
String refreshBroadcastId = UUID.randomUUID() + "_" + mRefreshCounter++;
- Log.v(
+ Log.d(
TAG,
- "Starting a new refresh with refreshReason:"
+ "Starting a new refresh with reason: "
+ refreshReason
- + " refreshBroadcastId:"
+ + ", and id: "
+ refreshBroadcastId);
mRefreshInProgress =
@@ -237,7 +234,7 @@ public final class SafetyCenterRefreshTracker {
*/
void clearRefreshForUser(@UserIdInt int userId) {
if (mRefreshInProgress == null) {
- Log.v(TAG, "Clear refresh for user called but no refresh in progress");
+ Log.d(TAG, "Clear refresh for user called but no refresh in progress");
return;
}
if (mRefreshInProgress.clearForUser(userId)) {
@@ -272,6 +269,15 @@ public final class SafetyCenterRefreshTracker {
int refreshReason = clearedRefresh.getReason();
int requestType = RefreshReasons.toRefreshRequestType(refreshReason);
+ Log.w(
+ TAG,
+ "Timeout after "
+ + clearedRefresh.getDurationSinceStart()
+ + " for refresh with reason: "
+ + refreshReason
+ + ", and id: "
+ + clearedRefresh.getId());
+
for (int i = 0; i < timedOutSources.size(); i++) {
SafetySourceKey sourceKey = timedOutSources.valueAt(i);
Duration duration = clearedRefresh.getDurationSinceSourceStart(sourceKey);
@@ -285,6 +291,15 @@ public final class SafetyCenterRefreshTracker {
refreshReason,
false);
}
+
+ Log.w(
+ TAG,
+ "Refresh with id: "
+ + clearedRefresh.getId()
+ + " timed out for tracked source id: "
+ + sourceKey.getSourceId()
+ + ", and user id: "
+ + sourceKey.getUserId());
}
SafetyCenterStatsdLogger.writeWholeRefreshSystemEvent(
@@ -298,7 +313,7 @@ public final class SafetyCenterRefreshTracker {
}
/**
- * Clears the any refresh in progress and returns it for the caller to do what it needs to.
+ * Clears the refresh in progress and returns it for the caller to do what it needs to.
*
* <p>If there was no refresh in progress then {@code null} is returned.
*/
@@ -306,11 +321,11 @@ public final class SafetyCenterRefreshTracker {
private RefreshInProgress clearRefreshInternal() {
RefreshInProgress refreshToClear = mRefreshInProgress;
if (refreshToClear == null) {
- Log.v(TAG, "Clear refresh called but no refresh in progress");
+ Log.d(TAG, "Clear refresh called but no refresh in progress");
return null;
}
- Log.v(TAG, "Clearing refresh with refreshBroadcastId:" + refreshToClear.getId());
+ Log.v(TAG, "Clearing refresh with id: " + refreshToClear.getId());
mRefreshInProgress = null;
return refreshToClear;
}
@@ -324,13 +339,7 @@ public final class SafetyCenterRefreshTracker {
String methodName, String refreshBroadcastId) {
RefreshInProgress refreshInProgress = mRefreshInProgress;
if (refreshInProgress == null || !refreshInProgress.getId().equals(refreshBroadcastId)) {
- Log.i(
- TAG,
- methodName
- + " called for invalid refresh broadcast id: "
- + refreshBroadcastId
- + "; no such refresh in"
- + " progress");
+ Log.i(TAG, methodName + " called with invalid refresh id: " + refreshBroadcastId);
return null;
}
return refreshInProgress;
@@ -435,19 +444,19 @@ public final class SafetyCenterRefreshTracker {
}
Log.v(
TAG,
- "Refresh started for sourceId:"
+ "Refresh with id: "
+ + mId
+ + " started for source id: "
+ safetySourceKey.getSourceId()
- + " userId:"
+ + ", user id: "
+ safetySourceKey.getUserId()
- + " with refreshBroadcastId:"
- + mId
- + " at currentElapsedMillis:"
+ + ", elapsed millis: "
+ currentElapsedMillis
- + " & tracking:"
+ + ", tracking: "
+ tracked
+ ", now "
+ mSourceRefreshesInFlight.size()
- + " tracked sources in flight.");
+ + " tracked sources in flight");
}
@Nullable
@@ -464,23 +473,23 @@ public final class SafetyCenterRefreshTracker {
: Duration.ofMillis(SystemClock.elapsedRealtime() - startElapsedMillis);
Log.v(
TAG,
- "Refresh completed for sourceId:"
+ "Refresh with id: "
+ + mId
+ + " completed for source id: "
+ safetySourceKey.getSourceId()
- + " userId:"
+ + ", user id: "
+ safetySourceKey.getUserId()
- + " with refreshBroadcastId:"
- + mId
- + " duration:"
+ + ", duration: "
+ duration
- + " successful:"
+ + ", successful: "
+ successful
- + " dataChanged:"
+ + ", data changed: "
+ dataChanged
- + " & tracking:"
+ + ", tracking: "
+ tracked
- + ", "
+ + ", now "
+ mSourceRefreshesInFlight.size()
- + " tracked sources still in flight.");
+ + " tracked sources in flight");
return duration;
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index f23f041bd..578997355 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -21,8 +21,8 @@ import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS;
import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOCALE_CHANGE;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
import static android.safetycenter.SafetyCenterManager.RefreshReason;
import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED;
@@ -35,11 +35,10 @@ import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriend
import static java.util.Objects.requireNonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.StatsManager;
-import android.app.StatsManager.StatsPullAtomCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -69,13 +68,17 @@ import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.ForegroundThread;
import com.android.permission.util.UserUtils;
+import com.android.safetycenter.data.AndroidLockScreenFix;
import com.android.safetycenter.data.SafetyCenterDataManager;
+import com.android.safetycenter.data.SafetyEventFix;
import com.android.safetycenter.internaldata.SafetyCenterIds;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueId;
@@ -85,16 +88,13 @@ import com.android.safetycenter.notifications.SafetyCenterNotificationChannels;
import com.android.safetycenter.notifications.SafetyCenterNotificationReceiver;
import com.android.safetycenter.notifications.SafetyCenterNotificationSender;
import com.android.safetycenter.pendingintents.PendingIntentSender;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.annotation.concurrent.NotThreadSafe;
/**
* Service for the safety center.
@@ -102,7 +102,6 @@ import javax.annotation.concurrent.NotThreadSafe;
* @hide
*/
@Keep
-@RequiresApi(TIRAMISU)
public final class SafetyCenterService extends SystemService {
private static final String TAG = "SafetyCenterService";
@@ -112,9 +111,8 @@ public final class SafetyCenterService extends SystemService {
@GuardedBy("mApiLock")
private final SafetyCenterTimeouts mSafetyCenterTimeouts = new SafetyCenterTimeouts();
- private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
-
- private final SafetyCenterNotificationChannels mNotificationChannels;
+ @GuardedBy("mApiLock")
+ private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
@GuardedBy("mApiLock")
private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@@ -132,6 +130,9 @@ public final class SafetyCenterService extends SystemService {
private final SafetyCenterListeners mSafetyCenterListeners;
@GuardedBy("mApiLock")
+ private final SafetyCenterNotificationChannels mNotificationChannels;
+
+ @GuardedBy("mApiLock")
private final SafetyCenterNotificationSender mNotificationSender;
@GuardedBy("mApiLock")
@@ -140,16 +141,15 @@ public final class SafetyCenterService extends SystemService {
@GuardedBy("mApiLock")
private final SafetyCenterDataChangeNotifier mSafetyCenterDataChangeNotifier;
- private final StatsPullAtomCallback mPullAtomCallback;
private final boolean mDeviceSupportsSafetyCenter;
/** Whether the {@link SafetyCenterConfig} was successfully loaded. */
- private volatile boolean mConfigAvailable;
+ private volatile boolean mConfigAvailable = false;
public SafetyCenterService(Context context) {
super(context);
- mSafetyCenterResourcesContext = new SafetyCenterResourcesContext(context);
- mSafetyCenterConfigReader = new SafetyCenterConfigReader(mSafetyCenterResourcesContext);
+ mSafetyCenterResourcesApk = new SafetyCenterResourcesApk(context);
+ mSafetyCenterConfigReader = new SafetyCenterConfigReader(mSafetyCenterResourcesApk);
mSafetyCenterRefreshTracker = new SafetyCenterRefreshTracker(context);
mSafetyCenterDataManager =
new SafetyCenterDataManager(
@@ -157,17 +157,17 @@ public final class SafetyCenterService extends SystemService {
mSafetyCenterDataFactory =
new SafetyCenterDataFactory(
context,
- mSafetyCenterResourcesContext,
+ mSafetyCenterResourcesApk,
mSafetyCenterConfigReader,
mSafetyCenterRefreshTracker,
- new PendingIntentFactory(context, mSafetyCenterResourcesContext),
+ new PendingIntentFactory(context, mSafetyCenterResourcesApk),
mSafetyCenterDataManager);
mSafetyCenterListeners = new SafetyCenterListeners(mSafetyCenterDataFactory);
- mNotificationChannels = new SafetyCenterNotificationChannels(mSafetyCenterResourcesContext);
+ mNotificationChannels = new SafetyCenterNotificationChannels(mSafetyCenterResourcesApk);
mNotificationSender =
SafetyCenterNotificationSender.newInstance(
context,
- mSafetyCenterResourcesContext,
+ mSafetyCenterResourcesApk,
mNotificationChannels,
mSafetyCenterDataManager);
mSafetyCenterBroadcastDispatcher =
@@ -176,13 +176,6 @@ public final class SafetyCenterService extends SystemService {
mSafetyCenterConfigReader,
mSafetyCenterRefreshTracker,
mSafetyCenterDataManager);
- mPullAtomCallback =
- new SafetyCenterPullAtomCallback(
- context,
- mApiLock,
- mSafetyCenterConfigReader,
- mSafetyCenterDataFactory,
- mSafetyCenterDataManager);
mSafetyCenterDataChangeNotifier =
new SafetyCenterDataChangeNotifier(mNotificationSender, mSafetyCenterListeners);
mDeviceSupportsSafetyCenter =
@@ -191,57 +184,98 @@ public final class SafetyCenterService extends SystemService {
Resources.getSystem()
.getIdentifier(
"config_enableSafetyCenter", "bool", "android"));
- if (!mDeviceSupportsSafetyCenter) {
- Log.i(TAG, "Device does not support safety center, safety center will be disabled.");
- }
}
@Override
public void onStart() {
publishBinderService(Context.SAFETY_CENTER_SERVICE, new Stub());
- if (mDeviceSupportsSafetyCenter) {
- synchronized (mApiLock) {
- mSafetyCenterResourcesContext.init();
- SafetyCenterFlags.init(mSafetyCenterResourcesContext);
- mConfigAvailable = mSafetyCenterConfigReader.loadConfig();
- if (mConfigAvailable) {
- mSafetyCenterDataManager.loadPersistableDataStateFromFile();
- new UserBroadcastReceiver().register(getContext());
- new SafetyCenterNotificationReceiver(
- this,
- mSafetyCenterDataManager,
- mSafetyCenterDataChangeNotifier,
- mApiLock)
- .register(getContext());
- new LocaleBroadcastReceiver().register(getContext());
- }
+ if (!mDeviceSupportsSafetyCenter) {
+ Log.i(TAG, "Device does not support Safety Center, it will be disabled");
+ return;
+ }
+
+ synchronized (mApiLock) {
+ boolean safetyCenterResourcesInitialized = mSafetyCenterResourcesApk.init();
+ if (!safetyCenterResourcesInitialized) {
+ Log.e(TAG, "Cannot init Safety Center resources, Safety Center will be disabled");
+ return;
}
+
+ SafetyCenterFlags.init(mSafetyCenterResourcesApk);
+
+ if (!mSafetyCenterConfigReader.loadConfig()) {
+ Log.e(TAG, "Cannot init Safety Center config, Safety Center will be disabled");
+ return;
+ }
+
+ mConfigAvailable = true;
+ mSafetyCenterDataManager.loadPersistableDataStateFromFile();
+ new UserBroadcastReceiver().register(getContext());
+ new SafetyCenterNotificationReceiver(
+ /* service= */ this,
+ mSafetyCenterDataManager,
+ mSafetyCenterDataChangeNotifier,
+ mApiLock)
+ .register(getContext());
+ new LocaleBroadcastReceiver().register(getContext());
}
}
@Override
public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_BOOT_COMPLETED && canUseSafetyCenter()) {
- registerSafetyCenterEnabledListener();
- registerSafetyCenterPullAtomCallback();
- mNotificationChannels.createAllChannelsForAllUsers(getContext());
+ if (phase != SystemService.PHASE_BOOT_COMPLETED || !canUseSafetyCenter()) {
+ return;
}
+
+ SafetyCenterPullAtomCallback pullAtomCallback;
+ synchronized (mApiLock) {
+ registerSafetyCenterEnabledListenerLocked();
+ pullAtomCallback = newSafetyCenterPullAtomCallbackLocked();
+ }
+ registerSafetyCenterPullAtomCallback(pullAtomCallback);
}
- private void registerSafetyCenterEnabledListener() {
- Executor foregroundThreadExecutor = ForegroundThread.getExecutor();
- SafetyCenterEnabledListener listener = new SafetyCenterEnabledListener();
- // Ensure the listener is called first with the current state on the same thread.
- foregroundThreadExecutor.execute(listener::setInitialState);
+ @GuardedBy("mApiLock")
+ private void registerSafetyCenterEnabledListenerLocked() {
+ SafetyCenterEnabledListener safetyCenterEnabledListener = new SafetyCenterEnabledListener();
DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_PRIVACY, foregroundThreadExecutor, listener);
+ DeviceConfig.NAMESPACE_PRIVACY,
+ ForegroundThread.getExecutor(),
+ safetyCenterEnabledListener);
+ // Set the initial state *after* registering the listener, in the unlikely event that the
+ // flag changes between creating the listener and registering it (in which case we could
+ // miss an update and end up with an inconsistent state).
+ setInitialStateLocked(safetyCenterEnabledListener);
}
- private void registerSafetyCenterPullAtomCallback() {
+ @GuardedBy("mApiLock")
+ @SuppressWarnings("GuardedBy")
+ // @GuardedBy is unable to infer that the `SafetyCenterService.this.mApiLock` in
+ // `SafetyCenterService` is the same as the one in `SafetyCenterEnabledListener` here, so it
+ // has to be suppressed.
+ private void setInitialStateLocked(SafetyCenterEnabledListener safetyCenterEnabledListener) {
+ safetyCenterEnabledListener.setInitialStateLocked();
+ }
+
+ @GuardedBy("mApiLock")
+ private SafetyCenterPullAtomCallback newSafetyCenterPullAtomCallbackLocked() {
+ return new SafetyCenterPullAtomCallback(
+ getContext(),
+ mApiLock,
+ mSafetyCenterConfigReader,
+ mSafetyCenterDataFactory,
+ mSafetyCenterDataManager);
+ }
+
+ private void registerSafetyCenterPullAtomCallback(
+ SafetyCenterPullAtomCallback pullAtomCallback) {
StatsManager statsManager =
requireNonNull(getContext().getSystemService(StatsManager.class));
statsManager.setPullAtomCallback(
- SAFETY_STATE, null, BackgroundThread.getExecutor(), mPullAtomCallback);
+ SAFETY_STATE,
+ /* metadata= */ null,
+ BackgroundThread.getExecutor(),
+ pullAtomCallback);
}
/** Service implementation of {@link ISafetyCenterManager.Stub}. */
@@ -275,6 +309,16 @@ public final class SafetyCenterService extends SystemService {
UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId);
synchronized (mApiLock) {
+ safetySourceData =
+ AndroidLockScreenFix.maybeOverrideSafetySourceData(
+ getContext(), safetySourceId, safetySourceData);
+ safetyEvent =
+ SafetyEventFix.maybeOverrideSafetyEvent(
+ mSafetyCenterDataManager,
+ safetySourceId,
+ safetySourceData,
+ safetyEvent,
+ userId);
boolean hasUpdate =
mSafetyCenterDataManager.setSafetySourceData(
safetySourceData, safetySourceId, safetyEvent, packageName, userId);
@@ -342,7 +386,7 @@ public final class SafetyCenterService extends SystemService {
== SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED) {
safetyCenterErrorDetails =
new SafetyCenterErrorDetails(
- mSafetyCenterResourcesContext.getStringByName(
+ mSafetyCenterResourcesApk.getStringByName(
"resolving_action_error"));
}
if (hasUpdate) {
@@ -363,7 +407,10 @@ public final class SafetyCenterService extends SystemService {
|| !checkApiEnabled("refreshSafetySources")) {
return;
}
- startRefreshingSafetySources(refreshReason, userId);
+
+ synchronized (mApiLock) {
+ startRefreshingSafetySourcesLocked(refreshReason, userId);
+ }
}
@Override
@@ -380,7 +427,10 @@ public final class SafetyCenterService extends SystemService {
|| !checkApiEnabled("refreshSpecificSafetySources")) {
return;
}
- startRefreshingSafetySources(refreshReason, userId, safetySourceIds);
+
+ synchronized (mApiLock) {
+ startRefreshingSafetySourcesLocked(refreshReason, userId, safetySourceIds);
+ }
}
@Override
@@ -392,7 +442,7 @@ public final class SafetyCenterService extends SystemService {
// search works by adding all the entries very rarely (and relies on filtering them out
// instead).
if (!canUseSafetyCenter()) {
- Log.w(TAG, "Called getSafetyCenterConfig, but Safety Center is not supported");
+ Log.i(TAG, "Called getSafetyCenterConfig, but Safety Center is not supported");
return null;
}
@@ -504,7 +554,7 @@ public final class SafetyCenterService extends SystemService {
PendingIntent onDismissPendingIntent =
safetySourceIssue.getOnDismissPendingIntent();
if (onDismissPendingIntent != null
- && !dispatchPendingIntent(onDismissPendingIntent, null)) {
+ && !dispatchPendingIntent(onDismissPendingIntent)) {
Log.w(
TAG,
"Error dispatching dismissal for issue: "
@@ -634,13 +684,14 @@ public final class SafetyCenterService extends SystemService {
/** Enforces cross user permission and returns whether the user is valid. */
private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, message, getContext());
+ UserUtils.enforceCrossUserPermission(
+ userId, /* allowAll= */ false, message, getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.w(
TAG,
"Called "
+ message
- + " with user id "
+ + " with user id: "
+ userId
+ ", which does not correspond to an existing user");
return false;
@@ -650,7 +701,7 @@ public final class SafetyCenterService extends SystemService {
TAG,
"Called "
+ message
- + " with user id "
+ + " with user id: "
+ userId
+ ", which is an unsupported user");
return false;
@@ -676,7 +727,7 @@ public final class SafetyCenterService extends SystemService {
packageManager.getPackageUidAsUser(
packageName, PackageInfoFlags.of(0), userId);
} catch (NameNotFoundException e) {
- Log.e(TAG, "packageName=" + packageName + ", not found for userId=" + userId, e);
+ Log.w(TAG, "Package: " + packageName + ", not found for user id: " + userId, e);
return false;
}
if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) {
@@ -684,9 +735,9 @@ public final class SafetyCenterService extends SystemService {
}
if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(actualUid)) {
throw new SecurityException(
- "packageName="
+ "Package: "
+ packageName
- + ", does not belong to callingUid="
+ + ", does not belong to calling uid: "
+ callingUid);
}
return true;
@@ -719,9 +770,11 @@ public final class SafetyCenterService extends SystemService {
ParcelFileDescriptor err,
String[] args) {
return new SafetyCenterShellCommandHandler(
- getContext(), this, mDeviceSupportsSafetyCenter)
+ getContext(),
+ /* safetyCenterManager= */ this,
+ mDeviceSupportsSafetyCenter)
.exec(
- this,
+ /* target= */ this,
in.getFileDescriptor(),
out.getFileDescriptor(),
err.getFileDescriptor(),
@@ -793,12 +846,10 @@ public final class SafetyCenterService extends SystemService {
* value maps to {@link SafetyCenterManager#isSafetyCenterEnabled()}. It should only be
* registered if the device supports SafetyCenter and the {@link SafetyCenterConfig} was loaded
* successfully.
- *
- * <p>This listener is not thread-safe; it should be called on a single thread.
*/
- @NotThreadSafe
private final class SafetyCenterEnabledListener implements OnPropertiesChangedListener {
+ @GuardedBy("mApiLock")
private boolean mSafetyCenterEnabled;
@Override
@@ -807,41 +858,64 @@ public final class SafetyCenterService extends SystemService {
return;
}
boolean safetyCenterEnabled =
- properties.getBoolean(PROPERTY_SAFETY_CENTER_ENABLED, false);
- if (mSafetyCenterEnabled == safetyCenterEnabled) {
- return;
+ properties.getBoolean(PROPERTY_SAFETY_CENTER_ENABLED, SdkLevel.isAtLeastU());
+ synchronized (mApiLock) {
+ if (mSafetyCenterEnabled == safetyCenterEnabled) {
+ Log.i(
+ TAG,
+ "Safety Center is already "
+ + (mSafetyCenterEnabled ? "enabled" : "disabled")
+ + ", ignoring change");
+ return;
+ }
+ onSafetyCenterEnabledChangedLocked(safetyCenterEnabled);
}
- onSafetyCenterEnabledChanged(safetyCenterEnabled);
}
- private void setInitialState() {
+ @GuardedBy("mApiLock")
+ private void setInitialStateLocked() {
mSafetyCenterEnabled = SafetyCenterFlags.getSafetyCenterEnabled();
- Log.w(TAG, "SafetyCenter is " + (mSafetyCenterEnabled ? "enabled." : "disabled."));
+ if (mSafetyCenterEnabled) {
+ onApiInitEnabledLocked();
+ }
+ Log.i(TAG, "Safety Center is " + (mSafetyCenterEnabled ? "enabled" : "disabled"));
}
- private void onSafetyCenterEnabledChanged(boolean safetyCenterEnabled) {
- Log.w(TAG, "SafetyCenter is now " + (safetyCenterEnabled ? "enabled." : "disabled."));
-
+ @GuardedBy("mApiLock")
+ private void onSafetyCenterEnabledChangedLocked(boolean safetyCenterEnabled) {
if (safetyCenterEnabled) {
- onApiEnabled();
+ onApiEnabledLocked();
} else {
- onApiDisabled();
+ onApiDisabledLocked();
}
+
mSafetyCenterEnabled = safetyCenterEnabled;
+ Log.i(TAG, "Safety Center is now " + (mSafetyCenterEnabled ? "enabled" : "disabled"));
}
- private void onApiEnabled() {
- synchronized (mApiLock) {
- mSafetyCenterBroadcastDispatcher.sendEnabledChanged();
- }
+ @GuardedBy("mApiLock")
+ private void onApiInitEnabledLocked() {
+ mNotificationChannels.createAllChannelsForAllUsers(getContext());
}
- private void onApiDisabled() {
- synchronized (mApiLock) {
- clearDataLocked();
- mSafetyCenterListeners.clear();
- mSafetyCenterBroadcastDispatcher.sendEnabledChanged();
- }
+ @GuardedBy("mApiLock")
+ private void onApiEnabledLocked() {
+ mNotificationChannels.createAllChannelsForAllUsers(getContext());
+ mSafetyCenterBroadcastDispatcher.sendEnabledChanged();
+ }
+
+ @GuardedBy("mApiLock")
+ private void onApiDisabledLocked() {
+ // We're not clearing the Safety Center notification channels here. The reason for this
+ // is that the NotificationManager will post a runnable to cancel all associated
+ // notifications when clearing the channels. Given this happens asynchronously, this can
+ // leak between test cases and cause notifications that should be active to be cleared
+ // inadvertently. We're ok with the inconsistency because the channels are hidden
+ // somewhat deeply under Settings anyway, and we're unlikely to turn off Safety Center
+ // in production.
+ clearDataLocked();
+ mSafetyCenterListeners.clear();
+ mSafetyCenterBroadcastDispatcher.sendEnabledChanged();
}
}
@@ -870,28 +944,13 @@ public final class SafetyCenterService extends SystemService {
if (stillInFlight == null) {
return;
}
- boolean showErrorEntriesOnTimeout =
- SafetyCenterFlags.getShowErrorEntriesOnTimeout();
- boolean setError =
- showErrorEntriesOnTimeout
- && !RefreshReasons.isBackgroundRefresh(mRefreshReason);
+ boolean setError = !RefreshReasons.isBackgroundRefresh(mRefreshReason);
for (int i = 0; i < stillInFlight.size(); i++) {
mSafetyCenterDataManager.markSafetySourceRefreshTimedOut(
stillInFlight.valueAt(i), setError);
}
mSafetyCenterDataChangeNotifier.updateDataConsumers(mUserProfileGroup);
- if (!showErrorEntriesOnTimeout) {
- mSafetyCenterListeners.deliverErrorForUserProfileGroup(
- mUserProfileGroup,
- new SafetyCenterErrorDetails(
- mSafetyCenterResourcesContext.getStringByName(
- "refresh_timeout")));
- }
}
-
- Log.v(
- TAG,
- "Cleared refresh with broadcastId:" + mRefreshBroadcastId + " after a timeout");
}
@Override
@@ -938,8 +997,12 @@ public final class SafetyCenterService extends SystemService {
mSafetyCenterListeners.deliverErrorForUserProfileGroup(
mUserProfileGroup,
new SafetyCenterErrorDetails(
- mSafetyCenterResourcesContext.getStringByName(
+ mSafetyCenterResourcesApk.getStringByName(
"resolving_action_error")));
+ Log.w(
+ TAG,
+ "Resolving action timed out for: "
+ + toUserFriendlyString(mSafetyCenterIssueActionId));
}
}
@@ -961,18 +1024,38 @@ public final class SafetyCenterService extends SystemService {
/** {@link BroadcastReceiver} which handles Locale changes. */
private final class LocaleBroadcastReceiver extends BroadcastReceiver {
- private static final String TAG = "LocaleBroadcastReceiver";
+ private static final String TAG = "SafetyCenterLocaleBroad";
void register(Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- context.registerReceiverForAllUsers(this, filter, null, null);
+ context.registerReceiverForAllUsers(
+ /* receiver= */ this,
+ filter,
+ /* broadcastPermission= */ null,
+ /* scheduler= */ null);
}
@Override
public void onReceive(Context context, Intent intent) {
+ if (!SafetyCenterFlags.getSafetyCenterEnabled()) {
+ Log.i(TAG, "Safety Center is disabled, ignoring intent: " + intent);
+ return;
+ }
+
+ String action = intent.getAction();
+ if (!TextUtils.equals(action, Intent.ACTION_LOCALE_CHANGED)) {
+ Log.w(TAG, "Received unexpected action: " + action);
+ return;
+ }
+
Log.d(TAG, "Locale changed broadcast received");
- mNotificationChannels.createAllChannelsForAllUsers(getContext());
+
+ int userId = ActivityManager.getCurrentUser();
+ synchronized (mApiLock) {
+ startRefreshingSafetySourcesLocked(REFRESH_REASON_DEVICE_LOCALE_CHANGE, userId);
+ mNotificationChannels.createAllChannelsForUser(getContext(), UserHandle.of(userId));
+ }
}
}
@@ -982,62 +1065,98 @@ public final class SafetyCenterService extends SystemService {
*/
private final class UserBroadcastReceiver extends BroadcastReceiver {
- private static final String TAG = "UserBroadcastReceiver";
+ private static final String TAG = "SafetyCenterUserBroadca";
void register(Context context) {
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- context.registerReceiverForAllUsers(this, filter, null, null);
+ context.registerReceiverForAllUsers(
+ /* receiver= */ this,
+ filter,
+ /* broadcastPermission= */ null,
+ /* scheduler= */ null);
}
@Override
public void onReceive(Context context, Intent intent) {
+ if (!SafetyCenterFlags.getSafetyCenterEnabled()) {
+ Log.i(TAG, "Safety Center is disabled, ignoring intent: " + intent);
+ return;
+ }
+
String action = intent.getAction();
if (action == null) {
- Log.w(TAG, "Received broadcast with null action!");
+ Log.w(TAG, "Received broadcast with null action");
return;
}
UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
if (userHandle == null) {
- Log.w(TAG, "Received " + action + " broadcast missing user extra!");
+ Log.w(TAG, "Received action: " + action + ", but missing user extra");
return;
}
int userId = userHandle.getIdentifier();
+ Log.d(TAG, "Received action: " + action + ", for user id: " + userId);
if (!UserProfileGroup.isSupported(userId, context)) {
Log.i(
TAG,
- "Received broadcast for user id "
+ "Received broadcast for user id: "
+ userId
+ ", which is an unsupported user");
return;
}
- Log.d(TAG, "Received " + action + " broadcast for user " + userId);
switch (action) {
case Intent.ACTION_USER_REMOVED:
case Intent.ACTION_MANAGED_PROFILE_REMOVED:
- removeUser(userId, true);
+ removeUserAndData(userId);
break;
case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
- removeUser(userId, false);
- // fall through!
- case Intent.ACTION_USER_ADDED:
+ removeUser(userId);
+ break;
+ case Intent.ACTION_USER_SWITCHED:
+ if (userId != ActivityManager.getCurrentUser()) {
+ Log.w(
+ TAG,
+ "Received broadcast for user id: "
+ + userId
+ + ", which is not the current user");
+ return;
+ }
+ // Fall through
case Intent.ACTION_MANAGED_PROFILE_ADDED:
case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
- startRefreshingSafetySources(REFRESH_REASON_OTHER, userId);
- mNotificationChannels.createAllChannelsForUser(getContext(), userHandle);
+ if (!UserUtils.isUserExistent(userId, getContext())) {
+ Log.w(
+ TAG,
+ "Received broadcast for user id: "
+ + userId
+ + ", which does not exist");
+ return;
+ }
+ synchronized (mApiLock) {
+ startRefreshingSafetySourcesLocked(REFRESH_REASON_OTHER, userId);
+ mNotificationChannels.createAllChannelsForUser(getContext(), userHandle);
+ }
break;
}
}
}
+ private void removeUserAndData(@UserIdInt int userId) {
+ removeUser(userId, /* clearDataPermanently= */ true);
+ }
+
+ private void removeUser(@UserIdInt int userId) {
+ removeUser(userId, /* clearDataPermanently= */ false);
+ }
+
private void removeUser(@UserIdInt int userId, boolean clearDataPermanently) {
UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId);
synchronized (mApiLock) {
@@ -1053,31 +1172,44 @@ public final class SafetyCenterService extends SystemService {
}
}
- private void startRefreshingSafetySources(
+ @GuardedBy("mApiLock")
+ private void startRefreshingSafetySourcesLocked(
@RefreshReason int refreshReason, @UserIdInt int userId) {
- startRefreshingSafetySources(refreshReason, userId, null);
+ startRefreshingSafetySourcesLocked(
+ refreshReason,
+ UserProfileGroup.fromUser(getContext(), userId),
+ /* selectedSafetySourceIds= */ null);
}
- private void startRefreshingSafetySources(
+ @GuardedBy("mApiLock")
+ private void startRefreshingSafetySourcesLocked(
@RefreshReason int refreshReason,
@UserIdInt int userId,
+ List<String> selectedSafetySourceIds) {
+ startRefreshingSafetySourcesLocked(
+ refreshReason,
+ UserProfileGroup.fromUser(getContext(), userId),
+ selectedSafetySourceIds);
+ }
+
+ @GuardedBy("mApiLock")
+ private void startRefreshingSafetySourcesLocked(
+ @RefreshReason int refreshReason,
+ UserProfileGroup userProfileGroup,
@Nullable List<String> selectedSafetySourceIds) {
- UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId);
- synchronized (mApiLock) {
- String refreshBroadcastId =
- mSafetyCenterBroadcastDispatcher.sendRefreshSafetySources(
- refreshReason, userProfileGroup, selectedSafetySourceIds);
- if (refreshBroadcastId == null) {
- return;
- }
+ String refreshBroadcastId =
+ mSafetyCenterBroadcastDispatcher.sendRefreshSafetySources(
+ refreshReason, userProfileGroup, selectedSafetySourceIds);
+ if (refreshBroadcastId == null) {
+ return;
+ }
- RefreshTimeout refreshTimeout =
- new RefreshTimeout(refreshBroadcastId, refreshReason, userProfileGroup);
- mSafetyCenterTimeouts.add(
- refreshTimeout, SafetyCenterFlags.getRefreshSourcesTimeout(refreshReason));
+ RefreshTimeout refreshTimeout =
+ new RefreshTimeout(refreshBroadcastId, refreshReason, userProfileGroup);
+ mSafetyCenterTimeouts.add(
+ refreshTimeout, SafetyCenterFlags.getRefreshSourcesTimeout(refreshReason));
- mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup);
- }
+ mSafetyCenterDataChangeNotifier.updateDataConsumers(userProfileGroup);
}
/**
@@ -1091,7 +1223,7 @@ public final class SafetyCenterService extends SystemService {
safetyCenterIssueActionId.getSafetyCenterIssueKey();
UserProfileGroup userProfileGroup =
UserProfileGroup.fromUser(getContext(), safetyCenterIssueKey.getUserId());
- executeIssueActionInternal(safetyCenterIssueActionId, userProfileGroup, null);
+ executeIssueActionInternal(safetyCenterIssueActionId, userProfileGroup, /* taskId= */ null);
}
private void executeIssueActionInternal(
@@ -1120,16 +1252,19 @@ public final class SafetyCenterService extends SystemService {
CharSequence errorMessage;
if (safetySourceIssueAction.willResolve()) {
errorMessage =
- mSafetyCenterResourcesContext.getStringByName("resolving_action_error");
+ mSafetyCenterResourcesApk.getStringByName("resolving_action_error");
} else {
- errorMessage =
- mSafetyCenterResourcesContext.getStringByName("redirecting_error");
+ errorMessage = mSafetyCenterResourcesApk.getStringByName("redirecting_error");
}
mSafetyCenterListeners.deliverErrorForUserProfileGroup(
userProfileGroup, new SafetyCenterErrorDetails(errorMessage));
return;
}
if (safetySourceIssueAction.willResolve()) {
+ Log.d(
+ TAG,
+ "Starting resolving action for: "
+ + toUserFriendlyString(safetyCenterIssueActionId));
mSafetyCenterDataManager.markSafetyCenterIssueActionInFlight(
safetyCenterIssueActionId);
ResolvingActionTimeout resolvingActionTimeout =
@@ -1141,6 +1276,10 @@ public final class SafetyCenterService extends SystemService {
}
}
+ private boolean dispatchPendingIntent(PendingIntent pendingIntent) {
+ return dispatchPendingIntent(pendingIntent, /* launchTaskId= */ null);
+ }
+
private boolean dispatchPendingIntent(
PendingIntent pendingIntent, @Nullable Integer launchTaskId) {
if (launchTaskId != null
diff --git a/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java b/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java
index 87e3372f7..82983f0bb 100644
--- a/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java
+++ b/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java
@@ -16,7 +16,6 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOCALE_CHANGE;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
@@ -27,14 +26,13 @@ import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CEN
import static java.util.Collections.unmodifiableMap;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.RemoteException;
import android.safetycenter.ISafetyCenterManager;
import android.safetycenter.SafetyCenterManager.RefreshReason;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.BasicShellCommandHandler;
import com.android.modules.utils.build.SdkLevel;
@@ -48,7 +46,6 @@ import java.util.Map;
*
* <p>Example usage: $ adb shell cmd safety_center refresh --reason PAGE_OPEN --user 10
*/
-@RequiresApi(TIRAMISU)
final class SafetyCenterShellCommandHandler extends BasicShellCommandHandler {
private static final Map<String, Integer> REASONS = createReasonMap();
@@ -89,11 +86,18 @@ final class SafetyCenterShellCommandHandler extends BasicShellCommandHandler {
return handleDefaultCommands(cmd);
}
} catch (RemoteException | IllegalArgumentException e) {
- e.printStackTrace(getErrPrintWriter());
+ printError(e);
return 1;
}
}
+ // We want to log the stack trace on a specific PrintWriter here, this is a false positive as
+ // the warning does not consider the overload that takes a PrintWriter as an argument (yet).
+ @SuppressWarnings("CatchAndPrintStackTrace")
+ private void printError(Throwable error) {
+ error.printStackTrace(getErrPrintWriter());
+ }
+
private int onEnabled() throws RemoteException {
getOutPrintWriter().println(mSafetyCenterManager.isSafetyCenterEnabled());
return 0;
diff --git a/service/java/com/android/safetycenter/SafetyCenterTimeouts.java b/service/java/com/android/safetycenter/SafetyCenterTimeouts.java
index f8bfd691e..b37951fb1 100644
--- a/service/java/com/android/safetycenter/SafetyCenterTimeouts.java
+++ b/service/java/com/android/safetycenter/SafetyCenterTimeouts.java
@@ -16,12 +16,8 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import android.os.Handler;
-import androidx.annotation.RequiresApi;
-
import com.android.permission.util.ForegroundThread;
import java.io.PrintWriter;
@@ -36,7 +32,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterTimeouts {
diff --git a/service/java/com/android/safetycenter/SafetySourceIssueInfo.java b/service/java/com/android/safetycenter/SafetySourceIssueInfo.java
index 51e6567d7..0dfa7c814 100644
--- a/service/java/com/android/safetycenter/SafetySourceIssueInfo.java
+++ b/service/java/com/android/safetycenter/SafetySourceIssueInfo.java
@@ -16,8 +16,6 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
import android.annotation.UserIdInt;
@@ -25,8 +23,6 @@ import android.safetycenter.SafetySourceIssue;
import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
-import androidx.annotation.RequiresApi;
-
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
import java.util.Objects;
@@ -36,7 +32,6 @@ import java.util.Objects;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetySourceIssueInfo {
private final SafetySourceIssue mSafetySourceIssue;
@@ -65,6 +60,7 @@ public final class SafetySourceIssueInfo {
public SafetyCenterIssueKey getSafetyCenterIssueKey() {
return mSafetyCenterIssueKey;
}
+
/** Returns the {@link SafetySourceIssue}. */
public SafetySourceIssue getSafetySourceIssue() {
return mSafetySourceIssue;
diff --git a/service/java/com/android/safetycenter/SafetySourceKey.java b/service/java/com/android/safetycenter/SafetySourceKey.java
index 511fbef73..9e1400e30 100644
--- a/service/java/com/android/safetycenter/SafetySourceKey.java
+++ b/service/java/com/android/safetycenter/SafetySourceKey.java
@@ -16,13 +16,9 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import android.annotation.UserIdInt;
import android.safetycenter.SafetySourceData;
-import androidx.annotation.RequiresApi;
-
import java.util.Objects;
/**
@@ -32,7 +28,7 @@ import java.util.Objects;
* @hide
*/
// TODO(b/219697341): Look into using AutoValue for this data class.
-@RequiresApi(TIRAMISU)
+
public final class SafetySourceKey {
private final String mSourceId;
diff --git a/service/java/com/android/safetycenter/SafetySources.java b/service/java/com/android/safetycenter/SafetySources.java
index c0b0bdc48..02d83d27b 100644
--- a/service/java/com/android/safetycenter/SafetySources.java
+++ b/service/java/com/android/safetycenter/SafetySources.java
@@ -16,20 +16,15 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import android.safetycenter.SafetySourceData;
import android.safetycenter.config.SafetySource;
import android.util.Log;
-import androidx.annotation.RequiresApi;
-
/**
* A helper class to facilitate working with {@link SafetySource} objects.
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetySources {
private static final String TAG = "SafetySources";
diff --git a/service/java/com/android/safetycenter/SafetySourcesGroups.java b/service/java/com/android/safetycenter/SafetySourcesGroups.java
index 5233302aa..a86eccada 100644
--- a/service/java/com/android/safetycenter/SafetySourcesGroups.java
+++ b/service/java/com/android/safetycenter/SafetySourcesGroups.java
@@ -16,16 +16,11 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import android.safetycenter.config.SafetySourcesGroup;
-import androidx.annotation.RequiresApi;
-
import com.android.modules.utils.build.SdkLevel;
/** Static utilities for working with {@link SafetySourcesGroup} objects. */
-@RequiresApi(TIRAMISU)
final class SafetySourcesGroups {
/**
diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java
index 8d3adc573..74b9b136f 100644
--- a/service/java/com/android/safetycenter/UserProfileGroup.java
+++ b/service/java/com/android/safetycenter/UserProfileGroup.java
@@ -16,11 +16,8 @@
package com.android.safetycenter;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static java.util.Objects.requireNonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -30,7 +27,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.util.UserUtils;
@@ -45,7 +42,6 @@ import java.util.Objects;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class UserProfileGroup {
private static final String TAG = "UserProfileGroup";
@@ -141,9 +137,9 @@ public final class UserProfileGroup {
Arrays.copyOf(
managedRunningProfilesUserIds, managedRunningProfilesUserIdsLen));
if (!userProfileGroup.contains(userId)) {
- Log.w(
+ Log.i(
TAG,
- "User id " + userId + " does not belong to " + userProfileGroup,
+ "User id: " + userId + " does not belong to: " + userProfileGroup,
new Exception());
}
return userProfileGroup;
@@ -167,7 +163,8 @@ public final class UserProfileGroup {
return context;
} else {
try {
- return context.createPackageContextAsUser(context.getPackageName(), 0, userHandle);
+ return context.createPackageContextAsUser(
+ context.getPackageName(), /* flags= */ 0, userHandle);
} catch (PackageManager.NameNotFoundException doesNotHappen) {
throw new IllegalStateException(doesNotHappen);
}
@@ -232,9 +229,9 @@ public final class UserProfileGroup {
profileParentAndManagedRunningProfilesUserIds[0] = mProfileParentUserId;
System.arraycopy(
mManagedRunningProfilesUserIds,
- 0,
+ /* srcPos= */ 0,
profileParentAndManagedRunningProfilesUserIds,
- 1,
+ /* destPos= */ 1,
mManagedRunningProfilesUserIds.length);
return profileParentAndManagedRunningProfilesUserIds;
}
diff --git a/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java b/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java
index 5db3cfbad..e46ba2f4d 100644
--- a/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java
+++ b/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java
@@ -16,9 +16,6 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -30,7 +27,7 @@ import android.safetycenter.SafetySourceIssue;
import android.safetycenter.SafetySourceStatus;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
import com.android.safetycenter.PendingIntentFactory;
@@ -41,9 +38,10 @@ import java.util.List;
/**
* A class to work around an issue with the {@code AndroidLockScreen} safety source, by potentially
* overriding its {@link SafetySourceData}.
+ *
+ * @hide
*/
-@RequiresApi(TIRAMISU)
-final class AndroidLockScreenFix {
+public final class AndroidLockScreenFix {
private static final String TAG = "AndroidLockScreenFix";
@@ -73,7 +71,7 @@ final class AndroidLockScreenFix {
* created (the key does take into account the request code).
*/
@Nullable
- static SafetySourceData maybeOverrideSafetySourceData(
+ public static SafetySourceData maybeOverrideSafetySourceData(
Context context, String sourceId, @Nullable SafetySourceData safetySourceData) {
if (safetySourceData == null) {
return null;
@@ -119,7 +117,9 @@ final class AndroidLockScreenFix {
safetySourceStatus.getSeverityLevel())
.setPendingIntent(
overridePendingIntent(
- context, safetySourceStatus.getPendingIntent(), false))
+ context,
+ safetySourceStatus.getPendingIntent(),
+ /* isIconAction= */ false))
.setEnabled(safetySourceStatus.isEnabled());
SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction();
if (iconAction != null) {
@@ -134,7 +134,8 @@ final class AndroidLockScreenFix {
Context context, SafetySourceStatus.IconAction iconAction) {
return new SafetySourceStatus.IconAction(
iconAction.getIconType(),
- overridePendingIntent(context, iconAction.getPendingIntent(), true));
+ overridePendingIntent(
+ context, iconAction.getPendingIntent(), /* isIconAction= */ true));
}
private static SafetySourceIssue overrideTiramisuSafetySourceIssue(
@@ -163,7 +164,8 @@ final class AndroidLockScreenFix {
return new SafetySourceIssue.Action.Builder(
action.getId(),
action.getLabel(),
- overridePendingIntent(context, action.getPendingIntent(), false))
+ overridePendingIntent(
+ context, action.getPendingIntent(), /* isIconAction= */ false))
.setWillResolve(action.willResolve())
.setSuccessMessage(action.getSuccessMessage())
.build();
@@ -206,7 +208,7 @@ final class AndroidLockScreenFix {
// This is important because there are scenarios where the Settings app provides different
// pending intents (e.g. in the work profile), and in this case we shouldn't override them.
if (isIconAction) {
- Log.w(
+ Log.i(
TAG,
"Replacing " + ANDROID_LOCK_SCREEN_SOURCE_ID + " icon action pending intent");
return PendingIntentFactory.getActivityPendingIntent(
@@ -215,7 +217,7 @@ final class AndroidLockScreenFix {
newLockScreenIconActionIntent(settingsPackageName),
PendingIntent.FLAG_IMMUTABLE);
}
- Log.w(TAG, "Replacing " + ANDROID_LOCK_SCREEN_SOURCE_ID + " entry or issue pending intent");
+ Log.i(TAG, "Replacing " + ANDROID_LOCK_SCREEN_SOURCE_ID + " entry or issue pending intent");
return PendingIntentFactory.getActivityPendingIntent(
settingsPackageContext,
ANDROID_LOCK_SCREEN_ENTRY_REQ_CODE,
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java
index 734732401..3925b64aa 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java
@@ -16,11 +16,8 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.safetycenter.logging.SafetyCenterStatsdLogger.toSystemEventResult;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.safetycenter.SafetyCenterData;
@@ -30,9 +27,10 @@ import android.safetycenter.SafetySourceErrorDetails;
import android.safetycenter.SafetySourceIssue;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySourcesGroup;
+import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.safetycenter.ApiLock;
import com.android.safetycenter.SafetyCenterConfigReader;
@@ -60,7 +58,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterDataManager {
@@ -88,7 +85,6 @@ public final class SafetyCenterDataManager {
new SafetyCenterIssueDismissalRepository(apiLock, safetyCenterConfigReader);
mSafetySourceDataRepository =
new SafetySourceDataRepository(
- context,
mSafetyCenterInFlightIssueActionRepository,
mSafetyCenterIssueDismissalRepository);
mSafetyCenterIssueRepository =
@@ -151,7 +147,12 @@ public final class SafetyCenterDataManager {
mSafetySourceDataRepository.setSafetySourceData(
safetySourceData, safetySourceId, userId);
boolean eventCausedChange =
- processSafetyEvent(safetySourceId, safetyEvent, userId, false, sourceDataDiffers);
+ processSafetyEvent(
+ safetySourceId,
+ safetyEvent,
+ userId,
+ /* isError= */ false,
+ sourceDataDiffers);
boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange;
if (safetyCenterDataChanged) {
@@ -199,7 +200,7 @@ public final class SafetyCenterDataManager {
String packageName,
@UserIdInt int userId) {
if (!mSafetySourceDataValidator.validateRequest(
- null, safetySourceId, packageName, userId)) {
+ /* safetySourceData= */ null, safetySourceId, packageName, userId)) {
return false;
}
SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
@@ -217,7 +218,12 @@ public final class SafetyCenterDataManager {
mSafetySourceDataRepository.reportSafetySourceError(
safetySourceErrorDetails, safetySourceId, userId);
boolean eventCausedChange =
- processSafetyEvent(safetySourceId, safetyEvent, userId, true, sourceDataDiffers);
+ processSafetyEvent(
+ safetySourceId,
+ safetyEvent,
+ userId,
+ /* isError= */ true,
+ sourceDataDiffers);
boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange;
if (safetyCenterDataChanged) {
@@ -225,7 +231,12 @@ public final class SafetyCenterDataManager {
}
mSafetySourceStateCollectedLogger.writeSourceUpdatedAtom(
- key, null, refreshReason, sourceDataDiffers, userId, safetyEvent);
+ key,
+ /* safetySourceData= */ null,
+ refreshReason,
+ sourceDataDiffers,
+ userId,
+ safetyEvent);
return safetyCenterDataChanged;
}
@@ -389,6 +400,11 @@ public final class SafetyCenterDataManager {
safetyCenterIssueActionId);
}
+ /** Returns a list of IDs of in-flight actions for the given source and user */
+ ArraySet<SafetyCenterIssueActionId> getInFlightActions(String sourceId, @UserIdInt int userId) {
+ return mSafetyCenterInFlightIssueActionRepository.getInFlightActions(sourceId, userId);
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////
////////////////////// SafetySourceDataRepository ///////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -407,7 +423,7 @@ public final class SafetyCenterDataManager {
public SafetySourceData getSafetySourceData(
String safetySourceId, String packageName, @UserIdInt int userId) {
if (!mSafetySourceDataValidator.validateRequest(
- null, safetySourceId, packageName, userId)) {
+ /* safetySourceData= */ null, safetySourceId, packageName, userId)) {
return null;
}
return mSafetySourceDataRepository.getSafetySourceData(
@@ -482,7 +498,7 @@ public final class SafetyCenterDataManager {
case SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
String refreshBroadcastId = safetyEvent.getRefreshBroadcastId();
if (refreshBroadcastId == null) {
- Log.w(TAG, "No refresh broadcast id in SafetyEvent of type " + type);
+ Log.w(TAG, "No refresh broadcast id in SafetyEvent of type: " + type);
return false;
}
return mSafetyCenterRefreshTracker.reportSourceRefreshCompleted(
@@ -491,12 +507,12 @@ public final class SafetyCenterDataManager {
case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
String safetySourceIssueId = safetyEvent.getSafetySourceIssueId();
if (safetySourceIssueId == null) {
- Log.w(TAG, "No safety source issue id in SafetyEvent of type " + type);
+ Log.w(TAG, "No safety source issue id in SafetyEvent of type: " + type);
return false;
}
String safetySourceIssueActionId = safetyEvent.getSafetySourceIssueActionId();
if (safetySourceIssueActionId == null) {
- Log.w(TAG, "No safety source issue action id in SafetyEvent of type " + type);
+ Log.w(TAG, "No safety source issue action id in SafetyEvent of type: " + type);
return false;
}
SafetyCenterIssueKey safetyCenterIssueKey =
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java
index 4fa6e5363..bcf83dd9e 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java
@@ -16,19 +16,17 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.SystemClock;
import android.safetycenter.SafetySourceIssue;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
@@ -42,7 +40,6 @@ import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
/** Maintains data about in-flight issue actions. */
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterInFlightIssueActionRepository {
@@ -113,6 +110,19 @@ final class SafetyCenterInFlightIssueActionRepository {
return mSafetyCenterIssueActionsInFlight.containsKey(safetyCenterIssueActionId);
}
+ /** Returns a list of IDs of in-flight actions for the given source and user */
+ ArraySet<SafetyCenterIssueActionId> getInFlightActions(String sourceId, @UserIdInt int userId) {
+ ArraySet<SafetyCenterIssueActionId> result = new ArraySet<>();
+ for (int i = 0; i < mSafetyCenterIssueActionsInFlight.size(); i++) {
+ SafetyCenterIssueActionId actionId = mSafetyCenterIssueActionsInFlight.keyAt(i);
+ SafetyCenterIssueKey issueKey = actionId.getSafetyCenterIssueKey();
+ if (sourceId.equals(issueKey.getSafetySourceId()) && issueKey.getUserId() == userId) {
+ result.add(actionId);
+ }
+ }
+ return result;
+ }
+
/**
* Returns {@link SafetySourceIssue.Action} identified by the given {@link
* SafetyCenterIssueActionId} and {@link SafetySourceIssue}.
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java
index d5218a0aa..1a86640dd 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java
@@ -16,7 +16,6 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
@@ -27,13 +26,13 @@ import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.safetycenter.config.SafetySourcesGroup;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.safetycenter.SafetySourceIssueInfo;
@@ -48,7 +47,6 @@ import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
/** Deduplicates issues based on deduplication info provided by the source and the issue. */
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterIssueDeduplicator {
@@ -56,7 +54,6 @@ final class SafetyCenterIssueDeduplicator {
private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository;
- @RequiresApi(TIRAMISU)
SafetyCenterIssueDeduplicator(
SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository) {
this.mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository;
@@ -72,7 +69,7 @@ final class SafetyCenterIssueDeduplicator {
* <p>In case any issue, in the bucket of duplicate issues, was dismissed, all issues of the
* same or lower severity will be dismissed as well.
*
- * @return deduplicated list of issues, and some other information gathere in the deduplication
+ * @return deduplicated list of issues, and some other information gathered in the deduplication
* process
*/
@RequiresApi(UPSIDE_DOWN_CAKE)
@@ -123,7 +120,7 @@ final class SafetyCenterIssueDeduplicator {
for (int i = 0; i < dedupBuckets.size(); i++) {
List<SafetySourceIssueInfo> duplicates = dedupBuckets.valueAt(i);
if (duplicates.isEmpty()) {
- Log.w(TAG, "List of duplicates in a dedupBucket is empty");
+ Log.w(TAG, "List of duplicates in a deduplication bucket is empty");
continue;
}
@@ -164,7 +161,7 @@ final class SafetyCenterIssueDeduplicator {
}
/**
- * Handles dismissals logic: in each bucket, dismissal details of the top (highest priority)
+ * Handles dismissals logic: in each bucket, dismissal details of the highest priority (top)
* dismissed issue will be copied to all other duplicate issues in that bucket, that are of
* equal or lower severity (not priority). Notification-dismissal details are handled similarly.
*/
@@ -329,7 +326,6 @@ final class SafetyCenterIssueDeduplicator {
}
/** Encapsulates deduplication result along with some additional information. */
- @RequiresApi(TIRAMISU) // to simplify code and minimize code path differences across SDKs
static final class DeduplicationInfo {
private final List<SafetySourceIssueInfo> mDeduplicatedIssues;
private final List<SafetySourceIssueInfo> mFilteredOutDuplicates;
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java
index bc9bfb2d6..c84aa54f3 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueDismissalRepository.java
@@ -16,11 +16,8 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.content.ApexEnvironment;
@@ -30,7 +27,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.BackgroundThread;
import com.android.safetycenter.ApiLock;
@@ -48,6 +45,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@@ -66,7 +64,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterIssueDismissalRepository {
@@ -130,11 +127,7 @@ final class SafetyCenterIssueDismissalRepository {
Duration timeSinceLastDismissal = Duration.between(dismissedAt, Instant.now());
boolean isTimeToResurface = timeSinceLastDismissal.compareTo(delay) >= 0;
- if (isTimeToResurface) {
- return false;
- }
-
- return true;
+ return !isTimeToResurface;
}
/**
@@ -342,7 +335,7 @@ final class SafetyCenterIssueDismissalRepository {
* and all following calls won't have any effect.
*/
void resurfaceHiddenIssueAfterPeriod(SafetyCenterIssueKey safetyCenterIssueKey) {
- IssueData issueData = getOrWarn(safetyCenterIssueKey, "resurfaceIssueAfterPeriod");
+ IssueData issueData = getOrWarn(safetyCenterIssueKey, "resurfacing hidden issue");
if (issueData == null) {
return;
}
@@ -439,13 +432,22 @@ final class SafetyCenterIssueDismissalRepository {
fout.flush();
try {
Files.copy(issueDismissalRepositoryFile.toPath(), new FileOutputStream(fd));
+ fout.println();
+ } catch (NoSuchFileException e) {
+ fout.println("<No File> (equivalent to empty issue list)");
} catch (IOException e) {
- // TODO(b/266202404)
- e.printStackTrace(fout);
+ printError(e, fout);
}
fout.println();
}
+ // We want to dump the stack trace on a specific PrintWriter here, this is a false positive as
+ // the warning does not consider the overload that takes a PrintWriter as an argument (yet).
+ @SuppressWarnings("CatchAndPrintStackTrace")
+ private void printError(Throwable error, PrintWriter fout) {
+ error.printStackTrace(fout);
+ }
+
@Nullable
private IssueData getOrWarn(SafetyCenterIssueKey issueKey, String reason) {
IssueData issueData = mIssues.get(issueKey);
@@ -492,9 +494,9 @@ final class SafetyCenterIssueDismissalRepository {
try {
persistedSafetyCenterIssues =
SafetyCenterIssuesPersistence.read(getIssueDismissalRepositoryFile());
- Log.i(TAG, "Safety Center persisted issues read successfully");
+ Log.d(TAG, "Safety Center persisted issues read successfully");
} catch (PersistenceException e) {
- Log.e(TAG, "Cannot read Safety Center persisted issues", e);
+ Log.w(TAG, "Cannot read Safety Center persisted issues", e);
}
load(persistedSafetyCenterIssues);
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java
index 566c28c1e..2e6f707a3 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterIssueRepository.java
@@ -16,8 +16,6 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.safetycenter.data.SafetyCenterIssueDeduplicator.DeduplicationInfo;
import static java.util.Collections.emptyList;
@@ -32,8 +30,6 @@ import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
import android.util.SparseArray;
-import androidx.annotation.RequiresApi;
-
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.SafetyCenterConfigReader;
@@ -56,7 +52,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>Responsible for generating lists of issues and deduplication of issues.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterIssueRepository {
@@ -92,19 +87,6 @@ final class SafetyCenterIssueRepository {
* Updates the class as per the current state of issues. Should be called after any state update
* that can affect issues.
*/
- void updateIssues(UserProfileGroup userProfileGroup) {
- updateIssues(userProfileGroup.getProfileParentUserId(), /* isManagedProfile= */ false);
-
- int[] managedProfileUserIds = userProfileGroup.getManagedProfilesUserIds();
- for (int i = 0; i < managedProfileUserIds.length; i++) {
- updateIssues(managedProfileUserIds[i], /* isManagedProfile= */ true);
- }
- }
-
- /**
- * Updates the class as per the current state of issues. Should be called after any state update
- * that can affect issues.
- */
void updateIssues(@UserIdInt int userId) {
updateIssues(userId, UserUtils.isManagedProfile(userId, mContext));
}
diff --git a/service/java/com/android/safetycenter/data/SafetyEventFix.java b/service/java/com/android/safetycenter/data/SafetyEventFix.java
new file mode 100644
index 000000000..4050eddfb
--- /dev/null
+++ b/service/java/com/android/safetycenter/data/SafetyEventFix.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.data;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED;
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+
+import android.annotation.UserIdInt;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
+
+import java.util.List;
+
+/**
+ * Works around sources sending unexpected {@link SafetyEvent}s by optionally replacing them using
+ * heuristics based on the incoming {@link SafetySourceData} and Safety Center's current state.
+ *
+ * @hide
+ */
+public final class SafetyEventFix {
+
+ private static final String TAG = "SafetyEventFix";
+
+ private SafetyEventFix() {}
+
+ /**
+ * Optionally returns a new {@link SafetyEvent} if heuristics indicate that the one provided by
+ * the source is inappropriate, otherwise returns the source-provided event unchanged.
+ *
+ * <p>If the incoming event has type {@link SafetyEvent#SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED}
+ * but the {@link SafetySourceData} no longer includes an issue, for which Safety Center has a
+ * record of an in-flight, resolving action, then the event will be exchanged for a new one of
+ * type {@link SafetyEvent#SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED}.
+ */
+ public static SafetyEvent maybeOverrideSafetyEvent(
+ SafetyCenterDataManager dataManager,
+ String safetySourceId,
+ @Nullable SafetySourceData safetySourceData,
+ SafetyEvent safetyEvent,
+ @UserIdInt int userId) {
+ if (safetySourceData == null
+ || safetyEvent.getType() != SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) {
+ return safetyEvent;
+ }
+
+ ArraySet<SafetyCenterIssueActionId> possiblySuccessfulActions =
+ dataManager.getInFlightActions(safetySourceId, userId);
+
+ if (possiblySuccessfulActions.isEmpty()) {
+ return safetyEvent;
+ }
+
+ // Discard any actions for which the issue is still present in the latest source data, they
+ // cannot have been resolved successfully!
+ ArraySet<String> presentSourceIssueIds = getSourceIssueIds(safetySourceData);
+ for (int i = possiblySuccessfulActions.size() - 1; i >= 0; i--) {
+ String sourceIssueId =
+ possiblySuccessfulActions
+ .valueAt(i)
+ .getSafetyCenterIssueKey()
+ .getSafetySourceIssueId();
+ if (presentSourceIssueIds.contains(sourceIssueId)) {
+ possiblySuccessfulActions.removeAt(i);
+ }
+ }
+
+ if (possiblySuccessfulActions.isEmpty()) {
+ return safetyEvent;
+ }
+
+ if (possiblySuccessfulActions.size() > 1) {
+ Log.i(TAG, "Multiple actions resolved, not overriding " + safetyEvent);
+ return safetyEvent;
+ }
+
+ SafetyCenterIssueActionId successfulAction = possiblySuccessfulActions.valueAt(0);
+ SafetyEvent replacement = newActionSucceededEvent(successfulAction);
+ Log.i(TAG, "Replacing incoming " + safetyEvent + " with " + replacement);
+ return replacement;
+ }
+
+ private static ArraySet<String> getSourceIssueIds(SafetySourceData safetySourceData) {
+ List<SafetySourceIssue> issues = safetySourceData.getIssues();
+ ArraySet<String> issueIds = new ArraySet<>(issues.size());
+ for (int i = 0; i < issues.size(); i++) {
+ issueIds.add(issues.get(i).getId());
+ }
+ return issueIds;
+ }
+
+ private static SafetyEvent newActionSucceededEvent(SafetyCenterIssueActionId actionId) {
+ return new SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED)
+ .setSafetySourceIssueId(actionId.getSafetyCenterIssueKey().getSafetySourceIssueId())
+ .setSafetySourceIssueActionId(actionId.getSafetySourceIssueActionId())
+ .build();
+ }
+}
diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java b/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java
index b47f7925e..15e420dce 100644
--- a/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java
+++ b/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java
@@ -16,18 +16,14 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__DATA_PROVIDED;
import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__NO_DATA_PROVIDED;
import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__REFRESH_TIMEOUT;
import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_CLEARED;
import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR;
-import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.annotation.UserIdInt;
-import android.content.Context;
import android.os.SystemClock;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyEvent;
@@ -38,7 +34,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.safetycenter.SafetySourceKey;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
@@ -57,7 +53,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetySourceDataRepository {
@@ -68,16 +63,13 @@ final class SafetySourceDataRepository {
private final ArrayMap<SafetySourceKey, Long> mSafetySourceLastUpdated = new ArrayMap<>();
private final ArrayMap<SafetySourceKey, Integer> mSourceStates = new ArrayMap<>();
- private final Context mContext;
private final SafetyCenterInFlightIssueActionRepository
mSafetyCenterInFlightIssueActionRepository;
private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository;
SafetySourceDataRepository(
- Context context,
SafetyCenterInFlightIssueActionRepository safetyCenterInFlightIssueActionRepository,
SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository) {
- mContext = context;
mSafetyCenterInFlightIssueActionRepository = safetyCenterInFlightIssueActionRepository;
mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository;
}
@@ -102,10 +94,6 @@ final class SafetySourceDataRepository {
String safetySourceId,
@UserIdInt int userId) {
SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
- safetySourceData =
- AndroidLockScreenFix.maybeOverrideSafetySourceData(
- mContext, safetySourceId, safetySourceData);
-
boolean sourceDataDiffers = !Objects.equals(safetySourceData, mSafetySourceData.get(key));
boolean removedSourceError = mSafetySourceErrors.remove(key);
@@ -339,9 +327,9 @@ final class SafetySourceDataRepository {
SafetySourceKey key = mSafetySourceErrors.valueAt(i);
fout.println("\t[" + i + "] " + key);
}
+ fout.println();
dumpArrayMap(fout, mSafetySourceLastUpdated, "LAST UPDATED");
dumpArrayMap(fout, mSourceStates, "SOURCE STATES");
- fout.println();
}
private static <K, V> void dumpArrayMap(PrintWriter fout, ArrayMap<K, V> map, String label) {
@@ -350,5 +338,6 @@ final class SafetySourceDataRepository {
for (int i = 0; i < count; i++) {
fout.println("\t[" + i + "] " + map.keyAt(i) + " -> " + map.valueAt(i));
}
+ fout.println();
}
}
diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java b/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java
index cc265d7f9..8a3151c9f 100644
--- a/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java
+++ b/service/java/com/android/safetycenter/data/SafetySourceDataValidator.java
@@ -16,9 +16,6 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -29,7 +26,7 @@ import android.safetycenter.SafetySourceStatus;
import android.safetycenter.config.SafetySource;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
@@ -48,11 +45,10 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetySourceDataValidator {
- private static final String TAG = "SafetySourceDataValidator";
+ private static final String TAG = "SafetySourceDataValidat";
private final Context mContext;
private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@@ -192,13 +188,13 @@ final class SafetySourceDataValidator {
&& !checkCerts(
packageName,
SafetyCenterFlags.getAdditionalAllowedPackageCerts(packageName))) {
- Log.e(
+ Log.w(
TAG,
- "Package "
+ "Package: "
+ packageName
- + " for source "
+ + ", for source: "
+ safetySourceId
- + " signed with invalid signature");
+ + " is signed with invalid signature");
throw new IllegalArgumentException("Invalid signature for package " + packageName);
}
}
@@ -210,7 +206,7 @@ final class SafetySourceDataValidator {
byte[] certificate = new Signature(certHash).toByteArray();
if (mPackageManager.hasSigningCertificate(
packageName, certificate, PackageManager.CERT_INPUT_SHA256)) {
- Log.d(TAG, "Package " + packageName + " has expected signature");
+ Log.v(TAG, "Package: " + packageName + " has expected signature");
hasMatchingCert = true;
}
} catch (IllegalArgumentException e) {
diff --git a/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java b/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java
index b6bf280ae..e73459598 100644
--- a/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java
+++ b/service/java/com/android/safetycenter/data/SafetySourceStateCollectedLogger.java
@@ -16,10 +16,7 @@
package com.android.safetycenter.data;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import android.annotation.ElapsedRealtimeLong;
-import android.annotation.Nullable;
import android.content.Context;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.SafetyEvent;
@@ -27,7 +24,7 @@ import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceIssue;
import android.safetycenter.SafetySourceStatus;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.SafetySourceIssueInfo;
@@ -44,7 +41,6 @@ import javax.annotation.concurrent.NotThreadSafe;
* Collates information from various data-related classes and uses that information to log {@code
* SafetySourceStateCollected} atoms.
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetySourceStateCollectedLogger {
@@ -132,10 +128,11 @@ final class SafetySourceStateCollectedLogger {
}
}
+ Integer severityLevel = maxSeverityLevel > Integer.MIN_VALUE ? maxSeverityLevel : null;
SafetyCenterStatsdLogger.writeSafetySourceStateCollected(
sourceKey.getSourceId(),
isManagedProfile,
- maxSeverityLevel > Integer.MIN_VALUE ? maxSeverityLevel : null,
+ severityLevel,
openIssuesCount,
dismissedIssuesCount,
getDuplicateCount(sourceKey),
diff --git a/service/java/com/android/safetycenter/data/package-info.java b/service/java/com/android/safetycenter/data/package-info.java
index 597847505..e82755370 100644
--- a/service/java/com/android/safetycenter/data/package-info.java
+++ b/service/java/com/android/safetycenter/data/package-info.java
@@ -14,6 +14,8 @@
* limitations under the License.
*/
@NonNullByDefault
+@RequiresTiramisuByDefault
package com.android.safetycenter.data;
import com.android.safetycenter.annotations.NonNullByDefault;
+import com.android.safetycenter.annotations.RequiresTiramisuByDefault;
diff --git a/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java b/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java
index d9514f56e..168d73a0f 100644
--- a/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java
+++ b/service/java/com/android/safetycenter/logging/SafetyCenterPullAtomCallback.java
@@ -16,8 +16,6 @@
package com.android.safetycenter.logging;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.permission.PermissionStatsLog.SAFETY_STATE;
import android.annotation.UserIdInt;
@@ -30,8 +28,6 @@ import android.safetycenter.config.SafetySourcesGroup;
import android.util.Log;
import android.util.StatsEvent;
-import androidx.annotation.RequiresApi;
-
import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.PermissionStatsLog;
@@ -56,7 +52,6 @@ import java.util.List;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback {
private static final String TAG = "SafetyCenterPullAtom";
@@ -93,17 +88,17 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback
return StatsManager.PULL_SKIP;
}
if (!SafetyCenterFlags.getSafetyCenterEnabled()) {
- Log.w(TAG, "Attempt to pull SAFETY_STATE, but Safety Center is disabled");
+ Log.i(TAG, "Attempt to pull SAFETY_STATE, but Safety Center is disabled");
return StatsManager.PULL_SKIP;
}
List<UserProfileGroup> userProfileGroups =
UserProfileGroup.getAllUserProfileGroups(mContext);
synchronized (mApiLock) {
if (!SafetyCenterFlags.getAllowStatsdLogging()) {
- Log.w(TAG, "Skipping pulling and writing atoms due to logging being disabled");
+ Log.i(TAG, "Skipping pulling and writing atoms due to logging being disabled");
return StatsManager.PULL_SKIP;
}
- Log.i(TAG, "Pulling and writing atoms…");
+ Log.d(TAG, "Pulling and writing atoms…");
for (int i = 0; i < userProfileGroups.size(); i++) {
UserProfileGroup userProfileGroup = userProfileGroups.get(i);
List<SafetySourcesGroup> loggableGroups =
@@ -111,8 +106,8 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback
statsEvents.add(
createOverallSafetyStateAtomLocked(userProfileGroup, loggableGroups));
// The SAFETY_SOURCE_STATE_COLLECTED atoms are written instead of being pulled,
- // they do not support pull but we want to collect them at the same time as
- // the above pulled atom.
+ // as they do not support pull. We still want to collect them at the same time as
+ // the above pulled atom, which is why they're written here.
writeSafetySourceStateCollectedAtomsLocked(userProfileGroup, loggableGroups);
}
}
@@ -165,7 +160,8 @@ public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback
int[] managedIds = userProfileGroup.getManagedRunningProfilesUserIds();
for (int k = 0; k < managedIds.length; k++) {
- writeSafetySourceStateCollectedAtomLocked(loggableSource, managedIds[k], true);
+ writeSafetySourceStateCollectedAtomLocked(
+ loggableSource, managedIds[k], /* isUserManaged= */ true);
}
}
}
diff --git a/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java b/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java
index 8ca662d27..710c3f7ac 100644
--- a/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java
+++ b/service/java/com/android/safetycenter/logging/SafetyCenterStatsdLogger.java
@@ -16,8 +16,6 @@
package com.android.safetycenter.logging;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED;
import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__ACTION__ISSUE_PRIMARY_ACTION_CLICKED;
import static com.android.permission.PermissionStatsLog.SAFETY_CENTER_INTERACTION_REPORTED__ACTION__ISSUE_SECONDARY_ACTION_CLICKED;
@@ -74,7 +72,6 @@ import static com.android.permission.PermissionStatsLog.SAFETY_STATE__OVERALL_SE
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
-import android.annotation.Nullable;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.SafetyCenterManager.RefreshRequestType;
import android.safetycenter.SafetyCenterStatus;
@@ -83,7 +80,7 @@ import android.safetycenter.SafetySourceData;
import android.util.Log;
import android.util.StatsEvent;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.PermissionStatsLog;
import com.android.safetycenter.SafetyCenterFlags;
@@ -101,7 +98,6 @@ import java.time.Duration;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetyCenterStatsdLogger {
private static final String TAG = "SafetyCenterStatsdLog";
diff --git a/service/java/com/android/safetycenter/logging/package-info.java b/service/java/com/android/safetycenter/logging/package-info.java
index dcc1828b4..47575f2d9 100644
--- a/service/java/com/android/safetycenter/logging/package-info.java
+++ b/service/java/com/android/safetycenter/logging/package-info.java
@@ -14,6 +14,8 @@
* limitations under the License.
*/
@NonNullByDefault
+@RequiresTiramisuByDefault
package com.android.safetycenter.logging;
import com.android.safetycenter.annotations.NonNullByDefault;
+import com.android.safetycenter.annotations.RequiresTiramisuByDefault;
diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java
index e2df717a7..ccd2bfabc 100644
--- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java
+++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationChannels.java
@@ -16,11 +16,6 @@
package com.android.safetycenter.notifications;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
@@ -31,10 +26,11 @@ import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceIssue;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.permission.util.UserUtils;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.SafetyCenterFlags;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.util.List;
@@ -43,7 +39,6 @@ import java.util.List;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetyCenterNotificationChannels {
private static final String TAG = "SafetyCenterNC";
@@ -53,11 +48,10 @@ public final class SafetyCenterNotificationChannels {
private static final String CHANNEL_ID_RECOMMENDATION = "safety_center_recommendation";
private static final String CHANNEL_ID_CRITICAL_WARNING = "safety_center_critical_warning";
- private final SafetyCenterResourcesContext mResourcesContext;
+ private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
- public SafetyCenterNotificationChannels(
- SafetyCenterResourcesContext safetyCenterResourceContext) {
- mResourcesContext = safetyCenterResourceContext;
+ public SafetyCenterNotificationChannels(SafetyCenterResourcesApk safetyCenterResourcesApk) {
+ mSafetyCenterResourcesApk = safetyCenterResourcesApk;
}
/** Returns a {@link NotificationManager} which will send notifications to the given user. */
@@ -70,18 +64,25 @@ public final class SafetyCenterNotificationChannels {
? contextAsUser.getSystemService(NotificationManager.class)
: null;
if (notificationManager == null) {
- Log.w(TAG, "Could not retrieve NotificationManager for user " + userHandle);
+ Log.w(
+ TAG,
+ "Could not retrieve NotificationManager for user id: "
+ + userHandle.getIdentifier());
}
return notificationManager;
}
@Nullable
- private static Context getContextAsUser(Context baseContext, UserHandle userHandle) {
+ static Context getContextAsUser(Context baseContext, UserHandle userHandle) {
+ // This call requires the INTERACT_ACROSS_USERS permission.
+ final long callingId = Binder.clearCallingIdentity();
try {
- return baseContext.createContextAsUser(userHandle, 0);
+ return baseContext.createContextAsUser(userHandle, /* flags= */ 0);
} catch (RuntimeException e) {
- Log.w(TAG, "Could not create Context as user " + userHandle, e);
+ Log.w(TAG, "Could not create Context as user id: " + userHandle.getIdentifier(), e);
return null;
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
}
}
@@ -102,10 +103,18 @@ public final class SafetyCenterNotificationChannels {
/**
* Creates all Safety Center {@link NotificationChannel}s instances and their group, for all
- * current users, dropping any calling identity so those channels can be unblockable. Throws a
- * {@link RuntimeException} if any channel is malformed and could not be created.
+ * current users, dropping any calling identity so those channels can be unblockable.
*/
public void createAllChannelsForAllUsers(Context context) {
+ if (!SafetyCenterFlags.getNotificationsEnabled()) {
+ // TODO(b/284271124): Decide what to do with existing channels if flag gets toggled.
+ Log.i(
+ TAG,
+ "Not creating notification channels because Safety Center notifications are"
+ + " disabled");
+ return;
+ }
+
List<UserHandle> users = UserUtils.getUserHandles(context);
for (int i = 0; i < users.size(); i++) {
createAllChannelsForUser(context, users.get(i));
@@ -115,31 +124,54 @@ public final class SafetyCenterNotificationChannels {
/**
* Creates all Safety Center {@link NotificationChannel}s instances and their group for the
* given {@link UserHandle}, dropping any calling identity so those channels can be unblockable.
- * Throws a {@link RuntimeException} if any channel is malformed and could not be created.
*/
public void createAllChannelsForUser(Context context, UserHandle user) {
+ if (!SafetyCenterFlags.getNotificationsEnabled()) {
+ // TODO(b/284271124): Decide what to do with existing channels if flag gets toggled.
+ Log.i(
+ TAG,
+ "Not creating notification channels because Safety Center notifications are"
+ + " disabled");
+ return;
+ }
+
+ NotificationManager notificationManager = getNotificationManagerForUser(context, user);
+ if (notificationManager == null) {
+ return;
+ }
+
try {
- NotificationManager notificationManager =
- requireNonNull(getNotificationManagerForUser(context, user));
createAllChannelsWithoutCallingIdentity(notificationManager);
} catch (RuntimeException e) {
- Log.w(TAG, "Error creating notification channels for user " + user.getIdentifier(), e);
+ Log.w(
+ TAG,
+ "Error creating notification channels for user id: " + user.getIdentifier(),
+ e);
}
}
@Nullable
private String getChannelIdForIssue(SafetySourceIssue issue) {
- switch (issue.getSeverityLevel()) {
+ int issueSeverityLevel = issue.getSeverityLevel();
+ switch (issueSeverityLevel) {
case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
return CHANNEL_ID_INFORMATION;
case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
return CHANNEL_ID_RECOMMENDATION;
case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
return CHANNEL_ID_CRITICAL_WARNING;
- default:
- Log.w(TAG, "No applicable notification channel for issue " + issue);
+ case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
+ Log.w(TAG, "SafetySourceData.SeverityLevel is unspecified for issue: " + issue);
return null;
}
+
+ Log.w(
+ TAG,
+ "Unexpected SafetySourceData.SeverityLevel: "
+ + issueSeverityLevel
+ + ", for issue: "
+ + issue);
+ return null;
}
/**
@@ -149,7 +181,8 @@ public final class SafetyCenterNotificationChannels {
* created.
*/
private void createAllChannelsWithoutCallingIdentity(NotificationManager notificationManager) {
- // Clearing calling identity to be able to make unblockable system notification channels
+ // Clearing calling identity to be able to make unblockable system notification channels and
+ // call this for other users with the INTERACT_ACROSS_USERS permission.
final long callingId = Binder.clearCallingIdentity();
try {
notificationManager.createNotificationChannelGroup(getChannelGroupDefinition());
@@ -161,6 +194,17 @@ public final class SafetyCenterNotificationChannels {
}
}
+ private void clearAllChannelsWithoutCallingIdentity(NotificationManager notificationManager) {
+ // Clearing calling identity to do this for other users with the INTERACT_ACROSS_USERS
+ // permission.
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ notificationManager.deleteNotificationChannelGroup(CHANNEL_GROUP_ID);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
private NotificationChannelGroup getChannelGroupDefinition() {
return new NotificationChannelGroup(
CHANNEL_GROUP_ID, getString("notification_channel_group_name"));
@@ -200,6 +244,6 @@ public final class SafetyCenterNotificationChannels {
}
private String getString(String name) {
- return mResourcesContext.getStringByName(name);
+ return mSafetyCenterResourcesApk.getStringByName(name);
}
}
diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java
index 62da1fc7d..0624bad82 100644
--- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java
+++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationFactory.java
@@ -16,20 +16,20 @@
package com.android.safetycenter.notifications;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID;
import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID;
import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_USER_HANDLE;
+import static com.android.safetycenter.notifications.SafetyCenterNotificationChannels.getContextAsUser;
+
import android.annotation.ColorInt;
-import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
@@ -37,14 +37,14 @@ import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceIssue;
import android.text.TextUtils;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
import com.android.safetycenter.PendingIntentFactory;
import com.android.safetycenter.internaldata.SafetyCenterIds;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.time.Duration;
import java.util.List;
@@ -53,24 +53,22 @@ import java.util.List;
* Factory that builds {@link Notification} objects from {@link SafetySourceIssue} instances with
* appropriate {@link PendingIntent}s for click and dismiss callbacks.
*/
-@RequiresApi(TIRAMISU)
final class SafetyCenterNotificationFactory {
- private static final String TAG = "SafetyCenterNF";
private static final int OPEN_SAFETY_CENTER_REQUEST_CODE = 1221;
private static final Duration SUCCESS_NOTIFICATION_TIMEOUT = Duration.ofSeconds(10);
private final Context mContext;
private final SafetyCenterNotificationChannels mNotificationChannels;
- private final SafetyCenterResourcesContext mResourcesContext;
+ private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
SafetyCenterNotificationFactory(
Context context,
SafetyCenterNotificationChannels notificationChannels,
- SafetyCenterResourcesContext resourcesContext) {
+ SafetyCenterResourcesApk safetyCenterResourcesApk) {
mContext = context;
mNotificationChannels = notificationChannels;
- mResourcesContext = resourcesContext;
+ mSafetyCenterResourcesApk = safetyCenterResourcesApk;
}
/**
@@ -84,13 +82,18 @@ final class SafetyCenterNotificationFactory {
Notification newNotificationForSuccessfulAction(
NotificationManager notificationManager,
SafetySourceIssue issue,
- SafetySourceIssue.Action action) {
+ SafetySourceIssue.Action action,
+ @UserIdInt int userId) {
String channelId = mNotificationChannels.getCreatedChannelId(notificationManager, issue);
-
if (channelId == null) {
return null;
}
+ PendingIntent contentIntent = newSafetyCenterPendingIntent(userId);
+ if (contentIntent == null) {
+ return null;
+ }
+
Notification.Builder builder =
new Notification.Builder(mContext, channelId)
.setSmallIcon(
@@ -99,7 +102,8 @@ final class SafetyCenterNotificationFactory {
.setContentTitle(action.getSuccessMessage())
.setShowWhen(true)
.setTimeoutAfter(SUCCESS_NOTIFICATION_TIMEOUT.toMillis())
- .setContentIntent(newSafetyCenterPendingIntent(null));
+ .setContentIntent(contentIntent)
+ .setAutoCancel(true);
Integer color = getNotificationColor(SafetySourceData.SEVERITY_LEVEL_INFORMATION);
if (color != null) {
@@ -122,7 +126,6 @@ final class SafetyCenterNotificationFactory {
SafetySourceIssue issue,
SafetyCenterIssueKey issueKey) {
String channelId = mNotificationChannels.getCreatedChannelId(notificationManager, issue);
-
if (channelId == null) {
return null;
}
@@ -140,6 +143,11 @@ final class SafetyCenterNotificationFactory {
}
}
+ PendingIntent contentIntent = newSafetyCenterPendingIntent(issueKey);
+ if (contentIntent == null) {
+ return null;
+ }
+
Notification.Builder builder =
new Notification.Builder(mContext, channelId)
.setSmallIcon(getNotificationIcon(issue.getSeverityLevel()))
@@ -147,7 +155,7 @@ final class SafetyCenterNotificationFactory {
.setShowWhen(true)
.setContentTitle(title)
.setContentText(text)
- .setContentIntent(newSafetyCenterPendingIntent(issueKey))
+ .setContentIntent(contentIntent)
.setDeleteIntent(
SafetyCenterNotificationReceiver.newNotificationDismissedIntent(
mContext, issueKey));
@@ -163,27 +171,59 @@ final class SafetyCenterNotificationFactory {
builder.addAction(notificationAction);
}
+ if (issue.getSeverityLevel() == SafetySourceData.SEVERITY_LEVEL_INFORMATION) {
+ builder.setAutoCancel(true);
+ }
+
return builder.build();
}
/**
- * Returns a {@link PendingIntent} to open Safety Center, optionally navigating to and/or
- * highlighting a specific issue if {@code issueKey} is given.
+ * Returns a {@link PendingIntent} to open Safety Center, navigating to a specific issue, or
+ * {@code null} if no such intent can be created.
*/
- private PendingIntent newSafetyCenterPendingIntent(@Nullable SafetyCenterIssueKey issueKey) {
- Intent intent = new Intent(Intent.ACTION_SAFETY_CENTER);
- if (issueKey != null) {
- // Set the encoded issue key as the intent's identifier to ensure the PendingIntents of
- // different notifications do not collide:
- intent.setIdentifier(SafetyCenterIds.encodeToString(issueKey));
- intent.putExtra(EXTRA_SAFETY_SOURCE_ID, issueKey.getSafetySourceId());
- intent.putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, issueKey.getSafetySourceIssueId());
- intent.putExtra(EXTRA_SAFETY_SOURCE_USER_HANDLE, UserHandle.of(issueKey.getUserId()));
+ @Nullable
+ private PendingIntent newSafetyCenterPendingIntent(SafetyCenterIssueKey issueKey) {
+ UserHandle userHandle = UserHandle.of(issueKey.getUserId());
+ Context userContext = getContextAsUser(mContext, userHandle);
+ if (userContext == null) {
+ return null;
+ }
+
+ Intent intent = newSafetyCenterIntent();
+ // Set the encoded issue key as the intent's identifier to ensure the PendingIntents of
+ // different notifications do not collide:
+ intent.setIdentifier(SafetyCenterIds.encodeToString(issueKey));
+ intent.putExtra(EXTRA_SAFETY_SOURCE_ID, issueKey.getSafetySourceId());
+ intent.putExtra(EXTRA_SAFETY_SOURCE_ISSUE_ID, issueKey.getSafetySourceIssueId());
+ intent.putExtra(EXTRA_SAFETY_SOURCE_USER_HANDLE, userHandle);
+
+ return PendingIntentFactory.getActivityPendingIntent(
+ userContext, OPEN_SAFETY_CENTER_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ /**
+ * Returns a {@link PendingIntent} to open Safety Center, or {@code null} if no such intent can
+ * be created.
+ */
+ @Nullable
+ private PendingIntent newSafetyCenterPendingIntent(@UserIdInt int userId) {
+ Context userContext = getContextAsUser(mContext, UserHandle.of(userId));
+ if (userContext == null) {
+ return null;
}
+ return PendingIntentFactory.getActivityPendingIntent(
+ userContext,
+ OPEN_SAFETY_CENTER_REQUEST_CODE,
+ newSafetyCenterIntent(),
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ private static Intent newSafetyCenterIntent() {
+ Intent intent = new Intent(Intent.ACTION_SAFETY_CENTER);
// This extra is defined in the PermissionController APK, cannot be referenced directly:
intent.putExtra("navigation_source_intent_extra", "NOTIFICATION");
- return PendingIntentFactory.getActivityPendingIntent(
- mContext, OPEN_SAFETY_CENTER_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE);
+ return intent;
}
private Icon getNotificationIcon(@SafetySourceData.SeverityLevel int severityLevel) {
@@ -191,7 +231,7 @@ final class SafetyCenterNotificationFactory {
if (severityLevel == SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING) {
iconResName = "ic_notification_badge_critical";
}
- Icon icon = mResourcesContext.getIconByDrawableName(iconResName);
+ Icon icon = mSafetyCenterResourcesApk.getIconByDrawableName(iconResName);
if (icon == null) {
// In case it was impossible to fetch the above drawable for any reason use this
// fallback which should be present on all Android devices:
@@ -207,12 +247,13 @@ final class SafetyCenterNotificationFactory {
if (severityLevel == SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING) {
colorResName = "notification_tint_critical";
}
- return mResourcesContext.getColorByName(colorResName);
+ return mSafetyCenterResourcesApk.getColorByName(colorResName);
}
private Bundle getNotificationExtras() {
Bundle extras = new Bundle();
- String appName = mResourcesContext.getStringByName("notification_channel_group_name");
+ String appName =
+ mSafetyCenterResourcesApk.getStringByName("notification_channel_group_name");
if (!TextUtils.isEmpty(appName)) {
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName);
}
@@ -222,7 +263,9 @@ final class SafetyCenterNotificationFactory {
private Notification.Action toNotificationAction(
SafetyCenterIssueKey issueKey, SafetySourceIssue.Action issueAction) {
PendingIntent pendingIntent = getPendingIntentForAction(issueKey, issueAction);
- return new Notification.Action.Builder(null, issueAction.getLabel(), pendingIntent).build();
+ return new Notification.Action.Builder(
+ /* icon= */ null, issueAction.getLabel(), pendingIntent)
+ .build();
}
private PendingIntent getPendingIntentForAction(
@@ -230,7 +273,7 @@ final class SafetyCenterNotificationFactory {
if (issueAction.willResolve()) {
return getReceiverPendingIntentForResolvingAction(issueKey, issueAction);
} else {
- return getDirectPendingIntentForNonResolvingAction(issueKey, issueAction);
+ return getDirectPendingIntentForNonResolvingAction(issueAction);
}
}
@@ -251,12 +294,7 @@ final class SafetyCenterNotificationFactory {
}
private PendingIntent getDirectPendingIntentForNonResolvingAction(
- SafetyCenterIssueKey issueKey, SafetySourceIssue.Action issueAction) {
+ SafetySourceIssue.Action issueAction) {
return issueAction.getPendingIntent();
}
-
- private static boolean isDarkTheme(Context context) {
- return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
- }
}
diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java
index 0b4ac5ab3..29af9a99b 100644
--- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java
+++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java
@@ -16,9 +16,6 @@
package com.android.safetycenter.notifications;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
-
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -27,7 +24,7 @@ import android.content.IntentFilter;
import android.safetycenter.SafetySourceIssue;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.GuardedBy;
import com.android.permission.util.UserUtils;
@@ -54,7 +51,6 @@ import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
public final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
private static final String TAG = "SafetyCenterNR";
@@ -120,13 +116,13 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
private static SafetyCenterIssueActionId getIssueActionIdExtra(Intent intent) {
String issueActionIdString = intent.getStringExtra(EXTRA_ISSUE_ACTION_ID);
if (issueActionIdString == null) {
- Log.w(TAG, "Received notification action broadcast with null issue action ID");
+ Log.w(TAG, "Received notification action broadcast with null issue action id");
return null;
}
try {
return SafetyCenterIds.issueActionIdFromString(issueActionIdString);
} catch (IllegalArgumentException e) {
- Log.w(TAG, "Could not decode the issue action ID", e);
+ Log.w(TAG, "Could not decode the issue action id", e);
return null;
}
}
@@ -162,22 +158,28 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NOTIFICATION_DISMISSED);
filter.addAction(ACTION_NOTIFICATION_ACTION_CLICKED);
- context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
+ context.registerReceiver(/* receiver= */ this, filter, Context.RECEIVER_NOT_EXPORTED);
}
@Override
public void onReceive(Context context, Intent intent) {
- if (!SafetyCenterFlags.getSafetyCenterEnabled()
- || !SafetyCenterFlags.getNotificationsEnabled()) {
+ if (!SafetyCenterFlags.getSafetyCenterEnabled()) {
+ Log.i(TAG, "Received notification broadcast but Safety Center is disabled");
+ return;
+ }
+
+ if (!SafetyCenterFlags.getNotificationsEnabled()) {
+ // TODO(b/284271124): Decide what to do with existing notifications
+ Log.i(TAG, "Received notification broadcast but notifications are disabled");
return;
}
- Log.d(TAG, "Received broadcast with action " + intent.getAction());
String action = intent.getAction();
if (action == null) {
- Log.w(TAG, "Received broadcast with null action!");
+ Log.w(TAG, "Received broadcast with null action");
return;
}
+ Log.d(TAG, "Received broadcast with action: " + action);
switch (action) {
case ACTION_NOTIFICATION_DISMISSED:
diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java
index 668158097..c74753050 100644
--- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java
+++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java
@@ -16,13 +16,11 @@
package com.android.safetycenter.notifications;
-import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED;
import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
import android.annotation.IntDef;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Notification;
import android.app.NotificationManager;
@@ -36,7 +34,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import androidx.annotation.RequiresApi;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
@@ -47,7 +45,7 @@ import com.android.safetycenter.data.SafetyCenterDataManager;
import com.android.safetycenter.internaldata.SafetyCenterIds;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
-import com.android.safetycenter.resources.SafetyCenterResourcesContext;
+import com.android.safetycenter.resources.SafetyCenterResourcesApk;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -66,7 +64,6 @@ import javax.annotation.concurrent.NotThreadSafe;
*
* @hide
*/
-@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterNotificationSender {
@@ -120,13 +117,13 @@ public final class SafetyCenterNotificationSender {
public static SafetyCenterNotificationSender newInstance(
Context context,
- SafetyCenterResourcesContext resourcesContext,
+ SafetyCenterResourcesApk safetyCenterResourcesApk,
SafetyCenterNotificationChannels notificationChannels,
SafetyCenterDataManager dataManager) {
return new SafetyCenterNotificationSender(
context,
new SafetyCenterNotificationFactory(
- context, notificationChannels, resourcesContext),
+ context, notificationChannels, safetyCenterResourcesApk),
dataManager);
}
@@ -137,7 +134,7 @@ public final class SafetyCenterNotificationSender {
* <p>The given {@link SafetyEvent} have type {@link
* SafetyEvent#SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED} and include issue and action IDs
* that correspond to a {@link SafetySourceIssue} for which a notification is currently
- * displayed. Otherwise this method has no effect.
+ * displayed. Otherwise, this method has no effect.
*
* @param sourceId of the source which reported the issue
* @param safetyEvent the source provided upon successful action resolution
@@ -145,6 +142,12 @@ public final class SafetyCenterNotificationSender {
*/
public void notifyActionSuccess(
String sourceId, SafetyEvent safetyEvent, @UserIdInt int userId) {
+ if (!SafetyCenterFlags.getNotificationsEnabled()) {
+ // TODO(b/284271124): Decide what to do with existing notifications if flag gets
+ // toggled.
+ return;
+ }
+
if (safetyEvent.getType() != SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) {
Log.w(TAG, "Received safety event of wrong type");
return;
@@ -193,7 +196,7 @@ public final class SafetyCenterNotificationSender {
Notification notification =
mNotificationFactory.newNotificationForSuccessfulAction(
- notificationManager, notifiedIssue, successfulAction);
+ notificationManager, notifiedIssue, successfulAction, userId);
if (notification == null) {
Log.w(TAG, "Could not create successful action notification");
return;
@@ -224,6 +227,7 @@ public final class SafetyCenterNotificationSender {
*/
public void updateNotifications(@UserIdInt int userId) {
if (!SafetyCenterFlags.getNotificationsEnabled()) {
+ // TODO(b/284271124): Decide what to do with existing notifications
return;
}
@@ -286,7 +290,7 @@ public final class SafetyCenterNotificationSender {
fout.println();
}
- /** Get all of the key-issue pairs for which notifications should be posted or updated now. */
+ /** Gets all the key-issue pairs for which notifications should be posted or updated now. */
private ArrayMap<SafetyCenterIssueKey, SafetySourceIssue> getIssuesToNotify(
@UserIdInt int userId) {
ArrayMap<SafetyCenterIssueKey, SafetySourceIssue> result = new ArrayMap<>();
@@ -325,14 +329,20 @@ public final class SafetyCenterNotificationSender {
@NotificationBehaviorInternal
private int getBehavior(SafetySourceIssue issue, SafetyCenterIssueKey issueKey) {
if (SdkLevel.isAtLeastU()) {
- switch (issue.getNotificationBehavior()) {
+ int notificationBehavior = issue.getNotificationBehavior();
+ switch (notificationBehavior) {
case SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER:
return NOTIFICATION_BEHAVIOR_INTERNAL_NEVER;
case SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED:
return NOTIFICATION_BEHAVIOR_INTERNAL_DELAYED;
case SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY:
return NOTIFICATION_BEHAVIOR_INTERNAL_IMMEDIATELY;
+ case SafetySourceIssue.NOTIFICATION_BEHAVIOR_UNSPECIFIED:
+ return getBehaviorForIssueWithUnspecifiedBehavior(issue, issueKey);
}
+ Log.w(
+ TAG,
+ "Unexpected SafetySourceIssue.NotificationBehavior: " + notificationBehavior);
}
// On Android T all issues are assumed to have "unspecified" behavior
return getBehaviorForIssueWithUnspecifiedBehavior(issue, issueKey);
diff --git a/service/java/com/android/safetycenter/notifications/package-info.java b/service/java/com/android/safetycenter/notifications/package-info.java
index 85b487b30..e16c06d58 100644
--- a/service/java/com/android/safetycenter/notifications/package-info.java
+++ b/service/java/com/android/safetycenter/notifications/package-info.java
@@ -14,6 +14,8 @@
* limitations under the License.
*/
@NonNullByDefault
+@RequiresTiramisuByDefault
package com.android.safetycenter.notifications;
import com.android.safetycenter.annotations.NonNullByDefault;
+import com.android.safetycenter.annotations.RequiresTiramisuByDefault;
diff --git a/service/java/com/android/safetycenter/package-info.java b/service/java/com/android/safetycenter/package-info.java
index 0bccf89fa..72774d21e 100644
--- a/service/java/com/android/safetycenter/package-info.java
+++ b/service/java/com/android/safetycenter/package-info.java
@@ -14,6 +14,8 @@
* limitations under the License.
*/
@NonNullByDefault
+@RequiresTiramisuByDefault
package com.android.safetycenter;
import com.android.safetycenter.annotations.NonNullByDefault;
+import com.android.safetycenter.annotations.RequiresTiramisuByDefault;
diff --git a/tests/cts/safetycenter/AndroidTest.xml b/tests/cts/safetycenter/AndroidTest.xml
index 5ec0c380e..6d8c3069c 100644
--- a/tests/cts/safetycenter/AndroidTest.xml
+++ b/tests/cts/safetycenter/AndroidTest.xml
@@ -43,9 +43,12 @@
aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
<option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
index 33f52b564..c344d7ebd 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
@@ -19,7 +19,7 @@ package android.safetycenter.cts
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntry
@@ -164,7 +164,7 @@ class SafetyCenterDataTest {
SafetyCenterData(status2, listOf(issue2), listOf(entryOrGroup2), listOf(staticEntryGroup2))
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getStatus_withDefaultBuilder_returnsStatus() {
val safetyCenterData = SafetyCenterData.Builder(status1).build()
@@ -172,7 +172,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getIssues_withDefaultBuilder_returnsEmptyList() {
val safetyCenterData = SafetyCenterData.Builder(status1).build()
@@ -180,7 +180,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getIssues_whenSetExplicitly_returnsIssues() {
val safetyCenterData =
SafetyCenterData.Builder(status1).addIssue(issue1).addIssue(issue2).build()
@@ -189,7 +189,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getEntriesOrGroups_withDefaultBuilder_returnsEmptyList() {
val safetyCenterData = SafetyCenterData.Builder(status1).build()
@@ -197,7 +197,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getEntriesOrGroups_whenSetExplicitly_returnsEntriesOrGroups() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -211,7 +211,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getStaticGroups_withDefaultBuilder_returnsEmptyList() {
val safetyCenterData = SafetyCenterData.Builder(status1).build()
@@ -219,7 +219,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getStaticEntryGroups_whenSetExplicitly_returnsStaticEntryGroups() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -233,7 +233,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getDismissedIssues_withDefaultBuilder_returnsEmptyList() {
val safetyCenterData = SafetyCenterData.Builder(status1).build()
@@ -241,7 +241,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getDismissedIssues_whenSetExplicitly_returnsIssues() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -253,7 +253,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getExtras_withDefaultBuilder_returnsEmptyBundle() {
val safetyCenterData = SafetyCenterData.Builder(status1).build()
@@ -261,7 +261,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getExtras_whenSetExplicitly_returnsExtras() {
val safetyCenterData =
SafetyCenterData.Builder(status1).setExtras(filledExtrasIssuesToGroups1).build()
@@ -275,7 +275,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getExtras_whenCleared_returnsEmptyBundle() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -299,7 +299,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getDismissedIssues_returnsDismissedIssues() {
val data3 = data1.withDismissedIssuesIfAtLeastU(listOf(issue2))
@@ -308,7 +308,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getDismissedIssues_mutationsAreNotAllowed() {
val mutatedDismissedIssues = data1.dismissedIssues
@@ -353,7 +353,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_addIssue_doesNotMutatePreviouslyBuiltInstance() {
val safetyCenterDataBuilder = SafetyCenterData.Builder(status1).addIssue(issue1)
val issues = safetyCenterDataBuilder.build().issues
@@ -364,7 +364,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_clearIssues_removesAllIssues() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -377,7 +377,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_addEntryOrGroup_doesNotMutatePreviouslyBuiltInstance() {
val safetyCenterDataBuilder =
SafetyCenterData.Builder(status1).addEntryOrGroup(entryOrGroup1)
@@ -389,7 +389,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_clearEntriesOrGroups_removesAllEntriesOrGroups() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -402,7 +402,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_addStaticEntryGroup_doesNotMutatePreviouslyBuiltInstance() {
val safetyCenterDataBuilder =
SafetyCenterData.Builder(status1).addStaticEntryGroup(staticEntryGroup1)
@@ -414,7 +414,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_clearStaticEntryGroups_removesAllStaticEntryGroups() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -427,7 +427,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_addDismissedIssue_doesNotMutatePreviouslyBuiltInstance() {
val safetyCenterDataBuilder = SafetyCenterData.Builder(status1).addDismissedIssue(issue1)
val dismissedIssues = safetyCenterDataBuilder.build().dismissedIssues
@@ -438,7 +438,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun builder_clearDismissedIssues_removesAllDismissedIssues() {
val safetyCenterData =
SafetyCenterData.Builder(status1)
@@ -463,7 +463,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun parcelRoundTrip_withDismissedIssues_recreatesEqual() {
val data3 = data1.withDismissedIssuesIfAtLeastU(listOf(issue2))
val data4 = data2.withDismissedIssuesIfAtLeastU(listOf(issue1))
@@ -473,7 +473,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun parcelRoundTrip_withExtras_recreatesEqual() {
val safetyCenterDataWithExtras = data1.withExtrasIfAtLeastU(filledAllExtras)
val safetyCenterDatafromParcel =
@@ -545,7 +545,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCode_atLeastU_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterData.CREATOR,
@@ -680,7 +680,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withSingleKnownExtra_containsKnownExtra() {
val safetyCenterDataWithExtras = data1.withExtrasIfAtLeastU(filledExtrasIssuesToGroups1)
@@ -693,7 +693,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withAllKnownExtras_containsKnownExtras() {
val safetyCenterDataWithAllExtras = data1.withExtrasIfAtLeastU(filledAllExtras)
@@ -706,7 +706,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withOneKnowAndOneUnknownExtra_containsKnownAndUnknownExtras() {
val safetyCenterDataWithExtras = data1.withExtrasIfAtLeastU(filledOneKnownOneUnknown)
@@ -719,7 +719,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withSingleUnknownExtra_containsUnknownExtras() {
val safetyCenterDataWithExtras = data1.withExtrasIfAtLeastU(unknownExtras)
@@ -732,7 +732,7 @@ class SafetyCenterDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withoutExtras_containsNoExtras() {
val safetyCenterDataWithoutExtras = data1
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
index 8d44f2736..be7ca343c 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
@@ -19,7 +19,6 @@ package android.safetycenter.cts
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetyCenterIssue
@@ -31,8 +30,8 @@ import androidx.test.ext.truth.os.ParcelableSubject.assertThat
import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFails
import kotlin.test.assertFailsWith
-import org.junit.Assume.assumeFalse
import org.junit.Test
import org.junit.runner.RunWith
@@ -111,7 +110,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getAttributionTitle_returnsAttributionTitle() {
assertThat(
SafetyCenterIssue.Builder(issue1)
@@ -130,7 +129,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getAttributionTitle_withNullAttributionTitle_returnsNull() {
val safetyCenterIssue =
SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
@@ -228,7 +227,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getGroupId_withNonNullValue_returnsGroupId() {
val issue = SafetyCenterIssue.Builder(issue1).setGroupId("group_id").build()
@@ -236,7 +235,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getGroupId_withNullValue_returnsNull() {
val issue =
SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
@@ -247,23 +246,16 @@ class SafetyCenterIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun getGroupId_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun getGroupId_withVersionLessThanU_throws() {
val issue =
SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
.build()
- val exception = assertFailsWith(UnsupportedOperationException::class) { issue.groupId }
-
- assertThat(exception)
- .hasMessageThat()
- .isEqualTo("Method not supported for versions lower than UPSIDE_DOWN_CAKE")
+ assertFails { issue.groupId }
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setGroupId_withNullValue_returnsNull() {
val issue = SafetyCenterIssue.Builder(issue1).setGroupId(null).build()
@@ -272,19 +264,8 @@ class SafetyCenterIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun setGroupId_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
-
- val exception =
- assertFailsWith(UnsupportedOperationException::class) {
- SafetyCenterIssue.Builder(issue1).setGroupId("group_id").build()
- }
-
- assertThat(exception)
- .hasMessageThat()
- .isEqualTo("Method not supported for versions lower than UPSIDE_DOWN_CAKE")
+ fun setGroupId_withVersionLessThanU_throws() {
+ assertFails { SafetyCenterIssue.Builder(issue1).setGroupId("group_id").build() }
}
@Test
@@ -300,7 +281,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
val safetyCenterIssue =
SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
@@ -330,7 +311,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
newUpsideDownCakeEqualsHashCodeToStringTester().test()
}
@@ -373,21 +354,14 @@ class SafetyCenterIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun action_getConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
-
- assertFailsWith(UnsupportedOperationException::class) { action1.confirmationDialogDetails }
+ fun action_getConfirmationDialogDetails_withVersionLessThanU_throws() {
+ assertFails { action1.confirmationDialogDetails }
}
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun action_setConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
- assertFailsWith(UnsupportedOperationException::class) {
+ fun action_setConfirmationDialogDetails_withVersionLessThanU_throws() {
+ assertFails {
SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1)
.setConfirmationDialogDetails(
ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -396,7 +370,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_getConfirmationDialogDetails_withDefaultBuilder_returnsNull() {
val action =
SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1).build()
@@ -405,7 +379,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_getConfirmationDialogDetails_whenSetExplicitly_returnsConfirmation() {
val action =
SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1)
@@ -431,7 +405,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
val action =
SafetyCenterIssue.Action.Builder(action1)
@@ -449,7 +423,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
issueActionNewTiramisuEqualsHashCodeToStringTester(
@@ -596,7 +570,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getTitle_returnsTitle() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -604,7 +578,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getText_returnsText() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -612,7 +586,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getAcceptButtonText_returnsAcceptButtonText() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -620,7 +594,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getDenyButtonText_returnsDenyButtonText() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -628,7 +602,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_describeContents_returns0() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -636,7 +610,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_parcelRoundTrip_recreatesEqual() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -644,7 +618,7 @@ class SafetyCenterIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = ConfirmationDialogDetails.CREATOR
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
index b6bb475d8..298d7643c 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -17,7 +17,6 @@
package android.safetycenter.cts
import android.content.Context
-import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.UserHandle.USER_NULL
@@ -40,7 +39,7 @@ import android.safetycenter.cts.testing.FakeExecutor
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.addOnSafetyCenterDataChangedListenerWithPermission
@@ -59,7 +58,6 @@ import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.set
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission
import com.android.safetycenter.testing.SafetyCenterEnabledChangedReceiver
import com.android.safetycenter.testing.SafetyCenterFlags
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.DYNAMIC_ALL_OPTIONAL_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.DYNAMIC_BAREBONE_ID
@@ -77,6 +75,7 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.STATIC
import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestHelper
import com.android.safetycenter.testing.SafetyCenterTestListener
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
@@ -89,15 +88,15 @@ import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_
import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID
import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED
import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.google.common.base.Preconditions.checkState
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import kotlin.test.assertFails
import kotlin.test.assertFailsWith
import kotlinx.coroutines.TimeoutCancellationException
-import org.junit.After
-import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
-import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -105,36 +104,14 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterManagerTest {
private val context: Context = getApplicationContext()
- private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context)
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
-
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
-
- @After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@Test
fun isSafetyCenterEnabled_withFlagEnabled_returnsTrue() {
@@ -251,7 +228,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_wronglySignedPackage_throwsIllegalArgumentException() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceWithFakeCert)
@@ -266,7 +243,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_wronglySignedPackageButAllowedByFlag_isAllowed() {
SafetyCenterFlags.allowedAdditionalPackageCerts =
mapOf(context.packageName to setOf(safetyCenterTestConfigs.packageCertHash))
@@ -281,7 +258,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_invalidPackageCertificate_throwsIllegalArgumentException() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceWithInvalidCert)
@@ -309,9 +286,9 @@ class SafetyCenterManagerTest {
assertThat(thrown)
.hasMessageThat()
- .isEqualTo(
- "Safety source: $DYNAMIC_IN_STATELESS_ID is in a stateless group but specified a " +
- "severity level: $SEVERITY_LEVEL_INFORMATION"
+ .matches(
+ "Safety source: $DYNAMIC_IN_STATELESS_ID is in a (stateless|rigid) group but " +
+ "specified a severity level: $SEVERITY_LEVEL_INFORMATION"
)
}
@@ -834,10 +811,7 @@ class SafetyCenterManagerTest {
val enabledChangedReceiver = SafetyCenterEnabledChangedReceiver(context)
assertFailsWith(TimeoutCancellationException::class) {
- enabledChangedReceiver.setSafetyCenterEnabledWithoutReceiverPermissionAndWait(
- false,
- TIMEOUT_SHORT
- )
+ enabledChangedReceiver.setSafetyCenterEnabledWithoutReceiverPermissionAndWait(false)
}
enabledChangedReceiver.unregister()
}
@@ -874,10 +848,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
assertFailsWith(TimeoutCancellationException::class) {
- SafetySourceReceiver.setSafetyCenterEnabledWithoutReceiverPermissionAndWait(
- false,
- TIMEOUT_SHORT
- )
+ SafetySourceReceiver.setSafetyCenterEnabledWithoutReceiverPermissionAndWait(false)
}
}
@@ -955,7 +926,7 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT
)
}
@@ -983,71 +954,6 @@ class SafetyCenterManagerTest {
}
@Test
- fun refreshSafetySources_reasonPageOpen_allowedByFlag_broadcastSent() {
- safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
- safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
- SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
- SafetySourceReceiver.setResponse(
- Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
- )
-
- safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
- REFRESH_REASON_PAGE_OPEN
- )
-
- val apiSafetySourceData =
- safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
- assertThat(apiSafetySourceData).isEqualTo(safetySourceTestData.informationWithIssue)
- }
-
- @Test
- fun refreshSafetySources_reasonPageOpen_allowedByFlagLater_broadcastSentLater() {
- safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
- safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
- SafetySourceReceiver.setResponse(
- Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
- )
-
- assertFailsWith(TimeoutCancellationException::class) {
- safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
- REFRESH_REASON_PAGE_OPEN,
- TIMEOUT_SHORT
- )
- }
- val apiSafetySourceDataBeforeSettingFlag =
- safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
- SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
- safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
- REFRESH_REASON_PAGE_OPEN
- )
- val apiSafetySourceDataAfterSettingFlag =
- safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
-
- assertThat(apiSafetySourceDataBeforeSettingFlag).isEqualTo(safetySourceTestData.information)
- assertThat(apiSafetySourceDataAfterSettingFlag)
- .isEqualTo(safetySourceTestData.informationWithIssue)
- }
-
- @Test
- fun refreshSafetySources_reasonPageOpen_noDataForSource_broadcastSent() {
- safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
- SafetySourceReceiver.setResponse(
- Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
- )
-
- safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
- REFRESH_REASON_PAGE_OPEN
- )
-
- val apiSafetySourceData =
- safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
- assertThat(apiSafetySourceData).isEqualTo(safetySourceTestData.information)
- }
-
- @Test
fun refreshSafetySources_whenSourceClearsData_sourceSendsNullData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
@@ -1135,8 +1041,7 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithoutReceiverPermissionAndWait(
- REFRESH_REASON_RESCAN_BUTTON_CLICK,
- TIMEOUT_SHORT
+ REFRESH_REASON_RESCAN_BUTTON_CLICK
)
}
val apiSafetySourceData =
@@ -1154,7 +1059,7 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT
)
}
}
@@ -1206,11 +1111,11 @@ class SafetyCenterManagerTest {
)
// Because wrong ID, refresh hasn't finished. Wait for timeout.
- listener.receiveSafetyCenterErrorDetails()
+ listener.waitForSafetyCenterRefresh(withErrorEntry = true)
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_LONG)
SafetySourceReceiver.setResponse(
- Request.Rescan(SINGLE_SOURCE_ID),
+ Request.Refresh(SINGLE_SOURCE_ID),
Response.SetData(safetySourceTestData.information)
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1280,7 +1185,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
assertThat(apiSafetySourceData1).isNull()
// Wait for the ongoing refresh to timeout.
- listener.receiveSafetyCenterErrorDetails()
+ listener.waitForSafetyCenterRefresh(withErrorEntry = true)
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_LONG)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
@@ -1332,18 +1237,11 @@ class SafetyCenterManagerTest {
REFRESH_REASON_RESCAN_BUTTON_CLICK
)
- val safetyCenterErrorDetailsFromListener = listener.receiveSafetyCenterErrorDetails()
- assertThat(safetyCenterErrorDetailsFromListener)
- .isEqualTo(
- SafetyCenterErrorDetails(
- safetyCenterResourcesContext.getStringByName("refresh_timeout")
- )
- )
+ listener.waitForSafetyCenterRefresh(withErrorEntry = true)
}
@Test
fun refreshSafetySources_withUntrackedSourceThatTimesOut_doesNotTimeOut() {
- SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
SafetyCenterFlags.untrackedSources = setOf(SOURCE_ID_1)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
// SOURCE_ID_1 will timeout
@@ -1359,14 +1257,11 @@ class SafetyCenterManagerTest {
REFRESH_REASON_RESCAN_BUTTON_CLICK
)
- assertFailsWith(TimeoutCancellationException::class) {
- listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT)
- }
+ listener.waitForSafetyCenterRefresh(withErrorEntry = false)
}
@Test
fun refreshSafetySources_withMultipleUntrackedSourcesThatTimeOut_doesNotTimeOut() {
- SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
SafetyCenterFlags.untrackedSources = setOf(SOURCE_ID_1, SOURCE_ID_2)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
// SOURCE_ID_1 and SOURCE_ID_2 will timeout
@@ -1380,9 +1275,7 @@ class SafetyCenterManagerTest {
REFRESH_REASON_RESCAN_BUTTON_CLICK
)
- assertFailsWith(TimeoutCancellationException::class) {
- listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT)
- }
+ listener.waitForSafetyCenterRefresh(withErrorEntry = false)
}
@Test
@@ -1396,25 +1289,20 @@ class SafetyCenterManagerTest {
REFRESH_REASON_RESCAN_BUTTON_CLICK
)
- val safetyCenterErrorDetailsFromListener = listener.receiveSafetyCenterErrorDetails()
- assertThat(safetyCenterErrorDetailsFromListener)
- .isEqualTo(
- SafetyCenterErrorDetails(
- safetyCenterResourcesContext.getStringByName("refresh_timeout")
- )
- )
+ listener.waitForSafetyCenterRefresh(withErrorEntry = true)
}
@Test
fun refreshSafetySources_withTrackedSourceThatHasNoReceiver_doesNotTimeOut() {
- SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceOtherPackageConfig)
val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.refreshSafetySourcesWithPermission(REFRESH_REASON_RESCAN_BUTTON_CLICK)
assertFailsWith(TimeoutCancellationException::class) {
- listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT)
+ // In this case a refresh isn't even started because there is only a single source
+ // without a receiver.
+ listener.receiveSafetyCenterData(TIMEOUT_SHORT)
}
}
@@ -1426,7 +1314,7 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT
)
}
}
@@ -1510,7 +1398,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun refreshSafetySources_withRefreshReasonPeriodic_noBackgroundRefreshSourceDoesNotSendData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
@@ -1522,7 +1410,7 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PERIODIC,
- TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT
)
}
@@ -1532,7 +1420,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun refreshSafetySources_withRefreshReasonPeriodic_backgroundRefreshSourceSendsData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
@@ -1549,7 +1437,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun refreshSafetySources_withSafetySourceIds_onlySpecifiedSourcesSendData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
SafetySourceReceiver.apply {
@@ -1584,7 +1472,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun refreshSafetySources_withEmptySafetySourceIds_noSourcesSendData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
@@ -1595,8 +1483,8 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- TIMEOUT_SHORT,
- emptyList()
+ safetySourceIds = emptyList(),
+ timeout = TIMEOUT_SHORT,
)
}
@@ -1606,27 +1494,19 @@ class SafetyCenterManagerTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun refreshSafetySources_versionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun refreshSafetySources_versionLessThanU_throws() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
- val exception =
- assertFailsWith(UnsupportedOperationException::class) {
- safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
- REFRESH_REASON_PAGE_OPEN,
- safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_3)
- )
- }
-
- assertThat(exception)
- .hasMessageThat()
- .isEqualTo("Method not supported for versions lower than UPSIDE_DOWN_CAKE")
+ assertFails {
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN,
+ safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_3)
+ )
+ }
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun refreshSafetySources_withSafetySourceIds_withoutPermission_throwsSecurityException() {
assertFailsWith(SecurityException::class) {
safetyCenterManager.refreshSafetySources(REFRESH_REASON_PAGE_OPEN, listOf())
@@ -2076,7 +1956,7 @@ class SafetyCenterManagerTest {
assertThat(error)
.isEqualTo(
SafetyCenterErrorDetails(
- safetyCenterResourcesContext.getStringByName("redirecting_error")
+ safetyCenterResourcesApk.getStringByName("redirecting_error")
)
)
}
@@ -2102,7 +1982,7 @@ class SafetyCenterManagerTest {
assertThat(error)
.isEqualTo(
SafetyCenterErrorDetails(
- safetyCenterResourcesContext.getStringByName("resolving_action_error")
+ safetyCenterResourcesApk.getStringByName("resolving_action_error")
)
)
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt
index 695265059..d882fc3cb 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt
@@ -42,12 +42,12 @@ import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.rep
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetyCenterConfigForTestsWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission
import com.android.safetycenter.testing.SafetyCenterEnabledChangedReceiver
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestHelper
import com.android.safetycenter.testing.SafetyCenterTestListener
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceReceiver
import com.android.safetycenter.testing.SafetySourceReceiver.Companion.executeSafetyCenterIssueActionWithPermissionAndWait
import com.android.safetycenter.testing.SafetySourceReceiver.Companion.refreshSafetySourcesWithReceiverPermissionAndWait
@@ -56,14 +56,13 @@ import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_
import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID
import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED
import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import kotlin.test.assertFailsWith
import kotlinx.coroutines.TimeoutCancellationException
-import org.junit.After
import org.junit.Assume.assumeTrue
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -72,39 +71,17 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterUnsupportedTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
private val context: Context = getApplicationContext()
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = !context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceDoesntSupportSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
-
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
- @After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- }
+ @get:Rule(order = 1)
+ val supportsSafetyCenterRule = SupportsSafetyCenterRule(context, requireSupportIs = false)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@Test
fun launchActivity_opensSettings() {
@@ -238,7 +215,7 @@ class SafetyCenterUnsupportedTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT
)
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
index 3dec4f509..4d1cb6a8b 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
@@ -16,7 +16,7 @@
package android.safetycenter.cts
-import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED
import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED
@@ -190,7 +190,7 @@ class SafetyEventTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
newTiramisuEqualsHashCodeToStringTester(
createCopyFromBuilder = { SafetyEvent.Builder(it).build() }
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
index d7208c098..ba2ab3d76 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
@@ -20,7 +20,7 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
-import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
@@ -96,7 +96,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getExtras_withDefaultBuilder_returnsEmptyBundle() {
val safetySourceData =
SafetySourceData.Builder().setStatus(createStatus(SEVERITY_LEVEL_INFORMATION)).build()
@@ -105,7 +105,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getExtras_whenSetExplicitly_returnsExtras() {
val safetySourceData =
SafetySourceData.Builder()
@@ -118,7 +118,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getExtras_whenCleared_returnsEmptyBundle() {
val safetySourceData =
SafetySourceData.Builder()
@@ -325,7 +325,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun parcelRoundTrip_withExtras_recreatesEqual() {
val safetySourceData =
SafetySourceData.Builder()
@@ -384,7 +384,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCode_atLeastU_usingEqualsHashCodeToStringTester() {
val firstStatus = createStatus(SEVERITY_LEVEL_INFORMATION, 1)
val secondStatus = createStatus(SEVERITY_LEVEL_INFORMATION, 2)
@@ -454,7 +454,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withExtras_containsHasExtras() {
val safetySourceDataWithExtras =
SafetySourceData.Builder()
@@ -468,7 +468,7 @@ class SafetySourceDataTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun toString_withoutExtras_doesNotContainHasExtras() {
val safetySourceDataWithoutExtras =
SafetySourceData.Builder().setStatus(createStatus(SEVERITY_LEVEL_INFORMATION)).build()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
index 4749c3616..2d19a3175 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
@@ -20,7 +20,6 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
-import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
@@ -42,8 +41,8 @@ import androidx.test.filters.SdkSuppress
import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFails
import kotlin.test.assertFailsWith
-import org.junit.Assume.assumeFalse
import org.junit.Test
import org.junit.runner.RunWith
@@ -119,22 +118,16 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun action_getConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun action_getConfirmationDialogDetails_withVersionLessThanU_throws() {
val action = Action.Builder("action_id", "Action label", pendingIntent1).build()
- assertFailsWith(UnsupportedOperationException::class) { action.confirmationDialogDetails }
+ assertFails { action.confirmationDialogDetails }
}
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun action_setConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
- assertFailsWith(UnsupportedOperationException::class) {
+ fun action_setConfirmationDialogDetails_withVersionLessThanU_throws() {
+ assertFails {
Action.Builder("action_id", "Action label", pendingIntent1)
.setConfirmationDialogDetails(
ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -143,7 +136,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_getConfirmationDialogDetails_withDefaultBuilder_returnsNull() {
val action = Action.Builder("action_id", "Action label", pendingIntent1).build()
@@ -151,7 +144,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_getConfirmationDialogDetails_whenSetExplicitly_returnsConfirmation() {
val action =
Action.Builder("action_id", "Action label", pendingIntent1)
@@ -186,7 +179,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_build_withActivityPendingIntentAndWillResolve_throwsIllegalArgumentException() {
assertFailsWith(IllegalArgumentException::class) {
Action.Builder("action_id", "Action label", pendingIntent1).setWillResolve(true).build()
@@ -211,7 +204,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
val action =
Action.Builder("action_id", "Action label", pendingIntent1)
@@ -229,7 +222,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun action_equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
actionNewTiramisuEqualsHashCodeToStringTester(
@@ -293,7 +286,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_getTitle_returnsTitle() {
val notification = Notification.Builder("Notification title", "Notification text").build()
@@ -301,7 +294,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_getText_returnsText() {
val notification = Notification.Builder("Notification title", "Notification text").build()
@@ -309,7 +302,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_getActions_withDefaultBuilder_returnsEmptyList() {
val notification = Notification.Builder("", "").build()
@@ -317,7 +310,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_getActions_returnsActions() {
val notification =
Notification.Builder("", "").addAction(action1).addAction(action2).build()
@@ -326,7 +319,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_getActions_mutationsAreNotAllowed() {
val notification =
Notification.Builder("", "").addAction(action1).addAction(action2).build()
@@ -335,7 +328,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_describeContents_returns0() {
val notification =
Notification.Builder("Notification title", "Notification text")
@@ -347,7 +340,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_parcelRoundTrip_recreatesEqual() {
val notification =
Notification.Builder("Notification title", "Notification text")
@@ -359,7 +352,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_withNullTitle_throwsNullPointerException() {
assertFailsWith(NullPointerException::class) {
Notification.Builder(Generic.asNull(), "Notification text")
@@ -367,7 +360,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_withNullText_throwsNullPointerException() {
assertFailsWith(NullPointerException::class) {
Notification.Builder("Notification title", Generic.asNull())
@@ -375,7 +368,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_addAction_doesNotMutatePreviouslyBuiltInstance() {
val notificationBuilder = Notification.Builder("", "").addAction(action1)
val actions = notificationBuilder.build().actions
@@ -386,7 +379,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_addAction_withNull_throwsIllegalArgumentException() {
assertFailsWith(NullPointerException::class) {
Notification.Builder("", "").addAction(Generic.asNull())
@@ -394,7 +387,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_addActions_keepsPreviouslyAddedActions() {
val notificationBuilder = Notification.Builder("", "").addAction(action1)
@@ -404,7 +397,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_addActions_doesNotMutatePreviouslyBuiltInstance() {
val notificationBuilder = Notification.Builder("", "").addActions(listOf(action1))
val actions = notificationBuilder.build().actions
@@ -415,7 +408,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_addActions_withNull_throwsIllegalArgumentException() {
assertFailsWith(NullPointerException::class) {
Notification.Builder("", "").addActions(Generic.asNull())
@@ -423,7 +416,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_builder_clearActions_removesAllActions() {
val notification =
Notification.Builder("", "")
@@ -437,7 +430,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_build_withDuplicateActionIds_throwsIllegalArgumentException() {
val notificationBuilder =
Notification.Builder("Notification title", "Notification text")
@@ -452,7 +445,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_build_withMoreThanTwoActions_throwsIllegalArgumentException() {
val notificationBuilder =
Notification.Builder("Notification title", "Notification text")
@@ -468,7 +461,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun notification_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = Notification.CREATOR,
@@ -490,7 +483,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getTitle_returnsTitle() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -498,7 +491,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getText_returnsText() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -506,7 +499,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getAcceptButtonText_returnsAcceptButtonText() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -514,7 +507,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_getDenyButtonText_returnsDenyButtonText() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -522,7 +515,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_describeContents_returns0() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -530,7 +523,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_parcelRoundTrip_recreatesEqual() {
val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
@@ -538,7 +531,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun actionConfirmation_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = ConfirmationDialogDetails.CREATOR
@@ -636,7 +629,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getAttributionTitle_withNullAttributionTitle_returnsNull() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -653,7 +646,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getAttributionTitle_returnsAttributionTitle() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -672,10 +665,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun getAttributionTitle_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun getAttributionTitle_withVersionLessThanU_throws() {
val safetySourceIssue =
SafetySourceIssue.Builder(
"Issue id",
@@ -687,15 +677,12 @@ class SafetySourceIssueTest {
.addAction(action1)
.build()
- assertFailsWith(UnsupportedOperationException::class) { safetySourceIssue.attributionTitle }
+ assertFails { safetySourceIssue.attributionTitle }
}
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun setAttributionTitle_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun setAttributionTitle_withVersionLessThanU_throws() {
val safetySourceIssueBuilder =
SafetySourceIssue.Builder(
"Issue id",
@@ -705,9 +692,7 @@ class SafetySourceIssueTest {
"issue_type_id"
)
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssueBuilder.setAttributionTitle("title")
- }
+ assertFails { safetySourceIssueBuilder.setAttributionTitle("title") }
}
@Test
@@ -760,7 +745,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getIssueCategory_whenSetExplicitlyWithUValueOnU_returnsIssueCategory() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -884,7 +869,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getDeduplicationId_withDefaultBuilder_returnsNull() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -901,7 +886,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getDeduplicationId_whenSetExplicitly_returnsDeduplicationId() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -920,10 +905,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun getDeduplicationId_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun getDeduplicationId_withVersionLessThanU_throws() {
val safetySourceIssue =
SafetySourceIssue.Builder(
"Issue id",
@@ -935,15 +917,12 @@ class SafetySourceIssueTest {
.addAction(action1)
.build()
- assertFailsWith(UnsupportedOperationException::class) { safetySourceIssue.deduplicationId }
+ assertFails { safetySourceIssue.deduplicationId }
}
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun setDeduplicationId_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun setDeduplicationId_withVersionLessThanU_throws() {
val safetySourceIssueBuilder =
SafetySourceIssue.Builder(
"Issue id",
@@ -953,9 +932,7 @@ class SafetySourceIssueTest {
"issue_type_id"
)
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssueBuilder.setDeduplicationId("id")
- }
+ assertFails { safetySourceIssueBuilder.setDeduplicationId("id") }
}
@Test
@@ -975,7 +952,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getCustomNotification_withDefaultBuilder_returnsNull() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -992,7 +969,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getCustomNotification_whenSetExplicitly_returnsCustomNotification() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1020,10 +997,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun getCustomNotification_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun getCustomNotification_withVersionLessThanU_throws() {
val safetySourceIssue =
SafetySourceIssue.Builder(
"Issue id",
@@ -1035,17 +1009,12 @@ class SafetySourceIssueTest {
.addAction(action1)
.build()
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssue.customNotification
- }
+ assertFails { safetySourceIssue.customNotification }
}
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun setCustomNotification_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun setCustomNotification_withVersionLessThanU_throws() {
val safetySourceIssueBuilder =
SafetySourceIssue.Builder(
"Issue id",
@@ -1055,13 +1024,11 @@ class SafetySourceIssueTest {
"issue_type_id"
)
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssueBuilder.setCustomNotification(null)
- }
+ assertFails { safetySourceIssueBuilder.setCustomNotification(null) }
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getNotificationBehavior_withDefaultBuilder_returnsUnspecified() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1079,7 +1046,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getNotificationBehavior_whenSetExplicitly_returnsSpecifiedBehavior() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1099,10 +1066,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun getNotificationBehavior_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun getNotificationBehavior_withVersionLessThanU_throws() {
val safetySourceIssue =
SafetySourceIssue.Builder(
"Issue id",
@@ -1114,13 +1078,11 @@ class SafetySourceIssueTest {
.addAction(action1)
.build()
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssue.notificationBehavior
- }
+ assertFails { safetySourceIssue.notificationBehavior }
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setNotificationBehavior_withInvalidNotificationBehavior_throwsIllegalArgumentException() {
val builder =
SafetySourceIssue.Builder(
@@ -1141,10 +1103,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun setNotificationBehavior_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun setNotificationBehavior_withVersionLessThanU_throws() {
val safetySourceIssueBuilder =
SafetySourceIssue.Builder(
"Issue id",
@@ -1154,13 +1113,11 @@ class SafetySourceIssueTest {
"issue_type_id"
)
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssueBuilder.setNotificationBehavior(0)
- }
+ assertFails { safetySourceIssueBuilder.setNotificationBehavior(0) }
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getIssueActionability_withDefaultBuilder_returnsManual() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1178,7 +1135,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getIssueActionability_whenSetExplicitly_returnsValueSet() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1198,10 +1155,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun getIssueActionability_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun getIssueActionability_withVersionLessThanU_throws() {
val safetySourceIssue =
SafetySourceIssue.Builder(
"Issue id",
@@ -1213,13 +1167,11 @@ class SafetySourceIssueTest {
.addAction(action1)
.build()
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssue.issueActionability
- }
+ assertFails { safetySourceIssue.issueActionability }
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setIssueActionability_withInvalidIssueActionability_throwsIllegalArgumentException() {
val builder =
SafetySourceIssue.Builder(
@@ -1240,10 +1192,7 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
- fun setIssueActionability_withVersionLessThanU_throwsUnsupportedOperationException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
+ fun setIssueActionability_withVersionLessThanU_throws() {
val safetySourceIssueBuilder =
SafetySourceIssue.Builder(
"Issue id",
@@ -1253,9 +1202,7 @@ class SafetySourceIssueTest {
"issue_type_id"
)
- assertFailsWith(UnsupportedOperationException::class) {
- safetySourceIssueBuilder.setIssueActionability(0)
- }
+ assertFails { safetySourceIssueBuilder.setIssueActionability(0) }
}
@Test
@@ -1366,9 +1313,6 @@ class SafetySourceIssueTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
fun build_withUIssueCategoryValueOnT_throwsIllegalArgumentException() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
val builder =
SafetySourceIssue.Builder(
"Issue id",
@@ -1480,7 +1424,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_withNoActionsAndManualActionabilityOnU_throwsIllegalArgumentException() {
val safetySourceIssueBuilder =
SafetySourceIssue.Builder(
@@ -1500,7 +1444,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_withNoActionsAndTipActionabilityOnU_success() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1517,7 +1461,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_withNoActionsAndAutomaticActionabilityOnU_success() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1574,7 +1518,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1601,7 +1545,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun parcelRoundTrip_recreatesEqual_atLeastUpsideDownCake() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -1636,7 +1580,7 @@ class SafetySourceIssueTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastUpsideDownCake() {
newUpsideDownCakeEqualsHashCodeToStringTester().test()
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
index 5aeea21f1..2a20cd45d 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
@@ -20,7 +20,7 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
-import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED
@@ -285,7 +285,7 @@ class SafetySourceStatusTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
newTiramisuEqualsHashCodeToStringTester(
createCopyFromBuilder = { SafetySourceStatus.Builder(it).build() }
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
index 7dff9dd54..68dcd4a11 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
@@ -16,7 +16,7 @@
package android.safetycenter.cts.config
-import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.config.SafetyCenterConfig
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
@@ -86,7 +86,7 @@ class SafetyCenterConfigTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
newTiramisuEqualsHashCodeToStringTester(
createCopyFromBuilder = { SafetyCenterConfig.Builder(it).build() }
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
index 055e82ad3..59cc6547a 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
@@ -17,7 +17,7 @@
package android.safetycenter.cts.config
import android.content.res.Resources
-import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.config.SafetySource
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
@@ -75,7 +75,7 @@ class SafetySourceTest {
}
@Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getOptionalPackageName_returnsPackageNameOrNull() {
assertThat(DYNAMIC_BAREBONE.optionalPackageName).isEqualTo(PACKAGE_NAME)
assertThat(dynamicAllOptional().optionalPackageName).isEqualTo(PACKAGE_NAME)
@@ -259,7 +259,7 @@ class SafetySourceTest {
assertThat(issueOnlyAllOptional().isRefreshOnPageOpenAllowed).isEqualTo(true)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
@Test
fun areNotificationsAllowed_returnsNotificationsAllowed() {
assertThat(DYNAMIC_BAREBONE.areNotificationsAllowed()).isFalse()
@@ -273,7 +273,7 @@ class SafetySourceTest {
assertThat(issueOnlyAllOptional().areNotificationsAllowed()).isTrue()
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
@Test
fun getDeduplicationGroupsList_returnsDeduplicationGroups() {
assertThat(DYNAMIC_BAREBONE.deduplicationGroup).isNull()
@@ -287,7 +287,7 @@ class SafetySourceTest {
assertThat(issueOnlyAllOptional().deduplicationGroup).isEqualTo(DEDUPLICATION_GROUP)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
@Test
fun getPackageCertificateHashes_returnsPackageCerts() {
assertThat(DYNAMIC_BAREBONE.packageCertificateHashes).isEmpty()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
index 015d18842..f741369eb 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
@@ -211,7 +211,7 @@ class SafetySourcesGroupTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_hiddenGroupWithDynamicSource_throwsIllegalStateException() {
val builder =
SafetySourcesGroup.Builder()
@@ -228,7 +228,7 @@ class SafetySourcesGroupTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_hiddenGroupWithStaticSource_throwsIllegalStateException() {
val builder =
SafetySourcesGroup.Builder()
@@ -245,7 +245,7 @@ class SafetySourcesGroupTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_statefulGroupWithIssueOnlySource_throwsIllegalStateException() {
val builder =
SafetySourcesGroup.Builder()
@@ -264,7 +264,7 @@ class SafetySourcesGroupTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun build_statelessGroupWithIssueOnlySource_throwsIllegalStateException() {
val builder =
SafetySourcesGroup.Builder()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt
index 32959629c..7c9e2cffc 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt
@@ -24,15 +24,14 @@ import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.safetycenter.config.SafetyCenterConfigParser
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,33 +39,12 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class XmlConfigTest {
private val context: Context = getApplicationContext()
- private val safetyCenterContext = SafetyCenterResourcesContext.forTests(context)
- private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
-
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
-
- @After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2)
+ val safetyCenterTestRule = SafetyCenterTestRule(SafetyCenterTestHelper(context))
@Test
fun safetyCenterConfigResource_validConfig() {
@@ -93,7 +71,7 @@ class XmlConfigTest {
}
private fun assertThatIntentResolves(intentAction: String) {
- val pm = safetyCenterContext.packageManager
+ val pm = context.packageManager
assertWithMessage("Intent '%s' cannot be resolved.", intentAction)
.that(pm.queryIntentActivities(Intent(intentAction), ResolveInfoFlags.of(0)))
.isNotEmpty()
@@ -109,8 +87,8 @@ class XmlConfigTest {
private fun parseXmlConfig() =
SafetyCenterConfigParser.parseXmlResource(
- safetyCenterContext.safetyCenterConfig!!,
- safetyCenterContext.resources!!
+ safetyCenterResourcesApk.safetyCenterConfig!!,
+ safetyCenterResourcesApk.resources
)
companion object {
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt
index 8fb56b09e..111f01243 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt
@@ -24,15 +24,14 @@ import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.resetRotation
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,36 +40,16 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterActivityTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
private val context: Context = getApplicationContext()
-
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@After
fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
getUiDevice().resetRotation()
}
diff --git a/tests/functional/safetycenter/multiusers/AndroidTest.xml b/tests/functional/safetycenter/multiusers/AndroidTest.xml
index c1e19d2e4..20032357a 100644
--- a/tests/functional/safetycenter/multiusers/AndroidTest.xml
+++ b/tests/functional/safetycenter/multiusers/AndroidTest.xml
@@ -43,9 +43,12 @@
aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
<option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/tests/functional/safetycenter/multiusers/TEST_MAPPING b/tests/functional/safetycenter/multiusers/TEST_MAPPING
index aedf92b86..65f53b453 100644
--- a/tests/functional/safetycenter/multiusers/TEST_MAPPING
+++ b/tests/functional/safetycenter/multiusers/TEST_MAPPING
@@ -1,30 +1,11 @@
{
- "presubmit": [
- {
- "name": "SafetyCenterFunctionalMultiUsersTestCases",
- "options": [
- {
- "exclude-annotation": "com.android.bedstead.harrier.annotations.Postsubmit"
- }
- ]
- }
- ],
"postsubmit": [
+ // Note that these test cases are running in postsubmit only. Bedstead tests can be pretty slow
+ // (due to adding/removing users, which makes the device somewhat unresponsive). This can also
+ // cause flakyness due to timeouts. In postsubmit, these tests have more time to run which makes
+ // them less flaky (and this flakyness is arguably less of an issue in this case).
{
"name": "SafetyCenterFunctionalMultiUsersTestCases"
}
- ],
- "mainline-presubmit": [
- {
- "name": "SafetyCenterFunctionalMultiUsersTestCases[com.google.android.permission.apex]",
- "options": [
- {
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
- "exclude-annotation": "com.android.bedstead.harrier.annotations.Postsubmit"
- }
- ]
- }
]
}
diff --git a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
index 07ae129ec..9534b597a 100644
--- a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
+++ b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
@@ -23,6 +23,7 @@ import android.content.Context
import android.content.Intent
import android.os.UserHandle
import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterEntry
import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN
import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
@@ -30,30 +31,34 @@ import android.safetycenter.SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_
import android.safetycenter.SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY
import android.safetycenter.SafetyCenterEntryGroup
import android.safetycenter.SafetyCenterEntryOrGroup
+import android.safetycenter.SafetyCenterIssue
import android.safetycenter.SafetyCenterManager
import android.safetycenter.SafetyCenterStaticEntry
import android.safetycenter.SafetyCenterStaticEntryGroup
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceData
import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
import com.android.bedstead.harrier.BedsteadJUnit4
import com.android.bedstead.harrier.DeviceState
import com.android.bedstead.harrier.annotations.EnsureHasAdditionalUser
import com.android.bedstead.harrier.annotations.EnsureHasCloneProfile
import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
-import com.android.bedstead.harrier.annotations.Postsubmit
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner
import com.android.bedstead.nene.TestApis
import com.android.bedstead.nene.types.OptionalBoolean.TRUE
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
+import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
+import com.android.safetycenter.testing.NotificationCharacteristics
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterDataWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetySourceDataWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.setSafetySourceDataWithPermission
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
+import com.android.safetycenter.testing.SafetyCenterFlags
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ACTION_TEST_ACTIVITY
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.DYNAMIC_BAREBONE_ID
@@ -76,27 +81,30 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.STATIC
import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withoutExtras
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceTestData
import com.android.safetycenter.testing.SafetySourceTestData.Companion.EVENT_SOURCE_STATE_CHANGED
+import com.android.safetycenter.testing.SafetySourceTestData.Companion.ISSUE_TYPE_ID
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
+import com.android.safetycenter.testing.TestNotificationListener
import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitAllTextNotDisplayed
import com.google.common.base.Preconditions.checkState
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
import org.junit.After
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.ClassRule
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* Functional tests for our APIs and UI on a device with multiple users. e.g. with a managed or
- * secondary user(s).
+ * additional user(s).
*/
+@LargeTest
@RunWith(BedsteadJUnit4::class)
class SafetyCenterMultiUsersTest {
@@ -104,70 +112,65 @@ class SafetyCenterMultiUsersTest {
@JvmField @ClassRule @Rule val deviceState: DeviceState = DeviceState()
}
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
private val context: Context = ApplicationProvider.getApplicationContext()
- private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context)
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestData = SafetyCenterTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
private var inQuietMode = false
- private val primaryProfileOnlyIssues =
- listOf(
- safetyCenterTestData.safetyCenterIssueCritical(
- DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueCritical(
- ISSUE_ONLY_BAREBONE_ID,
- attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueRecommendation(
- DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueRecommendation(
- ISSUE_ONLY_ALL_OPTIONAL_ID,
- attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueInformation(
- DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueInformation(
- ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ private val primaryProfileOnlyIssues: List<SafetyCenterIssue>
+ get() =
+ listOf(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ DYNAMIC_BAREBONE_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ DYNAMIC_DISABLED_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ )
)
- )
- private val dynamicBareboneDefault =
- safetyCenterTestData.safetyCenterEntryDefault(DYNAMIC_BAREBONE_ID)
+ private val dynamicBareboneDefault: SafetyCenterEntry
+ get() = safetyCenterTestData.safetyCenterEntryDefault(DYNAMIC_BAREBONE_ID)
- private val dynamicBareboneUpdated =
- safetyCenterTestData.safetyCenterEntryCritical(DYNAMIC_BAREBONE_ID)
+ private val dynamicBareboneUpdated: SafetyCenterEntry
+ get() = safetyCenterTestData.safetyCenterEntryCritical(DYNAMIC_BAREBONE_ID)
- private val dynamicDisabledDefault =
- safetyCenterTestData
- .safetyCenterEntryDefaultBuilder(DYNAMIC_DISABLED_ID)
- .setPendingIntent(null)
- .setEnabled(false)
- .build()
+ private val dynamicDisabledDefault: SafetyCenterEntry
+ get() =
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(DYNAMIC_DISABLED_ID)
+ .setPendingIntent(null)
+ .setEnabled(false)
+ .build()
- private val dynamicDisabledUpdated =
- safetyCenterTestData.safetyCenterEntryRecommendation(DYNAMIC_DISABLED_ID)
+ private val dynamicDisabledUpdated: SafetyCenterEntry
+ get() = safetyCenterTestData.safetyCenterEntryRecommendation(DYNAMIC_DISABLED_ID)
- private val dynamicDisabledForWorkDefaultBuilder
+ private val dynamicDisabledForWorkDefaultBuilder: SafetyCenterEntry.Builder
get() =
safetyCenterTestData
.safetyCenterEntryDefaultBuilder(
@@ -178,26 +181,54 @@ class SafetyCenterMultiUsersTest {
.setPendingIntent(null)
.setEnabled(false)
- private val dynamicDisabledForWorkDefault
+ private val dynamicDisabledForWorkDefault: SafetyCenterEntry
get() = dynamicDisabledForWorkDefaultBuilder.build()
- private val dynamicDisabledForWorkPaused
+ private val dynamicDisabledForWorkPausedUpdated: SafetyCenterEntry
get() =
- dynamicDisabledForWorkDefaultBuilder
- // TODO(b/233188021): This needs to use the Enterprise API to override the "work"
- // keyword.
- .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused"))
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(
+ DYNAMIC_DISABLED_ID,
+ deviceState.workProfile().id(),
+ title = "Ok title for Work",
+ pendingIntent = null
+ )
+ .setSummary(
+ safetyCenterResourcesApk.getStringByName("work_profile_paused"),
+ )
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
+ .setEnabled(false)
.build()
- private val dynamicDisabledForWorkUpdated
+ private val dynamicDisabledForWorkUpdated: SafetyCenterEntry
get() = safetyCenterEntryOkForWork(DYNAMIC_DISABLED_ID, deviceState.workProfile().id())
- private val dynamicHiddenUpdated =
- safetyCenterTestData.safetyCenterEntryUnspecified(DYNAMIC_HIDDEN_ID, pendingIntent = null)
+ private val dynamicHiddenUpdated: SafetyCenterEntry
+ get() =
+ safetyCenterTestData.safetyCenterEntryUnspecified(
+ DYNAMIC_HIDDEN_ID,
+ pendingIntent = null
+ )
- private val dynamicHiddenForWorkUpdated
+ private val dynamicHiddenForWorkUpdated: SafetyCenterEntry
get() = safetyCenterEntryOkForWork(DYNAMIC_HIDDEN_ID, deviceState.workProfile().id())
+ private val dynamicHiddenForWorkPausedUpdated
+ get() =
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(
+ DYNAMIC_HIDDEN_ID,
+ deviceState.workProfile().id(),
+ title = "Ok title for Work",
+ pendingIntent = null
+ )
+ .setSummary(
+ safetyCenterResourcesApk.getStringByName("work_profile_paused"),
+ )
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
+ .setEnabled(false)
+ .build()
+
private val staticGroupBuilder =
SafetyCenterEntryGroup.Builder(STATIC_GROUP_ID, "OK")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
@@ -213,70 +244,78 @@ class SafetyCenterMultiUsersTest {
private val staticAllOptional =
safetyCenterTestData.safetyCenterEntryDefaultStaticBuilder(STATIC_ALL_OPTIONAL_ID).build()
- private val staticAllOptionalForWorkBuilder
- get() =
- safetyCenterTestData
- .safetyCenterEntryDefaultStaticBuilder(
- STATIC_ALL_OPTIONAL_ID,
- userId = deviceState.workProfile().id(),
- title = "Paste"
- )
- .setPendingIntent(
- createTestActivityRedirectPendingIntentForUser(
- deviceState.workProfile().userHandle()
- )
+ private fun staticAllOptionalForWorkBuilder(inQuietMode: Boolean = false) =
+ safetyCenterTestData
+ .safetyCenterEntryDefaultStaticBuilder(
+ STATIC_ALL_OPTIONAL_ID,
+ userId = deviceState.workProfile().id(),
+ title = "Paste"
+ )
+ .setPendingIntent(
+ createTestActivityRedirectPendingIntentForUser(
+ deviceState.workProfile().userHandle(),
+ inQuietMode
)
+ )
private val staticAllOptionalForWork
- get() = staticAllOptionalForWorkBuilder.build()
+ get() = staticAllOptionalForWorkBuilder().build()
private val staticAllOptionalForWorkPaused
get() =
- staticAllOptionalForWorkBuilder
- // TODO(b/233188021): This needs to use the Enterprise API to override the "work"
- // keyword.
- .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused"))
+ staticAllOptionalForWorkBuilder(inQuietMode = true)
+ .setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused"))
.setEnabled(false)
.build()
- private val staticEntry =
- SafetyCenterStaticEntry.Builder("OK")
- .setSummary("OK")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build()
-
- private val staticEntryUpdated =
- SafetyCenterStaticEntry.Builder("Unspecified title")
- .setSummary("Unspecified summary")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build()
-
- private val staticEntryForWorkBuilder
+ private val staticEntry: SafetyCenterStaticEntry
get() =
- SafetyCenterStaticEntry.Builder("Paste")
+ SafetyCenterStaticEntry.Builder("OK")
.setSummary("OK")
- .setPendingIntent(
- createTestActivityRedirectPendingIntentForUser(
- deviceState.workProfile().userHandle()
- )
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build()
+
+ private val staticEntryUpdated: SafetyCenterStaticEntry
+ get() =
+ SafetyCenterStaticEntry.Builder("Unspecified title")
+ .setSummary("Unspecified summary")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build()
+
+ private fun staticEntryForWorkBuilder(
+ title: CharSequence = "Paste",
+ inQuietMode: Boolean = false
+ ) =
+ SafetyCenterStaticEntry.Builder(title)
+ .setSummary("OK")
+ .setPendingIntent(
+ createTestActivityRedirectPendingIntentForUser(
+ deviceState.workProfile().userHandle(),
+ inQuietMode
)
+ )
- private val staticEntryForWork
- get() = staticEntryForWorkBuilder.build()
+ private val staticEntryForWork: SafetyCenterStaticEntry
+ get() = staticEntryForWorkBuilder().build()
- private val staticEntryForWorkPaused
+ private val staticEntryForWorkPaused: SafetyCenterStaticEntry
get() =
- staticEntryForWorkBuilder
- // TODO(b/233188021): This needs to use the Enterprise API to override the "work"
- // keyword.
- .setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused"))
+ staticEntryForWorkBuilder(inQuietMode = true)
+ .setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused"))
.build()
- private val staticEntryForWorkUpdated =
- SafetyCenterStaticEntry.Builder("Unspecified title for Work")
- .setSummary("Unspecified summary")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build()
+ private val staticEntryForWorkPausedUpdated
+ get() =
+ staticEntryForWorkBuilder(title = "Unspecified title for Work", inQuietMode = true)
+ .setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused"))
+ .build()
+
+ private val staticEntryForWorkUpdated: SafetyCenterStaticEntry
+ get() =
+ SafetyCenterStaticEntry.Builder("Unspecified title for Work")
+ .setSummary("Unspecified summary")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build()
private val safetyCenterDataForAdditionalUser
get() =
@@ -298,34 +337,26 @@ class SafetyCenterMultiUsersTest {
emptyList()
)
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2)
+ val safetyCenterTestRule =
+ SafetyCenterTestRule(safetyCenterTestHelper, withNotifications = true)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
+ fun setRefreshTimeoutsBeforeTest() {
+ SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
}
@After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- resetQuietMode()
+ fun resetQuietModeAfterTest() {
+ setQuietMode(false)
}
@Test
@EnsureHasWorkProfile
- @Ignore
- // Tests that check the UI takes a lot of time and they might get timeout in the postsubmits.
- // TODO(b/242999951): Write this test using the APIs instead of checking the UI.
- fun launchActivity_withProfileOwner_displaysWorkPolicyInfo() {
+ fun getSafetyCenterData_withProfileOwner_hasWorkPolicyInfo() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig)
findWorkPolicyInfo()
@@ -333,10 +364,7 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasDeviceOwner
- @Ignore
- // Tests that check the UI takes a lot of time and they might get timeout in the postsubmits.
- // TODO(b/242999951): Write this test using the APIs instead of checking the UI.
- fun launchActivity_withDeviceOwner_displaysWorkPolicyInfo() {
+ fun getSafetyCenterData_withDeviceOwner_hasWorkPolicyInfo() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig)
findWorkPolicyInfo()
@@ -344,21 +372,24 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasWorkProfile
- @Ignore
- // Tests that check the UI takes a lot of time and they might get timeout in the postsubmits.
- // TODO(b/242999951): Write this test using the APIs instead of checking the UI.
- fun launchActivity_withQuietModeEnabled_shouldNotDisplayWorkPolicyInfo() {
+ fun launchActivity_withQuietModeEnabled_hasWorkPolicyInfo() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig)
- findWorkPolicyInfo()
setQuietMode(true)
+
+ findWorkPolicyInfo()
+ }
+
+ @Test
+ @EnsureHasNoWorkProfile
+ @EnsureHasNoDeviceOwner
+ fun launchActivity_withoutWorkProfileOrDeviceOwner_doesntHaveWorkPolicyInfo() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.workPolicyInfoConfig)
+
context.launchSafetyCenterActivity { waitAllTextNotDisplayed("Your work policy info") }
}
@Test
- @Ignore
- // Test involving toggling of quiet mode are flaky.
- // TODO(b/237365018): Re-enable them back once we figure out a way to make them stable.
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetySourceData_withQuietModeEnabled_dataIsNotCleared() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -381,7 +412,6 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
- @Postsubmit(reason = "Test takes too much time to setup")
fun getSafetySourceData_afterAdditionalUserRemoved_returnsNull() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
val additionalUserSafetyCenterManager =
@@ -409,7 +439,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetySourceData_withoutInteractAcrossUserPermission_shouldThrowError() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -422,7 +451,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetySourceData_withoutAppInstalledForWorkProfile_shouldReturnNull() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -444,7 +472,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetySourceData_withRemovedProfile_shouldReturnNull() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -466,10 +493,7 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
- @Ignore
- // Test involving toggling of quiet mode are flaky.
fun getSafetySourceData_withProfileInQuietMode_shouldReturnData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
val managedSafetyCenterManager =
@@ -490,7 +514,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasNoWorkProfile
fun getSafetyCenterData_withComplexConfigWithoutWorkProfile_returnsPrimaryDataFromConfig() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
@@ -506,9 +529,7 @@ class SafetyCenterMultiUsersTest {
SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
.setSummary(
- safetyCenterResourcesContext.getStringByName(
- "group_unknown_summary"
- )
+ safetyCenterResourcesApk.getStringByName("group_unknown_summary")
)
.setEntries(listOf(dynamicBareboneDefault, dynamicDisabledDefault))
.setSeverityUnspecifiedIconType(
@@ -528,7 +549,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetyCenterData_withComplexConfigWithoutDataProvided_returnsDataFromConfig() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
@@ -544,9 +564,7 @@ class SafetyCenterMultiUsersTest {
SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
.setSummary(
- safetyCenterResourcesContext.getStringByName(
- "group_unknown_summary"
- )
+ safetyCenterResourcesApk.getStringByName("group_unknown_summary")
)
.setEntries(
listOf(
@@ -579,7 +597,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetyCenterData_withComplexConfigWithPrimaryDataProvided_returnsPrimaryDataProvided() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
@@ -633,7 +650,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetyCenterData_withComplexConfigWithAllDataProvided_returnsAllDataProvided() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
@@ -744,15 +760,12 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
- @Ignore
- // Test involving toggling of quiet mode are flaky.
- // TODO(b/237365018): Re-enable them back once we figure out a way to make them stable.
fun getSafetyCenterData_withQuietMode_shouldHaveWorkProfilePausedSummaryAndNoWorkIssues() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
updatePrimaryProfileSources()
updateWorkProfileSources()
-
setQuietMode(true)
+
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
val safetyCenterDataFromComplexConfig =
@@ -768,8 +781,9 @@ class SafetyCenterMultiUsersTest {
listOf(
dynamicBareboneUpdated,
dynamicDisabledUpdated,
- dynamicDisabledForWorkPaused,
- dynamicHiddenUpdated
+ dynamicDisabledForWorkPausedUpdated,
+ dynamicHiddenUpdated,
+ dynamicHiddenForWorkPausedUpdated,
)
)
.setSeverityUnspecifiedIconType(
@@ -794,7 +808,7 @@ class SafetyCenterMultiUsersTest {
"OK",
listOf(
staticEntryUpdated,
- staticEntryForWorkPaused,
+ staticEntryForWorkPausedUpdated,
staticEntry,
staticEntryForWorkPaused
)
@@ -807,7 +821,6 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
- @Postsubmit(reason = "Test takes too much time to setup")
fun getSafetyCenterData_withDataForDifferentUserProfileGroup_shouldBeUnaffected() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
val dataForPrimaryUser = safetySourceTestData.information
@@ -830,7 +843,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Ignore // Removing a managed profile causes a refresh, which makes some tests flaky.
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetyCenterData_afterManagedProfileRemoved_returnsDefaultData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -845,9 +857,7 @@ class SafetyCenterMultiUsersTest {
SafetyCenterEntryGroup.Builder(SINGLE_SOURCE_GROUP_ID, "OK")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
.setSummary(
- safetyCenterResourcesContext.getStringByName(
- "group_unknown_summary"
- )
+ safetyCenterResourcesApk.getStringByName("group_unknown_summary")
)
.setEntries(
listOf(
@@ -905,7 +915,6 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
- @Postsubmit(reason = "Test takes too much time to setup")
fun getSafetyCenterData_afterAdditionalUserRemoved_returnsDefaultData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
val additionalUserSafetyCenterManager =
@@ -926,7 +935,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_primaryProfileIssueOnlySource_shouldNotBeAbleToSetDataToWorkProfile() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
@@ -944,7 +952,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_withoutInteractAcrossUserPermission_shouldThrowError() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -962,7 +969,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_withoutAppInstalledForWorkProfile_shouldNoOp() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -984,7 +990,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_withRemovedProfile_shouldNoOp() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -1006,7 +1011,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasCloneProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_withUnsupportedProfile_shouldNoOp() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -1027,10 +1031,7 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
- @Ignore
- // Test involving toggling of quiet mode are flaky.
fun setSafetySourceData_withProfileInQuietMode_shouldSetData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
val dataForWork = safetySourceTestData.informationWithIssueForWork
@@ -1051,7 +1052,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_issuesOnlySourceWithWorkProfile_shouldBeAbleToSetData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceAllProfileConfig)
@@ -1079,7 +1079,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_primaryProfileSource_shouldNotBeAbleToSetDataToWorkProfile() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -1096,7 +1095,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun setSafetySourceData_sourceWithWorkProfile_shouldBeAbleToSetData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -1122,7 +1120,32 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
+ @EnsureHasWorkProfile(installInstrumentedApp = TRUE)
+ fun setSafetySourceData_notificationsAllowed_workProfile_sendsNotification() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
+ SafetyCenterFlags.notificationsEnabled = true
+ SafetyCenterFlags.notificationsAllowedSources = setOf(SINGLE_SOURCE_ALL_PROFILE_ID)
+ SafetyCenterFlags.immediateNotificationBehaviorIssues =
+ setOf("$SINGLE_SOURCE_ALL_PROFILE_ID/$ISSUE_TYPE_ID")
+ val dataForWork = safetySourceTestData.informationWithIssueForWork
+ val managedSafetyCenterManager =
+ getSafetyCenterManagerForUser(deviceState.workProfile().userHandle())
+
+ managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
+ SINGLE_SOURCE_ALL_PROFILE_ID,
+ dataForWork
+ )
+
+ TestNotificationListener.waitForNotificationsMatching(
+ NotificationCharacteristics(
+ title = "Information issue title",
+ text = "Information issue summary",
+ actions = listOf("Review")
+ )
+ )
+ }
+
+ @Test
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
fun setSafetySourceData_forStoppedUser_shouldSetData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -1144,7 +1167,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
fun setSafetySourceData_forBothPrimaryAdditionalUser_shouldSetData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -1170,7 +1192,6 @@ class SafetyCenterMultiUsersTest {
}
@Test
- @Postsubmit(reason = "Test takes too much time to setup")
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
fun setSafetySourceData_forAdditionalUser_shouldNotAffectDataForPrimaryUser() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -1190,8 +1211,6 @@ class SafetyCenterMultiUsersTest {
private fun findWorkPolicyInfo() {
context.launchSafetyCenterActivity {
- // TODO(b/233188021): This needs to use the Enterprise API to override the "work"
- // keyword.
waitAllTextDisplayed("Your work policy info", "Settings managed by your IT admin")
}
}
@@ -1207,11 +1226,15 @@ class SafetyCenterMultiUsersTest {
}
}
- private fun createTestActivityRedirectPendingIntentForUser(user: UserHandle): PendingIntent {
+ private fun createTestActivityRedirectPendingIntentForUser(
+ user: UserHandle,
+ inQuietMode: Boolean = false
+ ): PendingIntent {
return callWithShellPermissionIdentity(INTERACT_ACROSS_USERS) {
SafetySourceTestData.createRedirectPendingIntent(
getContextForUser(user),
- Intent(ACTION_TEST_ACTIVITY)
+ Intent(ACTION_TEST_ACTIVITY),
+ inQuietMode
)
}
}
@@ -1238,16 +1261,23 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterDataWithPermission()
}
- private fun setQuietMode(value: Boolean) {
- deviceState.workProfile().setQuietMode(value)
- inQuietMode = value
- }
-
- private fun resetQuietMode() {
- if (!inQuietMode) {
+ private fun setQuietMode(enableQuietMode: Boolean) {
+ if (inQuietMode == enableQuietMode) {
return
}
- setQuietMode(false)
+ if (enableQuietMode) {
+ deviceState.workProfile().setQuietMode(true)
+ } else {
+ // This is needed to ensure the refresh broadcast doesn't leak onto other tests.
+ disableQuietModeAndWaitForRefreshToComplete()
+ }
+ inQuietMode = enableQuietMode
+ }
+
+ private fun disableQuietModeAndWaitForRefreshToComplete() {
+ val listener = safetyCenterTestHelper.addListener()
+ deviceState.workProfile().setQuietMode(false)
+ listener.waitForSafetyCenterRefresh()
}
private fun safetyCenterEntryOkForWork(sourceId: String, managedUserId: Int) =
diff --git a/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml b/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
index 5357ed4f7..a1826653f 100644
--- a/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
+++ b/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
@@ -43,9 +43,12 @@
aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
<option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
index ce8a70fbe..00832b2da 100644
--- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
+++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
@@ -17,11 +17,10 @@
package android.safetycenter.functional.ui
import android.content.Context
-import android.os.Build.VERSION.CODENAME
+import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
-import android.platform.test.rule.ScreenRecordRule
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
@@ -38,7 +37,6 @@ import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
import com.android.safetycenter.testing.SafetyCenterFlags
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ISSUE_ONLY_ALL_OPTIONAL_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
@@ -48,12 +46,15 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_4
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_5
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
import com.android.safetycenter.testing.SafetySourceTestData
import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID
import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID
+import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.MORE_ISSUES_LABEL
import com.android.safetycenter.testing.UiTestHelper.RESCAN_BUTTON_LABEL
import com.android.safetycenter.testing.UiTestHelper.clickConfirmDismissal
@@ -68,13 +69,12 @@ import com.android.safetycenter.testing.UiTestHelper.waitButtonDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitCollapsedIssuesDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitExpandedIssuesDisplayed
+import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceDataDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
import org.junit.After
-import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -83,40 +83,18 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterActivityTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
- @get:Rule val screenRecordRule = ScreenRecordRule()
-
private val context: Context = getApplicationContext()
-
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
- // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@After
fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
getUiDevice().resetRotation()
}
@@ -430,7 +408,6 @@ class SafetyCenterActivityTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun entryListWithEntryGroup_clickingAClickableDisabledEntry_redirectsToDifferentScreen() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -486,7 +463,6 @@ class SafetyCenterActivityTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun entryListWithSingleSource_clickingDefaultEntryImplicitIntent_redirectsToDifferentScreen() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.implicitIntentSingleSourceConfig)
@@ -554,7 +530,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_withAttribution_hasProperContentDescriptions() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -727,7 +703,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_resolveIssue_withDialogClickYes_resolves() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
@@ -753,7 +729,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_resolveIssue_withDialog_rotates_clickYes_resolves() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
@@ -783,7 +759,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_resolveIssue_withDialogClicksNo_cancels() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
@@ -885,7 +861,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_withAttributionTitleSetBySource_displaysAttributionTitle() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -896,7 +872,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_attributionNotSetBySource_displaysGroupTitleAsAttribution() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -907,7 +883,7 @@ class SafetyCenterActivityTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun issueCard_attributionNotSetBySourceAndGroupTitleNull_doesNotDisplayAttributionTitle() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceNoGroupTitleConfig)
@@ -920,9 +896,6 @@ class SafetyCenterActivityTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
fun issueCard_attributionNotSetBySourceOnTiramisu_doesNotDisplayAttributionTitle() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(CODENAME == "UpsideDownCake")
- assumeFalse(CODENAME == "VanillaIceCream")
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
val data = safetySourceTestData.recommendationWithGeneralIssue
@@ -932,7 +905,6 @@ class SafetyCenterActivityTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun launchActivity_fromQuickSettings_issuesExpanded() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1423,10 +1395,6 @@ class SafetyCenterActivityTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
fun launchSafetyCenter_enableSubpagesFlagOnT_stillShowsExpandAndCollapseEntries() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(CODENAME == "UpsideDownCake")
- assumeFalse(CODENAME == "VanillaIceCream")
-
SafetyCenterFlags.showSubpages = true
val sourceTestData = safetySourceTestData.information
val config = safetyCenterTestConfigs.multipleSourceGroupsConfig
@@ -1480,17 +1448,40 @@ class SafetyCenterActivityTest {
}
}
- companion object {
- private const val EXPAND_ISSUE_GROUP_QS_FRAGMENT_KEY = "expand_issue_group_qs_fragment_key"
- private const val SAFETY_SOURCE_1_TITLE = "Safety Source 1 Title"
- private const val SAFETY_SOURCE_1_SUMMARY = "Safety Source 1 Summary"
- private const val SAFETY_SOURCE_2_TITLE = "Safety Source 2 Title"
- private const val SAFETY_SOURCE_2_SUMMARY = "Safety Source 2 Summary"
- private const val SAFETY_SOURCE_3_TITLE = "Safety Source 3 Title"
- private const val SAFETY_SOURCE_3_SUMMARY = "Safety Source 3 Summary"
- private const val SAFETY_SOURCE_4_TITLE = "Safety Source 4 Title"
- private const val SAFETY_SOURCE_4_SUMMARY = "Safety Source 4 Summary"
- private const val SAFETY_SOURCE_5_TITLE = "Safety Source 5 Title"
- private const val SAFETY_SOURCE_5_SUMMARY = "Safety Source 5 Summary"
+ @Test
+ fun launchActivity_openWithPrivacyControlsIntent_showsPrivacyControls() {
+ context.launchSafetyCenterActivity(intentAction = PRIVACY_CONTROLS_ACTION) {
+ waitPageTitleDisplayed("Privacy controls")
+ }
+ }
+
+ @Test
+ fun launchActivity_openWithPrivacyControlsIntentWithScDisabled_showsLegacyPrivacyPage() {
+ // This test should technically run on T+ but we have to restrict it to V+ as b/286690307 is
+ // causing a flake which was only fixed on master.
+ assumeTrue(
+ Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE || Build.VERSION.CODENAME == "VanillaIceCream"
+ )
+ safetyCenterTestHelper.setEnabled(false)
+
+ context.launchSafetyCenterActivity(intentAction = PRIVACY_CONTROLS_ACTION) {
+ waitDisplayed(By.pkg(context.getSettingsPackageName()))
+ waitPageTitleDisplayed("Privacy")
+ }
+ }
+
+ private companion object {
+ const val EXPAND_ISSUE_GROUP_QS_FRAGMENT_KEY = "expand_issue_group_qs_fragment_key"
+ const val SAFETY_SOURCE_1_TITLE = "Safety Source 1 Title"
+ const val SAFETY_SOURCE_1_SUMMARY = "Safety Source 1 Summary"
+ const val SAFETY_SOURCE_2_TITLE = "Safety Source 2 Title"
+ const val SAFETY_SOURCE_2_SUMMARY = "Safety Source 2 Summary"
+ const val SAFETY_SOURCE_3_TITLE = "Safety Source 3 Title"
+ const val SAFETY_SOURCE_3_SUMMARY = "Safety Source 3 Summary"
+ const val SAFETY_SOURCE_4_TITLE = "Safety Source 4 Title"
+ const val SAFETY_SOURCE_4_SUMMARY = "Safety Source 4 Summary"
+ const val SAFETY_SOURCE_5_TITLE = "Safety Source 5 Title"
+ const val SAFETY_SOURCE_5_SUMMARY = "Safety Source 5 Summary"
+ const val PRIVACY_CONTROLS_ACTION = "android.settings.PRIVACY_CONTROLS"
}
}
diff --git a/tests/functional/safetycenter/singleuser/AndroidManifest.xml b/tests/functional/safetycenter/singleuser/AndroidManifest.xml
index 7ca38dede..18a3b167a 100644
--- a/tests/functional/safetycenter/singleuser/AndroidManifest.xml
+++ b/tests/functional/safetycenter/singleuser/AndroidManifest.xml
@@ -17,14 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.safetycenter.functional">
<application>
- <service android:name=".testing.TestNotificationListener"
- android:label="TestNotificationListener"
- android:exported="false"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
- <intent-filter>
- <action android:name="android.service.notification.NotificationListenerService" />
- </intent-filter>
- </service>
+
<uses-library android:name="android.test.runner"/>
</application>
diff --git a/tests/functional/safetycenter/singleuser/AndroidTest.xml b/tests/functional/safetycenter/singleuser/AndroidTest.xml
index d9d5b1361..3aa173508 100644
--- a/tests/functional/safetycenter/singleuser/AndroidTest.xml
+++ b/tests/functional/safetycenter/singleuser/AndroidTest.xml
@@ -43,9 +43,12 @@
aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
<option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
index 75d2fd57d..e2a7802b3 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
@@ -19,7 +19,6 @@ package android.safetycenter.functional
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.UserHandle
@@ -70,17 +69,17 @@ import com.android.safetycenter.internaldata.SafetyCenterBundles
import com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY
import com.android.safetycenter.internaldata.SafetyCenterEntryId
import com.android.safetycenter.internaldata.SafetyCenterIds
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.Coroutines.waitForWithTimeout
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterDataWithPermission
+import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetySourceDataWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.refreshSafetySourcesWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission
import com.android.safetycenter.testing.SafetyCenterFlags
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ACTION_TEST_ACTIVITY_EXPORTED
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID
@@ -116,6 +115,7 @@ import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withAttri
import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withDismissedIssuesIfAtLeastU
import com.android.safetycenter.testing.SafetyCenterTestData.Companion.withoutExtras
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
@@ -130,15 +130,15 @@ import com.android.safetycenter.testing.SafetySourceTestData.Companion.INFORMATI
import com.android.safetycenter.testing.SafetySourceTestData.Companion.RECOMMENDATION_ISSUE_ID
import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.google.common.base.Preconditions.checkState
import com.google.common.truth.Truth.assertThat
import java.time.Duration
import kotlin.test.assertFailsWith
import kotlinx.coroutines.TimeoutCancellationException
-import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
-import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -146,594 +146,628 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterManagerTest {
private val context: Context = getApplicationContext()
- private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context)
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestData = SafetyCenterTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
- private val safetyCenterStatusOk =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_summary")
- )
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
- .build()
+ private val safetyCenterStatusOk: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ private val safetyCenterStatusUnknownScanning: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName("scanning_title"),
+ safetyCenterResourcesApk.getStringByName("loading_summary")
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
+ .setRefreshStatus(REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS)
+ .build()
+
+ private val safetyCenterStatusOkOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ private val safetyCenterStatusOkReviewOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_ok_review_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ private val safetyCenterStatusOkReview: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_ok_review_title"
+ ),
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_ok_review_summary"
+ )
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
+ .build()
+
+ private val safetyCenterStatusGeneralRecommendationOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_safety_recommendation_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ private val safetyCenterStatusAccountRecommendationOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_account_recommendation_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
+
+ private val safetyCenterStatusDeviceRecommendationOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_device_recommendation_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+ .build()
- private val safetyCenterStatusUnknownScanning =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName("scanning_title"),
- safetyCenterResourcesContext.getStringByName("loading_summary")
+ private val safetyCenterStatusGeneralCriticalOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_critical_safety_warning_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ private val safetyCenterStatusGeneralCriticalTwoAlerts: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_critical_safety_warning_title"
+ ),
+ safetyCenterTestData.getAlertString(2)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ private val safetyCenterStatusAccountCriticalOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_critical_account_warning_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ private val safetyCenterStatusAccountCriticalTwoAlerts: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_critical_account_warning_title"
+ ),
+ safetyCenterTestData.getAlertString(2)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ private val safetyCenterStatusDeviceCriticalOneAlert: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_critical_device_warning_title"
+ ),
+ safetyCenterTestData.getAlertString(1)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ private val safetyCenterStatusDeviceCriticalTwoAlerts: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_critical_device_warning_title"
+ ),
+ safetyCenterTestData.getAlertString(2)
+ )
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+
+ private val safetyCenterEntryOrGroupRecommendation: SafetyCenterEntryOrGroup
+ get() =
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ )
+
+ private val safetyCenterEntryOrGroupCritical: SafetyCenterEntryOrGroup
+ get() =
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryCritical(SINGLE_SOURCE_ID)
+ )
+
+ private val safetyCenterEntryGroupMixedFromComplexConfig: SafetyCenterEntryOrGroup
+ get() =
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(MIXED_STATEFUL_GROUP_ID, "OK")
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
+ .setSummary(safetyCenterResourcesApk.getStringByName("group_unknown_summary"))
+ .setEntries(
+ listOf(
+ safetyCenterTestData.safetyCenterEntryDefault(DYNAMIC_IN_STATEFUL_ID),
+ SafetyCenterEntry.Builder(
+ SafetyCenterTestData.entryId(STATIC_IN_STATEFUL_ID),
+ "OK"
+ )
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
+ .setSummary("OK")
+ .setPendingIntent(
+ safetySourceTestData.testActivityRedirectPendingIntent
+ )
+ .setSeverityUnspecifiedIconType(
+ SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON
+ )
+ .build()
+ )
+ )
+ .setSeverityUnspecifiedIconType(
+ SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
+ )
+ .build()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
- .setRefreshStatus(REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS)
- .build()
- private val safetyCenterStatusOkOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterTestData.getAlertString(1)
+ private val safetyCenterStaticEntryGroupFromComplexConfig: SafetyCenterStaticEntryGroup
+ get() =
+ SafetyCenterStaticEntryGroup(
+ "OK",
+ listOf(
+ SafetyCenterStaticEntry.Builder("OK")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build(),
+ SafetyCenterStaticEntry.Builder("OK")
+ .setSummary("OK")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build()
+ )
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
- .build()
- private val safetyCenterStatusOkReviewOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_title"
- ),
- safetyCenterTestData.getAlertString(1)
+ private val safetyCenterStaticEntryGroupMixedFromComplexConfig: SafetyCenterStaticEntryGroup
+ get() =
+ SafetyCenterStaticEntryGroup(
+ "OK",
+ listOf(
+ SafetyCenterStaticEntry.Builder("OK")
+ .setSummary("OK")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build(),
+ SafetyCenterStaticEntry.Builder("OK")
+ .setSummary("OK")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build()
+ )
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
- .build()
- private val safetyCenterStatusOkReview =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_title"
- ),
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_summary"
+ private val safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig:
+ SafetyCenterStaticEntryGroup
+ get() =
+ SafetyCenterStaticEntryGroup(
+ "OK",
+ listOf(
+ SafetyCenterStaticEntry.Builder("Unspecified title")
+ .setSummary("Unspecified summary")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build(),
+ SafetyCenterStaticEntry.Builder("OK")
+ .setSummary("OK")
+ .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
+ .build()
)
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
- .build()
- private val safetyCenterStatusGeneralRecommendationOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_safety_recommendation_title"
+ private val safetyCenterDataFromConfigScanning: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusUnknownScanning,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(1)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
- .build()
- private val safetyCenterStatusAccountRecommendationOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_account_recommendation_title"
+ private val safetyCenterDataFromConfig: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusUnknown,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(1)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
- .build()
- private val safetyCenterStatusDeviceRecommendationOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_device_recommendation_title"
+ private val safetyCenterDataUnspecified: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOk,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryUnspecified(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(1)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
- .build()
- private val safetyCenterStatusGeneralCriticalOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_critical_safety_warning_title"
+ private val safetyCenterDataOk: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOk,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(1)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- private val safetyCenterStatusGeneralCriticalTwoAlerts =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_critical_safety_warning_title"
+ private val safetyCenterDataOkWithIconAction: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOk,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData
+ .safetyCenterEntryOkBuilder(SINGLE_SOURCE_ID)
+ .setIconAction(
+ ICON_ACTION_TYPE_INFO,
+ safetySourceTestData.testActivityRedirectPendingIntent
+ )
+ .build()
+ )
),
- safetyCenterTestData.getAlertString(2)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- private val safetyCenterStatusAccountCriticalOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_critical_account_warning_title"
+ private val safetyCenterDataUnknownScanningWithError: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusUnknownScanning,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(1)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- private val safetyCenterStatusAccountCriticalTwoAlerts =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_critical_account_warning_title"
+ private val safetyCenterDataUnknownReviewError: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusUnknown,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(2)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- private val safetyCenterStatusDeviceCriticalOneAlert =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_critical_device_warning_title"
+ private val safetyCenterDataOkOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOkOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID)),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID)
+ )
),
- safetyCenterTestData.getAlertString(1)
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- private val safetyCenterStatusDeviceCriticalTwoAlerts =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_critical_device_warning_title"
- ),
- safetyCenterTestData.getAlertString(2)
+ private val safetyCenterDataOkReviewCriticalEntry: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOkReview,
+ emptyList(),
+ listOf(safetyCenterEntryOrGroupCritical),
+ emptyList()
)
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- private val safetyCenterEntryOrGroupRecommendation =
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
- )
+ private val safetyCenterDataOkReviewRecommendationEntry: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOkReview,
+ emptyList(),
+ listOf(safetyCenterEntryOrGroupRecommendation),
+ emptyList()
+ )
- private val safetyCenterEntryOrGroupCritical =
- SafetyCenterEntryOrGroup(safetyCenterTestData.safetyCenterEntryCritical(SINGLE_SOURCE_ID))
+ private val safetyCenterDataOkReviewOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOkReviewOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID)),
+ listOf(safetyCenterEntryOrGroupCritical),
+ emptyList()
+ )
- private val safetyCenterEntryGroupMixedFromComplexConfig =
- SafetyCenterEntryOrGroup(
- SafetyCenterEntryGroup.Builder(MIXED_STATEFUL_GROUP_ID, "OK")
- .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
- .setSummary(safetyCenterResourcesContext.getStringByName("group_unknown_summary"))
- .setEntries(
- listOf(
- safetyCenterTestData.safetyCenterEntryDefault(DYNAMIC_IN_STATEFUL_ID),
- SafetyCenterEntry.Builder(
- SafetyCenterTestData.entryId(STATIC_IN_STATEFUL_ID),
- "OK"
- )
- .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
- .setSummary("OK")
- .setPendingIntent(
- safetySourceTestData.testActivityRedirectPendingIntent
- )
- .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON)
- .build()
+ private val safetyCenterDataGeneralRecommendationOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusGeneralRecommendationOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
)
- )
- .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
- .build()
- )
-
- private val safetyCenterStaticEntryGroupFromComplexConfig =
- SafetyCenterStaticEntryGroup(
- "OK",
- listOf(
- SafetyCenterStaticEntry.Builder("OK")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build(),
- SafetyCenterStaticEntry.Builder("OK")
- .setSummary("OK")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build()
+ ),
+ emptyList()
)
- )
- private val safetyCenterStaticEntryGroupMixedFromComplexConfig =
- SafetyCenterStaticEntryGroup(
- "OK",
- listOf(
- SafetyCenterStaticEntry.Builder("OK")
- .setSummary("OK")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build(),
- SafetyCenterStaticEntry.Builder("OK")
- .setSummary("OK")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build()
+ private val safetyCenterDataGeneralRecommendationAlertWithConfirmation: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusGeneralRecommendationOneAlert,
+ listOf(
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ SINGLE_SOURCE_ID,
+ confirmationDialog = true
+ )
+ ),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ )
+ ),
+ emptyList()
)
- )
- private val safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig =
- SafetyCenterStaticEntryGroup(
- "OK",
- listOf(
- SafetyCenterStaticEntry.Builder("Unspecified title")
- .setSummary("Unspecified summary")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build(),
- SafetyCenterStaticEntry.Builder("OK")
- .setSummary("OK")
- .setPendingIntent(safetySourceTestData.testActivityRedirectPendingIntent)
- .build()
+ private val safetyCenterDataAccountRecommendationOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusAccountRecommendationOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ )
+ ),
+ emptyList()
)
- )
-
- private val safetyCenterDataFromConfigScanning =
- SafetyCenterData(
- safetyCenterStatusUnknownScanning,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataFromConfig =
- SafetyCenterData(
- safetyCenterTestData.safetyCenterStatusUnknown,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataUnspecified =
- SafetyCenterData(
- safetyCenterStatusOk,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryUnspecified(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataOk =
- SafetyCenterData(
- safetyCenterStatusOk,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID))
- ),
- emptyList()
- )
-
- private val safetyCenterDataOkWithIconAction =
- SafetyCenterData(
- safetyCenterStatusOk,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData
- .safetyCenterEntryOkBuilder(SINGLE_SOURCE_ID)
- .setIconAction(
- ICON_ACTION_TYPE_INFO,
- safetySourceTestData.testActivityRedirectPendingIntent
- )
- .build()
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataUnknownScanningWithError =
- SafetyCenterData(
- safetyCenterStatusUnknownScanning,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataUnknownReviewError =
- SafetyCenterData(
- safetyCenterTestData.safetyCenterStatusUnknown,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataOkOneAlert =
- SafetyCenterData(
- safetyCenterStatusOkOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID)),
- listOf(
- SafetyCenterEntryOrGroup(safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID))
- ),
- emptyList()
- )
-
- private val safetyCenterDataOkReviewCriticalEntry =
- SafetyCenterData(
- safetyCenterStatusOkReview,
- emptyList(),
- listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
- )
-
- private val safetyCenterDataOkReviewRecommendationEntry =
- SafetyCenterData(
- safetyCenterStatusOkReview,
- emptyList(),
- listOf(safetyCenterEntryOrGroupRecommendation),
- emptyList()
- )
-
- private val safetyCenterDataOkReviewOneAlert =
- SafetyCenterData(
- safetyCenterStatusOkReviewOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID)),
- listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
- )
-
- private val safetyCenterDataGeneralRecommendationOneAlert =
- SafetyCenterData(
- safetyCenterStatusGeneralRecommendationOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataGeneralRecommendationAlertWithConfirmation =
- SafetyCenterData(
- safetyCenterStatusGeneralRecommendationOneAlert,
- listOf(
- safetyCenterTestData.safetyCenterIssueRecommendation(
- SINGLE_SOURCE_ID,
- confirmationDialog = true
- )
- ),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataAccountRecommendationOneAlert =
- SafetyCenterData(
- safetyCenterStatusAccountRecommendationOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataDeviceRecommendationOneAlert =
- SafetyCenterData(
- safetyCenterStatusDeviceRecommendationOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
- listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
- )
- ),
- emptyList()
- )
-
- private val safetyCenterDataGeneralCriticalOneAlert =
- SafetyCenterData(
- safetyCenterStatusGeneralCriticalOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
- listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
- )
- private val safetyCenterDataAccountCriticalOneAlert =
- SafetyCenterData(
- safetyCenterStatusAccountCriticalOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
- listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
- )
+ private val safetyCenterDataDeviceRecommendationOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusDeviceRecommendationOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ )
+ ),
+ emptyList()
+ )
- private val safetyCenterDataDeviceCriticalOneAlert =
- SafetyCenterData(
- safetyCenterStatusDeviceCriticalOneAlert,
- listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
- listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
- )
+ private val safetyCenterDataGeneralCriticalOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusGeneralCriticalOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
+ listOf(safetyCenterEntryOrGroupCritical),
+ emptyList()
+ )
- private val safetyCenterDataCriticalOneAlertInFlight =
- SafetyCenterData(
- safetyCenterStatusGeneralCriticalOneAlert,
- listOf(
- safetyCenterTestData.safetyCenterIssueCritical(
- SINGLE_SOURCE_ID,
- isActionInFlight = true
- )
- ),
- listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
- )
+ private val safetyCenterDataAccountCriticalOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusAccountCriticalOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
+ listOf(safetyCenterEntryOrGroupCritical),
+ emptyList()
+ )
- private val safetyCenterDataOkReviewOneDismissedAlertInFlight =
- SafetyCenterData(
- safetyCenterStatusOkReview,
- emptyList(),
+ private val safetyCenterDataDeviceCriticalOneAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusDeviceCriticalOneAlert,
+ listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
listOf(safetyCenterEntryOrGroupCritical),
emptyList()
)
- .withDismissedIssuesIfAtLeastU(
+
+ private val safetyCenterDataCriticalOneAlertInFlight: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusGeneralCriticalOneAlert,
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
SINGLE_SOURCE_ID,
isActionInFlight = true
)
- )
+ ),
+ listOf(safetyCenterEntryOrGroupCritical),
+ emptyList()
)
- private val safetyCenterDataFromComplexConfig =
- SafetyCenterData(
- safetyCenterTestData.safetyCenterStatusUnknown,
- emptyList(),
- listOf(
- SafetyCenterEntryOrGroup(
- SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
- .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
- .setSummary(
- safetyCenterResourcesContext.getStringByName("group_unknown_summary")
+ private val safetyCenterDataOkReviewOneDismissedAlertInFlight: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOkReview,
+ emptyList(),
+ listOf(safetyCenterEntryOrGroupCritical),
+ emptyList()
+ )
+ .withDismissedIssuesIfAtLeastU(
+ listOf(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ SINGLE_SOURCE_ID,
+ isActionInFlight = true
)
- .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY)
- .setEntries(
- listOf(
- safetyCenterTestData.safetyCenterEntryDefault(DYNAMIC_BAREBONE_ID),
- safetyCenterTestData
- .safetyCenterEntryDefaultBuilder(DYNAMIC_ALL_OPTIONAL_ID)
- .setEnabled(false)
- .build(),
- safetyCenterTestData
- .safetyCenterEntryDefaultBuilder(DYNAMIC_DISABLED_ID)
- .setPendingIntent(null)
- .setEnabled(false)
- .build(),
- safetyCenterTestData
- .safetyCenterEntryDefaultBuilder(DYNAMIC_OTHER_PACKAGE_ID)
- .setPendingIntent(null)
- .setEnabled(false)
- .build()
+ )
+ )
+
+ private val safetyCenterDataFromComplexConfig: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusUnknown,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
+ .setSummary(
+ safetyCenterResourcesApk.getStringByName("group_unknown_summary")
)
- )
- .build()
+ .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY)
+ .setEntries(
+ listOf(
+ safetyCenterTestData.safetyCenterEntryDefault(
+ DYNAMIC_BAREBONE_ID
+ ),
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(DYNAMIC_ALL_OPTIONAL_ID)
+ .setEnabled(false)
+ .build(),
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(DYNAMIC_DISABLED_ID)
+ .setPendingIntent(null)
+ .setEnabled(false)
+ .build(),
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(DYNAMIC_OTHER_PACKAGE_ID)
+ .setPendingIntent(null)
+ .setEnabled(false)
+ .build()
+ )
+ )
+ .build()
+ ),
+ safetyCenterEntryGroupMixedFromComplexConfig
),
- safetyCenterEntryGroupMixedFromComplexConfig
- ),
- listOf(
- safetyCenterStaticEntryGroupFromComplexConfig,
- safetyCenterStaticEntryGroupMixedFromComplexConfig
+ listOf(
+ safetyCenterStaticEntryGroupFromComplexConfig,
+ safetyCenterStaticEntryGroupMixedFromComplexConfig
+ )
)
- )
- private val safetyCenterDataFromComplexConfigUpdated =
- SafetyCenterData(
- safetyCenterTestData.safetyCenterStatusCritical(6),
- listOf(
- safetyCenterTestData.safetyCenterIssueCritical(
- DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueCritical(
- ISSUE_ONLY_BAREBONE_ID,
- groupId = ISSUE_ONLY_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueRecommendation(
- DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueRecommendation(
- ISSUE_ONLY_ALL_OPTIONAL_ID,
- groupId = ISSUE_ONLY_GROUP_ID
- ),
- safetyCenterTestData.safetyCenterIssueInformation(
- DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ private val safetyCenterDataFromComplexConfigUpdated: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusCritical(6),
+ listOf(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ DYNAMIC_BAREBONE_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ DYNAMIC_DISABLED_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ )
),
- safetyCenterTestData.safetyCenterIssueInformation(
- ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
- )
- ),
- listOf(
- SafetyCenterEntryOrGroup(
- SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
- .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
- .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY)
- .setSummary("Critical summary")
- .setEntries(
- listOf(
- safetyCenterTestData.safetyCenterEntryCritical(DYNAMIC_BAREBONE_ID),
- safetyCenterTestData
- .safetyCenterEntryDefaultBuilder(DYNAMIC_ALL_OPTIONAL_ID)
- .setEnabled(false)
- .build(),
- safetyCenterTestData.safetyCenterEntryRecommendation(
- DYNAMIC_DISABLED_ID
- ),
- safetyCenterTestData.safetyCenterEntryUnspecified(
- DYNAMIC_HIDDEN_ID,
- pendingIntent = null
- ),
- safetyCenterTestData.safetyCenterEntryOk(
- DYNAMIC_HIDDEN_WITH_SEARCH_ID
- ),
- safetyCenterTestData
- .safetyCenterEntryDefaultBuilder(DYNAMIC_OTHER_PACKAGE_ID)
- .setPendingIntent(null)
- .setEnabled(false)
- .build()
+ listOf(
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY)
+ .setSummary("Critical summary")
+ .setEntries(
+ listOf(
+ safetyCenterTestData.safetyCenterEntryCritical(
+ DYNAMIC_BAREBONE_ID
+ ),
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(DYNAMIC_ALL_OPTIONAL_ID)
+ .setEnabled(false)
+ .build(),
+ safetyCenterTestData.safetyCenterEntryRecommendation(
+ DYNAMIC_DISABLED_ID
+ ),
+ safetyCenterTestData.safetyCenterEntryUnspecified(
+ DYNAMIC_HIDDEN_ID,
+ pendingIntent = null
+ ),
+ safetyCenterTestData.safetyCenterEntryOk(
+ DYNAMIC_HIDDEN_WITH_SEARCH_ID
+ ),
+ safetyCenterTestData
+ .safetyCenterEntryDefaultBuilder(DYNAMIC_OTHER_PACKAGE_ID)
+ .setPendingIntent(null)
+ .setEnabled(false)
+ .build()
+ )
)
- )
- .build()
+ .build()
+ ),
+ safetyCenterEntryGroupMixedFromComplexConfig
),
- safetyCenterEntryGroupMixedFromComplexConfig
- ),
- listOf(
- safetyCenterStaticEntryGroupFromComplexConfig,
- safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig
+ listOf(
+ safetyCenterStaticEntryGroupFromComplexConfig,
+ safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig
+ )
)
- )
-
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
-
- @After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@Test
- fun refreshSafetySources_withShowEntriesOnTimeout_marksSafetySourceAsError() {
+ fun refreshSafetySources_timeout_marksSafetySourceAsError() {
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
- SafetyCenterFlags.showErrorEntriesOnTimeout = true
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
val listener = safetyCenterTestHelper.addListener()
@@ -751,9 +785,8 @@ class SafetyCenterManagerTest {
}
@Test
- fun refreshSafetySources_withShowEntriesOnTimeout_keepsShowingErrorUntilClearedBySource() {
+ fun refreshSafetySources_timeout_keepsShowingErrorUntilClearedBySource() {
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
- SafetyCenterFlags.showErrorEntriesOnTimeout = true
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -781,18 +814,15 @@ class SafetyCenterManagerTest {
}
@Test
- fun refreshSafetySources_withShowEntriesOnTimeout_doesntSetErrorForBackgroundRefreshes() {
+ fun refreshSafetySources_timeout_doesntSetErrorForBackgroundRefreshes() {
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
- SafetyCenterFlags.showErrorEntriesOnTimeout = true
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(REFRESH_REASON_OTHER)
- val safetyCenterBeforeTimeout = listener.receiveSafetyCenterData()
- assertThat(safetyCenterBeforeTimeout.status.refreshStatus)
- .isEqualTo(REFRESH_STATUS_DATA_FETCH_IN_PROGRESS)
- val safetyCenterDataAfterTimeout = listener.receiveSafetyCenterData()
+ val safetyCenterDataAfterTimeout =
+ listener.waitForSafetyCenterRefresh(withErrorEntry = false)
assertThat(safetyCenterDataAfterTimeout).isEqualTo(safetyCenterDataFromConfig)
}
@@ -813,9 +843,9 @@ class SafetyCenterManagerTest {
val status1 = listener.receiveSafetyCenterData().status
assertThat(status1.refreshStatus).isEqualTo(REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS)
assertThat(status1.title.toString())
- .isEqualTo(safetyCenterResourcesContext.getStringByName("scanning_title"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("scanning_title"))
assertThat(status1.summary.toString())
- .isEqualTo(safetyCenterResourcesContext.getStringByName("loading_summary"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("loading_summary"))
val status2 = listener.receiveSafetyCenterData().status
assertThat(status2.refreshStatus).isEqualTo(REFRESH_STATUS_NONE)
assertThat(status2).isEqualTo(safetyCenterStatusOk)
@@ -837,9 +867,9 @@ class SafetyCenterManagerTest {
val status1 = listener.receiveSafetyCenterData().status
assertThat(status1.refreshStatus).isEqualTo(REFRESH_STATUS_DATA_FETCH_IN_PROGRESS)
assertThat(status1.title.toString())
- .isEqualTo(safetyCenterResourcesContext.getStringByName("scanning_title"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("scanning_title"))
assertThat(status1.summary.toString())
- .isEqualTo(safetyCenterResourcesContext.getStringByName("loading_summary"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("loading_summary"))
val status2 = listener.receiveSafetyCenterData().status
assertThat(status2.refreshStatus).isEqualTo(REFRESH_STATUS_NONE)
assertThat(status2).isEqualTo(safetyCenterStatusOk)
@@ -863,13 +893,78 @@ class SafetyCenterManagerTest {
assertThat(status1.refreshStatus).isEqualTo(REFRESH_STATUS_DATA_FETCH_IN_PROGRESS)
assertThat(status1.title.toString()).isEqualTo(safetyCenterStatusOk.title.toString())
assertThat(status1.summary.toString())
- .isEqualTo(safetyCenterResourcesContext.getStringByName("loading_summary"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("loading_summary"))
val status2 = listener.receiveSafetyCenterData().status
assertThat(status2.refreshStatus).isEqualTo(REFRESH_STATUS_NONE)
assertThat(status2).isEqualTo(safetyCenterStatusOk)
}
@Test
+ fun refreshSafetySources_reasonPageOpen_allowedByFlag_broadcastSent() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
+ safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
+ SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceTestData.informationWithIssue)
+ )
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+
+ val apiSafetySourceData =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(apiSafetySourceData).isEqualTo(safetySourceTestData.informationWithIssue)
+ }
+
+ @Test
+ fun refreshSafetySources_reasonPageOpen_allowedByFlagLater_broadcastSentLater() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
+ safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceTestData.informationWithIssue)
+ )
+
+ assertFailsWith(TimeoutCancellationException::class) {
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN,
+ timeout = TIMEOUT_SHORT
+ )
+ }
+ val apiSafetySourceDataBeforeSettingFlag =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+ val apiSafetySourceDataAfterSettingFlag =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+
+ assertThat(apiSafetySourceDataBeforeSettingFlag).isEqualTo(safetySourceTestData.information)
+ assertThat(apiSafetySourceDataAfterSettingFlag)
+ .isEqualTo(safetySourceTestData.informationWithIssue)
+ }
+
+ @Test
+ fun refreshSafetySources_reasonPageOpen_noDataForSource_broadcastSent() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceTestData.information)
+ )
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+
+ val apiSafetySourceData =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(apiSafetySourceData).isEqualTo(safetySourceTestData.information)
+ }
+
+ @Test
fun getSafetyCenterData_withoutDataProvided_returnsDataFromConfig() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -945,7 +1040,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_attributionTitleProvidedBySource_returnsIssueWithAttributionTitle() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
@@ -961,7 +1056,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_attributionTitleNotProvided_returnsGroupTitleAsAttributionTitle() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue)
@@ -974,7 +1069,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_attributionNotSetAndGroupTitleNull_returnsNullAttributionTitle() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceNoGroupTitleConfig)
safetyCenterTestHelper.setData(
@@ -1002,9 +1097,6 @@ class SafetyCenterManagerTest {
@Test
@SdkSuppress(maxSdkVersion = TIRAMISU)
fun getSafetyCenterData_attributionNotSetBySourceOnTiramisu_returnsNullAttributionTitle() {
- // TODO(b/258228790): Remove after U is no longer in pre-release
- assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
- assumeFalse(Build.VERSION.CODENAME == "VanillaIceCream")
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue)
@@ -1079,7 +1171,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withActionConfirmation_returnsRecommendationWithActionConfirmation() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
@@ -1159,7 +1251,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withRecommendationDataIssue_returnsDataRecommendationStatus() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1184,7 +1276,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withCriticalDataIssue_returnsDataCriticalStatus() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1209,7 +1301,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withRecommendationPasswordsIssue_returnsDataRecommendationStatus() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1234,7 +1326,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withCriticalPasswordsIssue_returnsDataCriticalStatus() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1259,7 +1351,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withRecommendationPersonalIssue_returnsDataRecommendationStatus() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1284,7 +1376,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withCriticalPersonalIssue_returnsDataCriticalStatus() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1309,7 +1401,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_infoStatusTipFirstIssueSingleTip_infoStatusWithTipSummary() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1329,7 +1421,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_infoStatusTipFirstIssueMultiTips_infoStatusWithTipsSummary() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1365,7 +1457,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_infoStatusActionFirstIssueSingleAction_infoStatusWithActionSummary() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1385,7 +1477,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_infoStatusActionFirstIssueMultiActions_infoStatusWithActionsSummary() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1421,7 +1513,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_infoStatusManualFirstIssueSingleManual_infoStatusWithAlertSummary() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1447,7 +1539,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_infoStatusManualFirstIssueMultiManual_infoStatusWithAlertsSummary() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceConfig)
safetyCenterTestHelper.setData(
@@ -1485,7 +1577,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_withStaticEntryGroups_hasStaticEntriesToIdsMapping() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.staticSourcesConfig)
@@ -1565,7 +1657,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesOfSameSeverities_issueOfFirstSourceInConfigShown() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1597,7 +1689,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesInDifferentSourceGroups_topIssueRelevantForBothGroups() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1627,7 +1719,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesInSameSourceGroups_topIssueRelevantForThatGroup() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1657,7 +1749,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_noDuplicateIssues_noGroupBelongingSpecified() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1685,7 +1777,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_differentDuplicationId_bothIssuesShown() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1722,7 +1814,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_differentDuplicationGroup_bothIssuesShown() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1759,7 +1851,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_threeDuplicateIssues_onlyOneIssueShown() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1798,7 +1890,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesOfDifferentSeverities_moreSevereIssueShown() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1830,7 +1922,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_multipleDuplicationsOfIssues_correctlyDeduplicated() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1906,7 +1998,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesBothDismissed_topOneShownAsDismissed() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1947,7 +2039,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesLowerSeverityOneDismissed_topOneShown() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -1985,7 +2077,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesHigherSeverityOneDismissed_topOneShownAsDismissed() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -2023,7 +2115,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_dupIssuesLowerPrioritySameSeverityOneDismissed_topShownAsDismissed() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -2061,7 +2153,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_dupIssuesTopOneDismissedThenDisappears_bottomOneReemergesTimely() {
SafetyCenterFlags.tempHiddenIssueResurfaceDelay = Duration.ZERO
SafetyCenterFlags.resurfaceIssueMaxCounts = mapOf(SEVERITY_LEVEL_CRITICAL_WARNING to 99L)
@@ -2115,7 +2207,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_dupsOfDiffSeveritiesTopOneDismissedThenGone_bottomOneReemergesTimely() {
SafetyCenterFlags.tempHiddenIssueResurfaceDelay = Duration.ZERO
SafetyCenterFlags.resurfaceIssueMaxCounts =
@@ -2178,7 +2270,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesLowerOneResurfaces_lowerOneStillFilteredOut() {
SafetyCenterFlags.resurfaceIssueMaxCounts =
mapOf(
@@ -2241,7 +2333,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_duplicateIssuesTopOneResurfaces_topOneShown() {
SafetyCenterFlags.resurfaceIssueMaxCounts =
mapOf(
@@ -2302,7 +2394,7 @@ class SafetyCenterManagerTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun getSafetyCenterData_dupIssuesTopOneResolved_bottomOneReemergesAfterTemporaryHiddenPeriod() {
SafetyCenterFlags.tempHiddenIssueResurfaceDelay = RESURFACE_DELAY
safetyCenterTestHelper.setConfig(
@@ -2836,7 +2928,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
assertThat(group.summary)
- .isEqualTo(safetyCenterResourcesContext.getStringByName("group_unknown_summary"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("group_unknown_summary"))
assertThat(group.severityLevel).isEqualTo(ENTRY_SEVERITY_LEVEL_UNKNOWN)
}
@@ -2850,7 +2942,7 @@ class SafetyCenterManagerTest {
.getGroup(ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID)
assertThat(initialGroup.summary)
- .isEqualTo(safetyCenterResourcesContext.getStringByName("group_unknown_summary"))
+ .isEqualTo(safetyCenterResourcesApk.getStringByName("group_unknown_summary"))
assertThat(initialGroup.severityLevel).isEqualTo(ENTRY_SEVERITY_LEVEL_UNKNOWN)
safetyCenterTestHelper.setData(DYNAMIC_BAREBONE_ID, safetySourceTestData.unspecified)
@@ -3345,7 +3437,7 @@ class SafetyCenterManagerTest {
assertThat(error)
.isEqualTo(
SafetyCenterErrorDetails(
- safetyCenterResourcesContext.getStringByName("resolving_action_error")
+ safetyCenterResourcesApk.getStringByName("resolving_action_error")
)
)
}
@@ -3379,7 +3471,7 @@ class SafetyCenterManagerTest {
assertThat(error)
.isEqualTo(
SafetyCenterErrorDetails(
- safetyCenterResourcesContext.getStringByName("resolving_action_error")
+ safetyCenterResourcesApk.getStringByName("resolving_action_error")
)
)
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
index 70d6468fa..86bc5f88e 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
@@ -28,19 +28,20 @@ import android.safetycenter.SafetyCenterStatus
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceErrorDetails
import android.safetycenter.SafetySourceIssue
-import android.safetycenter.functional.testing.NotificationCharacteristics
-import android.safetycenter.functional.testing.TestNotificationListener
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.DisableAnimationRule
+import com.android.compatibility.common.util.FreezeRotationRule
import com.android.safetycenter.pendingintents.PendingIntentSender
+import com.android.safetycenter.testing.Coroutines
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
+import com.android.safetycenter.testing.NotificationCharacteristics
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.executeBlockAndExit
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.clearAllSafetySourceDataForTestsWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission
import com.android.safetycenter.testing.SafetyCenterFlags
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1
@@ -48,20 +49,23 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_5
import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
import com.android.safetycenter.testing.SafetySourceTestData
import com.android.safetycenter.testing.SafetySourceTestData.Companion.ISSUE_TYPE_ID
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.safetycenter.testing.StatusBarNotificationWithChannel
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
+import com.android.safetycenter.testing.TestNotificationListener
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.google.common.truth.Truth.assertThat
import java.time.Duration
import kotlin.test.assertFailsWith
import kotlinx.coroutines.TimeoutCancellationException
-import org.junit.After
-import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -74,41 +78,23 @@ class SafetyCenterNotificationTest {
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
private val safetyCenterManager =
requireNotNull(context.getSystemService(SafetyCenterManager::class.java)) {
- "Could not get system service"
+ "Could not get SafetyCenterManager"
}
- // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2)
+ val safetyCenterTestRule =
+ SafetyCenterTestRule(safetyCenterTestHelper, withNotifications = true)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
-
- @Before
- fun setUp() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- TestNotificationListener.setup()
+ fun enableNotificationsForTestSourceBeforeTest() {
SafetyCenterFlags.notificationsEnabled = true
setFlagsForImmediateNotifications(SINGLE_SOURCE_ID)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
}
- @After
- fun tearDown() {
- if (!shouldRunTests) {
- return
- }
- // It is important to reset the notification listener last because it waits/ensures that
- // all notifications have been removed before returning.
- safetyCenterTestHelper.reset()
- TestNotificationListener.reset()
- }
-
@Test
fun setSafetySourceData_withNoIssue_noNotification() {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
@@ -153,7 +139,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withNotificationBehaviorNever_noNotification() {
val data =
safetySourceTestData
@@ -172,7 +158,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withNotificationBehaviorDelay_noImmediateNotification() {
SafetyCenterFlags.notificationsMinDelay = Duration.ofDays(1)
val data =
@@ -192,7 +178,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withNotificationBehaviorDelay_sendsNotificationAfterDelay() {
SafetyCenterFlags.notificationsMinDelay = Duration.ofDays(1)
val delayedNotificationIssue =
@@ -237,7 +223,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withNotificationBehaviorDelayOfZero_sendsNotificationImmediately() {
SafetyCenterFlags.immediateNotificationBehaviorIssues = emptySet()
SafetyCenterFlags.notificationsMinDelay = Duration.ofSeconds(0)
@@ -264,7 +250,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withNotificationBehaviorImmediately_sendsNotification() {
SafetyCenterFlags.immediateNotificationBehaviorIssues = emptySet()
val data =
@@ -341,7 +327,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withNotificationsAllowedForSourceByConfig_sendsNotification() {
SafetyCenterFlags.notificationsAllowedSources = emptySet()
SafetyCenterFlags.immediateNotificationBehaviorIssues = emptySet()
@@ -372,7 +358,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withCustomNotification_usesCustomValues() {
val intent1 = safetySourceTestData.testActivityRedirectPendingIntent(identifier = "1")
val intent2 = safetySourceTestData.testActivityRedirectPendingIntent(identifier = "2")
@@ -418,7 +404,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_withEmptyCustomActions_notificationHasNoActions() {
val notification =
SafetySourceIssue.Notification.Builder("Custom title", "Custom text")
@@ -539,6 +525,22 @@ class SafetyCenterNotificationTest {
TestNotificationListener.waitForZeroNotifications()
}
+ // TODO(b/284271124): Decide what to do with existing notifications when flag flipped off
+ @Test
+ fun setSafetySourceData_removingAnIssue_afterFlagTurnedOff_noNotificationChanges() {
+ val data1 = safetySourceTestData.recommendationWithAccountIssue
+ val data2 = safetySourceTestData.information
+
+ safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data1)
+
+ TestNotificationListener.waitForSingleNotification()
+
+ SafetyCenterFlags.notificationsEnabled = false
+ safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data2)
+
+ TestNotificationListener.waitForZeroNotificationEvents()
+ }
+
@Test
fun reportSafetySourceError_sourceWithNotification_cancelsNotification() {
val data = safetySourceTestData.recommendationWithAccountIssue
@@ -630,7 +632,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_duplicateIssues_sendsOneNotification() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -660,7 +662,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun setSafetySourceData_duplicateIssueOfLowerSeverityDismissed_sendsNotification() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -784,7 +786,7 @@ class SafetyCenterNotificationTest {
}
@Test
- @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun dismissingNotification_withDuplicateIssues_allDismissed() {
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -908,6 +910,85 @@ class SafetyCenterNotificationTest {
}
@Test
+ fun successNotification_notificationHasAutoCancel() {
+ safetyCenterTestHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage
+ )
+ val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
+ val action =
+ notificationWithChannel.statusBarNotification.notification.actions.firstOrNull()
+ checkNotNull(action) { "Notification action unexpectedly null" }
+ SafetySourceReceiver.setResponse(
+ Request.ResolveAction(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceTestData.information)
+ )
+ sendActionPendingIntentAndWaitWithPermission(action)
+ val issueSolvedNotificationWithChannel =
+ TestNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ "Issue solved",
+ "",
+ actions = emptyList(),
+ )
+ )
+
+ assertThat(issueSolvedNotificationWithChannel.hasAutoCancel()).isTrue()
+ }
+
+ // TODO(b/284271124): Decide what to do with existing notifications when flag flipped off
+ @Test
+ fun sendActionPendingIntent_flagDisabled_pendingIntentNotSentToSource() {
+ safetyCenterTestHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage
+ )
+ val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
+ val action =
+ notificationWithChannel.statusBarNotification.notification.actions.firstOrNull()
+ checkNotNull(action) { "Notification action unexpectedly null" }
+ SafetySourceReceiver.setResponse(
+ Request.ResolveAction(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceTestData.information)
+ )
+ SafetyCenterFlags.notificationsEnabled = false
+
+ assertFailsWith(TimeoutCancellationException::class) {
+ sendActionPendingIntentAndWaitWithPermission(action, timeout = TIMEOUT_SHORT)
+ }
+ }
+
+ @Test
+ fun sendActionPendingIntent_sourceStateChangedSafetyEvent_successNotification() {
+ safetyCenterTestHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage
+ )
+ val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
+ val action =
+ notificationWithChannel.statusBarNotification.notification.actions.firstOrNull()
+ checkNotNull(action) { "Notification action unexpectedly null" }
+ SafetySourceReceiver.setResponse(
+ Request.ResolveAction(SINGLE_SOURCE_ID),
+ Response.SetData(
+ safetySourceTestData.information,
+ overrideSafetyEvent =
+ SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()
+ )
+ )
+
+ sendActionPendingIntentAndWaitWithPermission(action)
+
+ TestNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ "Issue solved",
+ "",
+ actions = emptyList(),
+ )
+ )
+ }
+
+ @Test
fun sendActionPendingIntent_error_updatesListenerDoesNotRemoveNotification() {
// Here we cause a notification with an action to be posted and prepare the fake receiver
// to resolve that action successfully.
@@ -941,12 +1022,10 @@ class SafetyCenterNotificationTest {
safetySourceTestData.recommendationWithDeviceIssue
)
val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
- val contentIntent = notificationWithChannel.statusBarNotification.notification.contentIntent
- executeBlockAndExit(
- launchActivity = { PendingIntentSender.send(contentIntent) },
- block = { waitSourceIssueDisplayed(safetySourceTestData.recommendationDeviceIssue) }
- )
+ sendContentPendingIntent(notificationWithChannel) {
+ waitSourceIssueDisplayed(safetySourceTestData.recommendationDeviceIssue)
+ }
}
@Test
@@ -962,34 +1041,70 @@ class SafetyCenterNotificationTest {
safetySourceTestData.criticalWithResolvingGeneralIssue
)
val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
- val contentIntent = notificationWithChannel.statusBarNotification.notification.contentIntent
- executeBlockAndExit(
- launchActivity = { PendingIntentSender.send(contentIntent) },
- block = {
- waitSourceIssueDisplayed(safetySourceTestData.criticalResolvingGeneralIssue)
- waitSourceIssueDisplayed(safetySourceTestData.recommendationGeneralIssue)
- }
+ sendContentPendingIntent(notificationWithChannel) {
+ waitSourceIssueDisplayed(safetySourceTestData.criticalResolvingGeneralIssue)
+ waitSourceIssueDisplayed(safetySourceTestData.recommendationDeviceIssue)
+ }
+ }
+
+ @Test
+ fun whenGreenIssue_notificationHasAutoCancel() {
+ safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue)
+ val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
+
+ assertThat(notificationWithChannel.hasAutoCancel()).isTrue()
+ }
+
+ @Test
+ fun whenNotGreenIssue_notificationDoesntHaveAutoCancel() {
+ safetyCenterTestHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceTestData.recommendationWithDeviceIssue
)
+ val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
+
+ assertThat(notificationWithChannel.hasAutoCancel()).isFalse()
}
- companion object {
- private val SafetyCenterData.inFlightActions: List<SafetyCenterIssue.Action>
+ private companion object {
+ val SafetyCenterData.inFlightActions: List<SafetyCenterIssue.Action>
get() = issues.flatMap { it.actions }.filter { it.isInFlight }
- private fun sendActionPendingIntentAndWaitWithPermission(action: Notification.Action) {
+ fun sendActionPendingIntentAndWaitWithPermission(
+ action: Notification.Action,
+ timeout: Duration = Coroutines.TIMEOUT_LONG
+ ) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
PendingIntentSender.send(action.actionIntent)
// Sending the action's PendingIntent above is asynchronous and we need to wait for
// it to be received by the fake receiver below.
- SafetySourceReceiver.receiveResolveAction()
+ SafetySourceReceiver.receiveResolveAction(timeout)
}
}
- private fun setFlagsForImmediateNotifications(vararg sourceIds: String) {
+ fun setFlagsForImmediateNotifications(vararg sourceIds: String) {
SafetyCenterFlags.notificationsAllowedSources = sourceIds.toSet()
SafetyCenterFlags.immediateNotificationBehaviorIssues =
sourceIds.map { "$it/$ISSUE_TYPE_ID" }.toSet()
}
+
+ fun StatusBarNotificationWithChannel.hasAutoCancel(): Boolean {
+ val autoCancelMask =
+ statusBarNotification.notification.flags and Notification.FLAG_AUTO_CANCEL
+ return autoCancelMask != 0
+ }
+
+ fun sendContentPendingIntent(
+ statusBarNotificationWithChannel: StatusBarNotificationWithChannel,
+ andExecuteBlock: () -> Unit = {}
+ ) {
+ val contentIntent =
+ statusBarNotificationWithChannel.statusBarNotification.notification.contentIntent
+ executeBlockAndExit(
+ launchActivity = { PendingIntentSender.send(contentIntent) },
+ block = andExecuteBlock
+ )
+ }
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterShellCommandsTest.kt
index b0860d751..7c5b41944 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterShellCommandsTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterShellCommandsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,20 +14,23 @@
* limitations under the License.
*/
-package android.safetycenter.cts
+package android.safetycenter.functional
+import android.Manifest.permission.INTERACT_ACROSS_USERS
+import android.app.ActivityManager
import android.content.Context
import android.safetycenter.SafetyCenterManager
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compatibility.common.util.SystemUtil
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.isSafetyCenterEnabledWithPermission
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
+import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.safetycenter.testing.deviceSupportsSafetyCenter
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-/** CTS tests for Safety Center's shell commands. */
+/** Tests for Safety Center's shell commands. */
@RunWith(AndroidJUnit4::class)
class SafetyCenterShellCommandsTest {
private val context: Context = getApplicationContext()
@@ -54,6 +57,39 @@ class SafetyCenterShellCommandsTest {
assertThat(packageName).isEqualTo(context.packageManager.permissionControllerPackageName)
}
+ @Test
+ fun clearData_executesSuccessfully() {
+ executeShellCommand("cmd safety_center clear-data")
+ }
+
+ @Test
+ fun refresh_executesSuccessfully() {
+ val currentUser =
+ callWithShellPermissionIdentity(INTERACT_ACROSS_USERS) {
+ ActivityManager.getCurrentUser()
+ }
+ executeShellCommand("cmd safety_center refresh --reason OTHER --user $currentUser")
+ }
+
+ @Test
+ fun help_containsAllCommands() {
+ val help = executeShellCommand("cmd safety_center help")
+
+ assertThat(help).contains("help")
+ assertThat(help).contains("enabled")
+ assertThat(help).contains("supported")
+ assertThat(help).contains("package-name")
+ assertThat(help).contains("clear-data")
+ assertThat(help).contains("refresh")
+ }
+
+ @Test
+ fun dump_containsSafetyCenterService() {
+ val dump = executeShellCommand("dumpsys safety_center")
+
+ assertThat(dump).contains("SafetyCenterService")
+ }
+
private fun executeShellCommand(command: String): String =
- SystemUtil.runShellCommand(command).trim()
+ SystemUtil.runShellCommandOrThrow(command).trim()
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt
index 9c4d720d3..73d6a0737 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt
@@ -16,29 +16,23 @@
package android.safetycenter.functional.ui
-import android.Manifest.permission.MANAGE_SENSOR_PRIVACY
-import android.Manifest.permission.OBSERVE_SENSOR_PRIVACY
import android.content.Context
-import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.Sensors.CAMERA
import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
-import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE
-import android.platform.test.rule.ScreenRecordRule
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
+import com.android.safetycenter.testing.EnableSensorRule
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterQsActivity
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestHelper
-import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.safetycenter.testing.SafetyCenterTestRule
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
-import org.junit.After
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -48,66 +42,22 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterQsActivityTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
- @get:Rule val screenRecordRule = ScreenRecordRule()
-
private val context: Context = getApplicationContext()
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
- private val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
- private var shouldRunTests =
- context.deviceSupportsSafetyCenter() &&
- deviceSupportsSensorToggle(CAMERA) &&
- deviceSupportsSensorToggle(MICROPHONE)
- private var oldCameraState: Boolean = false
- private var oldMicrophoneState: Boolean = false
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val enableCameraRule = EnableSensorRule(context, CAMERA)
+ @get:Rule(order = 3) val enableMicrophoneRule = EnableSensorRule(context, MICROPHONE)
+ @get:Rule(order = 4) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 5) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 6) val freezeRotationRule = FreezeRotationRule()
@Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
+ fun setTestConfigBeforeTest() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
}
- @After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- }
-
- @Before
- fun enablePrivacyControlsBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- oldCameraState = isSensorEnabled(CAMERA)
- setSensorState(CAMERA, true)
-
- oldMicrophoneState = isSensorEnabled(MICROPHONE)
- setSensorState(MICROPHONE, true)
- }
-
- @After
- fun restorePrivacyControlsAfterTest() {
- if (!shouldRunTests) {
- return
- }
- setSensorState(CAMERA, oldCameraState)
- setSensorState(MICROPHONE, oldMicrophoneState)
- }
-
@Test
fun launchActivity_fromQuickSettings_hasContentDescriptions() {
context.launchSafetyCenterQsActivity {
@@ -122,7 +72,6 @@ class SafetyCenterQsActivityTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun launchActivity_togglePrivacyControls_hasUpdatedDescriptions() {
context.launchSafetyCenterQsActivity {
// Toggle privacy controls
@@ -134,25 +83,4 @@ class SafetyCenterQsActivityTest {
waitDisplayed(By.desc("Switch. Mic access. Blocked"))
}
}
-
- private fun deviceSupportsSensorToggle(sensor: Int): Boolean {
- return sensorPrivacyManager.supportsSensorToggle(sensor) &&
- sensorPrivacyManager.supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)
- }
-
- private fun isSensorEnabled(sensor: Int): Boolean {
- val isSensorDisabled =
- callWithShellPermissionIdentity(OBSERVE_SENSOR_PRIVACY) {
- sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor)
- }
- return !isSensorDisabled
- }
-
- private fun setSensorState(sensor: Int, enabled: Boolean) {
- val disableSensor = !enabled
- // The sensor is enabled iff the privacy control is disabled.
- callWithShellPermissionIdentity(MANAGE_SENSOR_PRIVACY, OBSERVE_SENSOR_PRIVACY) {
- sensorPrivacyManager.setSensorPrivacy(sensor, disableSensor)
- }
- }
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt
index f4ed328ae..5655b935b 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt
@@ -22,26 +22,24 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
import com.android.safetycenter.testing.SafetySourceTestData
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.RESCAN_BUTTON_LABEL
import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitButtonDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitButtonNotDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitNotDisplayed
-import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,42 +48,17 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SafetyCenterStatusCardTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
private val context: Context = getApplicationContext()
-
- private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context)
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestData = SafetyCenterTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
- // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
-
- @Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
- }
-
- @After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@Test
fun withUnknownStatus_displaysScanningOnLoad() {
@@ -93,8 +66,8 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("scanning_title"),
- safetyCenterResourcesContext.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("scanning_title"),
+ safetyCenterResourcesApk.getStringByName("loading_summary")
)
}
}
@@ -109,8 +82,8 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterResourcesApk.getStringByName("loading_summary")
)
}
}
@@ -122,12 +95,8 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_title"
- ),
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_summary"
- )
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_review_title"),
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_review_summary")
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -143,8 +112,8 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -174,7 +143,7 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
safetyCenterTestData.getAlertString(1)
)
waitButtonNotDisplayed(RESCAN_BUTTON_LABEL)
@@ -191,7 +160,7 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName(
+ safetyCenterResourcesApk.getStringByName(
"overall_severity_level_safety_recommendation_title"
),
safetyCenterTestData.getAlertString(1)
@@ -210,7 +179,7 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName(
+ safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_safety_warning_title"
),
safetyCenterTestData.getAlertString(1)
@@ -229,15 +198,15 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL) { it.click() }
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("scanning_title"),
- safetyCenterResourcesContext.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("scanning_title"),
+ safetyCenterResourcesApk.getStringByName("loading_summary")
)
}
}
@@ -256,14 +225,14 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL) { it.click() }
waitAllTextDisplayed(
- safetyCenterResourcesContext.getStringByName(
+ safetyCenterResourcesApk.getStringByName(
"overall_severity_level_safety_recommendation_title"
),
safetyCenterTestData.getAlertString(1)
diff --git a/tests/functional/safetycenter/subpages/Android.bp b/tests/functional/safetycenter/subpages/Android.bp
new file mode 100644
index 000000000..4279ca26f
--- /dev/null
+++ b/tests/functional/safetycenter/subpages/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "SafetyCenterSubpagesTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "androidx.test.monitor",
+ "compatibility-device-preconditions",
+ "kotlin-test",
+ "platform-test-rules",
+ "safety-center-test-util-lib",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/functional/safetycenter/subpages/AndroidManifest.xml b/tests/functional/safetycenter/subpages/AndroidManifest.xml
new file mode 100644
index 000000000..ea59a99c6
--- /dev/null
+++ b/tests/functional/safetycenter/subpages/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.safetycenter.functional.subpages">
+ <application>
+
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Functional tests for SafetyCenter Subpages"
+ android:targetPackage="android.safetycenter.functional.subpages"/>
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+</manifest>
diff --git a/tests/functional/safetycenter/subpages/AndroidTest.xml b/tests/functional/safetycenter/subpages/AndroidTest.xml
new file mode 100644
index 000000000..c3245e9d7
--- /dev/null
+++ b/tests/functional/safetycenter/subpages/AndroidTest.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<configuration description="Config for SafetyCenter subpages testcases">
+
+ <object
+ class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController"
+ type="module_controller"/>
+
+ <option name="config-descriptor:metadata" key="component" value="framework"/>
+ <option name="config-descriptor:metadata" key="parameter"
+ value="not_instant_app"/>
+ <option name="config-descriptor:metadata" key="parameter"
+ value="not_multi_abi"/>
+ <option name="config-descriptor:metadata" key="parameter"
+ value="secondary_user"/>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
+
+ <option name="test-suite-tag" value="functional"/>
+
+ <target_preparer
+ class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="SafetyCenterSubpagesTestCases.apk"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- Ensure all broadcasts are dispatched prior to running our tests, to make sure they
+ aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
+ causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
+ <option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
+ <!-- Disable syncing to prevent overwriting flags during testing. -->
+ <option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
+ <option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
+ </target_preparer>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/android.safetycenter.functional.subpages/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.safetycenter.functional.subpages"/>
+ <option name="exclude-annotation" value="org.junit.Ignore"/>
+ <option name="runtime-hint" value="10m"/>
+ </test>
+</configuration>
diff --git a/tests/functional/safetycenter/subpages/TEST_MAPPING b/tests/functional/safetycenter/subpages/TEST_MAPPING
new file mode 100644
index 000000000..455ad21d0
--- /dev/null
+++ b/tests/functional/safetycenter/subpages/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+ "presubmit": [
+ {
+ "name": "SafetyCenterSubpagesTestCases"
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "SafetyCenterSubpagesTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt
index 0c90029e0..7ad83b949 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt
+++ b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt
@@ -17,13 +17,15 @@
package android.safetycenter.functional.ui
import android.content.Context
-import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.content.pm.PackageManager
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
import android.os.Bundle
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID
import android.safetycenter.config.SafetySource
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
@@ -31,23 +33,24 @@ import com.android.compatibility.common.util.UiAutomatorUtils2
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAndExit
import com.android.safetycenter.testing.SafetyCenterFlags
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.PRIVACY_SOURCE_ID_1
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceTestData
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.MORE_ISSUES_LABEL
import com.android.safetycenter.testing.UiTestHelper.clickMoreIssuesCard
import com.android.safetycenter.testing.UiTestHelper.resetRotation
import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed
+import com.android.safetycenter.testing.UiTestHelper.waitAllTextNotDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitButtonDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
import org.junit.After
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -55,42 +58,27 @@ import org.junit.runner.RunWith
/** Functional tests for the Privacy subpage in Safety Center. */
@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
class PrivacySubpageTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
private val context: Context = getApplicationContext()
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
+ private val sensorPrivacyManager: SensorPrivacyManager =
+ context.getSystemService(SensorPrivacyManager::class.java)!!
- // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
-
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@Before
fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
SafetyCenterFlags.showSubpages = true
}
@After
fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
UiAutomatorUtils2.getUiDevice().resetRotation()
}
@@ -166,13 +154,20 @@ class PrivacySubpageTest {
extras.putString(EXTRA_SAFETY_SOURCES_GROUP_ID, config.safetySourcesGroups.first().id)
context.launchSafetyCenterActivity(extras) {
- waitAllTextDisplayed(
- "Camera access",
- "Microphone access",
- "Show clipboard access",
- "Show passwords",
- "Location access"
+ waitAllText(
+ displayed = sensorPrivacyManager.supportsSensorToggle(CAMERA),
+ text = "Camera access"
+ )
+ waitAllText(
+ displayed = sensorPrivacyManager.supportsSensorToggle(MICROPHONE),
+ text = "Microphone access"
+ )
+ waitAllTextDisplayed("Show clipboard access")
+ waitAllText(
+ displayed = getPermissionControllerBool("config_display_show_password_toggle"),
+ text = "Show passwords"
)
+ waitAllTextDisplayed("Location access")
}
}
@@ -216,6 +211,34 @@ class PrivacySubpageTest {
}
}
+ private fun waitAllText(displayed: Boolean, text: String) {
+ if (displayed) {
+ waitAllTextDisplayed(text)
+ } else {
+ waitAllTextNotDisplayed(text)
+ }
+ }
+
+ private fun getPermissionControllerBool(resourceName: String): Boolean {
+ val permissionControllerContext = getPermissionControllerContext()
+ val resourceId =
+ permissionControllerContext.resources.getIdentifier(
+ resourceName,
+ "bool",
+ "com.android.permissioncontroller"
+ )
+ return permissionControllerContext.resources.getBoolean(resourceId)
+ }
+
+ private fun getPermissionControllerContext(): Context {
+ val permissionControllerPkg = context.packageManager.permissionControllerPackageName
+ try {
+ return context.createPackageContext(permissionControllerPkg, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ throw RuntimeException(e)
+ }
+ }
+
companion object {
private const val EXTRA_SETTINGS_FRAGMENT_ARGS_KEY = ":settings:fragment_args_key"
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt
index 3aef2097a..4731f99b6 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt
+++ b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt
@@ -17,9 +17,7 @@
package android.safetycenter.functional.ui
import android.content.Context
-import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
-import android.platform.test.rule.ScreenRecordRule
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceIssue
@@ -27,21 +25,16 @@ import android.safetycenter.config.SafetySource
import android.safetycenter.config.SafetySourcesGroup
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
-import com.android.compatibility.common.util.RetryRule
import com.android.compatibility.common.util.UiAutomatorUtils2
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAndExit
-import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAndExitAllowingRetries
import com.android.safetycenter.testing.SafetyCenterFlags
-import com.android.safetycenter.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.MULTIPLE_SOURCES_GROUP_ID_1
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
@@ -52,10 +45,12 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_5
import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
import com.android.safetycenter.testing.SafetySourceTestData
+import com.android.safetycenter.testing.SupportsSafetyCenterRule
import com.android.safetycenter.testing.UiTestHelper.MORE_ISSUES_LABEL
import com.android.safetycenter.testing.UiTestHelper.clickConfirmDismissal
import com.android.safetycenter.testing.UiTestHelper.clickDismissIssueCard
@@ -76,66 +71,34 @@ import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitPageTitleNotDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
-import java.util.concurrent.TimeUnit
import org.junit.After
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.Timeout
import org.junit.runner.RunWith
/** Functional tests for generic subpages in Safety Center. */
@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
class SafetyCenterSubpagesTest {
- @get:Rule val disableAnimationRule = DisableAnimationRule()
-
- @get:Rule val freezeRotationRule = FreezeRotationRule()
-
- @get:Rule val screenRecordRule = ScreenRecordRule()
-
- // It is necessery to couple RetryRule and Timeout to ensure that all the retries together are
- // restricted with the test timeout
- @get:Rule val retryRule = RetryRule(/* retries= */ 3)
- @get:Rule
- val timeoutRule =
- Timeout(
- InstrumentationRegistry.getArguments().getString("timeout_msec", "60000").toLong(),
- TimeUnit.MILLISECONDS
- )
-
private val context: Context = getApplicationContext()
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
- private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context)
-
- // JUnit's Assume is not supported in @BeforeClass by the tests runner, so this is used to
- // manually skip the setup and teardown methods.
- private val shouldRunTests = context.deviceSupportsSafetyCenter()
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
- @Before
- fun assumeDeviceSupportsSafetyCenterToRunTests() {
- assumeTrue(shouldRunTests)
- }
+ @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
+ @get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
@Before
- fun enableSafetyCenterBeforeTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.setup()
+ fun enableSubpagesBeforeTest() {
SafetyCenterFlags.showSubpages = true
}
@After
- fun clearDataAfterTest() {
- if (!shouldRunTests) {
- return
- }
- safetyCenterTestHelper.reset()
+ fun resetRotationAfterTest() {
UiAutomatorUtils2.getUiDevice().resetRotation()
}
@@ -331,7 +294,6 @@ class SafetyCenterSubpagesTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun entryListWithSingleSource_clickingTheInfoIcon_redirectsToDifferentScreen() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
val sourceTestData = safetySourceTestData.informationWithIconAction
@@ -496,7 +458,6 @@ class SafetyCenterSubpagesTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun issueCard_updateSafetySourceData_subpageDisplaysUpdatedIssue() {
val initialDataToDisplay = safetySourceTestData.informationWithIssueWithAttributionTitle
val updatedDataToDisplay = safetySourceTestData.criticalWithIssueWithAttributionTitle
@@ -532,7 +493,7 @@ class SafetyCenterSubpagesTest {
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
- openPageAndExitAllowingRetries(context.getString(sourcesGroup.titleResId)) {
+ openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitSourceIssueDisplayed(issue)
waitButtonDisplayed(action.label) { it.click() }
@@ -552,7 +513,7 @@ class SafetyCenterSubpagesTest {
val issue = sourceData.issues[0]
context.launchSafetyCenterActivity {
- openPageAndExitAllowingRetries(context.getString(sourcesGroup.titleResId)) {
+ openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitSourceIssueDisplayed(issue)
clickDismissIssueCard()
@@ -573,7 +534,7 @@ class SafetyCenterSubpagesTest {
val issue = sourceData.issues[0]
context.launchSafetyCenterActivity {
- openPageAndExitAllowingRetries(context.getString(sourcesGroup.titleResId)) {
+ openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitSourceIssueDisplayed(issue)
clickDismissIssueCard()
waitAllTextDisplayed("Dismiss this alert?")
@@ -616,7 +577,7 @@ class SafetyCenterSubpagesTest {
val issue = sourceData.issues[0]
context.launchSafetyCenterActivity {
- openPageAndExitAllowingRetries(context.getString(sourcesGroup.titleResId)) {
+ openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitSourceIssueDisplayed(issue)
clickDismissIssueCard()
waitAllTextDisplayed("Dismiss this alert?")
@@ -640,7 +601,7 @@ class SafetyCenterSubpagesTest {
val issue = sourceData.issues[0]
context.launchSafetyCenterActivity {
- openPageAndExitAllowingRetries(context.getString(sourcesGroup.titleResId)) {
+ openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitSourceIssueDisplayed(issue)
clickDismissIssueCard()
waitAllTextDisplayed("Dismiss this alert?")
@@ -929,7 +890,6 @@ class SafetyCenterSubpagesTest {
}
@Test
- @ScreenRecordRule.ScreenRecord
fun settingsSearch_openWithGenericIntentExtra_showsGenericSubpage() {
val config = safetyCenterTestConfigs.multipleSourcesConfig
safetyCenterTestHelper.setConfig(config)
@@ -974,9 +934,7 @@ class SafetyCenterSubpagesTest {
waitAllTextDisplayed(
context.getString(source.titleResId),
context.getString(source.summaryResId),
- safetyCenterResourcesContext.getStringByName(
- "test_single_source_group_id_footer"
- )
+ safetyCenterResourcesApk.getStringByName("test_single_source_group_id_footer")
)
}
}
diff --git a/tests/hostside/safetycenter/Android.bp b/tests/hostside/safetycenter/Android.bp
index 499e44f62..c66cae23a 100644
--- a/tests/hostside/safetycenter/Android.bp
+++ b/tests/hostside/safetycenter/Android.bp
@@ -27,6 +27,7 @@ java_test_host {
libs: [
"tradefed",
"junit",
+ "compatibility-host-util",
],
static_libs: [
"cts-statsd-atom-host-test-utils",
diff --git a/tests/hostside/safetycenter/AndroidTest.xml b/tests/hostside/safetycenter/AndroidTest.xml
index 09ddf9d84..a28b70c3c 100644
--- a/tests/hostside/safetycenter/AndroidTest.xml
+++ b/tests/hostside/safetycenter/AndroidTest.xml
@@ -28,9 +28,12 @@
aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
<option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.StayAwakePreparer" />
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt
index c72166c65..784701b8a 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterInteractionLoggingHelperTests.kt
@@ -26,8 +26,9 @@ import com.android.safetycenter.testing.SafetyCenterActivityLauncher.openPageAnd
import com.android.safetycenter.testing.SafetyCenterFlags
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestHelper
-import org.junit.After
+import com.android.safetycenter.testing.SafetyCenterTestRule
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,17 +48,13 @@ class SafetyCenterInteractionLoggingHelperTests {
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
+ @get:Rule val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+
@Before
fun setUp() {
- safetyCenterTestHelper.setup()
SafetyCenterFlags.showSubpages = true
}
- @After
- fun tearDown() {
- safetyCenterTestHelper.reset()
- }
-
@Test
fun openSafetyCenter() {
context.launchSafetyCenterActivity {}
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
index f4677cfed..458516379 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
@@ -25,9 +25,10 @@ import com.android.safetycenter.testing.SafetyCenterFlags
import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceTestData
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,20 +49,16 @@ class SafetyCenterNotificationLoggingHelperTests {
private val safetySourceTestData = SafetySourceTestData(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
+ @get:Rule val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+
@Before
fun setUp() {
- safetyCenterTestHelper.setup()
SafetyCenterFlags.notificationsEnabled = true
SafetyCenterFlags.notificationsAllowedSources = setOf(SINGLE_SOURCE_ID)
SafetyCenterFlags.allowStatsdLogging = true
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
}
- @After
- fun tearDown() {
- safetyCenterTestHelper.reset()
- }
-
@Test
fun sendNotification() {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, newTestDataWithNotifiableIssue())
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt
index b7bd60d10..d020aac0a 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt
@@ -23,16 +23,22 @@ import android.safetycenter.SafetySourceErrorDetails
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compatibility.common.util.SystemUtil
-import com.android.safetycenter.testing.*
+import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission
+import com.android.safetycenter.testing.SafetyCenterFlags
+import com.android.safetycenter.testing.SafetyCenterTestConfigs
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_2
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_3
+import com.android.safetycenter.testing.SafetyCenterTestHelper
+import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
+import com.android.safetycenter.testing.SafetySourceReceiver
import com.android.safetycenter.testing.SafetySourceReceiver.Companion.refreshSafetySourcesWithReceiverPermissionAndWait
-import org.junit.After
+import com.android.safetycenter.testing.SafetySourceTestData
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,18 +50,14 @@ class SafetySourceStateCollectedLoggingHelperTests {
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
+ @get:Rule val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+
@Before
fun setUp() {
- safetyCenterTestHelper.setup()
SafetyCenterFlags.allowStatsdLogging = true
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
}
- @After
- fun tearDown() {
- safetyCenterTestHelper.reset()
- }
-
@Test
fun triggerStatsPull() {
val label = 1 // Arbitrary label in [0, 16)
@@ -124,13 +126,11 @@ class SafetySourceStateCollectedLoggingHelperTests {
@Test
fun refreshAllSources_reasonPageOpen_oneSuccessOneErrorOneTimeout() {
- SafetyCenterFlags.setAllRefreshTimeoutsTo(Coroutines.TIMEOUT_SHORT)
simulateRefresh(Response.SetData(safetySourceTestData.information), Response.Error, null)
}
@Test
fun refreshAllSources_reasonButtonClick_oneSuccessOneErrorOneTimeout() {
- SafetyCenterFlags.setAllRefreshTimeoutsTo(Coroutines.TIMEOUT_SHORT)
simulateRefresh(
Response.SetData(safetySourceTestData.information),
Response.Error,
@@ -154,8 +154,17 @@ class SafetySourceStateCollectedLoggingHelperTests {
if (source3Response != null) {
SafetySourceReceiver.setResponse(Request.Refresh(SOURCE_ID_3), source3Response)
}
+
+ val atLeastOneTimeout =
+ source1Response == null || source2Response == null || source3Response == null
+ if (atLeastOneTimeout) {
+ SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
+ }
+
+ // Refresh sources and wait until the refresh has fully completed / timed out to ensure that
+ // things are logged.
+ val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(refreshReason)
- // Give time for responses to all sources
- Thread.sleep(Coroutines.TIMEOUT_SHORT.toMillis())
+ listener.waitForSafetyCenterRefresh()
}
}
diff --git a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterInteractionLoggingHostTest.kt b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterInteractionLoggingHostTest.kt
index 5ef8ed84a..5fe9e0a2a 100644
--- a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterInteractionLoggingHostTest.kt
+++ b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterInteractionLoggingHostTest.kt
@@ -20,6 +20,7 @@ import android.cts.statsdatom.lib.ConfigUtils
import android.cts.statsdatom.lib.ReportUtils
import android.safetycenter.hostside.rules.HelperAppRule
import android.safetycenter.hostside.rules.RequireSafetyCenterRule
+import com.android.compatibility.common.util.ApiLevelUtil
import com.android.os.AtomsProto.Atom
import com.android.os.AtomsProto.SafetyCenterInteractionReported
import com.android.os.AtomsProto.SafetyCenterInteractionReported.Action
@@ -28,23 +29,19 @@ import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
import com.google.common.truth.Truth.assertThat
import org.junit.After
+import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
import org.junit.runner.RunWith
/** Host-side tests for Safety Center statsd logging. */
@RunWith(DeviceJUnit4ClassRunner::class)
class SafetyCenterInteractionLoggingHostTest : BaseHostJUnit4Test() {
- private val safetyCenterRule = RequireSafetyCenterRule(this)
- private val helperAppRule = HelperAppRule(this, HelperApp.APK_NAME, HelperApp.PACKAGE_NAME)
-
- @Rule
- @JvmField
- val rules: RuleChain = RuleChain.outerRule(safetyCenterRule).around(helperAppRule)
+ @get:Rule(order = 1) val safetyCenterRule = RequireSafetyCenterRule(this)
+ @get:Rule(order = 2)
+ val helperAppRule = HelperAppRule(this, HelperApp.APK_NAME, HelperApp.PACKAGE_NAME)
@Before
fun setUp() {
@@ -75,6 +72,8 @@ class SafetyCenterInteractionLoggingHostTest : BaseHostJUnit4Test() {
@Test
fun sendNotification_recordsNotificationPostedEvent() {
+ assumeAtLeastUpsideDownCake("Safety Center notification APIs require Android U+")
+
helperAppRule.runTest(
testClassName = ".SafetyCenterNotificationLoggingHelperTests",
testMethodName = "sendNotification"
@@ -89,6 +88,8 @@ class SafetyCenterInteractionLoggingHostTest : BaseHostJUnit4Test() {
@Test
fun openSubpageFromIntentExtra_recordsEventWithUnknownNavigationSource() {
+ assumeAtLeastUpsideDownCake("Safety Center subpages require Android U+")
+
helperAppRule.runTest(TEST_CLASS_NAME, testMethodName = "openSubpageFromIntentExtra")
val safetyCenterViewedAtoms = getInteractionReportedAtoms(Action.SAFETY_CENTER_VIEWED)
@@ -103,25 +104,24 @@ class SafetyCenterInteractionLoggingHostTest : BaseHostJUnit4Test() {
}
@Test
- @Ignore
- // TODO(b/278202773): Fix/de-flake this test
fun openSubpageFromHomepage_recordsEventWithSafetyCenterNavigationSource() {
+ assumeAtLeastUpsideDownCake("Safety Center subpages require Android U+")
+
helperAppRule.runTest(TEST_CLASS_NAME, testMethodName = "openSubpageFromHomepage")
val safetyCenterViewedAtoms = getInteractionReportedAtoms(Action.SAFETY_CENTER_VIEWED)
+ val subpageViewedEvent = safetyCenterViewedAtoms.find { it.viewType == ViewType.SUBPAGE }
- assertThat(safetyCenterViewedAtoms.map { it.viewType })
- .containsExactly(ViewType.FULL, ViewType.SUBPAGE, ViewType.FULL)
- .inOrder()
- assertThat(safetyCenterViewedAtoms[1].navigationSource)
+ assertThat(subpageViewedEvent).isNotNull()
+ assertThat(subpageViewedEvent!!.navigationSource)
.isEqualTo(SafetyCenterInteractionReported.NavigationSource.SAFETY_CENTER)
assertThat(safetyCenterViewedAtoms.map { it.sessionId }.distinct()).hasSize(1)
}
@Test
- @Ignore
- // TODO(b/278202773): Fix/de-flake this test
fun openSubpageFromSettingsSearch_recordsEventWithSettingsNavigationSource() {
+ assumeAtLeastUpsideDownCake("Safety Center subpages require Android U+")
+
helperAppRule.runTest(TEST_CLASS_NAME, testMethodName = "openSubpageFromSettingsSearch")
val safetyCenterViewedAtoms = getInteractionReportedAtoms(Action.SAFETY_CENTER_VIEWED)
@@ -142,6 +142,10 @@ class SafetyCenterInteractionLoggingHostTest : BaseHostJUnit4Test() {
.mapNotNull { it.atom.safetyCenterInteractionReported }
.filter { it.action == action }
+ private fun assumeAtLeastUpsideDownCake(message: String) {
+ assumeTrue(message, ApiLevelUtil.isAtLeast(device, 34))
+ }
+
private companion object {
const val TEST_CLASS_NAME = ".SafetyCenterInteractionLoggingHelperTests"
}
diff --git a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt
index 65e47fa91..4563d2e63 100644
--- a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt
+++ b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt
@@ -31,18 +31,14 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
import org.junit.runner.RunWith
@RunWith(DeviceJUnit4ClassRunner::class)
class SafetyCenterSystemEventReportedLoggingHostTest : BaseHostJUnit4Test() {
- private val safetyCenterRule = RequireSafetyCenterRule(this)
- private val helperAppRule = HelperAppRule(this, HelperApp.APK_NAME, HelperApp.PACKAGE_NAME)
-
- @Rule
- @JvmField
- val rules: RuleChain = RuleChain.outerRule(safetyCenterRule).around(helperAppRule)
+ @get:Rule(order = 1) val safetyCenterRule = RequireSafetyCenterRule(this)
+ @get:Rule(order = 2)
+ val helperAppRule = HelperAppRule(this, HelperApp.APK_NAME, HelperApp.PACKAGE_NAME)
@Before
fun setUp() {
@@ -183,18 +179,18 @@ class SafetyCenterSystemEventReportedLoggingHostTest : BaseHostJUnit4Test() {
@Test
fun refreshAllSources_secondTime_someSourcesChanged_dataChangedCorrect() {
helperAppRule.runTest(
- ".SafetySourceStateCollectedLoggingHelperTests",
- "refreshAllSources_twiceDifferentData_onlySource1Unchanged"
+ ".SafetySourceStateCollectedLoggingHelperTests",
+ "refreshAllSources_twiceDifferentData_onlySource1Unchanged"
)
val systemEventAtoms =
- ReportUtils.getEventMetricDataList(device).mapNotNull {
- it.atom.safetyCenterSystemEventReported
- }
+ ReportUtils.getEventMetricDataList(device).mapNotNull {
+ it.atom.safetyCenterSystemEventReported
+ }
assertWithMessage("the number of atoms with dataChanged=false")
- .that(systemEventAtoms.count { !it.dataChanged })
- .isEqualTo(1) // Only source 1
+ .that(systemEventAtoms.count { !it.dataChanged })
+ .isEqualTo(1) // Only source 1
}
companion object {
diff --git a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetySourceStateCollectedLoggingHostTest.kt b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetySourceStateCollectedLoggingHostTest.kt
index 22a380e12..a86041b5b 100644
--- a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetySourceStateCollectedLoggingHostTest.kt
+++ b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetySourceStateCollectedLoggingHostTest.kt
@@ -29,19 +29,15 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.RuleChain
import org.junit.runner.RunWith
/** Host-side tests for Safety Center statsd logging. */
@RunWith(DeviceJUnit4ClassRunner::class)
class SafetySourceStateCollectedLoggingHostTest : BaseHostJUnit4Test() {
- private val safetyCenterRule = RequireSafetyCenterRule(this)
- private val helperAppRule = HelperAppRule(this, HelperApp.APK_NAME, HelperApp.PACKAGE_NAME)
-
- @Rule
- @JvmField
- val rules: RuleChain = RuleChain.outerRule(safetyCenterRule).around(helperAppRule)
+ @get:Rule(order = 1) val safetyCenterRule = RequireSafetyCenterRule(this)
+ @get:Rule(order = 2)
+ val helperAppRule = HelperAppRule(this, HelperApp.APK_NAME, HelperApp.PACKAGE_NAME)
@Before
fun setUp() {
@@ -66,8 +62,10 @@ class SafetySourceStateCollectedLoggingHostTest : BaseHostJUnit4Test() {
val sourceStateAtoms = getSafetySourceStateCollectedAtoms()
+ // This assertion purposefully uses containsAtLeast and not containsExact because on test
+ // devices with multiple primary users there will be multiple atoms per source.
assertThat(sourceStateAtoms.map { it.encodedSafetySourceId })
- .containsExactly(
+ .containsAtLeast(
SOURCE_1_ENCODED_SOURCE_ID,
SOURCE_2_ENCODED_SOURCE_ID,
SOURCE_3_ENCODED_SOURCE_ID
diff --git a/tests/utils/safetycenter/AndroidManifest.xml b/tests/utils/safetycenter/AndroidManifest.xml
index 9d7c52e62..ce3724318 100644
--- a/tests/utils/safetycenter/AndroidManifest.xml
+++ b/tests/utils/safetycenter/AndroidManifest.xml
@@ -40,11 +40,25 @@
<activity android:name=".TestActivity"
android:exported="false">
- <intent-filter>
+ <intent-filter android:priority="-1">
+ <action android:name="com.android.safetycenter.testing.action.TEST_ACTIVITY"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Create an alias at higher priority, disabled. We have seen flakes where implicit
+ intents for TEST_ACTIVITY fail owing to multiple receivers, perhaps due to an older
+ CTS APK hanging around. We turn this component on (and off in tidyup) in tests, in
+ the hope of only resolving to the actively running test in these cases. -->
+ <activity-alias android:name=".TestActivityPriority"
+ android:targetActivity=".TestActivity"
+ android:enabled="false"
+ android:exported="false">
+ <intent-filter android:priority="0">
<action android:name="com.android.safetycenter.testing.action.TEST_ACTIVITY"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
- </activity>
+ </activity-alias>
<activity-alias android:name=".TestActivityExported"
android:targetActivity=".TestActivity"
@@ -54,5 +68,14 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity-alias>
+
+ <service android:name=".TestNotificationListener"
+ android:label="TestNotificationListener"
+ android:exported="false"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
index 44c0686e2..a7009b19e 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
@@ -17,6 +17,7 @@
package com.android.safetycenter.testing
import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
import java.time.Duration
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO
@@ -29,8 +30,19 @@ import kotlinx.coroutines.withTimeoutOrNull
/** A class that facilitates interacting with coroutines. */
object Coroutines {
+ /**
+ * The timeout of a test case, typically varies depending on whether the test is running
+ * locally, on pre-submit or post-submit.
+ */
+ val TEST_TIMEOUT: Duration
+ get() =
+ Duration.ofMillis(
+ InstrumentationRegistry.getArguments().getString("timeout_msec", "60000").toLong()
+ )
+
/** A long timeout, to be used for actions that are expected to complete. */
- val TIMEOUT_LONG: Duration = Duration.ofSeconds(25)
+ val TIMEOUT_LONG: Duration
+ get() = TEST_TIMEOUT.dividedBy(2)
/** A short timeout, to be used for actions that are expected not to complete. */
val TIMEOUT_SHORT: Duration = Duration.ofSeconds(1)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt
new file mode 100644
index 000000000..1ed0ecbc3
--- /dev/null
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.testing
+
+import android.Manifest.permission.MANAGE_SENSOR_PRIVACY
+import android.Manifest.permission.OBSERVE_SENSOR_PRIVACY
+import android.content.Context
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE
+import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import org.junit.Assume.assumeTrue
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A JUnit [TestRule] to ensure a given [sensor] is enabled.
+ *
+ * This rule disables sensor privacy before a test and restores the prior state afterwards.
+ */
+class EnableSensorRule(context: Context, val sensor: Int) : TestRule {
+
+ private val sensorPrivacyManager: SensorPrivacyManager =
+ context.getSystemService(SensorPrivacyManager::class.java)!!
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ assumeTrue(
+ "Test device does not support toggling sensor $sensor",
+ supportsSensorToggle()
+ )
+ val oldSensorPrivacy = isSensorPrivacyEnabled()
+ setSensorPrivacy(false)
+ try {
+ base.evaluate()
+ } finally {
+ setSensorPrivacy(oldSensorPrivacy)
+ }
+ }
+ }
+ }
+
+ private fun supportsSensorToggle(): Boolean =
+ sensorPrivacyManager.supportsSensorToggle(sensor) &&
+ sensorPrivacyManager.supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)
+
+ private fun isSensorPrivacyEnabled(): Boolean =
+ callWithShellPermissionIdentity(OBSERVE_SENSOR_PRIVACY) {
+ sensorPrivacyManager.isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor)
+ }
+
+ private fun setSensorPrivacy(enabled: Boolean) {
+ callWithShellPermissionIdentity(MANAGE_SENSOR_PRIVACY, OBSERVE_SENSOR_PRIVACY) {
+ sensorPrivacyManager.setSensorPrivacy(sensor, enabled)
+ }
+ }
+}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/NotificationCharacteristics.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt
index b91c72422..177c2359c 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/NotificationCharacteristics.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.safetycenter.functional.testing
+package com.android.safetycenter.testing
import android.app.Notification
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt
index 537eb7ead..40515fa33 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt
@@ -28,7 +28,6 @@ import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.test.uiautomator.By
-import com.android.compatibility.common.util.RetryableException
import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
@@ -46,13 +45,14 @@ object SafetyCenterActivityLauncher {
*/
fun Context.launchSafetyCenterActivity(
intentExtras: Bundle? = null,
+ intentAction: String = ACTION_SAFETY_CENTER,
withReceiverPermission: Boolean = false,
preventTrampolineToSettings: Boolean = true,
block: () -> Unit
) {
val launchSafetyCenterIntent =
createIntent(
- ACTION_SAFETY_CENTER,
+ intentAction,
intentExtras,
preventTrampolineToSettings = preventTrampolineToSettings
)
@@ -80,19 +80,6 @@ object SafetyCenterActivityLauncher {
executeBlockAndExit(block) { waitDisplayed(By.text(entryPoint)) { it.click() } }
}
- /**
- * Launches a page in Safety Center and exits it once [block] completes, throwing a
- * [RetryableException] for any [RuntimeException] thrown by [block] to allow [RetryRule] to
- * retry the test invocation.
- */
- fun openPageAndExitAllowingRetries(entryPoint: String, block: () -> Unit) {
- try {
- openPageAndExit(entryPoint, block)
- } catch (e: Throwable) {
- throw RetryableException(e, "Exception occurred when checking a Safety Center page")
- }
- }
-
private fun createIntent(
intentAction: String,
intentExtras: Bundle?,
@@ -107,6 +94,7 @@ object SafetyCenterActivityLauncher {
return launchIntent
}
+ /** Executes the given [block] and presses the back button to exit. */
fun executeBlockAndExit(block: () -> Unit, launchActivity: () -> Unit) {
val uiDevice = getUiDevice()
uiDevice.waitForIdle()
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt
index b948dc52c..f8926caac 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt
@@ -24,8 +24,8 @@ import android.content.IntentFilter
import android.os.Build.VERSION_CODES.TIRAMISU
import android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED
import androidx.annotation.RequiresApi
-import com.android.compatibility.common.util.SystemUtil
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
+import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.Coroutines.runBlockingWithTimeout
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
import java.time.Duration
@@ -55,20 +55,18 @@ class SafetyCenterEnabledChangedReceiver(private val context: Context) : Broadca
fun setSafetyCenterEnabledWithReceiverPermissionAndWait(
value: Boolean,
timeout: Duration = TIMEOUT_LONG
- ) =
+ ): Boolean =
callWithShellPermissionIdentity(READ_SAFETY_CENTER_STATUS) {
- setSafetyCenterEnabledWithoutReceiverPermissionAndWait(value, timeout)
+ SafetyCenterFlags.isEnabled = value
+ receiveSafetyCenterEnabledChanged(timeout)
}
fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait(
value: Boolean,
- timeout: Duration = TIMEOUT_LONG
- ): Boolean {
+ ) {
SafetyCenterFlags.isEnabled = value
- if (timeout < TIMEOUT_LONG) {
- SystemUtil.waitForBroadcasts()
- }
- return receiveSafetyCenterEnabledChanged(timeout)
+ WaitForBroadcasts.waitForBroadcasts()
+ receiveSafetyCenterEnabledChanged(TIMEOUT_SHORT)
}
fun unregister() {
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
index aad979229..714565d62 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
@@ -20,9 +20,7 @@ import android.Manifest.permission.READ_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.annotation.TargetApi
import android.app.job.JobInfo
-import android.content.Context
import android.content.pm.PackageManager
-import android.content.res.Resources
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_PRIVACY
@@ -35,6 +33,8 @@ import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED
import android.safetycenter.SafetySourceData
+import com.android.modules.utils.build.SdkLevel
+import com.android.safetycenter.testing.Coroutines.TEST_TIMEOUT
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
import java.time.Duration
@@ -45,7 +45,7 @@ object SafetyCenterFlags {
/** Flag that determines whether Safety Center is enabled. */
private val isEnabledFlag =
- Flag("safety_center_is_enabled", defaultValue = false, BooleanParser())
+ Flag("safety_center_is_enabled", defaultValue = SdkLevel.isAtLeastU(), BooleanParser())
/** Flag that determines whether Safety Center can send notifications. */
private val notificationsFlag =
@@ -101,13 +101,6 @@ object SafetyCenterFlags {
DurationParser()
)
- /**
- * Flag that determines whether we should show error entries for sources that timeout when
- * refreshing them.
- */
- private val showErrorEntriesOnTimeoutFlag =
- Flag("safety_center_show_error_entries_on_timeout", defaultValue = false, BooleanParser())
-
/** Flag that determines whether we should replace the IconAction of the lock screen source. */
private val replaceLockScreenIconActionFlag =
Flag("safety_center_replace_lock_screen_icon_action", defaultValue = true, BooleanParser())
@@ -116,11 +109,16 @@ object SafetyCenterFlags {
* Flag that determines the time for which a Safety Center refresh is allowed to wait for a
* source to respond to a refresh request before timing out and marking the refresh as finished,
* depending on the refresh reason.
+ *
+ * Unlike the production code, this flag is set to [TEST_TIMEOUT] for all refresh reasons by
+ * default for convenience. UI tests typically will set some data manually rather than going
+ * through a full refresh, and we don't want to timeout the refresh and potentially end up with
+ * error entries in this case (as it could lead to flakyness).
*/
private val refreshSourceTimeoutsFlag =
Flag(
"safety_center_refresh_sources_timeouts_millis",
- defaultValue = getAllRefreshTimeoutsMap(TIMEOUT_LONG),
+ defaultValue = getAllRefreshTimeoutsMap(TEST_TIMEOUT),
MapParser(IntParser(), DurationParser())
)
@@ -287,13 +285,6 @@ object SafetyCenterFlags {
MapParser(StringParser(), SetParser(StringParser(), delimiter = "|"))
)
- /**
- * Flag that determines whether background refreshes require charging in
- * [SafetyCenterBackgroundRefreshJobService]. See [JobInfo.setRequiresCharging] for details.
- */
- private val backgroundRefreshRequiresChargingFlag =
- Flag("safety_center_background_requires_charging", defaultValue = false, BooleanParser())
-
/** Every Safety Center flag. */
private val FLAGS: List<Flag<*>> =
listOf(
@@ -303,7 +294,6 @@ object SafetyCenterFlags {
notificationsMinDelayFlag,
immediateNotificationBehaviorIssuesFlag,
notificationResurfaceIntervalFlag,
- showErrorEntriesOnTimeoutFlag,
replaceLockScreenIconActionFlag,
refreshSourceTimeoutsFlag,
resolveActionTimeoutFlag,
@@ -320,14 +310,7 @@ object SafetyCenterFlags {
showSubpagesFlag,
overrideRefreshOnPageOpenSourcesFlag,
backgroundRefreshIsEnabledFlag,
- periodicBackgroundRefreshIntervalFlag,
- backgroundRefreshRequiresChargingFlag
- )
-
- /** Returns whether the device supports Safety Center. */
- fun Context.deviceSupportsSafetyCenter() =
- resources.getBoolean(
- Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android")
+ periodicBackgroundRefreshIntervalFlag
)
/** A property that allows getting and setting the [isEnabledFlag]. */
@@ -348,14 +331,11 @@ object SafetyCenterFlags {
/** A property that allows getting and setting the [notificationResurfaceIntervalFlag]. */
var notificationResurfaceInterval: Duration by notificationResurfaceIntervalFlag
- /** A property that allows getting and setting the [showErrorEntriesOnTimeoutFlag]. */
- var showErrorEntriesOnTimeout: Boolean by showErrorEntriesOnTimeoutFlag
-
/** A property that allows getting and setting the [replaceLockScreenIconActionFlag]. */
var replaceLockScreenIconAction: Boolean by replaceLockScreenIconActionFlag
/** A property that allows getting and setting the [refreshSourceTimeoutsFlag]. */
- var refreshTimeouts: Map<Int, Duration> by refreshSourceTimeoutsFlag
+ private var refreshTimeouts: Map<Int, Duration> by refreshSourceTimeoutsFlag
/** A property that allows getting and setting the [resolveActionTimeoutFlag]. */
var resolveActionTimeout: Duration by resolveActionTimeoutFlag
@@ -392,15 +372,6 @@ object SafetyCenterFlags {
/** A property that allows getting and setting the [overrideRefreshOnPageOpenSourcesFlag]. */
var overrideRefreshOnPageOpenSources: Set<String> by overrideRefreshOnPageOpenSourcesFlag
- /** A property that allows getting and settings the [backgroundRefreshIsEnabledFlag]. */
- var backgroundRefreshIsEnabled: Boolean by backgroundRefreshIsEnabledFlag
-
- /** A property that allows getting and settings the [periodicBackgroundRefreshIntervalFlag]. */
- var periodicBackgroundRefreshInterval: Duration by periodicBackgroundRefreshIntervalFlag
-
- /** A property that allows getting and settings the [backgroundRefreshRequiresChargingFlag]. */
- var backgroundRefreshRequiresCharging: Boolean by backgroundRefreshRequiresChargingFlag
-
/**
* Returns a snapshot of all the Safety Center flags.
*
@@ -454,7 +425,7 @@ object SafetyCenterFlags {
/** Returns the [isEnabledFlag] value of the Safety Center flags snapshot. */
fun Properties.isSafetyCenterEnabled() =
- getBoolean(isEnabledFlag.name, /* defaultValue */ false)
+ getBoolean(isEnabledFlag.name, isEnabledFlag.defaultValue)
@TargetApi(UPSIDE_DOWN_CAKE)
private fun getAllRefreshTimeoutsMap(refreshTimeout: Duration): Map<Int, Duration> =
@@ -527,11 +498,7 @@ object SafetyCenterFlags {
.joinToString(entriesDelimiter)
}
- private class Flag<T>(
- val name: String,
- private val defaultValue: T,
- private val parser: Parser<T>
- ) {
+ private class Flag<T>(val name: String, val defaultValue: T, private val parser: Parser<T>) {
val defaultStringValue = parser.toString(defaultValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt
index 5ff42e23c..31e9855ae 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt
@@ -48,7 +48,7 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId
import com.android.safetycenter.internaldata.SafetyCenterIssueId
import com.android.safetycenter.internaldata.SafetyCenterIssueKey
-import com.android.safetycenter.resources.SafetyCenterResourcesContext
+import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_GROUP_ID
import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ACTION_ID
import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID
@@ -66,23 +66,24 @@ import java.util.Locale
@RequiresApi(TIRAMISU)
class SafetyCenterTestData(context: Context) {
- private val safetyCenterResourcesContext = SafetyCenterResourcesContext.forTests(context)
+ private val safetyCenterResourcesApk = SafetyCenterResourcesApk.forTests(context)
private val safetySourceTestData = SafetySourceTestData(context)
/**
* The [SafetyCenterStatus] used when the overall status is unknown and no scan is in progress.
*/
- val safetyCenterStatusUnknown =
- SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_title"
- ),
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_ok_review_summary"
+ val safetyCenterStatusUnknown: SafetyCenterStatus
+ get() =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_ok_review_title"
+ ),
+ safetyCenterResourcesApk.getStringByName(
+ "overall_severity_level_ok_review_summary"
+ )
)
- )
- .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
- .build()
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
+ .build()
/**
* Returns a [SafetyCenterStatus] with one alert and the given [statusResource] and
@@ -103,7 +104,7 @@ class SafetyCenterTestData(context: Context) {
numAlerts: Int,
): SafetyCenterStatus =
SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(statusResource),
+ safetyCenterResourcesApk.getStringByName(statusResource),
getAlertString(numAlerts)
)
.setSeverityLevel(overallSeverityLevel)
@@ -117,11 +118,8 @@ class SafetyCenterTestData(context: Context) {
numTipIssues: Int,
): SafetyCenterStatus =
SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName(
- "overall_severity_level_tip_summary",
- numTipIssues
- )
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues)
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -134,8 +132,8 @@ class SafetyCenterTestData(context: Context) {
numAutomaticIssues: Int,
): SafetyCenterStatus =
SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesContext.getStringByName(
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
+ getIcuPluralsString(
"overall_severity_level_action_taken_summary",
numAutomaticIssues
)
@@ -149,7 +147,7 @@ class SafetyCenterTestData(context: Context) {
*/
fun safetyCenterStatusCritical(numAlerts: Int) =
SafetyCenterStatus.Builder(
- safetyCenterResourcesContext.getStringByName(
+ safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_safety_warning_title"
),
getAlertString(numAlerts)
@@ -428,7 +426,7 @@ class SafetyCenterTestData(context: Context) {
private fun getIcuPluralsString(name: String, count: Int, vararg formatArgs: Any): String {
val messageFormat =
MessageFormat(
- safetyCenterResourcesContext.getStringByName(name, formatArgs),
+ safetyCenterResourcesApk.getStringByName(name, formatArgs),
Locale.getDefault()
)
val arguments = ArrayMap<String, Any>()
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt
index 82f7326fd..2902cdd6a 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt
@@ -27,6 +27,7 @@ import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceData
import android.safetycenter.config.SafetyCenterConfig
import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC
+import android.util.Log
import androidx.annotation.RequiresApi
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.addOnSafetyCenterDataChangedListenerWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.clearAllSafetySourceDataForTestsWithPermission
@@ -44,7 +45,7 @@ import com.google.common.util.concurrent.MoreExecutors.directExecutor
/** A class that facilitates settings up Safety Center in tests. */
@RequiresApi(TIRAMISU)
-class SafetyCenterTestHelper(private val context: Context) {
+class SafetyCenterTestHelper(val context: Context) {
private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
private val userManager = context.getSystemService(UserManager::class.java)!!
@@ -55,14 +56,17 @@ class SafetyCenterTestHelper(private val context: Context) {
* values. To be called before each test.
*/
fun setup() {
- SafetySourceReceiver.setup()
+ Log.d(TAG, "setup")
Coroutines.enableDebugging()
+ SafetySourceReceiver.setup()
+ TestActivity.enableHighPriorityAlias()
SafetyCenterFlags.setup()
setEnabled(true)
}
/** Resets the state of Safety Center. To be called after each test. */
fun reset() {
+ Log.d(TAG, "reset")
setEnabled(true)
listeners.forEach {
safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(it)
@@ -72,12 +76,14 @@ class SafetyCenterTestHelper(private val context: Context) {
safetyCenterManager.clearAllSafetySourceDataForTestsWithPermission()
safetyCenterManager.clearSafetyCenterConfigForTestsWithPermission()
resetFlags()
+ TestActivity.disableHighPriorityAlias()
SafetySourceReceiver.reset()
Coroutines.resetDebugging()
}
/** Enables or disables SafetyCenter based on [value]. */
fun setEnabled(value: Boolean) {
+ Log.d(TAG, "setEnabled to $value")
val safetyCenterConfig = safetyCenterManager.getSafetyCenterConfigWithPermission()
if (safetyCenterConfig == null) {
// No broadcasts are dispatched when toggling the flag when SafetyCenter is not
@@ -87,8 +93,8 @@ class SafetyCenterTestHelper(private val context: Context) {
SafetyCenterFlags.isEnabled = value
return
}
- val currentValue = safetyCenterManager.isSafetyCenterEnabledWithPermission()
- if (currentValue == value) {
+ if (value == isEnabled()) {
+ Log.d(TAG, "isEnabled is already $value")
return
}
setEnabledWaitingForSafetyCenterBroadcastIdle(value, safetyCenterConfig)
@@ -96,6 +102,7 @@ class SafetyCenterTestHelper(private val context: Context) {
/** Sets the given [SafetyCenterConfig]. */
fun setConfig(config: SafetyCenterConfig) {
+ Log.d(TAG, "setConfig")
require(isEnabled())
safetyCenterManager.setSafetyCenterConfigForTestsWithPermission(config)
}
@@ -107,6 +114,7 @@ class SafetyCenterTestHelper(private val context: Context) {
* initial SafetyCenter update
*/
fun addListener(skipInitialData: Boolean = true): SafetyCenterTestListener {
+ Log.d(TAG, "addListener")
require(isEnabled())
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
@@ -126,6 +134,7 @@ class SafetyCenterTestHelper(private val context: Context) {
safetySourceData: SafetySourceData?,
safetyEvent: SafetyEvent = EVENT_SOURCE_STATE_CHANGED
) {
+ Log.d(TAG, "setData for $safetySourceId")
require(isEnabled())
safetyCenterManager.setSafetySourceDataWithPermission(
safetySourceId,
@@ -137,6 +146,8 @@ class SafetyCenterTestHelper(private val context: Context) {
/** Dismisses the [SafetyCenterIssue] for the given [safetyCenterIssueId]. */
@RequiresApi(UPSIDE_DOWN_CAKE)
fun dismissSafetyCenterIssue(safetyCenterIssueId: String) {
+ Log.d(TAG, "dismissSafetyCenterIssue")
+ require(isEnabled())
safetyCenterManager.dismissSafetyCenterIssueWithPermission(safetyCenterIssueId)
}
@@ -155,6 +166,7 @@ class SafetyCenterTestHelper(private val context: Context) {
// Wait for all ACTION_SAFETY_CENTER_ENABLED_CHANGED broadcasts to be dispatched to
// avoid them leaking onto other tests.
if (safetyCenterConfig.containsTestSource()) {
+ Log.d(TAG, "Waiting for test source enabled changed broadcast")
SafetySourceReceiver.receiveSafetyCenterEnabledChanged()
// The explicit ACTION_SAFETY_CENTER_ENABLED_CHANGED broadcast is also sent to the
// dynamically registered receivers.
@@ -166,6 +178,7 @@ class SafetyCenterTestHelper(private val context: Context) {
// 2: test finishes, 3: new test starts, 4: a test config is set, 5: broadcast from 1
// dispatched).
if (userManager.isSystemUser) {
+ Log.d(TAG, "Waiting for system enabled changed broadcast")
// The implicit broadcast is only sent to the system user.
enabledChangedReceiver.receiveSafetyCenterEnabledChanged()
}
@@ -188,4 +201,8 @@ class SafetyCenterTestHelper(private val context: Context) {
.any { it.packageName == context.packageName }
private fun isEnabled() = safetyCenterManager.isSafetyCenterEnabledWithPermission()
+
+ private companion object {
+ const val TAG: String = "SafetyCenterTestHelper"
+ }
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt
index d62214843..8ce5c25d4 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt
@@ -18,9 +18,14 @@ package com.android.safetycenter.testing
import android.os.Build.VERSION_CODES.TIRAMISU
import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterEntry
import android.safetycenter.SafetyCenterErrorDetails
import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener
+import android.safetycenter.SafetyCenterStaticEntry
+import android.safetycenter.SafetyCenterStatus
+import android.text.TextUtils
import androidx.annotation.RequiresApi
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.runBlockingWithTimeout
import java.time.Duration
@@ -46,9 +51,63 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener {
runBlockingWithTimeout { errorChannel.send(errorDetails) }
}
- /** Waits for a [SafetyCenterData] update from SafetyCenter within the given [timeout]. */
- fun receiveSafetyCenterData(timeout: Duration = TIMEOUT_LONG) =
- runBlockingWithTimeout(timeout) { dataChannel.receive() }
+ /**
+ * Waits for a [SafetyCenterData] update from SafetyCenter within the given [timeout].
+ *
+ * Optionally, a predicate can be used to wait for the [SafetyCenterData] to be [matching].
+ */
+ fun receiveSafetyCenterData(
+ timeout: Duration = TIMEOUT_LONG,
+ matching: (SafetyCenterData) -> Boolean = { true }
+ ): SafetyCenterData =
+ runBlockingWithTimeout(timeout) {
+ var safetyCenterData = dataChannel.receive()
+ while (!matching(safetyCenterData)) {
+ safetyCenterData = dataChannel.receive()
+ }
+ safetyCenterData
+ }
+
+ /**
+ * Waits for a full Safety Center refresh to complete, where each change to the underlying
+ * [SafetyCenterData] must happen within the given [timeout].
+ *
+ * @param withErrorEntry optionally check whether we should expect the [SafetyCenterData] to
+ * have or not have at least one an error entry after the refresh completes
+ * @return the [SafetyCenterData] after the refresh completes
+ */
+ fun waitForSafetyCenterRefresh(
+ timeout: Duration = TIMEOUT_LONG,
+ withErrorEntry: Boolean? = null
+ ): SafetyCenterData {
+ receiveSafetyCenterData(timeout) {
+ it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS ||
+ it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS
+ }
+ val afterRefresh =
+ receiveSafetyCenterData(timeout) {
+ it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_NONE
+ }
+ if (withErrorEntry == null) {
+ return afterRefresh
+ }
+ val errorMessage =
+ SafetyCenterTestData(getApplicationContext())
+ .getRefreshErrorString(numberOfErrorEntries = 1)
+ val containsErrorEntry = afterRefresh.containsAnyEntryWithSummary(errorMessage)
+ if (withErrorEntry && !containsErrorEntry) {
+ throw AssertionError(
+ "No error entry with message: \"$errorMessage\" found in SafetyCenterData" +
+ " after refresh: $afterRefresh"
+ )
+ } else if (!withErrorEntry && containsErrorEntry) {
+ throw AssertionError(
+ "Found an error entry with message: \"$errorMessage\" in SafetyCenterData" +
+ " after refresh: $afterRefresh"
+ )
+ }
+ return afterRefresh
+ }
/**
* Waits for a [SafetyCenterErrorDetails] update from SafetyCenter within the given [timeout].
@@ -61,4 +120,23 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener {
dataChannel.cancel()
errorChannel.cancel()
}
+
+ private companion object {
+ fun SafetyCenterData.containsAnyEntryWithSummary(summary: CharSequence): Boolean =
+ entries().any { TextUtils.equals(it.summary, summary) } ||
+ staticEntries().any { TextUtils.equals(it.summary, summary) }
+
+ fun SafetyCenterData.entries(): List<SafetyCenterEntry> =
+ entriesOrGroups.flatMap {
+ val entry = it.entry
+ if (entry != null) {
+ listOf(entry)
+ } else {
+ it.entryGroup!!.entries
+ }
+ }
+
+ fun SafetyCenterData.staticEntries(): List<SafetyCenterStaticEntry> =
+ staticEntryGroups.flatMap { it.staticEntries }
+ }
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt
new file mode 100644
index 000000000..dcbc4ebe9
--- /dev/null
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.testing
+
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** A JUnit [TestRule] that performs setup and reset steps before and after Safety Center tests. */
+class SafetyCenterTestRule(
+ private val safetyCenterTestHelper: SafetyCenterTestHelper,
+ private val withNotifications: Boolean = false
+) : TestRule {
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ setup()
+ try {
+ base.evaluate()
+ } finally {
+ reset()
+ }
+ }
+ }
+ }
+
+ private fun setup() {
+ safetyCenterTestHelper.setup()
+ if (withNotifications) {
+ TestNotificationListener.setup(safetyCenterTestHelper.context)
+ }
+ }
+
+ private fun reset() {
+ safetyCenterTestHelper.reset()
+ if (withNotifications) {
+ // It is important to reset the notification listener last because it waits/ensures that
+ // all notifications have been removed before returning.
+ TestNotificationListener.reset(safetyCenterTestHelper.context)
+ }
+ }
+}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt
index 2bd662ee8..8386228b8 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt
@@ -221,7 +221,7 @@ class SafetySourceIntentHandler {
safetyEventForResponse: (Response) -> SafetyEvent
) {
val response = mutex.withLock { requestsToResponses[request] } ?: return
- val safetyEvent = safetyEventForResponse(response)
+ val safetyEvent = response.overrideSafetyEvent ?: safetyEventForResponse(response)
when (response) {
is Response.Error ->
reportSafetySourceError(request.sourceId, SafetySourceErrorDetails(safetyEvent))
@@ -270,6 +270,13 @@ class SafetySourceIntentHandler {
*/
sealed interface Response {
+ /**
+ * If non-null, the [SafetyEvent] to use when calling any applicable [SafetyCenterManager]
+ * methods.
+ */
+ val overrideSafetyEvent: SafetyEvent?
+ get() = null
+
/** Creates an error [Response]. */
object Error : Response
@@ -282,10 +289,13 @@ class SafetySourceIntentHandler {
* @param overrideBroadcastId an optional override of the broadcast id to use in the
* [SafetyEvent] sent to the [SafetyCenterManager], in case of [Request.Refresh] or
* [Request.Rescan]. This is used to simulate a misuse of the [SafetyCenterManager] APIs
+ * @param overrideSafetyEvent like [overrideBroadcastId] but allows the whole [SafetyEvent]
+ * to be override to send different types of [SafetyEvent].
*/
data class SetData(
val safetySourceData: SafetySourceData,
- val overrideBroadcastId: String? = null
+ val overrideBroadcastId: String? = null,
+ override val overrideSafetyEvent: SafetyEvent? = null
) : Response
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt
index 2ba87040a..29072c989 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt
@@ -35,8 +35,8 @@ import android.safetycenter.SafetyCenterManager
import android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED
import androidx.annotation.RequiresApi
import androidx.test.core.app.ApplicationProvider
-import com.android.compatibility.common.util.SystemUtil
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
+import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.Coroutines.runBlockingWithTimeout
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.executeSafetyCenterIssueActionWithPermission
@@ -164,46 +164,38 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun SafetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
refreshReason: Int,
- timeout: Duration = TIMEOUT_LONG,
- safetySourceIds: List<String>? = null
- ) =
+ safetySourceIds: List<String>? = null,
+ timeout: Duration = TIMEOUT_LONG
+ ): String =
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
- refreshSafetySourcesWithoutReceiverPermissionAndWait(
- refreshReason,
- timeout,
- safetySourceIds
- )
+ refreshSafetySourcesWithPermission(refreshReason, safetySourceIds)
+ receiveRefreshSafetySources(timeout)
}
fun SafetyCenterManager.refreshSafetySourcesWithoutReceiverPermissionAndWait(
refreshReason: Int,
- timeout: Duration,
safetySourceIds: List<String>? = null
- ): String {
+ ) {
refreshSafetySourcesWithPermission(refreshReason, safetySourceIds)
- if (timeout < TIMEOUT_LONG) {
- SystemUtil.waitForBroadcasts()
- }
- return receiveRefreshSafetySources(timeout)
+ WaitForBroadcasts.waitForBroadcasts()
+ receiveRefreshSafetySources(TIMEOUT_SHORT)
}
fun setSafetyCenterEnabledWithReceiverPermissionAndWait(
value: Boolean,
timeout: Duration = TIMEOUT_LONG
- ) =
+ ): Boolean =
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
- setSafetyCenterEnabledWithoutReceiverPermissionAndWait(value, timeout)
+ SafetyCenterFlags.isEnabled = value
+ receiveSafetyCenterEnabledChanged(timeout)
}
fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait(
value: Boolean,
- timeout: Duration = TIMEOUT_LONG
- ): Boolean {
+ ) {
SafetyCenterFlags.isEnabled = value
- if (timeout < TIMEOUT_LONG) {
- SystemUtil.waitForBroadcasts()
- }
- return receiveSafetyCenterEnabledChanged(timeout)
+ WaitForBroadcasts.waitForBroadcasts()
+ receiveSafetyCenterEnabledChanged(TIMEOUT_SHORT)
}
fun SafetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
index 97e2078e0..6511f85f2 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
@@ -812,13 +812,26 @@ class SafetySourceTestData(private val context: Context) {
}
/** Returns a [PendingIntent] that redirects to [intent]. */
- fun createRedirectPendingIntent(context: Context, intent: Intent): PendingIntent {
+ fun createRedirectPendingIntent(
+ context: Context,
+ intent: Intent,
+ inQuietMode: Boolean = false
+ ): PendingIntent {
val explicitIntent = Intent(intent).setPackage(context.packageName)
val redirectIntent =
if (intentResolves(context, explicitIntent)) {
explicitIntent
} else if (intentResolves(context, intent)) {
- intent
+ // We have seen some flakiness where implicit intents find multiple receivers
+ // and the ResolveActivity pops up. A test cannot handle this, so crash. Most
+ // likely the cause is other test's APKs being left hanging around by flaky
+ // test infrastructure.
+ val intentWithFlag = Intent(intent)
+ intentWithFlag.flags =
+ intentWithFlag.flags or Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT
+ intentWithFlag
+ } else if (inQuietMode) {
+ explicitIntent
} else {
throw IllegalStateException("Intent doesn't resolve")
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/StatusBarNotificationWithChannel.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt
index d9a998c3e..53ea34362 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/StatusBarNotificationWithChannel.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.safetycenter.functional.testing
+package com.android.safetycenter.testing
import android.app.NotificationChannel
import android.service.notification.StatusBarNotification
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt
new file mode 100644
index 000000000..bfb5c4bd7
--- /dev/null
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.testing
+
+import android.content.Context
+import android.content.res.Resources
+
+/**
+ * Returns whether the device supports Safety Center according to the `config_enableSafetyCenter`
+ * boolean system resource.
+ */
+fun Context.deviceSupportsSafetyCenter(): Boolean {
+ val resId = Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android")
+ return resources.getBoolean(resId)
+}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt
new file mode 100644
index 000000000..7227873ff
--- /dev/null
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SupportsSafetyCenterRule.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.testing
+
+import android.content.Context
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit [TestRule] for on-device tests that requires Safety Center to be supported. This rule does
+ * not require Safety Center to be enabled.
+ *
+ * For tests which should only run on devices where Safety Center is not supported, instantiate with
+ * [requireSupportIs] set to `false` to invert the condition.
+ */
+class SupportsSafetyCenterRule(private val context: Context, requireSupportIs: Boolean = true) :
+ TestRule {
+
+ private val shouldSupportSafetyCenter: Boolean = requireSupportIs
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ val support = context.deviceSupportsSafetyCenter()
+ if (shouldSupportSafetyCenter) {
+ assumeTrue("Test device does not support Safety Center", support)
+ } else {
+ assumeFalse("Test device supports Safety Center", support)
+ }
+ base.evaluate()
+ }
+ }
+ }
+}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt
index 124f44101..eceffb74f 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt
@@ -16,9 +16,15 @@
package com.android.safetycenter.testing
import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+import android.content.pm.PackageManager.DONT_KILL_APP
import android.os.Bundle
import android.view.View
import android.widget.TextView
+import androidx.test.core.app.ApplicationProvider
/** An activity used in tests to assert the redirects. */
class TestActivity : Activity() {
@@ -32,4 +38,32 @@ class TestActivity : Activity() {
val exitButton: View? = findViewById(R.id.button)
exitButton?.setOnClickListener { finish() }
}
+
+ companion object {
+
+ /**
+ * Enable a higher-priority alias of TestActivity.
+ *
+ * <p>We have seen flakes where implicit intents for TEST_ACTIVITY fail owing to multiple
+ * receivers, perhaps due to an older CTS APK hanging around. This component should be
+ * turned on (and off in tidyup) in tests in the hope of only resolving to the actively
+ * running test in these cases.
+ */
+ fun enableHighPriorityAlias() {
+ setAliasEnabledState(COMPONENT_ENABLED_STATE_ENABLED)
+ }
+ /** @see [enableHighPriorityAlias] */
+ fun disableHighPriorityAlias() {
+ setAliasEnabledState(COMPONENT_ENABLED_STATE_DISABLED)
+ }
+ private fun setAliasEnabledState(state: Int) {
+ val name =
+ ComponentName(getApplicationContext(), TestActivity::class.java.name + "Priority")
+ getApplicationContext()
+ .packageManager
+ .setComponentEnabledSetting(name, state, DONT_KILL_APP)
+ }
+
+ private fun getApplicationContext(): Context = ApplicationProvider.getApplicationContext()
+ }
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/TestNotificationListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt
index 113ad3b23..db714fa97 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/testing/TestNotificationListener.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package android.safetycenter.functional.testing
+package com.android.safetycenter.testing
import android.app.NotificationChannel
import android.content.ComponentName
+import android.content.Context
import android.os.ConditionVariable
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
@@ -84,15 +85,7 @@ class TestNotificationListener : NotificationListenerService() {
}
companion object {
- private const val TAG = "TestNotificationListene"
-
- private val id: String =
- "android.safetycenter.functional/" + TestNotificationListener::class.java.name
- private val componentName =
- ComponentName(
- "android.safetycenter.functional",
- TestNotificationListener::class.java.name
- )
+ private const val TAG = "SafetyCenterTestNotif"
private val connected = ConditionVariable(false)
private val disconnected = ConditionVariable(true)
@@ -132,7 +125,10 @@ class TestNotificationListener : NotificationListenerService() {
count: Int,
timeout: Duration = TIMEOUT_LONG
): List<StatusBarNotificationWithChannel> {
- return waitForNotificationsToSatisfy(timeout, description = "$count notifications") {
+ return waitForNotificationsToSatisfy(
+ timeout = timeout,
+ description = "$count notifications"
+ ) {
it.size == count
}
}
@@ -162,18 +158,20 @@ class TestNotificationListener : NotificationListenerService() {
): List<StatusBarNotificationWithChannel> {
val charsList = characteristics.toList()
return waitForNotificationsToSatisfy(
- timeout,
+ timeout = timeout,
description = "notification(s) matching characteristics $charsList"
- ) { NotificationCharacteristics.areMatching(it, charsList) }
+ ) {
+ NotificationCharacteristics.areMatching(it, charsList)
+ }
}
/**
- * Blocks until [forAtLeast] has elapsed, or throw an [AssertionError] if any notification
- * is posted or removed before then.
+ * Blocks for [TIMEOUT_SHORT], or throw an [AssertionError] if any notification is posted or
+ * removed before then.
*/
- fun waitForZeroNotificationEvents(forAtLeast: Duration = TIMEOUT_SHORT) {
+ fun waitForZeroNotificationEvents() {
val event =
- runBlockingWithTimeoutOrNull(forAtLeast) {
+ runBlockingWithTimeoutOrNull(TIMEOUT_SHORT) {
safetyCenterNotificationEvents.receive()
}
assertThat(event).isNull()
@@ -185,10 +183,6 @@ class TestNotificationListener : NotificationListenerService() {
description: String,
predicate: (List<StatusBarNotificationWithChannel>) -> Boolean
): List<StatusBarNotificationWithChannel> {
- fun formatError(notifs: List<StatusBarNotificationWithChannel>): String {
- return "Expected: $description, but the actual notifications were: $notifs"
- }
-
// First we wait at most timeout for the active notifications to satisfy the given
// predicate or otherwise we throw:
val satisfyingNotifications =
@@ -197,7 +191,11 @@ class TestNotificationListener : NotificationListenerService() {
waitForNotificationsToSatisfyAsync(predicate)
}
} catch (e: TimeoutCancellationException) {
- throw AssertionError(formatError(getSafetyCenterNotifications()), e)
+ throw AssertionError(
+ "Expected: $description, but notifications were " +
+ "${getSafetyCenterNotifications()} after waiting for $timeout",
+ e
+ )
}
// Assuming the predicate was satisfied, now we ensure it is not violated for the
@@ -208,7 +206,10 @@ class TestNotificationListener : NotificationListenerService() {
}
if (nonSatisfyingNotifications != null) {
// In this case the negated-predicate was satisfied before forAtLeast had elapsed
- throw AssertionError(formatError(nonSatisfyingNotifications))
+ throw AssertionError(
+ "Expected: $description to settle, but notifications changed to " +
+ "$nonSatisfyingNotifications within $forAtLeast"
+ )
}
return satisfyingNotifications
@@ -281,9 +282,11 @@ class TestNotificationListener : NotificationListenerService() {
fun cancelAndWait(key: String, timeout: Duration = TIMEOUT_LONG) {
getInstanceOrThrow().cancelNotification(key)
waitForNotificationsToSatisfy(
- timeout,
+ timeout = timeout,
description = "no notification with the key $key"
- ) { notifications -> notifications.none { it.statusBarNotification.key == key } }
+ ) { notifications ->
+ notifications.none { it.statusBarNotification.key == key }
+ }
waitForIssueCacheToContainAnyDismissedNotification()
}
@@ -313,10 +316,12 @@ class TestNotificationListener : NotificationListenerService() {
}
/** Runs a shell command to allow or disallow the listener. Use before and after test. */
- private fun toggleListenerAccess(allowed: Boolean) {
- // TODO(b/260335646): Try to do this using the AndroidTest.xml instead of in code
+ private fun toggleListenerAccess(context: Context, allowed: Boolean) {
+ val componentName = ComponentName(context, TestNotificationListener::class.java)
val verb = if (allowed) "allow" else "disallow"
- SystemUtil.runShellCommand("cmd notification ${verb}_listener $id")
+ SystemUtil.runShellCommand(
+ "cmd notification ${verb}_listener ${componentName.flattenToString()}"
+ )
if (allowed) {
requestRebind(componentName)
if (!connected.block(TIMEOUT_LONG.toMillis())) {
@@ -332,22 +337,29 @@ class TestNotificationListener : NotificationListenerService() {
}
/** Prepare the [TestNotificationListener] for a notification test */
- fun setup() {
- toggleListenerAccess(true)
+ fun setup(context: Context) {
+ toggleListenerAccess(context, true)
}
/** Clean up the [TestNotificationListener] after executing a notification test. */
- fun reset() {
+ fun reset(context: Context) {
waitForNotificationsToSatisfy(
forAtLeast = Duration.ZERO,
description = "all Safety Center notifications removed in tear down"
- ) { it.isEmpty() }
- toggleListenerAccess(false)
+ ) {
+ it.isEmpty()
+ }
+ toggleListenerAccess(context, false)
safetyCenterNotificationEvents.cancel()
safetyCenterNotificationEvents = Channel(capacity = Channel.UNLIMITED)
}
private fun StatusBarNotification.isSafetyCenterNotification(): Boolean =
- packageName == "android" && notification.channelId.startsWith("safety_center")
+ packageName == "android" &&
+ notification.channelId.startsWith("safety_center") &&
+ // Don't consider the grouped system notifications to be a SC notification, in some
+ // scenarios a "ranker_group" notification can remain even when there are no more
+ // notifications associated with the channel. See b/293593539 for more details.
+ tag != "ranker_group"
}
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
index ec676e3d9..0348a9c52 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
@@ -22,17 +22,17 @@ import android.os.SystemClock
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceIssue
import android.safetycenter.config.SafetySourcesGroup
-import android.util.Log
import androidx.annotation.RequiresApi
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.StaleObjectException
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice
import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
-import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
+import com.android.compatibility.common.util.UiDumpUtils
import java.time.Duration
import java.util.concurrent.TimeoutException
import java.util.regex.Pattern
@@ -46,19 +46,28 @@ object UiTestHelper {
const val MORE_ISSUES_LABEL = "More alerts"
private const val DISMISS_ISSUE_LABEL = "Dismiss"
- private val WAIT_TIMEOUT = Duration.ofSeconds(10)
- private val NOT_DISPLAYED_TIMEOUT = Duration.ofMillis(500)
+ private val WAIT_TIMEOUT = Duration.ofSeconds(20)
private val TAG = UiTestHelper::class.java.simpleName
/**
- * Waits for the given [selector] to be displayed and performs the given [uiObjectAction] on it.
+ * Waits for the given [selector] to be displayed, and optionally perform a given
+ * [uiObjectAction] on it.
*/
fun waitDisplayed(selector: BySelector, uiObjectAction: (UiObject2) -> Unit = {}) {
- waitFor("$selector to be displayed", WAIT_TIMEOUT) {
- uiObjectAction(waitFindObject(selector, it.toMillis()))
- true
+ val whenToTimeout = currentElapsedRealtime() + WAIT_TIMEOUT
+ var remaining = WAIT_TIMEOUT
+ while (remaining > Duration.ZERO) {
+ getUiDevice().waitForIdle()
+ try {
+ uiObjectAction(waitFindObject(selector, remaining.toMillis()))
+ return
+ } catch (e: StaleObjectException) {
+ // Found, but stale before uiObjectAction could be performed, retry
+ remaining = whenToTimeout - currentElapsedRealtime()
+ }
}
+ throw TimeoutException("Timed out waiting for $selector to be displayed")
}
/** Waits for all the given [textToFind] to be displayed. */
@@ -77,16 +86,19 @@ object UiTestHelper {
/** Waits for the given [selector] not to be displayed. */
fun waitNotDisplayed(selector: BySelector) {
- waitFor("$selector not to be displayed", NOT_DISPLAYED_TIMEOUT) {
- waitFindObjectOrNull(selector, it.toMillis()) == null
+ val timeoutMs = WAIT_TIMEOUT.toMillis()
+ // TODO(b/294038848): Add scrolling to make sure it is properly gone.
+ val view = getUiDevice().wait(Until.gone(selector), timeoutMs)
+ if (view == null) {
+ throw UiDumpUtils.wrapWithUiDump(
+ TimeoutException("View not found after waiting for $timeoutMs $selector")
+ )
}
}
/** Waits for all the given [textToFind] not to be displayed. */
fun waitAllTextNotDisplayed(vararg textToFind: CharSequence?) {
- for (text in textToFind) {
- if (text != null) waitNotDisplayed(By.text(text.toString()))
- }
+ waitNotDisplayed(By.text(anyOf(*textToFind)))
}
/** Waits for a button with the given [label] not to be displayed. */
@@ -131,7 +143,7 @@ object UiTestHelper {
fun waitCollapsedIssuesDisplayed(vararg sourceIssues: SafetySourceIssue) {
waitSourceIssueDisplayed(sourceIssues.first())
waitAllTextDisplayed(MORE_ISSUES_LABEL)
- sourceIssues.asSequence().drop(1).forEach { waitSourceIssueNotDisplayed(it) }
+ waitAllTextNotDisplayed(*sourceIssues.drop(1).map { it.title }.toTypedArray())
}
/** Waits for all the [SafetySourceIssue] to be displayed with the [MORE_ISSUES_LABEL] card. */
@@ -221,35 +233,16 @@ object UiTestHelper {
}
private fun buttonSelector(label: CharSequence): BySelector {
- return By.clickable(true).text(Pattern.compile("$label|${label.toString().uppercase()}"))
+ return By.clickable(true).text(anyOf(label, label.toString().uppercase()))
}
- private fun waitFor(
- message: String,
- uiAutomatorConditionTimeout: Duration,
- uiAutomatorCondition: (Duration) -> Boolean
- ) {
- val elapsedStartMillis = SystemClock.elapsedRealtime()
- while (true) {
- getUiDevice().waitForIdle()
- val durationSinceStart =
- Duration.ofMillis(SystemClock.elapsedRealtime() - elapsedStartMillis)
- if (durationSinceStart >= WAIT_TIMEOUT) {
- break
- }
- val remainingTime = WAIT_TIMEOUT - durationSinceStart
- val uiAutomatorTimeout = minOf(uiAutomatorConditionTimeout, remainingTime)
- try {
- if (uiAutomatorCondition(uiAutomatorTimeout)) {
- return
- } else {
- Log.d(TAG, "Failed condition for $message, will retry if within timeout")
- }
- } catch (e: StaleObjectException) {
- Log.d(TAG, "StaleObjectException for $message, will retry if within timeout", e)
+ private fun anyOf(vararg anyTextToFind: CharSequence?): Pattern {
+ val regex =
+ anyTextToFind.filterNotNull().joinToString(separator = "|") {
+ Pattern.quote(it.toString())
}
- }
-
- throw TimeoutException("Timed out waiting for $message")
+ return Pattern.compile(regex)
}
+ private fun currentElapsedRealtime(): Duration =
+ Duration.ofMillis(SystemClock.elapsedRealtime())
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/WaitForBroadcasts.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/WaitForBroadcasts.kt
new file mode 100644
index 000000000..3c19ea180
--- /dev/null
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/WaitForBroadcasts.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.safetycenter.testing
+
+import android.util.Log
+import androidx.annotation.GuardedBy
+import com.android.compatibility.common.util.SystemUtil
+import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
+import com.android.safetycenter.testing.Coroutines.runBlockingWithTimeout
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/** A class to help waiting on broadcasts to be processed by the system. */
+object WaitForBroadcasts {
+
+ private const val TAG: String = "WaitForBroadcasts"
+
+ private val mutex = Mutex()
+ @GuardedBy("mutex") private var currentJob: Job? = null
+
+ /**
+ * Waits for broadcasts for at most [TIMEOUT_LONG] and prints a warning if that operation timed
+ * out.
+ *
+ * The [SystemUtil.waitForBroadcasts] operation will keep on running even after the timeout as
+ * it is not interruptible. Further calls to [WaitForBroadcasts.waitForBroadcasts] will re-use
+ * the currently running [SystemUtil.waitForBroadcasts] call if it hasn't completed.
+ */
+ fun waitForBroadcasts() {
+ try {
+ runBlockingWithTimeout {
+ mutex
+ .withLock {
+ val newJob = currentJob.maybeStartNewWaitForBroadcasts()
+ currentJob = newJob
+ newJob
+ }
+ .join()
+ }
+ } catch (e: TimeoutCancellationException) {
+ Log.w(TAG, "Waiting for broadcasts timed out, proceeding anyway", e)
+ }
+ }
+
+ // We're using a GlobalScope here as there doesn't seem to be a straightforward way to timeout
+ // and interrupt the waitForBroadcasts() call. Given it's uninterruptible, we'd rather just have
+ // at most one globally-bound waitForBroadcasts() call running at any given time.
+ @OptIn(DelicateCoroutinesApi::class)
+ private fun Job?.maybeStartNewWaitForBroadcasts(): Job =
+ if (this != null && isActive) {
+ this
+ } else {
+ GlobalScope.launch(Dispatchers.IO) { SystemUtil.waitForBroadcasts() }
+ }
+}