summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--OWNERS2
-rw-r--r--PermissionController/Android.bp10
-rw-r--r--PermissionController/AndroidManifest.xml3
-rw-r--r--PermissionController/TEST_MAPPING61
-rw-r--r--PermissionController/lint-baseline.xml216
-rw-r--r--PermissionController/res/values/styles.xml2
-rw-r--r--PermissionController/res/xml/roles.xml26
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java110
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java38
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java63
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java59
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permission.java6
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java19
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java14
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java41
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java14
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java13
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/Constants.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt24
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING65
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java84
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt28
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java59
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt60
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java27
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING27
-rw-r--r--PermissionController/tests/inprocess/Android.bp5
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java258
-rw-r--r--PermissionController/tests/mocking/Android.bp2
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt9
-rw-r--r--PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt9
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt1
-rw-r--r--TEST_MAPPING53
-rw-r--r--flags/Android.bp65
-rw-r--r--flags/flags.aconfig7
-rw-r--r--flags/java/com/android/permission/flags/PermissionsFlags.java20
-rw-r--r--framework-s/java/android/app/role/RoleManager.java10
-rw-r--r--service/java/com/android/role/RoleService.java60
-rw-r--r--service/java/com/android/role/TEST_MAPPING33
-rw-r--r--service/lint-baseline.xml18
-rw-r--r--tests/cts/permissionmultiuser/Android.bp47
-rw-r--r--tests/cts/permissionmultiuser/AndroidManifest.xml34
-rw-r--r--tests/cts/permissionmultiuser/AndroidTest.xml63
-rw-r--r--tests/cts/permissionmultiuser/OWNERS3
-rw-r--r--tests/cts/permissionmultiuser/RequestLocationApp/Android.bp25
-rw-r--r--tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml23
-rw-r--r--tests/cts/permissionmultiuser/TEST_MAPPING7
-rw-r--r--tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt473
67 files changed, 1730 insertions, 819 deletions
diff --git a/OWNERS b/OWNERS
index 8f1443bc6..cd03f0db3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,5 @@
+#Bug component: 137825
+
include platform/frameworks/base:/core/java/android/permission/OWNERS
include platform/packages/modules/common:/MODULES_OWNERS # see go/mainline-owners-policy
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp
index 892f12f2b..f02022bbb 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -152,6 +152,14 @@ android_app {
"lottie",
"safety-label",
"role-controller",
+ "permissions-flags-lib",
+ "androidx.compose.runtime_runtime",
+ "androidx.annotation_annotation",
+ "androidx.compose.ui_ui",
+ "androidx.compose.foundation_foundation",
+ "androidx.wear.compose_compose-foundation",
+ "androidx.compose.material3_material3",
+ "androidx.activity_activity-compose",
],
proto: {
@@ -161,9 +169,11 @@ android_app {
lint: {
strict_updatability_linting: true,
+ error_checks: ["Recycle"],
},
optimize: {
+ proguard_compatibility: false, // TODO(b/215530220): remove when this is default behavior
proguard_flags_files: ["proguard.flags"],
},
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index 874ba35d6..794d111da 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -399,6 +399,7 @@
<activity android:name="com.android.permissioncontroller.role.ui.RequestRoleActivity"
android:excludeFromRecents="true"
android:exported="true"
+ android:launchMode="singleTop"
android:theme="@style/RequestRole.FilterTouches">
<intent-filter android:priority="1">
<action android:name="android.app.role.action.REQUEST_ROLE" />
@@ -606,8 +607,6 @@
</intent-filter>
</activity>
- <!-- Unexported empty activity for in-process tests -->
- <activity android:name="android.app.Activity" />
</application>
</manifest>
diff --git a/PermissionController/TEST_MAPPING b/PermissionController/TEST_MAPPING
index 0ae3818fd..869bb2020 100644
--- a/PermissionController/TEST_MAPPING
+++ b/PermissionController/TEST_MAPPING
@@ -8,6 +8,9 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
],
"file_patterns": ["res/xml/roles\\.xml"]
@@ -23,7 +26,7 @@
],
"presubmit-large": [
{
- "name": "CtsPermission3TestCases",
+ "name": "CtsPermissionUiTestCases",
"options": [
{
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
@@ -44,6 +47,9 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
],
"file_patterns": ["res/xml/roles\\.xml"]
@@ -73,7 +79,7 @@
]
},
{
- "name": "CtsPermission3TestCases[com.google.android.permission.apex]",
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]",
"options": [
{
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
@@ -81,6 +87,57 @@
]
}
],
+ "postsubmit": [
+ {
+ "name": "CtsRoleTestCases",
+ "file_patterns": ["res/xml/roles\\.xml"]
+ },
+ {
+ "name": "PermissionUiTestCases"
+ },
+ {
+ "name": "CtsPermissionUiTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
+ }
+ ],
+ "file_patterns": ["res/xml/roles\\.xml"]
+ },
+ {
+ "name": "PermissionUiTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238773220): These tests currently fails on R base image
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageCustomPermissionsFragmentTest#groupSummaryGetsUpdatedWhenPermissionGetsGranted"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageCustomPermissionsFragmentTest#groupSummaryGetsUpdatedWhenPermissionGetsRevoked"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenAppGetsInstalled"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenDefinerGetsUninstalled"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenUserGetsUninstalled"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]"
+ }
+ ],
"imports": [
{
"path": "vendor/xts/gts-tests/hostsidetests/permissioncontroller"
diff --git a/PermissionController/lint-baseline.xml b/PermissionController/lint-baseline.xml
index 05a307234..546ed596d 100644
--- a/PermissionController/lint-baseline.xml
+++ b/PermissionController/lint-baseline.xml
@@ -3,61 +3,6 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`"
- errorLine1=" val attributedOpEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
- line="191"
- column="67"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`"
- errorLine1=" val opEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
- line="156"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`"
- errorLine1=" for (i in 0 until it.discreteAccessCount) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
- line="155"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`"
- errorLine1=" for (i in 0 until it.discreteAccessCount) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
- line="190"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOpsRequest.Builder#setHistoryFlags`"
- errorLine1=" .setHistoryFlags(HISTORY_FLAG_DISCRETE or HISTORY_FLAG_GET_ATTRIBUTION_CHAINS)"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt"
- line="101"
- column="18"/>
- </issue>
-
- <issue
- id="NewApi"
message="Call requires API level 31 (current min is 30): `android.apphibernation.AppHibernationManager#isHibernatingForUser`"
errorLine1=" if (hibernationManager.isHibernatingForUser(pkg.packageName)) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
@@ -180,17 +125,6 @@
<issue
id="NewApi"
message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getIssues`"
- errorLine1=" ) : this(safetyCenterData.status, hasIssues = safetyCenterData.issues.size &gt; 0)"
- errorLine2=" ~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt"
- line="18"
- column="68"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getIssues`"
errorLine1=" issues"
errorLine2=" ~~~~~~">
<location
@@ -213,17 +147,6 @@
<issue
id="NewApi"
message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getStatus`"
- errorLine1=" ) : this(safetyCenterData.status, hasIssues = safetyCenterData.issues.size &gt; 0)"
- errorLine2=" ~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt"
- line="18"
- column="31"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getStatus`"
errorLine1=" status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS"
errorLine2=" ~~~~~~">
<location
@@ -356,17 +279,6 @@
<issue
id="NewApi"
message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus#getRefreshStatus`"
- errorLine1=" when (status.refreshStatus) {"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt"
- line="65"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus#getRefreshStatus`"
errorLine1=" status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS"
errorLine2=" ~~~~~~~~~~~~~">
<location
@@ -421,39 +333,6 @@
<issue
id="NewApi"
- message="Call requires API level 34 (current min is 33): `getParentGroupId`"
- errorLine1=" String groupId = getParentGroupId(preferenceKey);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java"
- line="89"
- column="30"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 34 (current min is 33): `openRelevantSubpage`"
- errorLine1=" frag = openRelevantSubpage(groupId);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java"
- line="86"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 34 (current min is 33): `openRelevantSubpage`"
- errorLine1=" frag = openRelevantSubpage(groupId);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java"
- line="90"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
message="Class requires API level 31 (current min is 30): `android.apphibernation.AppHibernationManager`"
errorLine1=" userContext.getSystemService(APP_HIBERNATION_SERVICE) as AppHibernationManager"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
@@ -498,68 +377,93 @@
<issue
id="NewApi"
- message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`"
- errorLine1=" AppOpsManager.OnOpNotedListener,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Field requires API level 33 (current min is 30): `getTAG`"
+ errorLine1=" MoreIssuesCardPreference.TAG,"
+ errorLine2=" ~~~">
+ <location
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardAnimator.kt"
+ line="107"
+ column="46"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`">
+ <location
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
+ line="153"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`">
+ <location
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
+ line="188"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`">
+ <location
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
+ line="152"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`">
+ <location
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt"
+ line="187"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOpsRequest.Builder#setHistoryFlags`">
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt"
- line="45"
- column="5"/>
+ line="104"/>
</issue>
<issue
id="NewApi"
- message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`"
- errorLine1=" AppOpsManager.OnOpNotedListener,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 34 (current min is 33): `getParentGroupId`">
<location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt"
- line="38"
- column="5"/>
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java"
+ line="91"/>
</issue>
<issue
id="NewApi"
- message="Field requires API level 33 (current min is 30): `getTAG`"
- errorLine1=" MoreIssuesCardPreference.TAG,"
- errorLine2=" ~~~">
+ message="Call requires API level 34 (current min is 33): `openRelevantSubpage`">
<location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardAnimator.kt"
- line="107"
- column="46"/>
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java"
+ line="88"/>
</issue>
<issue
id="NewApi"
- message="Method reference requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus::severityLevel`"
- errorLine1=" val severityLevel: Int by status::severityLevel"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 34 (current min is 33): `openRelevantSubpage`">
<location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt"
- line="30"
- column="31"/>
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java"
+ line="92"/>
</issue>
<issue
id="NewApi"
- message="Method reference requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus::summary`"
- errorLine1=" val originalSummary: CharSequence by status::summary"
- errorLine2=" ~~~~~~~~~~~~~~~">
+ message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`">
<location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt"
- line="29"
- column="42"/>
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt"
+ line="46"/>
</issue>
<issue
id="NewApi"
- message="Method reference requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus::title`"
- errorLine1=" val title: CharSequence by status::title"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`">
<location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt"
- line="28"
- column="32"/>
+ file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt"
+ line="43"/>
</issue>
</issues> \ No newline at end of file
diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml
index a859b3ce9..a03b71e9d 100644
--- a/PermissionController/res/values/styles.xml
+++ b/PermissionController/res/values/styles.xml
@@ -51,7 +51,7 @@
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:orientation">vertical</item>
- <item name="android:paddingTop">18dp</item>
+ <item name="android:paddingTop">24dp</item>
<item name="android:paddingBottom">24dp</item>
<item name="android:paddingLeft">24dp</item>
<item name="android:paddingRight">24dp</item>
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 2f8f5a291..ca664475b 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -1101,7 +1101,6 @@
-->
<role
name="android.app.role.SYSTEM_UI"
- behavior="SystemUiRoleBehavior"
defaultHolders="config_systemUi"
exclusive="true"
minSdkVersion="31"
@@ -1610,4 +1609,29 @@
</service>
</required-components>
</role>
+
+ <role
+ name="android.app.role.RETAIL_DEMO"
+ behavior="RetailDemoRoleBehavior"
+ defaultHolders="config_defaultRetailDemo"
+ exclusive="true"
+ minSdkVersion="35"
+ static="true"
+ visible="false">
+ <permissions>
+ <permission name="android.permission.ACCESS_BLOBS_ACROSS_USERS" />
+ <permission name="android.permission.CHANGE_CONFIGURATION" />
+ <permission name="android.permission.MODIFY_DAY_NIGHT_MODE" />
+ <permission name="android.permission.MODIFY_PHONE_STATE" />
+ <permission name="android.permission.OBSERVE_APP_USAGE" />
+ <permission name="android.permission.QUERY_USERS" />
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+ <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
+ <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
+ <permission name="android.permission.WRITE_SETTINGS" />
+ </permissions>
+ <app-op-permissions>
+ <app-op-permission name="android.permission.PACKAGE_USAGE_STATS" />
+ </app-op-permissions>
+ </role>
</roles>
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
index 12710cfd3..eb1ceb3fa 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -54,10 +55,6 @@ public class AssistantRoleBehavior implements RoleBehavior {
private static final String LOG_TAG = AssistantRoleBehavior.class.getSimpleName();
- private static final Intent ASSIST_SERVICE_PROBE =
- new Intent(VoiceInteractionService.SERVICE_INTERFACE);
- private static final Intent ASSIST_ACTIVITY_PROBE = new Intent(Intent.ACTION_ASSIST);
-
@Override
public void onRoleAdded(@NonNull Role role, @NonNull Context context) {
PackageManager packageManager = context.getPackageManager();
@@ -82,81 +79,72 @@ public class AssistantRoleBehavior implements RoleBehavior {
@Override
public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
+ return getQualifyingPackagesInternal(null, user, context);
+ }
+
+ @Nullable
+ @Override
+ public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ return !getQualifyingPackagesInternal(packageName, Process.myUserHandle(), context)
+ .isEmpty();
+ }
+
+ @NonNull
+ private List<String> getQualifyingPackagesInternal(@Nullable String filterPackageName,
+ @NonNull UserHandle user, @NonNull Context context) {
Context userContext = UserUtils.getUserContext(context, user);
ActivityManager userActivityManager = userContext.getSystemService(ActivityManager.class);
PackageManager userPackageManager = userContext.getPackageManager();
- Set<String> availableAssistants = new ArraySet<>();
+ Set<String> packageNames = new ArraySet<>();
if (!userActivityManager.isLowRamDevice()) {
- List<ResolveInfo> services = userPackageManager.queryIntentServices(
- ASSIST_SERVICE_PROBE, PackageManager.GET_META_DATA
+ Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ if (filterPackageName != null) {
+ serviceIntent.setPackage(filterPackageName);
+ }
+ List<ResolveInfo> serviceResolveInfos = userPackageManager.queryIntentServices(
+ serviceIntent, PackageManager.GET_META_DATA
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- int numServices = services.size();
- for (int i = 0; i < numServices; i++) {
- ResolveInfo service = services.get(i);
-
- if (isAssistantVoiceInteractionService(userPackageManager, service.serviceInfo)) {
- availableAssistants.add(service.serviceInfo.packageName);
+ int serviceResolveInfosSize = serviceResolveInfos.size();
+ for (int i = 0; i < serviceResolveInfosSize; i++) {
+ ResolveInfo serviceResolveInfo = serviceResolveInfos.get(i);
+
+ ServiceInfo serviceInfo = serviceResolveInfo.serviceInfo;
+ if (!isAssistantVoiceInteractionService(userPackageManager, serviceInfo)) {
+ if (filterPackageName != null) {
+ Log.w(LOG_TAG, "Package " + filterPackageName
+ + " has an unqualified voice interaction service");
+ }
+ continue;
}
+
+ packageNames.add(serviceInfo.packageName);
}
}
- List<ResolveInfo> activities = userPackageManager.queryIntentActivities(
- ASSIST_ACTIVITY_PROBE, PackageManager.MATCH_DEFAULT_ONLY
+ Intent activityIntent = new Intent(Intent.ACTION_ASSIST);
+ if (filterPackageName != null) {
+ activityIntent.setPackage(filterPackageName);
+ }
+ List<ResolveInfo> activityResolveInfos = userPackageManager.queryIntentActivities(
+ activityIntent, PackageManager.MATCH_DEFAULT_ONLY
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- int numActivities = activities.size();
- for (int i = 0; i < numActivities; i++) {
- availableAssistants.add(activities.get(i).activityInfo.packageName);
- }
-
- return new ArrayList<>(availableAssistants);
- }
+ int activityResolveInfosSize = activityResolveInfos.size();
+ for (int i = 0; i < activityResolveInfosSize; i++) {
+ ResolveInfo activityResolveInfo = activityResolveInfos.get(i);
- @Nullable
- @Override
- public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName,
- @NonNull Context context) {
- ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- PackageManager packageManager = context.getPackageManager();
-
- boolean hasAssistantService = false;
- if (!activityManager.isLowRamDevice()) {
- Intent pkgServiceProbe = new Intent(ASSIST_SERVICE_PROBE).setPackage(packageName);
- List<ResolveInfo> services = packageManager.queryIntentServices(pkgServiceProbe,
- PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- hasAssistantService = !services.isEmpty();
- int numServices = services.size();
- for (int i = 0; i < numServices; i++) {
- ResolveInfo service = services.get(i);
-
- if (isAssistantVoiceInteractionService(packageManager, service.serviceInfo)) {
- return true;
- }
+ ActivityInfo activityInfo = activityResolveInfo.activityInfo;
+ if (!activityInfo.exported) {
+ continue;
}
- }
- Intent pkgActivityProbe = new Intent(ASSIST_ACTIVITY_PROBE).setPackage(packageName);
- boolean hasAssistantActivity = !packageManager.queryIntentActivities(pkgActivityProbe,
- PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE).isEmpty();
- if (!hasAssistantActivity) {
- Log.w(LOG_TAG, "Package " + packageName + " not qualified for " + role.getName()
- + " due to " + (hasAssistantService ? "unqualified" : "missing")
- + " service and missing activity");
+ packageNames.add(activityInfo.packageName);
}
- return hasAssistantActivity;
- }
-
- @Override
- public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
- }
-
- @Override
- public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
+ return new ArrayList<>(packageNames);
}
private boolean isAssistantVoiceInteractionService(@NonNull PackageManager pm,
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
index 3254bc6e4..0bcaa99cf 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
@@ -22,12 +22,16 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
+import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.role.controller.model.AppOpPermissions;
import com.android.role.controller.model.Permissions;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.RoleBehavior;
@@ -51,6 +55,14 @@ public class HomeRoleBehavior implements RoleBehavior {
android.Manifest.permission.WRITE_CALL_LOG,
android.Manifest.permission.READ_CONTACTS);
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private static final List<String> WEAR_PERMISSIONS = Arrays.asList(
+ android.Manifest.permission.POST_NOTIFICATIONS,
+ android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+ private static final List<String> WEAR_APP_OP_PERMISSIONS = Arrays.asList(
+ android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+
@Override
public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
@@ -97,11 +109,12 @@ public class HomeRoleBehavior implements RoleBehavior {
public static boolean isSettingsApplication(@NonNull ApplicationInfo applicationInfo,
@NonNull Context context) {
PackageManager packageManager = context.getPackageManager();
- ResolveInfo resolveInfo = packageManager.resolveActivity(new Intent(
- Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY
+ ResolveInfo resolveInfo = packageManager.resolveActivity(
+ new Intent(Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- if (resolveInfo == null || resolveInfo.activityInfo == null) {
+ if (resolveInfo == null || resolveInfo.activityInfo == null
+ || !resolveInfo.activityInfo.exported) {
return false;
}
return Objects.equals(applicationInfo.packageName, resolveInfo.activityInfo.packageName);
@@ -131,6 +144,16 @@ public class HomeRoleBehavior implements RoleBehavior {
Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES),
true, false, true, false, false, context);
}
+
+ if (SdkLevel.isAtLeastT()) {
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ Permissions.grant(packageName, WEAR_PERMISSIONS,
+ true, false, true, false, false, context);
+ for (String permission : WEAR_APP_OP_PERMISSIONS) {
+ AppOpPermissions.grant(packageName, permission, true, context);
+ }
+ }
+ }
}
@Override
@@ -145,6 +168,15 @@ public class HomeRoleBehavior implements RoleBehavior {
Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES),
true, false, false, context);
}
+
+ if (SdkLevel.isAtLeastT()) {
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ Permissions.revoke(packageName, WEAR_PERMISSIONS, true, false, false, context);
+ for (String permission : WEAR_APP_OP_PERMISSIONS) {
+ AppOpPermissions.revoke(packageName, permission, context);
+ }
+ }
+ }
}
/**
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java
new file mode 100644
index 000000000..831714843
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java
@@ -0,0 +1,63 @@
+/*
+ * 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.behavior;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
+import java.util.Arrays;
+
+/**
+ * Class for behavior of the Retail Demo role.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class RetailDemoRoleBehavior implements RoleBehavior {
+
+ @Override
+ public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ if (Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVICE_DEMO_MODE, 0) == 0) {
+ return false;
+ }
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ if (!(devicePolicyManager.isDeviceOwnerApp(packageName)
+ || devicePolicyManager.isProfileOwnerApp(packageName))) {
+ return false;
+ }
+ // Fallback to do additional default check.
+ return null;
+ }
+
+ @Override
+ public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ return userManager.isSystemUser() || userManager.isMainUser() || userManager.isDemoUser();
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java
deleted file mode 100644
index 210c2313f..000000000
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.role.controller.behavior;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.modules.utils.build.SdkLevel;
-import com.android.role.controller.model.AppOpPermissions;
-import com.android.role.controller.model.Role;
-import com.android.role.controller.model.RoleBehavior;
-
-import java.util.Arrays;
-import java.util.List;
-
-/** The role behavior for system ui. */
-public class SystemUiRoleBehavior implements RoleBehavior {
-
- private static final List<String> WEAR_APP_OP_PERMISSIONS =
- Arrays.asList(android.Manifest.permission.SYSTEM_ALERT_WINDOW);
-
- @Override
- public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
- if (SdkLevel.isAtLeastT()) {
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- for (String permission : WEAR_APP_OP_PERMISSIONS) {
- AppOpPermissions.grant(packageName, permission, true, context);
- }
- }
- }
- }
-
- @Override
- public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
- if (SdkLevel.isAtLeastT()) {
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- for (String permission : WEAR_APP_OP_PERMISSIONS) {
- AppOpPermissions.revoke(packageName, permission, context);
- }
- }
- }
- }
-}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
index 0c4a14574..2d02d4204 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
@@ -60,9 +60,9 @@ public class Permission {
* @return whether this permission is available
*/
public boolean isAvailable() {
- // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
- if (mMinSdkVersion >= 34) {
- return SdkLevel.isAtLeastU();
+ // Workaround to match the value 35+ for V+ in roles.xml before SDK finalization.
+ if (mMinSdkVersion >= 35) {
+ return SdkLevel.isAtLeastV();
} else {
return Build.VERSION.SDK_INT >= mMinSdkVersion;
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java
index 58c878e56..25c97aefb 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java
@@ -16,12 +16,11 @@
package com.android.role.controller.model;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.Bundle;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -52,11 +51,15 @@ public class RequiredActivity extends RequiredComponent {
return userPackageManager.queryIntentActivities(intent, flags);
}
+ @Override
+ protected boolean isComponentQualified(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.activityInfo.exported;
+ }
+
@NonNull
@Override
- protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) {
- return new ComponentName(resolveInfo.activityInfo.packageName,
- resolveInfo.activityInfo.name);
+ protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.activityInfo;
}
@Override
@@ -69,10 +72,4 @@ public class RequiredActivity extends RequiredComponent {
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
return resolveInfo.activityInfo.permission;
}
-
- @Nullable
- @Override
- protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) {
- return resolveInfo.activityInfo.metaData;
- }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java
index 945fda3c3..1e23af256 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java
@@ -16,12 +16,11 @@
package com.android.role.controller.model;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.Bundle;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -53,9 +52,8 @@ public class RequiredBroadcastReceiver extends RequiredComponent {
@NonNull
@Override
- protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) {
- return new ComponentName(resolveInfo.activityInfo.packageName,
- resolveInfo.activityInfo.name);
+ protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.activityInfo;
}
@Override
@@ -68,10 +66,4 @@ public class RequiredBroadcastReceiver extends RequiredComponent {
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
return resolveInfo.activityInfo.permission;
}
-
- @Nullable
- @Override
- protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) {
- return resolveInfo.activityInfo.metaData;
- }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java
index ae6156e7f..f1ef50209 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
@@ -108,9 +109,9 @@ public abstract class RequiredComponent {
* @return whether this required component is available
*/
public boolean isAvailable() {
- // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
- if (mMinTargetSdkVersion >= 34) {
- return SdkLevel.isAtLeastU();
+ // Workaround to match the value 35+ for V+ in roles.xml before SDK finalization.
+ if (mMinTargetSdkVersion >= 35) {
+ return SdkLevel.isAtLeastV();
} else {
return Build.VERSION.SDK_INT >= mMinTargetSdkVersion;
}
@@ -195,6 +196,10 @@ public abstract class RequiredComponent {
for (int resolveInfosIndex = 0; resolveInfosIndex < resolveInfosSize; resolveInfosIndex++) {
ResolveInfo resolveInfo = resolveInfos.get(resolveInfosIndex);
+ if (!isComponentQualified(resolveInfo)) {
+ continue;
+ }
+
if (mFlags != 0) {
int componentFlags = getComponentFlags(resolveInfo);
if ((componentFlags & mFlags) != mFlags) {
@@ -209,8 +214,9 @@ public abstract class RequiredComponent {
}
}
+ ComponentInfo componentInfo = getComponentComponentInfo(resolveInfo);
if (hasMetaData) {
- Bundle componentMetaData = getComponentMetaData(resolveInfo);
+ Bundle componentMetaData = componentInfo.metaData;
if (componentMetaData == null) {
componentMetaData = Bundle.EMPTY;
}
@@ -229,13 +235,14 @@ public abstract class RequiredComponent {
}
}
- ComponentName componentName = getComponentComponentName(resolveInfo);
- String componentPackageName = componentName.getPackageName();
+ String componentPackageName = componentInfo.packageName;
if (componentPackageNames.contains(componentPackageName)) {
continue;
}
-
componentPackageNames.add(componentPackageName);
+
+ ComponentName componentName = new ComponentName(componentPackageName,
+ componentInfo.name);
componentNames.add(componentName);
}
return componentNames;
@@ -256,15 +263,19 @@ public abstract class RequiredComponent {
protected abstract List<ResolveInfo> queryIntentComponentsAsUser(@NonNull Intent intent,
int flags, @NonNull UserHandle user, @NonNull Context context);
+ protected boolean isComponentQualified(@NonNull ResolveInfo resolveInfo) {
+ return true;
+ }
+
/**
- * Get the {@code ComponentName} of a component.
+ * Get the {@code ComponentInfo} of a component.
*
* @param resolveInfo the {@code ResolveInfo} of the component
*
- * @return the {@code ComponentName} of the component
+ * @return the {@code ComponentInfo} of the component
*/
@NonNull
- protected abstract ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo);
+ protected abstract ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo);
/**
* Get the flags that have been set on a component.
@@ -285,16 +296,6 @@ public abstract class RequiredComponent {
@Nullable
protected abstract String getComponentPermission(@NonNull ResolveInfo resolveInfo);
- /**
- * Get the meta data associated with a component.
- *
- * @param resolveInfo the {@code ResolveInfo} of the component
- *
- * @return the meta data associated with a component
- */
- @Nullable
- protected abstract Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo);
-
@Override
public String toString() {
return "RequiredComponent{"
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java
index 7b53a25bb..b02062b11 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java
@@ -16,12 +16,11 @@
package com.android.role.controller.model;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.Bundle;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -53,9 +52,8 @@ public class RequiredContentProvider extends RequiredComponent {
@NonNull
@Override
- protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) {
- return new ComponentName(resolveInfo.providerInfo.packageName,
- resolveInfo.providerInfo.name);
+ protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.providerInfo;
}
@Override
@@ -70,10 +68,4 @@ public class RequiredContentProvider extends RequiredComponent {
//return resolveInfo.providerInfo.readPermission;
throw new UnsupportedOperationException();
}
-
- @Nullable
- @Override
- protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) {
- return resolveInfo.providerInfo.metaData;
- }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java
index f27aae013..0532e53b2 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java
@@ -16,12 +16,11 @@
package com.android.role.controller.model;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.os.Bundle;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -53,8 +52,8 @@ public class RequiredService extends RequiredComponent {
@NonNull
@Override
- protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) {
- return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+ protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.serviceInfo;
}
@Override
@@ -67,10 +66,4 @@ public class RequiredService extends RequiredComponent {
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
return resolveInfo.serviceInfo.permission;
}
-
- @Nullable
- @Override
- protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) {
- return resolveInfo.serviceInfo.metaData;
- }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
index aa6cba169..1d01fde58 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
@@ -392,9 +392,9 @@ public class Role {
* @return whether this role is available based on SDK version
*/
boolean isAvailableBySdkVersion() {
- // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
- if (mMinSdkVersion >= 34) {
- return SdkLevel.isAtLeastU();
+ // Workaround to match the value 35+ for V+ in roles.xml before SDK finalization.
+ if (mMinSdkVersion >= 35) {
+ return SdkLevel.isAtLeastV();
} else {
return Build.VERSION.SDK_INT >= mMinSdkVersion
&& Build.VERSION.SDK_INT <= mMaxSdkVersion;
diff --git a/PermissionController/src/com/android/permissioncontroller/Constants.java b/PermissionController/src/com/android/permissioncontroller/Constants.java
index 58b62dc54..a063fb607 100644
--- a/PermissionController/src/com/android/permissioncontroller/Constants.java
+++ b/PermissionController/src/com/android/permissioncontroller/Constants.java
@@ -320,6 +320,17 @@ public class Constants {
*/
public static final String UNUSED_APPS_SAFETY_CENTER_SEE_UNUSED_APPS_ID = "see_unused_apps";
+ /**
+ * Fallback Settings package name
+ */
+ public static final String SETTINGS_PACKAGE_NAME_FALLBACK = "com.android.settings";
+
+ /**
+ * Extra launcher icon for notification
+ */
+ public static final String NOTIFICATION_EXTRA_USE_LAUNCHER_ICON =
+ "com.android.car.notification.EXTRA_USE_LAUNCHER_ICON";
+
// TODO(b/231624295) add to API
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO =
diff --git a/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java b/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java
index 3b0e89b04..08e1b3560 100644
--- a/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java
@@ -27,6 +27,9 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.R;
+import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.preference.PreferenceFragment;
import com.android.car.ui.toolbar.MenuItem;
import com.android.car.ui.toolbar.ToolbarController;
@@ -56,6 +59,20 @@ public abstract class AutoSettingsFrameFragment extends PreferenceFragment {
return rootView;
}
+ @Override
+ public void onCarUiInsetsChanged(Insets insets) {
+ // don't allow scrolling behind the toolbar to be consistent with the rest of Settings
+ // reference UI. Scrolling behind toolbar also leads to flakier tests due to UI being
+ // visible but clicks are intercepted and dropped by the toolbar.
+ FocusArea focusArea = getView().findViewById(R.id.car_ui_focus_area);
+ focusArea.setHighlightPadding(
+ /* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0);
+ focusArea.setBoundsOffset(/* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0);
+ getView().setPadding(
+ insets.getLeft(), insets.getTop(), insets.getRight(), insets.getBottom());
+ getCarUiRecyclerView().setPadding(
+ /* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0);
+ }
/** Sets the header text of this fragment. */
public void setHeaderLabel(CharSequence label) {
diff --git a/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt b/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt
index b62f8d721..5fd780806 100644
--- a/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt
@@ -24,16 +24,13 @@ import android.app.PendingIntent
import android.app.Service
import android.car.Car
import android.car.drivingstate.CarUxRestrictionsManager
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.pm.PackageManager
import android.os.Bundle
import android.os.IBinder
import android.os.Process
import android.os.UserHandle
import android.permission.PermissionManager
-import android.provider.Settings
import android.text.BidiFormatter
import androidx.annotation.VisibleForTesting
import com.android.permissioncontroller.Constants
@@ -72,7 +69,6 @@ class DrivingDecisionReminderService : Service() {
companion object {
private const val LOG_TAG = "DrivingDecisionReminderService"
- private const val SETTINGS_PACKAGE_NAME_FALLBACK = "com.android.settings"
const val EXTRA_PACKAGE_NAME = "package_name"
const val EXTRA_PERMISSION_GROUP = "permission_group"
@@ -270,15 +266,9 @@ class DrivingDecisionReminderService : Service() {
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT or
PendingIntent.FLAG_IMMUTABLE)
- val settingsDrawable = KotlinUtils.getBadgedPackageIcon(
- application,
- getSettingsPackageName(applicationContext.packageManager),
- permissionReminders.first().user)
- val settingsIcon = if (settingsDrawable != null) {
- KotlinUtils.convertToBitmap(settingsDrawable)
- } else {
- null
- }
+ val settingsIcon = KotlinUtils.getSettingsIcon(application,
+ permissionReminders.first().user,
+ applicationContext.packageManager)
val b = Notification.Builder(this, Constants.PERMISSION_REMINDER_CHANNEL_ID)
.setContentTitle(title)
@@ -289,7 +279,7 @@ class DrivingDecisionReminderService : Service() {
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.addExtras(Bundle().apply {
- putBoolean("com.android.car.notification.EXTRA_USE_LAUNCHER_ICON", false)
+ putBoolean(Constants.NOTIFICATION_EXTRA_USE_LAUNCHER_ICON, false)
})
// Auto doesn't show icons for actions
.addAction(Notification.Action.Builder(/* icon= */ null,
@@ -302,12 +292,6 @@ class DrivingDecisionReminderService : Service() {
return b.build()
}
- private fun getSettingsPackageName(pm: PackageManager): String {
- val settingsIntent = Intent(Settings.ACTION_SETTINGS)
- val settingsComponent: ComponentName? = settingsIntent.resolveActivity(pm)
- return settingsComponent?.packageName ?: SETTINGS_PACKAGE_NAME_FALLBACK
- }
-
private fun logNotificationPresented() {
PermissionControllerStatsLog.write(
PermissionControllerStatsLog.PERMISSION_REMINDER_NOTIFICATION_INTERACTED,
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
index 1f6b5272a..06f9aa7f0 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
@@ -80,6 +80,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.DeviceUtils
import com.android.permissioncontroller.DumpableLog
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.R
@@ -841,7 +842,9 @@ class HibernationJobService : JobService() {
appsToHibernate, this@HibernationJobService, sessionId)
val unusedApps: Set<Pair<String, UserHandle>> = hibernatedApps + revokedApps
if (unusedApps.isNotEmpty()) {
- showUnusedAppsNotification(unusedApps.size, sessionId)
+ showUnusedAppsNotification(unusedApps.size,
+ sessionId,
+ Process.myUserHandle())
if (SdkLevel.isAtLeastT() &&
revokedApps.isNotEmpty() &&
getSystemService(SafetyCenterManager::class.java)!!.isSafetyCenterEnabled) {
@@ -861,7 +864,11 @@ class HibernationJobService : JobService() {
return true
}
- private suspend fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) {
+ private suspend fun showUnusedAppsNotification(
+ numUnused: Int,
+ sessionId: Long,
+ user: UserHandle
+ ) {
val notificationManager = getSystemService(NotificationManager::class.java)!!
val permissionReminderChannel = NotificationChannel(
@@ -889,6 +896,13 @@ class HibernationJobService : JobService() {
.setAutoCancel(true)
.setContentIntent(makeUnusedAppsIntent(this, sessionId))
val extras = Bundle()
+ if (DeviceUtils.isAuto(this)) {
+ val settingsIcon = KotlinUtils.getSettingsIcon(application,
+ user,
+ applicationContext.packageManager)
+ extras.putBoolean(Constants.NOTIFICATION_EXTRA_USE_LAUNCHER_ICON, false)
+ b.setLargeIcon(settingsIcon)
+ }
if (SdkLevel.isAtLeastT() &&
getSystemService(SafetyCenterManager::class.java)!!.isSafetyCenterEnabled) {
val notificationResources = KotlinUtils.getSafetyCenterNotificationResources(this)
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING
index 16ee9b47d..69a8f74be 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING
@@ -5,6 +5,9 @@
"options": [
{
"include-filter": "android.hibernation.cts.AppHibernationIntegrationTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
index 52f0fd1a5..fa3b7ef78 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
@@ -30,7 +30,7 @@
"name": "CtsHibernationTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
@@ -65,12 +65,8 @@
{
"name": "CtsHibernationTestCases[com.google.android.permission.apex]",
"options": [
- // TODO(b/238677038): This test currently fails on R base image
- {
- "exclude-filter": "android.hibernation.cts.AutoRevokeTest#testUnusedApp_uninstallApp"
- },
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
@@ -78,6 +74,63 @@
"postsubmit": [
{
"name": "CtsHibernationTestCases"
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.LocationAccessCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.NotificationListenerCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PlatformPermissionGroupMappingTest"
+ }
+ ]
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsPermissionTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.LocationAccessCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.NotificationListenerCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PlatformPermissionGroupMappingTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsHibernationTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677038): This test currently fails on R base image
+ {
+ "exclude-filter": "android.hibernation.cts.AutoRevokeTest#testUnusedApp_uninstallApp"
+ }
+ ]
}
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java
deleted file mode 100644
index 637eb5fc4..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.permission.compat;
-
-import android.text.Layout;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.method.LinkMovementMethod;
-import android.text.method.Touch;
-import android.view.MotionEvent;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-
-/**
- * Fixes the issue that links can be triggered for touches outside of line bounds for
- * {@link LinkMovementMethod}.
- * <p>
- * This is based on the fix in ag/22301465.
- */
-public class LinkMovementMethodCompat extends LinkMovementMethod {
- @Override
- public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer,
- @NonNull MotionEvent event) {
- int action = event.getAction();
-
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- boolean isOutOfLineBounds;
- if (y < 0 || y > layout.getHeight()) {
- isOutOfLineBounds = true;
- } else {
- int line = layout.getLineForVertical(y);
- isOutOfLineBounds = x < layout.getLineLeft(line) || x > layout.getLineRight(line);
- }
-
- if (isOutOfLineBounds) {
- Selection.removeSelection(buffer);
-
- // return LinkMovementMethod.super.onTouchEvent(widget, buffer, event);
- return Touch.onTouchEvent(widget, buffer, event);
- }
- }
-
- return super.onTouchEvent(widget, buffer, event);
- }
-
- /**
- * @return a {@link LinkMovementMethodCompat} instance
- */
- @NonNull
- public static LinkMovementMethodCompat getInstance() {
- if (sInstance == null) {
- sInstance = new LinkMovementMethodCompat();
- }
-
- return sInstance;
- }
-
- private static LinkMovementMethodCompat sInstance;
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt
index 0f6b6c000..caa05d1fb 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt
@@ -61,14 +61,14 @@ data class LightPackageInfo(
pI.permissions?.map { perm -> LightPermInfo(perm) } ?: emptyList(),
pI.requestedPermissions?.toList() ?: emptyList(),
pI.requestedPermissionsFlags?.toList() ?: emptyList(),
- pI.applicationInfo.uid,
- pI.applicationInfo.targetSdkVersion,
- pI.applicationInfo.isInstantApp,
- pI.applicationInfo.enabled,
- pI.applicationInfo.flags,
+ pI.applicationInfo!!.uid,
+ pI.applicationInfo!!.targetSdkVersion,
+ pI.applicationInfo!!.isInstantApp,
+ pI.applicationInfo!!.enabled,
+ pI.applicationInfo!!.flags,
pI.firstInstallTime,
pI.lastUpdateTime,
- if (SdkLevel.isAtLeastS()) pI.applicationInfo.areAttributionsUserVisible() else false,
+ if (SdkLevel.isAtLeastS()) pI.applicationInfo!!.areAttributionsUserVisible() else false,
if (SdkLevel.isAtLeastS()) buildAttributionTagsToLabelsMap(pI.attributions) else emptyMap())
/** Permissions which are granted according to the [requestedPermissionsFlags] */
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt
index 52e89e972..2fbf3be26 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt
@@ -84,6 +84,9 @@ suspend fun revokeAppPermissions(
// For each autorevoke-eligible app...
userApps.forEachInParallel(Main) forEachInParallelOuter@ { pkg: LightPackageInfo ->
if (pkg.grantedPermissions.isEmpty()) {
+ if (DEBUG_AUTO_REVOKE) {
+ DumpableLog.i(LOG_TAG, "${pkg.packageName}: no granted permissions")
+ }
return@forEachInParallelOuter
}
val packageName = pkg.packageName
@@ -97,9 +100,20 @@ suspend fun revokeAppPermissions(
return@forEachInParallelOuter
}
val targetSdk = pkg.targetSdkVersion
- val pkgPermGroups: Map<String, List<String>> =
+ val pkgPermGroups: Map<String, List<String>>? =
PackagePermissionsLiveData[packageName, user]
- .getInitializedValue() ?: return@forEachInParallelOuter
+ .getInitializedValue()
+
+ if (pkgPermGroups == null || pkgPermGroups.isEmpty()) {
+ if (DEBUG_AUTO_REVOKE) {
+ DumpableLog.i(LOG_TAG, "$packageName: no permission groups found.")
+ }
+ return@forEachInParallelOuter
+ }
+
+ if (DEBUG_AUTO_REVOKE) {
+ DumpableLog.i(LOG_TAG, "$packageName: perm groups: ${pkgPermGroups.keys}.")
+ }
// Determine which permGroups are revocable
val revocableGroups = mutableSetOf<String>()
@@ -126,6 +140,10 @@ suspend fun revokeAppPermissions(
}
}
+ if (DEBUG_AUTO_REVOKE) {
+ DumpableLog.i(LOG_TAG, "$packageName: initial revocable groups: $revocableGroups")
+ }
+
// Mark any groups that split from an install-time permission as unrevocable
for (fromPerm in
pkgPermGroups[PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS] ?: emptyList()) {
@@ -149,6 +167,9 @@ suspend fun revokeAppPermissions(
}
}
+ if (DEBUG_AUTO_REVOKE) {
+ DumpableLog.i(LOG_TAG, "$packageName: final revocable groups: $revocableGroups")
+ }
// For each revocable group, revoke all of its permissions
val anyPermsRevoked = AtomicBoolean(false)
pkgPermGroups.entries
@@ -161,6 +182,9 @@ suspend fun revokeAppPermissions(
val revocablePermissions = group.permissions.keys.toList()
if (revocablePermissions.isEmpty()) {
+ if (DEBUG_AUTO_REVOKE) {
+ DumpableLog.i(LOG_TAG, "$packageName: revocable permissions empty")
+ }
return@forEachInParallelInner
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
index 57c828c20..937d92998 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
@@ -78,6 +78,7 @@ import static java.util.concurrent.TimeUnit.DAYS;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
+import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -126,6 +127,8 @@ import androidx.annotation.WorkerThread;
import androidx.core.util.Preconditions;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.Constants;
+import com.android.permissioncontroller.DeviceUtils;
import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.model.AppPermissionGroup;
@@ -435,7 +438,7 @@ public class LocationAccessCheck {
}
addLocationNotificationIfNeeded(mAppOpsManager.getPackagesForOps(
- new String[]{OPSTR_FINE_LOCATION}));
+ new String[]{OPSTR_FINE_LOCATION}), service.getApplication());
service.jobFinished(params, false);
} catch (Exception e) {
Log.e(LOG_TAG, "Could not check for location access", e);
@@ -449,7 +452,7 @@ public class LocationAccessCheck {
}
}
- private void addLocationNotificationIfNeeded(@NonNull List<PackageOps> ops)
+ private void addLocationNotificationIfNeeded(@NonNull List<PackageOps> ops, Application app)
throws InterruptedException {
synchronized (sLock) {
List<UserPackage> packages = getLocationUsersLocked(ops);
@@ -506,7 +509,7 @@ public class LocationAccessCheck {
}
}
createPermissionReminderChannel(getUserHandleForUid(pkgInfo.applicationInfo.uid));
- createNotificationForLocationUser(pkgInfo);
+ createNotificationForLocationUser(pkgInfo, app);
}
}
@@ -642,7 +645,7 @@ public class LocationAccessCheck {
*
* @param pkg The {@link PackageInfo} for the package to to be changed
*/
- private void createNotificationForLocationUser(@NonNull PackageInfo pkg) {
+ private void createNotificationForLocationUser(@NonNull PackageInfo pkg, Application app) {
CharSequence pkgLabel = mPackageManager.getApplicationLabel(pkg.applicationInfo);
boolean safetyCenterBgLocationReminderEnabled = isSafetyCenterBgLocationReminderEnabled();
@@ -710,12 +713,18 @@ public class LocationAccessCheck {
b.setLargeIcon(pkgIconBmp);
}
+ Bundle extras = new Bundle();
+ if (DeviceUtils.isAuto(mContext)) {
+ Bitmap settingsIcon = KotlinUtils.INSTANCE.getSettingsIcon(app, user, mPackageManager);
+ b.setLargeIcon(settingsIcon);
+ extras.putBoolean(Constants.NOTIFICATION_EXTRA_USE_LAUNCHER_ICON, false);
+ }
+
if (!TextUtils.isEmpty(appLabel)) {
- Bundle extras = new Bundle();
String appNameSubstitute = appLabel.toString();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appNameSubstitute);
- b.addExtras(extras);
}
+ b.addExtras(extras);
notificationManager.notify(pkgName, LOCATION_ACCESS_CHECK_NOTIFICATION_ID, b.build());
markAsNotified(pkgName, user, false);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING
index b8dd3d77a..b71a85187 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING
@@ -4,21 +4,24 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "include-filter": "android.permission.cts.PermissionControllerTest"
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
},
{
- "include-filter": "android.permission.cts.OneTimePermissionTest"
+ "include-filter": "android.permission.cts.PermissionControllerTest"
},
{
- "exclude-annotation": "androidx.test.filters.FlakyTest"
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
}
]
},
{
- "name": "CtsPermission3TestCases",
+ "name": "CtsPermissionUiTestCases",
"options": [
{
- "include-filter": "android.permission3.cts.SafetyLabelChangesJobServiceTest"
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "include-filter": "android.permissionui.cts.SafetyLabelChangesJobServiceTest"
}
]
},
@@ -26,6 +29,9 @@
"name": "CtsPermissionTestCases",
"options": [
{
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
"include-filter": "android.permission.cts.LocationAccessCheckTest"
}
],
@@ -69,10 +75,43 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "include-filter": "android.permission.cts.LocationAccessCheckTest"
+ "include-filter": "android.permission.cts.PermissionControllerTest"
},
{
- "exclude-annotation": "androidx.test.filters.FlakyTest"
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "include-filter": "android.permissionui.cts.SafetyLabelChangesJobServiceTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.LocationAccessCheckTest"
+ }
+ ],
+ "file_patterns": ["LocationAccessCheck\\.java"]
+ },
+ {
+ "name": "CtsBackupTestCases",
+ "options": [
+ {
+ "include-filter": "android.backup.cts.PermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "PermissionControllerOutOfProcessTests",
+ "options": [
+ {
+ "include-filter": "com.android.permissioncontroller.tests.outofprocess.DumpTest"
}
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java
index e9ed63b9a..f753a883d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java
@@ -51,6 +51,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import androidx.annotation.NonNull;
@@ -95,24 +96,23 @@ public class AutoGrantPermissionsNotifier {
*/
private final ArrayList<String> mGrantedPermissions = new ArrayList<>();
+ private final NotificationManager mNotificationManager;
+
public AutoGrantPermissionsNotifier(@NonNull Context context,
@NonNull PackageInfo packageInfo) {
mPackageInfo = packageInfo;
+ mNotificationManager = getSystemServiceSafe(context, NotificationManager.class);
UserHandle callingUser = getUserHandleForUid(mPackageInfo.applicationInfo.uid);
mContext = context.createContextAsUser(callingUser, 0);
}
/**
- * Create the channel to which the notification about auto-granted permission should be posted
- * to.
+ * Create the channel to which the notification about auto-granted permission should be posted.
*
* @param user The user for which the permission was auto-granted.
* @param shouldAlertUser
*/
private void createAutoGrantNotifierChannel(boolean shouldNotifySilently) {
- NotificationManager notificationManager = getSystemServiceSafe(mContext,
- NotificationManager.class);
-
NotificationChannel autoGrantedPermissionsChannel = new NotificationChannel(
getNotificationChannelId(shouldNotifySilently),
mContext.getString(R.string.auto_granted_permissions),
@@ -121,7 +121,7 @@ public class AutoGrantPermissionsNotifier {
autoGrantedPermissionsChannel.enableVibration(false);
autoGrantedPermissionsChannel.setSound(Uri.EMPTY, null);
}
- notificationManager.createNotificationChannel(autoGrantedPermissionsChannel);
+ mNotificationManager.createNotificationChannel(autoGrantedPermissionsChannel);
}
/**
@@ -160,12 +160,14 @@ public class AutoGrantPermissionsNotifier {
String messageText = Utils.getEnterpriseString(mContext, LOCATION_AUTO_GRANTED_MESSAGE,
R.string.auto_granted_permission_notification_body, pkgLabel);
Notification.Builder notificationBuilder = (new Notification.Builder(mContext,
- getNotificationChannelId(shouldNotifySilently))).setContentTitle(title)
+ getNotificationChannelId(shouldNotifySilently)))
+ .setContentTitle(title)
.setContentText(messageText)
.setStyle(new Notification.BigTextStyle().bigText(messageText).setBigContentTitle(
title))
.setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID)
// NOTE: Different icons would be needed for different permissions.
+ .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY)
.setSmallIcon(R.drawable.ic_pin_drop)
.setLargeIcon(pkgIconBmp)
.setColor(mContext.getColor(android.R.color.system_notification_accent_color))
@@ -180,29 +182,33 @@ public class AutoGrantPermissionsNotifier {
notificationBuilder.addExtras(extras);
}
- String summaryTitle = mContext.getString(R.string.auto_granted_permissions);
-
- Notification.Builder summaryNotificationBuilder = new Notification.Builder(mContext,
- getNotificationChannelId(shouldNotifySilently))
- .setContentTitle(summaryTitle)
- .setSmallIcon(R.drawable.ic_pin_drop)
- .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID)
- .setGroupSummary(true);
-
- NotificationManager notificationManager = getSystemServiceSafe(mContext,
- NotificationManager.class);
// Cancel previous notifications for the same package to avoid redundant notifications.
// This code currently only deals with location-related notifications, which would all lead
// to the same Settings activity for managing location permissions.
// If ever extended to cover multiple types of notifications, then only multiple
// notifications of the same group should be canceled.
- notificationManager.cancel(
+ mNotificationManager.cancel(
mPackageInfo.packageName, PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID);
- notificationManager.notify(mPackageInfo.packageName,
+
+ mNotificationManager.notify(mPackageInfo.packageName,
PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID,
notificationBuilder.build());
- notificationManager.notify(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID,
- summaryNotificationBuilder.build());
+
+ // only show the summary notification if it is not already showing. Otherwise, this
+ // breaks the alerting behaviour.
+ if (!isNotificationActive(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID)) {
+ String summaryTitle = mContext.getString(R.string.auto_granted_permissions);
+
+ Notification.Builder summaryNotificationBuilder = new Notification.Builder(mContext,
+ getNotificationChannelId(shouldNotifySilently))
+ .setContentTitle(summaryTitle)
+ .setSmallIcon(R.drawable.ic_pin_drop)
+ .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID)
+ .setGroupSummary(true);
+
+ mNotificationManager.notify(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID,
+ summaryNotificationBuilder.build());
+ }
}
/**
@@ -248,5 +254,14 @@ public class AutoGrantPermissionsNotifier {
return ADMIN_AUTO_GRANTED_PERMISSIONS_ALERTING_NOTIFICATION_CHANNEL_ID;
}
}
+
+ private boolean isNotificationActive(int notificationId) {
+ for (StatusBarNotification notification : mNotificationManager.getActiveNotifications()) {
+ if (notification.getId() == notificationId) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index c173146eb..78bd8f5ed 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -21,6 +21,7 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
@@ -190,6 +191,10 @@ public class GrantPermissionsActivity extends SettingsActivity
}
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ if (DeviceUtils.isWear(this)) {
+ // Do not grab input focus and hide keyboard.
+ getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
+ }
int permissionsSdkLevel;
if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) {
@@ -427,7 +432,7 @@ public class GrantPermissionsActivity extends SettingsActivity
return;
}
- CharSequence appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
+ String appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
mTargetPackage, Process.myUserHandle());
Icon icon = null;
@@ -641,7 +646,6 @@ public class GrantPermissionsActivity extends SettingsActivity
logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result);
- showNextRequest();
if (result == CANCELED) {
setResultAndFinish();
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index 81af33294..cd1431eb5 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -31,6 +31,7 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.PERM
import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__OPEN;
import android.Manifest;
+import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -137,11 +138,12 @@ public final class ManagePermissionsActivity extends SettingsActivity {
*/
private static final int PROXY_ACTIVITY_REQUEST_CODE = 5;
+ @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
private static final String LAUNCH_PERMISSION_SETTINGS =
- "android.permission.LAUNCH_PERMISSION_SETTINGS";
+ Manifest.permission.LAUNCH_PERMISSION_SETTINGS;
- private static final String APP_PERMISSIONS_SETTINGS =
- "android.settings.APP_PERMISSIONS_SETTINGS";
+ @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ private static final String APP_PERMISSIONS_SETTINGS = Settings.ACTION_APP_PERMISSIONS_SETTINGS;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -304,7 +306,8 @@ public final class ManagePermissionsActivity extends SettingsActivity {
case Intent.ACTION_MANAGE_APP_PERMISSIONS:
case APP_PERMISSIONS_SETTINGS: {
- if (Objects.equals(action, APP_PERMISSIONS_SETTINGS)) {
+ if (!SdkLevel.isAtLeastV()
+ && Objects.equals(action, APP_PERMISSIONS_SETTINGS)) {
PermissionInfo permissionInfo;
try {
permissionInfo = getPackageManager()
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING
index de4c3a2b9..865ee6706 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING
@@ -5,6 +5,9 @@
"options": [
{
"include-filter": "android.permission.cts.OneTimePermissionTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
@@ -24,7 +27,15 @@
],
"postsubmit": [
{
- "name": "CtsPermission3TestCases"
+ "name": "CtsPermissionUiTestCases"
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
+ }
+ ]
}
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
index 6b09921cb..5a93a8e76 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
@@ -102,7 +102,7 @@ public class GrantPermissionsAutoViewHandler implements GrantPermissionsViewHand
mGroupIcon = icon;
mGroupMessage = message;
mDetailMessage = detailMessage;
- mButtonVisibilities = buttonVisibilities;
+ setButtonVisibilities(buttonVisibilities);
update();
}
@@ -193,11 +193,19 @@ public class GrantPermissionsAutoViewHandler implements GrantPermissionsViewHand
mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT);
mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX);
mDetailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE);
- mButtonVisibilities = savedInstanceState.getBooleanArray(ARG_BUTTON_VISIBILITIES);
+ setButtonVisibilities(savedInstanceState.getBooleanArray(ARG_BUTTON_VISIBILITIES));
update();
}
+ private void setButtonVisibilities(boolean[] visibilities) {
+ // If GrantPermissionsActivity sent the user directly to settings, button visibilities are
+ // not created. If the activity was then destroyed by the system, once the activity is
+ // recreated to perform onActivityResult, it will try to loadInstanceState in onCreate but
+ // the button visibilities were never set, so they will be null.
+ mButtonVisibilities = visibilities == null ? new boolean[0] : visibilities;
+ }
+
@Override
public void onBackPressed() {
if (mDialog != null) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt
index 88b5ebe87..d74e745d4 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt
@@ -22,10 +22,10 @@ import android.text.style.ClickableSpan
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
+import androidx.core.text.method.LinkMovementMethodCompat
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat
/** A preference for a footer with an icon and a link. */
class AppDataSharingUpdatesFooterPreference : Preference {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
index 3998ca141..6267ebad6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
@@ -33,8 +33,8 @@ import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.RequiresApi
+import androidx.core.text.method.LinkMovementMethodCompat
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat
import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler
import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.Companion.CANCELLED
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
index 1b17041b6..31b7c0d7d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
@@ -96,8 +96,7 @@ class PermissionAppsViewModel(
val categorizedAppsLiveData = CategorizedAppsLiveData(groupName)
@get:RequiresApi(Build.VERSION_CODES.S)
- val sensorStatusLiveData: SensorStatusLiveData by lazy(LazyThreadSafetyMode.NONE)
- @RequiresApi(Build.VERSION_CODES.S) {
+ val sensorStatusLiveData: SensorStatusLiveData by lazy(LazyThreadSafetyMode.NONE) {
SensorStatusLiveData()
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java
index c31aa5d63..9965a6b38 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java
@@ -43,8 +43,8 @@ import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.AppPermissions;
import com.android.permissioncontroller.permission.utils.Utils;
-import java.util.List;
import java.util.ArrayList;
+import java.util.List;
public class ReviewPermissionsWearFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceChangeListener {
@@ -290,7 +290,7 @@ public class ReviewPermissionsWearFragment extends PreferenceFragmentCompat
titlePref.setIcon(icon);
// Set message
- String appLabel = mAppPermissions.getAppLabel().toString();
+ String appLabel = Html.escapeHtml(mAppPermissions.getAppLabel().toString());
final int labelTemplateResId = isPackageUpdated()
? R.string.permission_review_title_template_update
: R.string.permission_review_title_template_install;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index 49a35cadf..f79e83f18 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -74,6 +74,7 @@ import androidx.navigation.NavController
import androidx.preference.Preference
import androidx.preference.PreferenceGroup
import com.android.modules.utils.build.SdkLevel
+import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.DeviceUtils
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.R
@@ -568,6 +569,25 @@ object KotlinUtils {
}
/**
+ * Get the settings icon
+ *
+ * @param app The current application
+ * @param user The user for whom we want the icon
+ * @param pm The PackageManager
+ *
+ * @return Bitmap of the setting's icon, or null
+ */
+ fun getSettingsIcon(
+ app: Application,
+ user: UserHandle,
+ pm: PackageManager
+ ): Bitmap? {
+ val settingsPackageName = getPackageNameForIntent(pm,
+ Settings.ACTION_SETTINGS) ?: Constants.SETTINGS_PACKAGE_NAME_FALLBACK
+ return getBadgedPackageIconBitmap(app, user, settingsPackageName)
+ }
+
+ /**
* Gets a package's badged icon from the system.
*
* @param app The current application
@@ -586,6 +606,33 @@ object KotlinUtils {
}
/**
+ * Get the icon of a package
+ *
+ * @param application The current application
+ * @param user The user for whom we want the icon
+ * @param packageName The name of the package whose icon we want
+ *
+ * @return Bitmap of the package icon, or null
+ */
+ fun getBadgedPackageIconBitmap(
+ application: Application,
+ user: UserHandle,
+ packageName: String
+ ): Bitmap? {
+ val drawable = getBadgedPackageIcon(
+ application,
+ packageName,
+ user)
+
+ val icon = if (drawable != null) {
+ convertToBitmap(drawable)
+ } else {
+ null
+ }
+ return icon
+ }
+
+ /**
* Gets a package's badged label from the system.
*
* @param app The current application
@@ -618,6 +665,19 @@ object KotlinUtils {
}
/**
+ * Returns the name of the package that resolves the specified intent action
+ *
+ * @param pm The PackageManager
+ * @param intentAction The name of the intent action
+ *
+ * @return The package's name, or null
+ */
+ fun getPackageNameForIntent(pm: PackageManager, intentAction: String): String? {
+ val intent = Intent(intentAction)
+ return intent.resolveActivity(pm)?.packageName
+ }
+
+ /**
* Gets a package's uid, using a cached liveData value, if the liveData is currently being
* observed (and thus has an up-to-date value).
*
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index 6b3dc98ad..c9a05c087 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -700,11 +700,16 @@ public final class Utils {
* @param groupName The name of the permission group
* @param context A context to resolve resources
* @param requestRes The resource id of the grant request message
- *
* @return The formatted message to be used as title when granting permissions
*/
- public static CharSequence getRequestMessage(CharSequence appLabel, String packageName,
- String groupName, Context context, @StringRes int requestRes) {
+ @NonNull
+ public static CharSequence getRequestMessage(
+ @NonNull String appLabel,
+ @NonNull String packageName,
+ @NonNull String groupName,
+ @NonNull Context context,
+ @StringRes int requestRes) {
+ String escapedAppLabel = Html.escapeHtml(appLabel);
boolean isIsolatedStorage;
try {
@@ -714,15 +719,21 @@ public final class Utils {
}
if (groupName.equals(STORAGE) && isIsolatedStorage) {
return Html.fromHtml(
- String.format(context.getResources().getConfiguration().getLocales().get(0),
+ String.format(
+ context.getResources().getConfiguration().getLocales().get(0),
context.getString(R.string.permgrouprequest_storage_isolated),
- appLabel), 0);
+ escapedAppLabel),
+ 0);
} else if (requestRes != 0) {
- return Html.fromHtml(context.getResources().getString(requestRes, appLabel), 0);
+ return Html.fromHtml(context.getResources().getString(requestRes, escapedAppLabel), 0);
}
- return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel,
- loadGroupDescription(context, groupName, context.getPackageManager())), 0);
+ return Html.fromHtml(
+ context.getString(
+ R.string.permission_warning_template,
+ escapedAppLabel,
+ loadGroupDescription(context, groupName, context.getPackageManager())),
+ 0);
}
private static CharSequence loadGroupDescription(Context context, String groupName,
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt
index 91a043a6a..2027581d5 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt
@@ -504,8 +504,8 @@ internal class NotificationListenerCheckInternal(
sessionId: Long
) {
val pkgLabel =
- Utils.getApplicationLabel(parentUserContext, pkg.applicationInfo)
- val uid = pkg.applicationInfo.uid
+ Utils.getApplicationLabel(parentUserContext, pkg.applicationInfo!!)
+ val uid = pkg.applicationInfo!!.uid
val deletePendingIntent =
getNotificationDeletePendingIntent(parentUserContext, componentName, uid, sessionId)
@@ -697,9 +697,9 @@ internal class NotificationListenerCheckInternal(
}
return null
}
- val pkgLabel = Utils.getApplicationLabel(parentUserContext, pkgInfo.applicationInfo)
+ val pkgLabel = Utils.getApplicationLabel(parentUserContext, pkgInfo.applicationInfo!!)
val safetySourceIssueId = getSafetySourceIssueIdFromComponentName(componentName)
- val uid = pkgInfo.applicationInfo.uid
+ val uid = pkgInfo.applicationInfo!!.uid
val disableNlsPendingIntent =
getDisableNlsPendingIntent(
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING
index dc01ab3e2..d766ee080 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING
@@ -33,8 +33,27 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsPermissionUiTestCases"
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.NotificationListenerCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.AccessibilityPrivacySourceTest"
+ }
+ ]
+ }
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
index d7718a2f2..a95df2fd5 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsRoleTestCases",
"options": [
{
- "exclude-annotation": "androidx.test.filters.FlakyTest"
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
@@ -21,7 +21,26 @@
"exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
},
{
- "exclude-annotation": "androidx.test.filters.FlakyTest"
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsRoleTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
}
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
index 827d42643..eee30914d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
@@ -302,4 +302,11 @@ public class RequestRoleActivity extends FragmentActivity {
}
return applicationInfo.uid;
}
+
+ @Override
+ protected void onNewIntent(@NonNull Intent intent) {
+ super.onNewIntent(intent);
+
+ Log.w(LOG_TAG, "Ignoring new intent: " + intent);
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING
index b6e659353..23c1398da 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING
@@ -11,7 +11,7 @@
],
"presubmit-large": [
{
- "name": "CtsPermission3TestCases",
+ "name": "CtsPermissionUiTestCases",
"options": [
{
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
@@ -21,7 +21,30 @@
],
"mainline-presubmit": [
{
- "name": "CtsPermission3TestCases[com.google.android.permission.apex]"
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "PermissionControllerMockingTests",
+ "options": [
+ {
+ "include-filter": "com.android.permissioncontroller.tests.mocking.safetylabel"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermissionUiTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]"
}
]
} \ No newline at end of file
diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp
index 78c767f1d..7d55ff9cc 100644
--- a/PermissionController/tests/inprocess/Android.bp
+++ b/PermissionController/tests/inprocess/Android.bp
@@ -33,10 +33,7 @@ android_test {
target_sdk_version: "30",
min_sdk_version: "30",
- srcs: [
- "src/**/*.kt",
- "src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java",
- ],
+ srcs: ["src/**/*.kt"],
libs: [
"android.test.base",
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java
deleted file mode 100644
index b4b18dbbe..000000000
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.permission.compat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.SystemClock;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.Spanned;
-import android.text.method.MovementMethod;
-import android.text.style.ClickableSpan;
-import android.util.TypedValue;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-/**
- * Test for {@link LinkMovementMethodCompat} without using Mockito, which is unavailable for
- * in-process tests.
- *
- * @see android.text.method.cts.LinkMovementMethodTest
- */
-public class LinkMovementMethodCompatTest {
- private static final String CONTENT = "clickable\nunclickable\nclickable";
-
- private Activity mActivity;
- private LinkMovementMethodCompat mMethod;
- private TextView mView;
- private Spannable mSpannable;
- private MockClickableSpan mClickable0;
- private MockClickableSpan mClickable1;
-
- @Rule
- public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
-
- @Before
- public void setup() throws Throwable {
- mActivity = mActivityRule.getActivity();
- mMethod = new LinkMovementMethodCompat();
-
- // Set the content view with a text view which contains 3 lines,
- mActivityRule.runOnUiThread(() -> mView = new TextViewNoIme(mActivity));
- mView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
- mView.setText(CONTENT, TextView.BufferType.SPANNABLE);
-
- mActivityRule.runOnUiThread(() -> mActivity.setContentView(mView));
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
- mSpannable = (Spannable) mView.getText();
- // make first line clickable
- mClickable0 = markClickable(0, CONTENT.indexOf('\n'));
- // make last line clickable
- mClickable1 = markClickable(CONTENT.lastIndexOf('\n'), CONTENT.length());
- }
-
- @Test
- public void testConstructor() {
- new LinkMovementMethodCompat();
- }
-
- @Test
- public void testGetInstance() {
- MovementMethod method0 = LinkMovementMethodCompat.getInstance();
- assertTrue(method0 instanceof LinkMovementMethodCompat);
-
- MovementMethod method1 = LinkMovementMethodCompat.getInstance();
- assertNotNull(method1);
- assertSame(method0, method1);
- }
-
- @UiThreadTest
- @Test
- public void testOnTouchEvent() {
- assertSelection(mSpannable, -1);
-
- // press on first line (Clickable)
- assertTrue(pressOnLine(0));
- assertSelectClickableLeftToRight(mSpannable, mClickable0);
-
- // release on first line
- mClickable0.clearClickCount();
- assertTrue(releaseOnLine(0));
- mClickable0.assertClickCount(1);
-
- // press on second line (unclickable)
- assertSelectClickableLeftToRight(mSpannable, mClickable0);
- // just clear selection
- pressOnLine(1);
- assertSelection(mSpannable, -1);
-
- // press on last line (Clickable)
- assertTrue(pressOnLine(2));
- assertSelectClickableLeftToRight(mSpannable, mClickable1);
-
- // release on last line
- mClickable1.clearClickCount();
- assertTrue(releaseOnLine(2));
- mClickable1.assertClickCount(1);
-
- // release on second line (unclickable)
- assertSelectClickableLeftToRight(mSpannable, mClickable1);
- // just clear selection
- releaseOnLine(1);
- assertSelection(mSpannable, -1);
- }
-
- @UiThreadTest
- @Test
- public void testOnTouchEvent_outsideLineBounds() {
- assertSelection(mSpannable, -1);
-
- // press on first line (clickable)
- assertTrue(pressOnLine(0));
- assertSelectClickableLeftToRight(mSpannable, mClickable0);
-
- // release above first line
- mClickable0.clearClickCount();
- float x = (mView.getLayout().getLineLeft(0) + mView.getLayout().getLineRight(0)) / 2f;
- float y = -1f;
- assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP));
- mClickable0.assertClickCount(0);
-
- // press on first line (clickable)
- assertTrue(pressOnLine(0));
- assertSelectClickableLeftToRight(mSpannable, mClickable0);
-
- // release to left of first line
- mClickable0.clearClickCount();
- x = mView.getLayout().getLineLeft(0) - 1f;
- y = (mView.getLayout().getLineTop(0) + mView.getLayout().getLineBottom(0)) / 2f;
- assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP));
- mClickable0.assertClickCount(0);
-
- // press on first line (clickable)
- assertTrue(pressOnLine(0));
- assertSelectClickableLeftToRight(mSpannable, mClickable0);
-
- // release to right of first line
- mClickable0.clearClickCount();
- x = mView.getLayout().getLineRight(0) + 1f;
- y = (mView.getLayout().getLineTop(0) + mView.getLayout().getLineBottom(0)) / 2f;
- assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP));
- mClickable0.assertClickCount(0);
-
- // press on last line (clickable)
- assertTrue(pressOnLine(2));
- assertSelectClickableLeftToRight(mSpannable, mClickable1);
-
- // release below last line
- mClickable1.clearClickCount();
- x = (mView.getLayout().getLineLeft(0) + mView.getLayout().getLineRight(0)) / 2f;
- y = mView.getLayout().getHeight() + 1f;
- assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP));
- mClickable1.assertClickCount(0);
- }
-
- private MockClickableSpan markClickable(final int start, final int end) throws Throwable {
- final MockClickableSpan clickableSpan = new MockClickableSpan();
- mActivityRule.runOnUiThread(() -> mSpannable.setSpan(clickableSpan, start, end,
- Spanned.SPAN_MARK_MARK));
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- return clickableSpan;
- }
- private boolean performMotionAtPoint(float x, float y, int action) {
- long now = SystemClock.uptimeMillis();
- return mMethod.onTouchEvent(mView, mSpannable,
- MotionEvent.obtain(now, now, action, x, y, 0));
- }
-
- private boolean performMotionOnLine(int line, int action) {
- float x = (mView.getLayout().getLineLeft(line) + mView.getLayout().getLineRight(line)) / 2f;
- float y = (mView.getLayout().getLineTop(line) + mView.getLayout().getLineBottom(line)) / 2f;
- return performMotionAtPoint(x, y, action);
- }
-
- private boolean pressOnLine(int line) {
- return performMotionOnLine(line, MotionEvent.ACTION_DOWN);
- }
-
- private boolean releaseOnLine(int line) {
- return performMotionOnLine(line, MotionEvent.ACTION_UP);
- }
-
- private void assertSelection(Spannable spannable, int start, int end) {
- assertEquals(start, Selection.getSelectionStart(spannable));
- assertEquals(end, Selection.getSelectionEnd(spannable));
- }
-
- private void assertSelection(Spannable spannable, int position) {
- assertSelection(spannable, position, position);
- }
-
- private void assertSelectClickableLeftToRight(Spannable spannable,
- ClickableSpan clickableSpan) {
- assertSelection(spannable, spannable.getSpanStart(clickableSpan),
- spannable.getSpanEnd(clickableSpan));
- }
-
- public static class TextViewNoIme extends TextView {
- public TextViewNoIme(@NonNull Context context) {
- super(context);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- return null;
- }
- }
-
- public static class MockClickableSpan extends ClickableSpan {
- private int mClickCount = 0;
-
- @Override
- public void onClick(@NonNull View widget) {
- ++mClickCount;
- }
-
- public void assertClickCount(int expectedClickCount) {
- assertEquals(expectedClickCount, mClickCount);
- }
-
- public void clearClickCount() {
- mClickCount = 0;
- }
- }
-}
diff --git a/PermissionController/tests/mocking/Android.bp b/PermissionController/tests/mocking/Android.bp
index 64903d615..0c7c3e675 100644
--- a/PermissionController/tests/mocking/Android.bp
+++ b/PermissionController/tests/mocking/Android.bp
@@ -139,5 +139,5 @@ android_test {
"mts-permission",
],
- kotlincflags: ["-Xjvm-default=enable"],
+ kotlincflags: ["-Xjvm-default=all"],
}
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 86aab9b60..694986312 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
@@ -61,6 +61,7 @@ import org.junit.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.AdditionalMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
@@ -605,6 +606,14 @@ class RuntimePermissionsUpgradeControllerTest {
verifyNotGranted(TEST_PKG_NAME, READ_MEDIA_VISUAL_USER_SELECTED)
}
+ @Test
+ fun ensureDatabaseResetToLatestIfAboveLatest() {
+ setInitialDatabaseVersion(Int.MAX_VALUE)
+ upgradeIfNeeded()
+ verify(permissionManager).runtimePermissionsVersion =
+ AdditionalMatchers.not(eq(Int.MAX_VALUE))
+ }
+
@After
fun resetSystem() {
// Send low memory notifications for all data repositories which will clear cached data
diff --git a/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt b/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt
index 4963a4683..8d82967e2 100644
--- a/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt
+++ b/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt
@@ -20,11 +20,14 @@ import android.os.ParcelFileDescriptor.AutoCloseInputStream
import android.os.UserHandle.myUserId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto
import com.google.common.truth.Truth.assertThat
import com.google.protobuf.InvalidProtocolBufferException
import org.junit.Assert.fail
+import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.nio.charset.StandardCharsets.UTF_8
@@ -48,6 +51,12 @@ class DumpTest {
}
}
+ @Before
+ fun setUp() {
+ // We no longer dump auto revoke data since T.
+ assumeFalse(SdkLevel.isAtLeastT())
+ }
+
@Test
fun autoRevokeDumpHasCurrentUser() {
val dump = getDump()
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt
index a5453d9e7..1b29c3fc6 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt
@@ -83,7 +83,6 @@ abstract class PermissionAppsFragmentTest(
}
}
- // TODO(b/280652042) Slow tests aren't good
@Test(timeout = 120000)
fun appDisappearsWhenUninstalled() {
assertNull(waitFindObjectOrNull(By.text(userPkg)))
diff --git a/TEST_MAPPING b/TEST_MAPPING
index aa594e81e..d2c722c63 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -6,7 +6,58 @@
],
"carpermission-presubmit" : [
{
- "name" : "CtsPermission3TestCases"
+ "name" : "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "alltests" : [
+ {
+ "name" : "PermissionControllerMockingTests"
+ },
+ {
+ "name" : "CtsPermissionTestCases"
+ },
+ {
+ "name" : "CtsPermissionUiTestCases"
+ },
+ {
+ "name" : "CtsPermissionPolicyTestCases"
+ },
+ {
+ "name" : "CtsAppOpsTestCases"
+ },
+ {
+ "name" : "CtsAppOps2TestCases"
+ },
+ {
+ "name": "PermissionControllerOutOfProcessTests"
+ },
+ {
+ "name" : "CtsRoleTestCases"
+ },
+ {
+ "name" : "CtsPermissionMultiUserTestCases"
+ },
+ {
+ "name": "CtsBackupTestCases",
+ "options": [
+ {
+ "include-filter": "android.backup.cts.PermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsDevicePolicyManagerTestCases",
+ "options": [
+ {
+ "include-annotation": "com.android.cts.devicepolicy.annotations.PermissionsTest"
+ }
+ ]
}
]
+ // TODO: Do we need to create a carpermission-postsubmit test-group?
}
diff --git a/flags/Android.bp b/flags/Android.bp
new file mode 100644
index 000000000..4f0241f91
--- /dev/null
+++ b/flags/Android.bp
@@ -0,0 +1,65 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+ name: "permissions-aconfig-flags",
+ package: "com.android.permission.flags",
+ srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "permissions-aconfig-flags-lib",
+ aconfig_declarations: "permissions-aconfig-flags",
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ visibility: [
+ "//packages/modules/Permission:__subpackages__",
+ ],
+}
+
+java_library {
+ name: "permissions-flags-lib",
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ target_sdk_version: "34",
+ srcs: [
+ "java/**/*.java",
+ ],
+ static_libs: [
+ "permissions-aconfig-flags-lib",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ visibility: [
+ "//packages/modules/Permission:__subpackages__",
+ ],
+} \ No newline at end of file
diff --git a/flags/flags.aconfig b/flags/flags.aconfig
new file mode 100644
index 000000000..6a8cd5307
--- /dev/null
+++ b/flags/flags.aconfig
@@ -0,0 +1,7 @@
+package: "com.android.permission.flags"
+
+flag {
+ name: "voice_activation_op_enabled"
+ namespace: "permissions"
+ description: "This flag is used to support hotword activation events in privacy dashboard"
+}
diff --git a/flags/java/com/android/permission/flags/PermissionsFlags.java b/flags/java/com/android/permission/flags/PermissionsFlags.java
new file mode 100644
index 000000000..afab3fae5
--- /dev/null
+++ b/flags/java/com/android/permission/flags/PermissionsFlags.java
@@ -0,0 +1,20 @@
+/*
+ * 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.permission.flags;
+
+/** Class used for flags that do not work with aconfig tooling */
+public final class PermissionsFlags {}
diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java
index de697f801..6b58da2d0 100644
--- a/framework-s/java/android/app/role/RoleManager.java
+++ b/framework-s/java/android/app/role/RoleManager.java
@@ -475,10 +475,10 @@ public final class RoleManager {
}
/**
- * Get package names of the applications holding the role for a default application.
+ * Get package name of the application holding the role for a default application.
* <p>
- * <strong>Note:</strong> Using this API requires holding
- * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}.
+ * Only roles describing default applications can be used with this method. They can have
+ * at most one holder.
*
* @param roleName the name of the default application role to get
*
@@ -506,8 +506,8 @@ public final class RoleManager {
/**
* Set a specific application as the default application.
* <p>
- * <strong>Note:</strong> Using this API requires holding
- * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}.
+ * Only roles describing default applications can be used with this method. They can have
+ * at most one holder.
*
* @param roleName the name of the default application role to set the role holder for
* @param packageName the package name of the application to set as the default application,
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
index 485be4e72..f18a9e79e 100644
--- a/service/java/com/android/role/RoleService.java
+++ b/service/java/com/android/role/RoleService.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
import android.app.role.IOnRoleHoldersChangedListener;
import android.app.role.IRoleManager;
import android.app.role.RoleControllerManager;
@@ -33,6 +34,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -41,6 +44,8 @@ import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -55,6 +60,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permission.compat.UserHandleCompat;
import com.android.permission.util.ArrayUtils;
import com.android.permission.util.CollectionUtils;
@@ -181,13 +187,14 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void onStart() {
publishBinderService(Context.ROLE_SERVICE, new Stub());
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- getContext().registerReceiverForAllUsers(new BroadcastReceiver() {
+ Context context = getContext();
+ IntentFilter packageIntentFilter = new IntentFilter();
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageIntentFilter.addDataScheme("package");
+ packageIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ context.registerReceiverForAllUsers(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int userId = UserHandleCompat.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
@@ -202,7 +209,44 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}
maybeGrantDefaultRolesAsync(userId);
}
- }, intentFilter, null, null);
+ }, packageIntentFilter, null, null);
+
+ if (SdkLevel.isAtLeastV()) {
+ IntentFilter devicePolicyIntentFilter = new IntentFilter();
+ devicePolicyIntentFilter.addAction(
+ DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
+ devicePolicyIntentFilter.addAction(
+ DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
+ devicePolicyIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ context.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userId = getSendingUser().getIdentifier();
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Device policy changed (" + intent.getAction()
+ + ") - re-running initial grants for user " + userId);
+ }
+ maybeGrantDefaultRolesAsync(userId);
+ }
+ }, devicePolicyIntentFilter, null, null);
+
+ context.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE), false,
+ new ContentObserver(ForegroundThread.getHandler()) {
+ public void onChange(boolean selfChange, Uri uri) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Settings.Global.DEVICE_DEMO_MODE changed.");
+ }
+ UserManager userManager =
+ context.getSystemService(UserManager.class);
+ List<UserHandle> users = userManager.getUserHandles(true);
+ int usersSize = users.size();
+ for (int i = 0; i < usersSize; i++) {
+ maybeGrantDefaultRolesAsync(users.get(i).getIdentifier());
+ }
+ }
+ });
+ }
}
@Override
diff --git a/service/java/com/android/role/TEST_MAPPING b/service/java/com/android/role/TEST_MAPPING
index 15173a9da..b3507f053 100644
--- a/service/java/com/android/role/TEST_MAPPING
+++ b/service/java/com/android/role/TEST_MAPPING
@@ -13,6 +13,9 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
@@ -30,6 +33,36 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsAppSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.appsecurity.cts.StatsdAppSecurityAtomTest#testRoleHolder"
+ }
+ ]
+ },
+ {
+ "name": "CtsRoleTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
}
]
}
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index 90ea8d411..dd7e79ef5 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -19,8 +19,24 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/modules/Permission/service/java/com/android/safetycenter/SafetyCenterService.java"
- line="660"
+ line="651"
column="40"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 34 (current min is 33): `getDeduplicationGroup`">
+ <location
+ file="packages/modules/Permission/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java"
+ line="315"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 34 (current min is 33): `getDeduplicationId`">
+ <location
+ file="packages/modules/Permission/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java"
+ line="316"/>
+ </issue>
+
</issues> \ No newline at end of file
diff --git a/tests/cts/permissionmultiuser/Android.bp b/tests/cts/permissionmultiuser/Android.bp
new file mode 100644
index 000000000..f577c82e3
--- /dev/null
+++ b/tests/cts/permissionmultiuser/Android.bp
@@ -0,0 +1,47 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsPermissionMultiUserTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ min_sdk_version: "30",
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "Harrier",
+ "modules-utils-build_system",
+ "Nene",
+ ],
+ data: [
+ ":CtsRequestLocationApp",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permissionmultiuser/AndroidManifest.xml b/tests/cts/permissionmultiuser/AndroidManifest.xml
new file mode 100644
index 000000000..15bc3af34
--- /dev/null
+++ b/tests/cts/permissionmultiuser/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionmultiuser.cts">
+
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permissionmultiuser.cts"
+ android:label="CTS multi-user tests for permissions">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/permissionmultiuser/AndroidTest.xml b/tests/cts/permissionmultiuser/AndroidTest.xml
new file mode 100644
index 000000000..697901361
--- /dev/null
+++ b/tests/cts/permissionmultiuser/AndroidTest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<configuration description="Config for CTS permissionmultiuser test cases">
+
+ <option name="test-suite-tag" value="cts" />
+
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <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="multiuser" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="restore-settings" value="true" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionMultiUserTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsRequestLocationApp.apk->/data/local/tmp/cts/permissionmultiuser/CtsRequestLocationApp.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="appops set android.permissionmultiuser.cts REQUEST_INSTALL_PACKAGES allow" />
+ <!-- disable DeprecatedAbi warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
+ <!-- disable DeprecatedTargetSdk warning -->
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permissionmultiuser.cts" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+
+</configuration>
diff --git a/tests/cts/permissionmultiuser/OWNERS b/tests/cts/permissionmultiuser/OWNERS
new file mode 100644
index 000000000..fb6099cf7
--- /dev/null
+++ b/tests/cts/permissionmultiuser/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/cts/permissionmultiuser/RequestLocationApp/Android.bp b/tests/cts/permissionmultiuser/RequestLocationApp/Android.bp
new file mode 100644
index 000000000..d645e15d7
--- /dev/null
+++ b/tests/cts/permissionmultiuser/RequestLocationApp/Android.bp
@@ -0,0 +1,25 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRequestLocationApp",
+ target_sdk_version: "30",
+ min_sdk_version: "30",
+}
diff --git a/tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml b/tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml
new file mode 100644
index 000000000..e5e7609eb
--- /dev/null
+++ b/tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionmultiuser.cts.requestlocation">
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionmultiuser/TEST_MAPPING b/tests/cts/permissionmultiuser/TEST_MAPPING
new file mode 100644
index 000000000..48f5ef537
--- /dev/null
+++ b/tests/cts/permissionmultiuser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPermissionMultiUserTestCases"
+ }
+ ]
+}
diff --git a/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt
new file mode 100644
index 000000000..c0469291f
--- /dev/null
+++ b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt
@@ -0,0 +1,473 @@
+/*
+ * 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 android.permissionmultiuser.cts
+
+import android.app.Instrumentation
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_MUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.app.UiAutomation
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Context.RECEIVER_EXPORTED
+import android.content.Intent
+import android.content.Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES
+import android.content.IntentFilter
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.EXTRA_STATUS
+import android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE
+import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
+import android.content.pm.PackageInstaller.STATUS_SUCCESS
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
+import android.content.pm.PackageManager.FEATURE_LEANBACK
+import android.os.Build
+import android.os.PersistableBundle
+import android.os.SystemClock
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.StaleObjectException
+import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.UiObject2
+import android.util.Log
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.bedstead.harrier.BedsteadJUnit4
+import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.annotations.EnsureHasPermission
+import com.android.bedstead.harrier.annotations.EnsureSecureSettingSet
+import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature
+import com.android.bedstead.harrier.annotations.RequireNotWatch
+import com.android.bedstead.harrier.annotations.RequireRunOnAdditionalUser
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile
+import com.android.bedstead.harrier.annotations.RequireSdkVersion
+import com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS
+import com.android.compatibility.common.util.ApiTest
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.waitForBroadcasts
+import com.android.compatibility.common.util.UiAutomatorUtils
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests the UI that displays information about apps' updates to their data sharing policies when
+ * device has multiple users.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@RequireSdkVersion(min = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+@RequireDoesNotHaveFeature(FEATURE_LEANBACK)
+@RequireNotWatch(reason = "Data sharing update page is unavailable on watch")
+@RunWith(BedsteadJUnit4::class)
+@EnsureSecureSettingSet(key = "user_setup_complete", value = "1")
+class AppDataSharingUpdatesTest {
+
+ @get:Rule
+ val deviceConfigSafetyLabelChangeNotificationsEnabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED,
+ true.toString())
+
+ @get:Rule
+ val deviceConfigDataSharingUpdatesPeriod =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS,
+ "600000")
+
+ /**
+ * This rule serves to limit the max number of safety labels that can be persisted, so that
+ * repeated tests don't overwhelm the disk storage on the device.
+ */
+ @get:Rule
+ val deviceConfigMaxSafetyLabelsPersistedPerApp =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_MAX_SAFETY_LABELS_PERSISTED_PER_APP,
+ "2")
+
+ @Before
+ fun registerInstallSessionResultReceiver() {
+ context.registerReceiver(
+ installSessionResultReceiver, IntentFilter(INSTALL_ACTION_CALLBACK), RECEIVER_EXPORTED)
+ }
+
+ @After
+ fun unregisterInstallSessionResultReceiver() {
+ try {
+ context.unregisterReceiver(installSessionResultReceiver)
+ } catch (ignored: IllegalArgumentException) {}
+ }
+
+ @Test
+ @EnsureHasPermission(INTERACT_ACROSS_USERS)
+ @RequireRunOnWorkProfile
+ @ApiTest(apis = ["android.content.Intent#ACTION_REVIEW_APP_DATA_SHARING_UPDATES"])
+ fun openDataSharingUpdatesPage_workProfile_whenAppHasUpdateAndLocationGranted_showUpdates() {
+ installPackageViaSession(LOCATION_PACKAGE_APK_PATH, createAppMetadataWithNoSharing())
+ waitForBroadcasts()
+ installPackageViaSession(
+ LOCATION_PACKAGE_APK_PATH, createAppMetadataWithLocationSharingNoAds())
+ waitForBroadcasts()
+ grantLocationPermission(LOCATION_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivityForUser(deviceState.initialUser().userHandle())
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), true)
+ } finally {
+ pressBack()
+ uninstallPackage(LOCATION_PACKAGE_NAME)
+ }
+ }
+
+ @Test
+ @EnsureHasPermission(INTERACT_ACROSS_USERS)
+ @RequireRunOnAdditionalUser
+ @ApiTest(apis = ["android.content.Intent#ACTION_REVIEW_APP_DATA_SHARING_UPDATES"])
+ fun openDataSharingUpdatesPage_additionalUser_whenAppHasUpdateAndLocationGranted_showUpdates() {
+ installPackageViaSession(LOCATION_PACKAGE_APK_PATH, createAppMetadataWithNoSharing())
+ waitForBroadcasts()
+ installPackageViaSession(
+ LOCATION_PACKAGE_APK_PATH, createAppMetadataWithLocationSharingNoAds())
+ waitForBroadcasts()
+ grantLocationPermission(LOCATION_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivityForUser(deviceState.additionalUser().userHandle())
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), true)
+ } finally {
+ pressBack()
+ uninstallPackage(LOCATION_PACKAGE_NAME)
+ }
+
+ deviceState.initialUser().switchTo()
+
+ startAppDataSharingUpdatesActivityForUser(deviceState.initialUser().userHandle())
+
+ try {
+ // Verify that state does not leak across users.
+ assertNoUpdatesPresent()
+ findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), false)
+ } finally {
+ pressBack()
+ }
+ }
+
+ /** Companion object for [AppDataSharingUpdatesTest]. */
+ companion object {
+ @JvmField @ClassRule @Rule val deviceState: DeviceState = DeviceState()
+
+ @JvmStatic
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.context
+ @JvmStatic private val uiAutomation: UiAutomation = instrumentation.uiAutomation
+ @JvmStatic private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+ @JvmStatic private val packageManager: PackageManager = context.packageManager
+ @JvmStatic private val packageInstaller = packageManager.packageInstaller
+ private data class SessionResult(val status: Int?)
+ private val TAG = AppDataSharingUpdatesTest::class.simpleName
+
+ private const val APK_DIRECTORY = "/data/local/tmp/cts/permissionmultiuser"
+ private const val LOCATION_PACKAGE_NAME = "android.permissionmultiuser.cts.requestlocation"
+ private const val LOCATION_PACKAGE_APK_PATH = "CtsRequestLocationApp.apk"
+ private const val INSTALL_ACTION_CALLBACK = "AppDataSharingUpdatesTest.install_callback"
+ private const val PACKAGE_INSTALLER_TIMEOUT = 60000L
+ private const val IDLE_TIMEOUT_MILLIS: Long = 1000
+ private const val TIMEOUT_MILLIS: Long = 20000
+
+ private const val KEY_VERSION = "version"
+ private const val KEY_SAFETY_LABELS = "safety_labels"
+ private const val KEY_DATA_SHARED = "data_shared"
+ private const val KEY_DATA_LABELS = "data_labels"
+ private const val KEY_PURPOSES = "purposes"
+ private const val INITIAL_SAFETY_LABELS_VERSION = 1L
+ private const val INITIAL_TOP_LEVEL_VERSION = 1L
+ private const val LOCATION_CATEGORY = "location"
+ private const val APPROX_LOCATION = "approx_location"
+ private const val PURPOSE_FRAUD_PREVENTION_SECURITY = 4
+
+ private const val DATA_SHARING_UPDATES = "Data sharing updates for location"
+ private const val DATA_SHARING_UPDATES_SUBTITLE =
+ "These apps have changed the way they may share your location data. They may not" +
+ " have shared it before, or may now share it for advertising or marketing" +
+ " purposes."
+ private const val DATA_SHARING_NO_UPDATES_MESSAGE = "No updates at this time"
+ private const val UPDATES_IN_LAST_30_DAYS = "Updated within 30 days"
+ private const val DATA_SHARING_UPDATES_FOOTER_MESSAGE =
+ "The developers of these apps provided info about their data sharing practices" +
+ " to an app store. They may update it over time.\n\nData sharing" +
+ " practices may vary based on your app version, use, region, and age."
+ private const val LOCATION_PACKAGE_NAME_SUBSTRING = "android.permissionmultiuser"
+ private const val PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS =
+ "data_sharing_update_period_millis"
+ private const val PROPERTY_MAX_SAFETY_LABELS_PERSISTED_PER_APP =
+ "max_safety_labels_persisted_per_app"
+
+ private var installSessionResult = LinkedBlockingQueue<SessionResult>()
+
+ private val installSessionResultReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
+ val msg = intent.getStringExtra(EXTRA_STATUS_MESSAGE)
+ Log.d(TAG, "status: $status, msg: $msg")
+
+ installSessionResult.offer(SessionResult(status))
+ }
+ }
+
+ /** Installs an app with the provided [appMetadata] */
+ private fun installPackageViaSession(
+ apkName: String,
+ appMetadata: PersistableBundle? = null,
+ packageSource: Int? = null
+ ) {
+ val session = createPackageInstallerSession(packageSource)
+ runWithShellPermissionIdentity {
+ writePackageInstallerSession(session, apkName)
+ if (appMetadata != null) {
+ setAppMetadata(session, appMetadata)
+ }
+ commitPackageInstallerSession(session)
+
+ // No need to click installer UI here due to running in shell permission identity
+ // and not needing user interaction to complete install.
+ // Install should have succeeded.
+ val result = getInstallSessionResult()
+ assertThat(result.status).isEqualTo(STATUS_SUCCESS)
+ }
+ }
+
+ private fun createPackageInstallerSession(
+ packageSource: Int? = null
+ ): PackageInstaller.Session {
+ val sessionParam = SessionParams(SessionParams.MODE_FULL_INSTALL)
+ if (packageSource != null) {
+ sessionParam.setPackageSource(packageSource)
+ }
+
+ val sessionId = packageInstaller.createSession(sessionParam)
+ return packageInstaller.openSession(sessionId)
+ }
+
+ private fun writePackageInstallerSession(
+ session: PackageInstaller.Session,
+ apkName: String
+ ) {
+ val apkFile = File(APK_DIRECTORY, apkName)
+ apkFile.inputStream().use { fileOnDisk ->
+ session
+ .openWrite(/* name= */ apkName, /* offsetBytes= */ 0, /* lengthBytes= */ -1)
+ .use { sessionFile -> fileOnDisk.copyTo(sessionFile) }
+ }
+ }
+
+ private fun commitPackageInstallerSession(session: PackageInstaller.Session) {
+ // PendingIntent that triggers a INSTALL_ACTION_CALLBACK broadcast that gets received by
+ // installSessionResultReceiver when install actions occur with this session
+ val installActionPendingIntent =
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ Intent(INSTALL_ACTION_CALLBACK).setPackage(context.packageName),
+ FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
+ session.commit(installActionPendingIntent.intentSender)
+ }
+
+ private fun setAppMetadata(session: PackageInstaller.Session, data: PersistableBundle) {
+ try {
+ session.setAppMetadata(data)
+ } catch (e: Exception) {
+ session.abandon()
+ throw e
+ }
+ }
+
+ private fun getInstallSessionResult(
+ timeout: Long = PACKAGE_INSTALLER_TIMEOUT
+ ): SessionResult {
+ return installSessionResult.poll(timeout, TimeUnit.MILLISECONDS)
+ ?: SessionResult(null /* status */)
+ }
+
+ private fun uninstallPackage(packageName: String) {
+ runShellCommand("pm uninstall $packageName").trim()
+ }
+
+ private fun pressBack() {
+ uiDevice.pressBack()
+ uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS)
+ }
+
+ /** Returns an App Metadata [PersistableBundle] representation where no data is shared. */
+ private fun createAppMetadataWithNoSharing(): PersistableBundle {
+ return createMetadataWithDataShared(PersistableBundle())
+ }
+
+ /**
+ * Returns an App Metadata [PersistableBundle] representation where location data is shared,
+ * but not for advertising purpose.
+ */
+ private fun createAppMetadataWithLocationSharingNoAds(): PersistableBundle {
+ val locationBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(
+ APPROX_LOCATION,
+ PersistableBundle().apply {
+ putIntArray(
+ KEY_PURPOSES,
+ listOf(PURPOSE_FRAUD_PREVENTION_SECURITY).toIntArray())
+ })
+ }
+
+ val dataSharedBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(LOCATION_CATEGORY, locationBundle)
+ }
+
+ return createMetadataWithDataShared(dataSharedBundle)
+ }
+
+ /**
+ * Returns an App Metadata [PersistableBundle] representation where with the provided data
+ * shared.
+ */
+ private fun createMetadataWithDataShared(
+ dataSharedBundle: PersistableBundle
+ ): PersistableBundle {
+ val dataLabelBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(KEY_DATA_SHARED, dataSharedBundle)
+ }
+
+ val safetyLabelBundle =
+ PersistableBundle().apply {
+ putLong(KEY_VERSION, INITIAL_SAFETY_LABELS_VERSION)
+ putPersistableBundle(KEY_DATA_LABELS, dataLabelBundle)
+ }
+
+ return PersistableBundle().apply {
+ putLong(KEY_VERSION, INITIAL_TOP_LEVEL_VERSION)
+ putPersistableBundle(KEY_SAFETY_LABELS, safetyLabelBundle)
+ }
+ }
+
+ /**
+ * Starts activity with intent [ACTION_REVIEW_APP_DATA_SHARING_UPDATES] for the provided
+ * user.
+ */
+ fun startAppDataSharingUpdatesActivityForUser(userHandle: UserHandle) {
+ runWithShellPermissionIdentity {
+ context.startActivityAsUser(
+ Intent(ACTION_REVIEW_APP_DATA_SHARING_UPDATES).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ },
+ userHandle)
+ }
+ }
+
+ private fun assertUpdatesPresent() {
+ findView(By.descContains(DATA_SHARING_UPDATES), true)
+ findView(By.textContains(DATA_SHARING_UPDATES_SUBTITLE), true)
+ findView(By.textContains(UPDATES_IN_LAST_30_DAYS), true)
+ findView(By.textContains(DATA_SHARING_UPDATES_FOOTER_MESSAGE), true)
+ }
+
+ private fun assertNoUpdatesPresent() {
+ findView(By.descContains(DATA_SHARING_UPDATES), true)
+ findView(By.textContains(DATA_SHARING_UPDATES_SUBTITLE), true)
+ findView(By.textContains(DATA_SHARING_NO_UPDATES_MESSAGE), true)
+ findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), false)
+ findView(By.textContains(UPDATES_IN_LAST_30_DAYS), false)
+ findView(By.textContains(DATA_SHARING_UPDATES_FOOTER_MESSAGE), true)
+ }
+
+ private fun grantLocationPermission(packageName: String) {
+ uiAutomation.grantRuntimePermission(
+ packageName, android.Manifest.permission.ACCESS_FINE_LOCATION)
+ }
+
+ protected fun waitFindObject(
+ selector: BySelector,
+ timeoutMillis: Long = 20_000L
+ ): UiObject2 {
+ uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS)
+ val startTime = SystemClock.elapsedRealtime()
+ return try {
+ UiAutomatorUtils.waitFindObject(selector, timeoutMillis)
+ } catch (e: StaleObjectException) {
+ val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+ if (remainingTime <= 0) {
+ throw e
+ }
+ UiAutomatorUtils.waitFindObject(selector, remainingTime)
+ }
+ }
+
+ private fun findView(selector: BySelector, expected: Boolean) {
+ val timeoutMillis =
+ if (expected) {
+ 10000L
+ } else {
+ 1000L
+ }
+
+ val exception =
+ try {
+ uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS)
+ val startTime = SystemClock.elapsedRealtime()
+ try {
+ UiAutomatorUtils.waitFindObject(selector, timeoutMillis)
+ } catch (e: StaleObjectException) {
+ val remainingTime =
+ timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+ if (remainingTime <= 0) {
+ throw e
+ }
+ UiAutomatorUtils.waitFindObject(selector, remainingTime)
+ }
+ null
+ } catch (e: Exception) {
+ e
+ }
+ Assert.assertTrue("Expected to find view: $expected", (exception == null) == expected)
+ }
+ }
+}