summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--PermissionController/jarjar-rules.txt4
-rw-r--r--PermissionController/res/layout-v33/preference_issue_card.xml133
-rw-r--r--PermissionController/res/layout-v33/preference_more_issues_card.xml11
-rw-r--r--PermissionController/res/layout-v33/preference_safety_status.xml11
-rw-r--r--PermissionController/res/values-b+sr+Latn/strings.xml2
-rw-r--r--PermissionController/res/values-fa/strings.xml4
-rw-r--r--PermissionController/res/values-it/strings.xml4
-rw-r--r--PermissionController/res/values-iw/strings.xml2
-rw-r--r--PermissionController/res/values-ja/strings.xml2
-rw-r--r--PermissionController/res/values-night-v33/themes.xml2
-rw-r--r--PermissionController/res/values-ro/strings.xml2
-rw-r--r--PermissionController/res/values-sr/strings.xml2
-rw-r--r--PermissionController/res/values-sv/strings.xml2
-rw-r--r--PermissionController/res/values-te/strings.xml6
-rw-r--r--PermissionController/res/values-v33/attrs.xml5
-rw-r--r--PermissionController/res/values-v33/styles.xml4
-rw-r--r--PermissionController/res/values-v33/themes.xml12
-rw-r--r--PermissionController/res/values-watch/donottranslate.xml52
-rw-r--r--PermissionController/res/values-zh-rHK/strings.xml2
-rw-r--r--PermissionController/res/xml/roles.xml41
-rw-r--r--PermissionController/role-controller/Android.bp2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java29
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java16
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java38
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java49
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java36
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt46
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt38
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java33
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java29
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt20
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt50
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt3
-rw-r--r--PermissionController/tests/inprocess/Android.bp3
-rw-r--r--SafetyCenter/Resources/res/values-fa-v35/strings.xml2
-rw-r--r--SafetyCenter/Resources/res/values-fa/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-hy-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-ja/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-ka-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-kn-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-lv-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-si-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-sv-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-tr-v35/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-ur-v35/strings.xml4
-rw-r--r--framework-s/api/system-current.txt4
-rw-r--r--framework-s/java/android/app/ecm/EnhancedConfirmationManager.java20
-rw-r--r--framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl3
-rw-r--r--framework-s/java/android/app/role/IRoleManager.aidl4
-rw-r--r--framework-s/java/android/app/role/RoleManager.java92
-rw-r--r--framework-s/java/android/permission/internal/compat/UserHandleCompat.java (renamed from service/java/com/android/permission/compat/UserHandleCompat.java)2
-rw-r--r--framework-s/java/android/permission/internal/compat/package-info.java (renamed from service/java/com/android/permission/compat/package-info.java)2
-rw-r--r--service/api/system-server-current.txt14
-rw-r--r--service/jarjar-rules.txt4
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java80
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java56
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java59
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationService.java143
-rw-r--r--service/java/com/android/permission/util/UserUtils.java81
-rw-r--r--service/java/com/android/role/RoleService.java235
-rw-r--r--service/java/com/android/role/RoleShellCommand.java29
-rw-r--r--service/java/com/android/role/RoleUserState.java37
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java3
-rw-r--r--service/java/com/android/safetycenter/UserProfileGroup.java12
-rw-r--r--service/lint-baseline.xml11
-rw-r--r--tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java3
-rw-r--r--tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java17
-rwxr-xr-xtests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java73
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt39
-rw-r--r--tests/cts/permissionpolicy/res/raw/OWNERS1
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml221
-rw-r--r--tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml12
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt15
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/SignaturePermissionAllowlistConfigTest.kt38
-rw-r--r--tests/cts/permissionui/AndroidManifest.xml18
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt231
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt10
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt171
-rw-r--r--tests/cts/role/Android.bp2
-rw-r--r--tests/cts/role/AndroidTest.xml1
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerTest.java47
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt42
-rw-r--r--tests/cts/rolemultiuser/Android.bp50
-rw-r--r--tests/cts/rolemultiuser/AndroidManifest.xml32
-rw-r--r--tests/cts/rolemultiuser/AndroidTest.xml53
-rw-r--r--tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp23
-rw-r--r--tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml24
-rw-r--r--tests/cts/rolemultiuser/OWNERS3
-rw-r--r--tests/cts/rolemultiuser/TEST_MAPPING12
-rw-r--r--tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt475
-rw-r--r--tests/functional/safetycenter/singleuser/AndroidTest.xml4
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt3
112 files changed, 2977 insertions, 477 deletions
diff --git a/Android.bp b/Android.bp
index 9b1857741..f2aab3568 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,6 +110,7 @@ bootclasspath_fragment {
package_prefixes: [
"android.app.role",
"android.app.ecm",
+ "android.permission.internal",
"android.permission.jarjar",
"android.safetycenter",
"android.safetylabel",
diff --git a/PermissionController/jarjar-rules.txt b/PermissionController/jarjar-rules.txt
index 7bfda1ab1..05fe2a148 100644
--- a/PermissionController/jarjar-rules.txt
+++ b/PermissionController/jarjar-rules.txt
@@ -22,4 +22,8 @@ rule android.os.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
rule android.os.FeatureFlags* com.android.permissioncontroller.jarjar.@0
rule android.os.FeatureFlags com.android.permissioncontroller.jarjar.@0
rule android.os.Flags com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.FeatureFlags com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.Flags com.android.permissioncontroller.jarjar.@0
# LINT.ThenChange(PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java:applyJarjarTransform)
diff --git a/PermissionController/res/layout-v33/preference_issue_card.xml b/PermissionController/res/layout-v33/preference_issue_card.xml
index e6d749142..107c778a1 100644
--- a/PermissionController/res/layout-v33/preference_issue_card.xml
+++ b/PermissionController/res/layout-v33/preference_issue_card.xml
@@ -13,81 +13,84 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/issue_card"
- android:clickable="false"
- android:screenReaderFocusable="true"
- style="@style/SafetyCenterCard.Issue">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/issue_card"
+ android:clickable="false"
+ android:screenReaderFocusable="true"
+ style="@style/SafetyCenterCard.Issue">
- <ImageButton
- android:id="@+id/issue_card_dismiss_btn"
- android:src="@drawable/ic_safety_issue_dismiss"
- android:contentDescription="@string/safety_center_issue_card_dismiss_button"
- style="@style/SafetyCenterIssueDismiss" />
+ <ImageButton
+ android:id="@+id/issue_card_dismiss_btn"
+ android:src="@drawable/ic_safety_issue_dismiss"
+ android:contentDescription="@string/safety_center_issue_card_dismiss_button"
+ style="@style/SafetyCenterIssueDismiss" />
- <TextView
- android:id="@+id/issue_card_attribution_title"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueAttributionTitle" />
+ <TextView
+ android:id="@+id/issue_card_attribution_title"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueAttributionTitle" />
- <TextView
- android:id="@+id/issue_card_title"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueTitle" />
+ <TextView
+ android:id="@+id/issue_card_title"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueTitle" />
- <TextView
- android:id="@+id/issue_card_subtitle"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueSubtitle" />
+ <TextView
+ android:id="@+id/issue_card_subtitle"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueSubtitle" />
- <TextView
- android:id="@+id/issue_card_summary"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueSummary" />
+ <TextView
+ android:id="@+id/issue_card_summary"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueSummary" />
- <include
- android:id="@+id/issue_card_action_button_list"
- layout="?attr/scActionButtonListLayout"/>
+ <include
+ android:id="@+id/issue_card_action_button_list"
+ layout="?attr/scActionButtonListLayout"/>
- <com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView
- android:id="@+id/issue_card_protected_by_android"
- android:importantForAccessibility="no"
- style="@style/SafetyCenterIssueSafetyProtectionSection" />
+ <com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView
+ android:id="@+id/issue_card_protected_by_android"
+ android:importantForAccessibility="no"
+ style="@style/SafetyCenterIssueSafetyProtectionSection" />
- <ImageView
- android:id="@+id/resolved_issue_image"
- android:src="@drawable/safety_center_issue_resolved_avd"
- android:importantForAccessibility="no"
- style="@style/SafetyCenterIssueCardResolvedImage" />
+ <ImageView
+ android:id="@+id/resolved_issue_image"
+ android:src="@drawable/safety_center_issue_resolved_avd"
+ android:importantForAccessibility="no"
+ style="@style/SafetyCenterIssueCardResolvedImage" />
- <TextView
- android:id="@+id/resolved_issue_text"
- android:text="@string/safety_center_resolved_issue_fallback"
- style="@style/SafetyCenterIssueCardResolvedTitle" />
+ <TextView
+ android:id="@+id/resolved_issue_text"
+ android:text="@string/safety_center_resolved_issue_fallback"
+ style="@style/SafetyCenterIssueCardResolvedTitle" />
- <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn,
- issue_card_subtitle or issue_card_protected_by_android since the version of
- ConstraintLayout we're using doesn't allow us to override the group's visibility on
- individual group members. See b/242705351 for context. -->
- <androidx.constraintlayout.widget.Group
- android:id="@+id/default_issue_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="visible"
- app:constraint_referenced_ids="issue_card_title,issue_card_summary,issue_card_action_button_list" />
+ <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn,
+ issue_card_subtitle or issue_card_protected_by_android since the version of
+ ConstraintLayout we're using doesn't allow us to override the group's visibility on
+ individual group members. See b/242705351 for context. -->
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/default_issue_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="visible"
+ app:constraint_referenced_ids="issue_card_title,issue_card_summary,issue_card_action_button_list" />
- <androidx.constraintlayout.widget.Group
- android:id="@+id/resolved_issue_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- app:constraint_referenced_ids="resolved_issue_image,resolved_issue_text" />
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/resolved_issue_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:constraint_referenced_ids="resolved_issue_image,resolved_issue_text" />
-</androidx.constraintlayout.widget.ConstraintLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/PermissionController/res/layout-v33/preference_more_issues_card.xml b/PermissionController/res/layout-v33/preference_more_issues_card.xml
index c93125762..d0ac6b1f5 100644
--- a/PermissionController/res/layout-v33/preference_more_issues_card.xml
+++ b/PermissionController/res/layout-v33/preference_more_issues_card.xml
@@ -13,8 +13,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/issue_card"
- style="@style/SafetyCenterMoreIssuesCollapsed"/>
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView
+ android:id="@+id/more_issues_card"
+ style="@style/SafetyCenterMoreIssuesCollapsed"/>
+</FrameLayout> \ No newline at end of file
diff --git a/PermissionController/res/layout-v33/preference_safety_status.xml b/PermissionController/res/layout-v33/preference_safety_status.xml
index 42bf1c22d..17adc035f 100644
--- a/PermissionController/res/layout-v33/preference_safety_status.xml
+++ b/PermissionController/res/layout-v33/preference_safety_status.xml
@@ -14,7 +14,12 @@
~ limitations under the License.
-->
-<com.android.permissioncontroller.safetycenter.ui.view.StatusCardView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:clickable="false"
- style="@style/SafetyCenterCard.Status" /> \ No newline at end of file
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.permissioncontroller.safetycenter.ui.view.StatusCardView
+ android:id="@+id/status_card"
+ android:clickable="false"
+ style="@style/SafetyCenterCard.Status" />
+</FrameLayout> \ No newline at end of file
diff --git a/PermissionController/res/values-b+sr+Latn/strings.xml b/PermissionController/res/values-b+sr+Latn/strings.xml
index 6dd7a9cb6..75e5a94d3 100644
--- a/PermissionController/res/values-b+sr+Latn/strings.xml
+++ b/PermissionController/res/values-b+sr+Latn/strings.xml
@@ -369,7 +369,7 @@
<string name="role_dialer_search_keywords" msgid="3324448983559188087">"brojčanik"</string>
<string name="role_sms_label" msgid="8456999857547686640">"Podrazumevana apl. za SMS"</string>
<string name="role_sms_short_label" msgid="4371444488034692243">"Aplikacija za SMS"</string>
- <string name="role_sms_description" msgid="3424020199148153513">"Aplikacije koje vam omogućavaju da koristite broj telefona da biste slali i primali kratke SMS-ove, slike, video snimke i još toga"</string>
+ <string name="role_sms_description" msgid="3424020199148153513">"Aplikacije koje vam omogućavaju da koristite broj telefona da biste slali i primali kratke tekstualne poruke, slike, video snimke i drugo"</string>
<string name="role_sms_request_title" msgid="7953552109601185602">"Želite li da podesite <xliff:g id="APP_NAME">%1$s</xliff:g> kao podrazumevanu aplikacju za SMS?"</string>
<string name="role_sms_request_description" msgid="2691004766132144886">"Ova aplikacija će dobiti pristup kameri, kontaktima, fajlovima i medijima, mikrofonu, telefonu i SMS-ovima"</string>
<string name="role_sms_search_keywords" msgid="8022048144395047352">"SMS, slanje SMS-ova, poruke, slanje poruka"</string>
diff --git a/PermissionController/res/values-fa/strings.xml b/PermissionController/res/values-fa/strings.xml
index 1ef41ea6d..ae6063dcc 100644
--- a/PermissionController/res/values-fa/strings.xml
+++ b/PermissionController/res/values-fa/strings.xml
@@ -581,7 +581,7 @@
<string name="sensor_permissions_qs" msgid="1022267900031317472">"اجازه‌ها"</string>
<string name="safety_privacy_qs_tile_title" msgid="727301867710374052">"امنیت و حریم خصوصی"</string>
<string name="safety_privacy_qs_tile_subtitle" msgid="3621544532041936749">"بررسی وضعیت"</string>
- <string name="privacy_controls_qs" msgid="5780144882040591169">"تنظیمات حریم خصوصی شما"</string>
+ <string name="privacy_controls_qs" msgid="5780144882040591169">"کنترل‌های حریم خصوصی شما"</string>
<string name="security_settings_button_label_qs" msgid="8280343822465962330">"تنظیمات بیشتر"</string>
<string name="camera_toggle_label_qs" msgid="3880261453066157285">"دسترسی به دوربین"</string>
<string name="microphone_toggle_label_qs" msgid="8132912469813396552">"دسترسی به میکروفون"</string>
@@ -624,7 +624,7 @@
<string name="safety_center_background_location_access_reminder_summary" msgid="7431657777510537658">"این برنامه همیشه می‌تواند به مکانتان دسترسی داشته باشد، حتی وقتی بسته باشد.\n\nبرخی‌از برنامه‌های ایمنی و اضطراری برای اینکه عملکرد موردانتظار را داشته باشند باید به مکان شما در پس‌زمینه دسترسی داشته باشند."</string>
<string name="safety_center_background_location_access_revoked" msgid="6972274943343442213">"دسترسی تغییر کرد"</string>
<string name="safety_center_view_recent_location_access" msgid="3524391299490678243">"دیدن استفاده اخیر از مکان"</string>
- <string name="privacy_controls_title" msgid="7605929972256835199">"تنظیمات حریم خصوصی"</string>
+ <string name="privacy_controls_title" msgid="7605929972256835199">"کنترل‌های حریم خصوصی"</string>
<string name="camera_toggle_title" msgid="1251201397431837666">"دسترسی به دوربین"</string>
<string name="mic_toggle_title" msgid="2649991093496110162">"دسترسی به میکروفون"</string>
<string name="perm_toggle_description" msgid="7801326363741451379">"برای برنامه‌ها و سرویس‌ها"</string>
diff --git a/PermissionController/res/values-it/strings.xml b/PermissionController/res/values-it/strings.xml
index 857734db1..c855b4ce6 100644
--- a/PermissionController/res/values-it/strings.xml
+++ b/PermissionController/res/values-it/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="6098036489833144040">"App di controllo autorizzazioni"</string>
- <string name="ok" msgid="1936281769725676272">"OK"</string>
+ <string name="ok" msgid="1936281769725676272">"Ok"</string>
<string name="permission_search_keyword" msgid="1214451577494730543">"autorizzazioni"</string>
<string name="cancel" msgid="8943320028373963831">"Annulla"</string>
<string name="back" msgid="6249950659061523680">"Indietro"</string>
@@ -422,7 +422,7 @@
<string name="system_uses_microphone_and_camera" msgid="5124478304275138804">"La videocamera e il microfono sono in uso per un servizio di sistema"</string>
<string name="system_uses_camera" msgid="1911223105234441470">"La videocamera è in uso per un servizio di sistema"</string>
<string name="other_use" msgid="6564855051022776692">"Altro uso:"</string>
- <string name="ongoing_usage_dialog_ok" msgid="103556809118460072">"OK"</string>
+ <string name="ongoing_usage_dialog_ok" msgid="103556809118460072">"Ok"</string>
<string name="ongoing_usage_dialog_title" msgid="683836493556628569">"Uso recente di <xliff:g id="TYPES_LIST">%s</xliff:g>"</string>
<string name="ongoing_usage_dialog_title_mic" msgid="5966714811125593992">"Utilizzo recente di Microfono"</string>
<string name="ongoing_usage_dialog_title_camera" msgid="7819329688650711470">"Utilizzo recente di Fotocamera"</string>
diff --git a/PermissionController/res/values-iw/strings.xml b/PermissionController/res/values-iw/strings.xml
index a936d3e4e..0fc33a62b 100644
--- a/PermissionController/res/values-iw/strings.xml
+++ b/PermissionController/res/values-iw/strings.xml
@@ -346,7 +346,7 @@
<string name="no_apps_allowed" msgid="7718822655254468631">"אין אפליקציות שקיבלו הרשאה"</string>
<string name="no_apps_allowed_full" msgid="8011716991498934104">"לאף אפליקציה אין הרשאה לכל הקבצים"</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"לאף אפליקציה אין הרשאה למדיה בלבד"</string>
- <string name="no_apps_denied" msgid="7663435886986784743">"אין אפליקציות שלא קיבלו ההרשאה"</string>
+ <string name="no_apps_denied" msgid="7663435886986784743">"אין אפליקציות שלא קיבלו הרשאה"</string>
<string name="car_permission_selected" msgid="180837028920791596">"נבחר"</string>
<string name="settings" msgid="5409109923158713323">"הגדרות"</string>
<string name="accessibility_service_dialog_title_single" msgid="7956432823014102366">"לשירות <xliff:g id="SERVICE_NAME">%s</xliff:g> יש גישה מלאה למכשיר שלך"</string>
diff --git a/PermissionController/res/values-ja/strings.xml b/PermissionController/res/values-ja/strings.xml
index ecd742f55..e7322b07b 100644
--- a/PermissionController/res/values-ja/strings.xml
+++ b/PermissionController/res/values-ja/strings.xml
@@ -74,7 +74,7 @@
<string name="days_ago" msgid="6650359081551335629">"{count,plural, =0{今日}=1{1 日前}other{# 日前}}"</string>
<string name="app_disable_dlg_positive" msgid="7418444149981904940">"アプリを無効にする"</string>
<string name="app_disable_dlg_text" msgid="3126943217146120240">"このアプリを無効にすると、Android などの他のアプリが正しく動作しなくなるおそれがあります。このアプリはデバイスにプリインストールされているため、削除できません。無効にするには、このアプリをオフにし、デバイスにアプリが表示されないようにします。"</string>
- <string name="app_permission_manager" msgid="3903811137630909550">"権限マネージャ"</string>
+ <string name="app_permission_manager" msgid="3903811137630909550">"権限マネージャー"</string>
<string name="never_ask_again" msgid="4728762438198560329">"今後表示しない"</string>
<string name="no_permissions" msgid="3881676756371148563">"権限がありません"</string>
<string name="additional_permissions" msgid="5801285469338873430">"その他の権限"</string>
diff --git a/PermissionController/res/values-night-v33/themes.xml b/PermissionController/res/values-night-v33/themes.xml
index 9b6f638a6..31940ede5 100644
--- a/PermissionController/res/values-night-v33/themes.xml
+++ b/PermissionController/res/values-night-v33/themes.xml
@@ -52,6 +52,8 @@
@style/SecondarySafetyCenterActionButton.Responsive
</item>
+ <item name="scCardSideMargin">@dimen/sc_spacing_large</item>
+
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
diff --git a/PermissionController/res/values-ro/strings.xml b/PermissionController/res/values-ro/strings.xml
index 177d5ab17..5f88c1376 100644
--- a/PermissionController/res/values-ro/strings.xml
+++ b/PermissionController/res/values-ro/strings.xml
@@ -514,7 +514,7 @@
<string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Aplicația dorește să înregistreze conținut audio permanent, chiar și când nu o folosești. "<annotation id="link">"Acordă această permisiune din setări."</annotation></string>
<string name="permgrouprequest_activityRecognition" msgid="5415121592794230330">"Permiți aplicației &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să-ți acceseze activitatea fizică?"</string>
<string name="permgrouprequest_device_aware_activityRecognition" msgid="1243869530588745374">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze activitatea ta fizică de pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgrouprequest_camera" msgid="5123097035410002594">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să fotografieze și să înregistreze video?"</string>
+ <string name="permgrouprequest_camera" msgid="5123097035410002594">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să fotografieze și să înregistreze videoclipuri?"</string>
<string name="permgrouprequest_device_aware_camera" msgid="5340173564041615494">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să facă fotografii și să înregistreze videoclipuri pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequestdetail_camera" msgid="9085323239764667883">"Aplicația va putea să fotografieze și să înregistreze videoclipuri doar când o folosești"</string>
<string name="permgroupbackgroundrequest_camera" msgid="1274286575704213875">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să facă fotografii și să înregistreze videoclipuri?"</string>
diff --git a/PermissionController/res/values-sr/strings.xml b/PermissionController/res/values-sr/strings.xml
index 95bbe7f72..d89ebba68 100644
--- a/PermissionController/res/values-sr/strings.xml
+++ b/PermissionController/res/values-sr/strings.xml
@@ -369,7 +369,7 @@
<string name="role_dialer_search_keywords" msgid="3324448983559188087">"бројчаник"</string>
<string name="role_sms_label" msgid="8456999857547686640">"Подразумевана апл. за SMS"</string>
<string name="role_sms_short_label" msgid="4371444488034692243">"Апликација за SMS"</string>
- <string name="role_sms_description" msgid="3424020199148153513">"Апликације које вам омогућавају да користите број телефона да бисте слали и примали кратке SMS-ове, слике, видео снимке и још тога"</string>
+ <string name="role_sms_description" msgid="3424020199148153513">"Апликације које вам омогућавају да користите број телефона да бисте слали и примали кратке текстуалне поруке, слике, видео снимке и друго"</string>
<string name="role_sms_request_title" msgid="7953552109601185602">"Желите ли да подесите <xliff:g id="APP_NAME">%1$s</xliff:g> као подразумевану апликацју за SMS?"</string>
<string name="role_sms_request_description" msgid="2691004766132144886">"Ова апликација ће добити приступ камери, контактима, фајловима и медијима, микрофону, телефону и SMS-овима"</string>
<string name="role_sms_search_keywords" msgid="8022048144395047352">"SMS, слање SMS-ова, поруке, слање порука"</string>
diff --git a/PermissionController/res/values-sv/strings.xml b/PermissionController/res/values-sv/strings.xml
index 937a12200..c5d3969de 100644
--- a/PermissionController/res/values-sv/strings.xml
+++ b/PermissionController/res/values-sv/strings.xml
@@ -450,7 +450,7 @@
<string name="special_app_access_no_apps" msgid="4102911722787886970">"Inga appar"</string>
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Jobbprofiler stöds inte"</string>
<string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Obs! Om du startar om mobilen och har ställt in ett skärmlås kan appen inte startas förrän du låser upp mobilen."</string>
- <string name="assistant_confirmation_message" msgid="7476540402884416212">"Assistenten kan läsa information om appar som används i systemet, inklusive information som visas på skärmen eller är åtkomlig i apparna."</string>
+ <string name="assistant_confirmation_message" msgid="7476540402884416212">"Assistent kan läsa information om appar som används i systemet, inklusive information som visas på skärmen eller är åtkomlig i apparna."</string>
<string name="incident_report_channel_name" msgid="3144954065936288440">"Dela felsökningsinformation"</string>
<string name="incident_report_notification_title" msgid="4635984625656519773">"Vill du dela detaljerad felsökningsinformation?"</string>
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> vill ladda upp felsökningsinformation."</string>
diff --git a/PermissionController/res/values-te/strings.xml b/PermissionController/res/values-te/strings.xml
index 13097cdf0..a461319a4 100644
--- a/PermissionController/res/values-te/strings.xml
+++ b/PermissionController/res/values-te/strings.xml
@@ -238,7 +238,7 @@
<string name="permission_description_summary_camera" msgid="108004375101882069">"ఈ అనుమతి ఉన్న యాప్‌లు ఫోటోలు తీయగలవు, వీడియోను రికార్డ్ చేయగలవు"</string>
<string name="permission_description_summary_contacts" msgid="2337798886460408996">"ఈ అనుమతి ఉన్న యాప్‌లు మీ కాంటాక్ట్‌లను యాక్సెస్ చేయగలవు"</string>
<string name="permission_description_summary_location" msgid="2817531799933480694">"ఈ అనుమతి ఉన్న యాప్‌లు ఈ పరికర లొకేషన్‌ను యాక్సెస్ చేయగలవు"</string>
- <string name="permission_description_summary_nearby_devices" msgid="8269183818275073741">"ఈ అనుమతి ఉన్న యాప్‌లు సమీప పరికరాలను గుర్తించవచ్చు, వాటి సంబంధిత స్థానాన్ని తెలుసుకోవచ్చు, అలాగే వాటికి కనెక్ట్ చేయవచ్చు"</string>
+ <string name="permission_description_summary_nearby_devices" msgid="8269183818275073741">"ఈ అనుమతి ఉన్న యాప్‌లు సమీప పరికరాలను గుర్తించవచ్చు, వాటి సంబంధిత పొజిషన్‌ను తెలుసుకోవచ్చు, అలాగే వాటికి కనెక్ట్ చేయవచ్చు"</string>
<string name="permission_description_summary_microphone" msgid="630834800308329907">"ఈ అనుమతి ఉన్న యాప్‌లు ఆడియోను రికార్డ్ చేయగలవు"</string>
<string name="permission_description_summary_phone" msgid="4515277217435233619">"ఈ అనుమతులు ఉన్న యాప్‌లు ఫోన్ కాల్స్‌ చేయగలవు, మేనేజ్ చేయగలవు"</string>
<string name="permission_description_summary_sensors" msgid="1836045815643119949">"ఈ అనుమతి ఉన్న యాప్‌లు మీ ఆరోగ్య స్థితిని తెలియజేసే గణాంకాల సెన్సార్ డేటాను యాక్సెస్ చేయగలవు"</string>
@@ -480,9 +480,9 @@
<string name="permgroupupgraderequest_location" msgid="8328408946822691636">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; కోసం లొకేష‌న్‌ యాక్సెస్‌ను మార్చాలా?"</string>
<string name="permgroupupgraderequest_device_aware_location" msgid="1812338666887726191">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;‌లో &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‌కు సంబంధించిన లొకేషన్ యాక్సెస్‌ను మార్చాలా?"</string>
<string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"మీరు యాప్ ఉపయోగించనప్పుడు కూడా ఈ యాప్ మీ లొకేష‌న్‌ను ఎప్పటికప్పుడు యాక్సెస్ చేయాల‌ని అనుకుంటోంది."<annotation id="link">"సెట్టింగ్‌లలో అనుమతించండి."</annotation></string>
- <string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"సమీప పరికరాల సంబంధిత స్థానాన్ని కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి \"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;\"ను అనుమతించాలా?"</string>
+ <string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"సమీప పరికరాల సంబంధిత పొజిషన్‌ను కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి \"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;\"ను అనుమతించాలా?"</string>
<string name="permgrouprequest_device_aware_nearby_devices" msgid="5293478278408567442">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;‌లో సమీప పరికరాలు కనుగొని, కనెక్ట్ అయి, వాటి దూరం అంచనా వేసేలా &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‌ను అనుమతించాలా?"</string>
- <string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"సమీప పరికరాల సంబంధిత స్థానాన్ని కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా? "<annotation id="link">"సెట్టింగ్‌లలో అనుమతించండి."</annotation></string>
+ <string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"సమీప పరికరాల సంబంధిత పొజిషన్‌ను కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా? "<annotation id="link">"సెట్టింగ్‌లలో అనుమతించండి."</annotation></string>
<string name="permgrouprequest_fineupgrade" msgid="2334242928821697672">"<xliff:g id="APP_NAME">&lt;b&gt;%1$s&lt;/b&gt;</xliff:g>కు సంబంధించిన లొకేషన్ యాక్సెస్‌ను సుమారు నుండి ఖచ్చితమైనదిగా మార్చాలా?"</string>
<string name="permgrouprequest_device_aware_fineupgrade" msgid="4453775952305587571">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;‌లో &lt;b&gt;<xliff:g id="APP_NAME">&lt;b&gt;%1$s&lt;/b&gt;</xliff:g>&lt;/b&gt; లొకేషన్ యాక్సెస్‌ను రమారమి నుండి ఖచ్చితమైన లొకేషన్‌కు మార్చాలా?"</string>
<string name="permgrouprequest_coarselocation" msgid="7244605063736425232">"ఈ పరికరానికి సంబంధించి సుమారుగా ఉన్న లొకేషన్‌ను యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ని అనుమతించాలా?"</string>
diff --git a/PermissionController/res/values-v33/attrs.xml b/PermissionController/res/values-v33/attrs.xml
index 4a417076d..b136ab718 100644
--- a/PermissionController/res/values-v33/attrs.xml
+++ b/PermissionController/res/values-v33/attrs.xml
@@ -39,8 +39,9 @@
<attr name="scStatusButtonStyle" format="reference" />
<attr name="scActionButtonListLayout" format="reference" />
<attr name="scActionButtonTheme" format="reference" />
- <attr name="scActionButtonStyle" format="reference"/>
- <attr name="scSecondaryActionButtonStyle" format="reference"/>
+ <attr name="scActionButtonStyle" format="reference" />
+ <attr name="scSecondaryActionButtonStyle" format="reference" />
+ <attr name="scCardSideMargin" format="dimension" />
<attr name="colorScShieldAccent" format="color" />
</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-v33/styles.xml b/PermissionController/res/values-v33/styles.xml
index 94344615b..7e959c2f4 100644
--- a/PermissionController/res/values-v33/styles.xml
+++ b/PermissionController/res/values-v33/styles.xml
@@ -317,8 +317,8 @@
<item name="android:paddingEnd">@dimen/sc_spacing_xxxlarge</item>
<item name="android:paddingTop">@dimen/sc_spacing_xxxlarge</item>
<item name="android:paddingBottom">@dimen/sc_card_margin_bottom</item>
- <item name="android:layout_marginStart">@dimen/sc_spacing_large</item>
- <item name="android:layout_marginEnd">@dimen/sc_spacing_large</item>
+ <item name="android:layout_marginStart">?attr/scCardSideMargin</item>
+ <item name="android:layout_marginEnd">?attr/scCardSideMargin</item>
<item name="android:background">@drawable/safety_center_card_background</item>
</style>
diff --git a/PermissionController/res/values-v33/themes.xml b/PermissionController/res/values-v33/themes.xml
index 82a8ef5b6..a3d1b16c3 100644
--- a/PermissionController/res/values-v33/themes.xml
+++ b/PermissionController/res/values-v33/themes.xml
@@ -57,6 +57,8 @@
@style/SecondarySafetyCenterActionButton.Fixed
</item>
+ <item name="scCardSideMargin">@dimen/sc_spacing_large</item>
+
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
@@ -69,6 +71,10 @@
<style name="Theme.SafetyCenterQs" parent="Theme.SafetyCenterQsBase" />
+ <style name="Theme.SafetyCenterQsExpressive" parent="Theme.SafetyCenterQs">
+ <item name="scCardSideMargin">0dp</item>
+ </style>
+
<style name="Theme.SafetyCenterBase" parent="Theme.PermissionController.Settings.FilterTouches">
<item name="colorSurface">@color/sc_surface_light</item>
<item name="colorSurfaceVariant">@color/sc_surface_variant_light</item>
@@ -104,6 +110,8 @@
@style/SecondarySafetyCenterActionButton.Responsive
</item>
+ <item name="scCardSideMargin">@dimen/sc_spacing_large</item>
+
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
@@ -115,4 +123,8 @@
</style>
<style name="Theme.SafetyCenter" parent="Theme.SafetyCenterBase" />
+
+ <style name="Theme.SafetyCenterExpressive" parent="Theme.SafetyCenter">
+ <item name="scCardSideMargin">0dp</item>
+ </style>
</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-watch/donottranslate.xml b/PermissionController/res/values-watch/donottranslate.xml
index 309c51388..43830a93c 100644
--- a/PermissionController/res/values-watch/donottranslate.xml
+++ b/PermissionController/res/values-watch/donottranslate.xml
@@ -51,32 +51,32 @@
<string name="wear_compose_material3_title_medium_font_family">font-family-flex-device-default</string>
<string name="wear_compose_material3_title_large_font_family">font-family-flex-device-default</string>
- <dimen name="wear_compose_material3_arc_small_font_size">14</dimen>
- <dimen name="wear_compose_material3_arc_medium_font_size">15</dimen>
- <dimen name="wear_compose_material3_arc_large_font_size">20</dimen>
- <dimen name="wear_compose_material3_body_extra_small_font_size">10</dimen>
- <dimen name="wear_compose_material3_body_small_font_size">12</dimen>
- <dimen name="wear_compose_material3_body_medium_font_size">14</dimen>
- <dimen name="wear_compose_material3_body_large_font_size">16</dimen>
- <dimen name="wear_compose_material3_display_small_font_size">24</dimen>
- <dimen name="wear_compose_material3_display_medium_font_size">30</dimen>
- <dimen name="wear_compose_material3_display_large_font_size">40</dimen>
- <dimen name="wear_compose_material3_label_small_font_size">13</dimen>
- <dimen name="wear_compose_material3_label_medium_font_size">15</dimen>
- <dimen name="wear_compose_material3_label_large_font_size">20</dimen>
- <dimen name="wear_compose_material3_numeral_extra_small_font_size">24</dimen>
- <dimen name="wear_compose_material3_numeral_small_font_size">30</dimen>
- <dimen name="wear_compose_material3_numeral_medium_font_size">40</dimen>
- <dimen name="wear_compose_material3_numeral_large_font_size">50</dimen>
- <dimen name="wear_compose_material3_numeral_extra_large_font_size">60</dimen>
- <dimen name="wear_compose_material3_title_small_font_size">14</dimen>
- <dimen name="wear_compose_material3_title_medium_font_size">16</dimen>
- <dimen name="wear_compose_material3_title_large_font_size">20</dimen>
+ <dimen name="wear_compose_material3_arc_small_font_size">14sp</dimen>
+ <dimen name="wear_compose_material3_arc_medium_font_size">15sp</dimen>
+ <dimen name="wear_compose_material3_arc_large_font_size">20sp</dimen>
+ <dimen name="wear_compose_material3_body_extra_small_font_size">10sp</dimen>
+ <dimen name="wear_compose_material3_body_small_font_size">12sp</dimen>
+ <dimen name="wear_compose_material3_body_medium_font_size">14sp</dimen>
+ <dimen name="wear_compose_material3_body_large_font_size">16sp</dimen>
+ <dimen name="wear_compose_material3_display_small_font_size">24sp</dimen>
+ <dimen name="wear_compose_material3_display_medium_font_size">30sp</dimen>
+ <dimen name="wear_compose_material3_display_large_font_size">40sp</dimen>
+ <dimen name="wear_compose_material3_label_small_font_size">13sp</dimen>
+ <dimen name="wear_compose_material3_label_medium_font_size">15sp</dimen>
+ <dimen name="wear_compose_material3_label_large_font_size">20sp</dimen>
+ <dimen name="wear_compose_material3_numeral_extra_small_font_size">24sp</dimen>
+ <dimen name="wear_compose_material3_numeral_small_font_size">30sp</dimen>
+ <dimen name="wear_compose_material3_numeral_medium_font_size">40sp</dimen>
+ <dimen name="wear_compose_material3_numeral_large_font_size">50sp</dimen>
+ <dimen name="wear_compose_material3_numeral_extra_large_font_size">60sp</dimen>
+ <dimen name="wear_compose_material3_title_small_font_size">14sp</dimen>
+ <dimen name="wear_compose_material3_title_medium_font_size">16sp</dimen>
+ <dimen name="wear_compose_material3_title_large_font_size">20sp</dimen>
- <dimen name="wear_compose_material3_shape_corner_extra_small_size">4</dimen>
- <dimen name="wear_compose_material3_shape_corner_small_size">8</dimen>
- <dimen name="wear_compose_material3_shape_corner_medium_size">18</dimen>
- <dimen name="wear_compose_material3_shape_corner_large_size">26</dimen>
- <dimen name="wear_compose_material3_shape_corner_extra_large_size">36</dimen>
+ <dimen name="wear_compose_material3_shape_corner_extra_small_size">4dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_small_size">8dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_medium_size">18dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_large_size">26dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_extra_large_size">36dp</dimen>
</resources>
diff --git a/PermissionController/res/values-zh-rHK/strings.xml b/PermissionController/res/values-zh-rHK/strings.xml
index 3f278f113..07a401ab3 100644
--- a/PermissionController/res/values-zh-rHK/strings.xml
+++ b/PermissionController/res/values-zh-rHK/strings.xml
@@ -362,7 +362,7 @@
<string name="role_browser_request_title" msgid="2895200507835937192">"要將 <xliff:g id="APP_NAME">%1$s</xliff:g> 設為預設瀏覽器應用程式嗎?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"無需任何權限"</string>
<string name="role_dialer_label" msgid="1100224146343237968">"預設電話應用程式"</string>
- <string name="role_dialer_short_label" msgid="7186888549465352489">"手機應用程式"</string>
+ <string name="role_dialer_short_label" msgid="7186888549465352489">"電話應用程式"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"此類應用程式允許你使用自己的裝置撥打和接聽電話"</string>
<string name="role_dialer_request_title" msgid="5959618560705912058">"要將「<xliff:g id="APP_NAME">%1$s</xliff:g>」設為預設手機應用程式嗎?"</string>
<string name="role_dialer_request_description" msgid="6288839625724909320">"此應用程式將可存取你的相機、通訊錄、麥克風、電話及短訊"</string>
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 64642f403..13aea57b7 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -1234,7 +1234,9 @@
<permission-set name="virtual_device" />
<!-- For capturing audio from the app on the device. -->
<permission name="android.permission.RECORD_AUDIO" />
-
+ <permission
+ name="android.permission.ADD_MIRROR_DISPLAY"
+ featureFlag="android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole" />
<!--TODO(b/201605314) For calling Telecom framework API for audio streaming-->
<!--<permission name="android.permission.PROVIDE_CALL_ENDPOINTS" />-->
</permissions>
@@ -1304,6 +1306,8 @@
<permissions>
<permission-set name="nearby_devices" />
<permission-set name="virtual_device" />
+ <permission-set name="notifications"
+ featureFlag="android.companion.virtualdevice.flags.Flags.notificationsForDeviceStreaming" />
</permissions>
</role>
@@ -1830,4 +1834,39 @@
shortLabel="@string/role_wallet_short_label"
uiBehavior="v35.WalletRoleUiBehavior"/>
+ <role
+ name="android.app.role.SYSTEM_DEPENDENCY_INSTALLER"
+ allowBypassingQualification="true"
+ defaultHolders="config_systemDependencyInstaller"
+ exclusive="true"
+ exclusivity="user"
+ featureFlag="android.content.pm.Flags.sdkDependencyInstaller"
+ static="true"
+ systemOnly="true"
+ visible="false">
+ <required-components>
+ <service permission="android.permission.BIND_DEPENDENCY_INSTALLER">
+ <intent-filter>
+ <action name="android.content.pm.action.INSTALL_DEPENDENCY" />
+ </intent-filter>
+ </service>
+ </required-components>
+ <permissions>
+ <permission name="android.permission.ACCESS_SHARED_LIBRARIES" />
+ <permission name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES" />
+ </permissions>
+ </role>
+
+ <!---
+ ~ A role for testing cross-user roles (exclusivity="profileGroup"). This should never be used
+ ~ to gate any actual functionality.
+ -->
+ <role
+ name="android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY"
+ exclusive="true"
+ exclusivity="profileGroup"
+ featureFlag="com.android.permission.flags.Flags.crossUserRoleEnabled"
+ showNone="true"
+ visible="false"/>
+
</roles>
diff --git a/PermissionController/role-controller/Android.bp b/PermissionController/role-controller/Android.bp
index 612c979b5..9eacf975f 100644
--- a/PermissionController/role-controller/Android.bp
+++ b/PermissionController/role-controller/Android.bp
@@ -28,11 +28,13 @@ java_library {
libs: [
"androidx.annotation_annotation",
"com.android.permission.flags-aconfig-java",
+ "framework-annotations-lib",
],
static_libs: [
"modules-utils-build_system",
"android.app.appfunctions.exported-flags-aconfig-java",
"android.companion.virtualdevice.flags-aconfig-java-export",
+ "android.content.pm.flags-aconfig-java-export",
"android.permission.flags-aconfig-java-export",
"android.os.flags-aconfig-java-export",
],
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 67a37bdef..e3269a146 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
@@ -37,6 +37,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseBooleanArray;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -107,6 +108,14 @@ public class Role {
*/
public static final int EXCLUSIVITY_PROFILE_GROUP = 2;
+ /** Set of valid exclusivity values. */
+ private static final SparseBooleanArray sExclusivityValues = new SparseBooleanArray();
+ static {
+ sExclusivityValues.put(EXCLUSIVITY_NONE, true);
+ sExclusivityValues.put(EXCLUSIVITY_USER, true);
+ sExclusivityValues.put(EXCLUSIVITY_PROFILE_GROUP, true);
+ }
+
/**
* The name of this role. Must be unique.
*/
@@ -323,12 +332,24 @@ public class Role {
}
public boolean isExclusive() {
- // TODO(b/373390494): Allow RoleBehavior to override this getExclusivity
- return mExclusivity != EXCLUSIVITY_NONE;
+ return getExclusivity() != EXCLUSIVITY_NONE;
}
+ @Exclusivity
public int getExclusivity() {
- // TODO(b/373390494): Allow RoleBehavior to override this
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled() && mBehavior != null) {
+ Integer exclusivity = mBehavior.getExclusivity();
+ if (exclusivity != null) {
+ if (!sExclusivityValues.get(exclusivity)) {
+ throw new IllegalArgumentException("Invalid exclusivity: " + exclusivity);
+ }
+ if (mShowNone && exclusivity == EXCLUSIVITY_NONE) {
+ throw new IllegalArgumentException(
+ "Role cannot be non-exclusive when showNone is true: " + exclusivity);
+ }
+ return exclusivity;
+ }
+ }
return mExclusivity;
}
@@ -384,8 +405,6 @@ public class Role {
* @see #mShowNone
*/
public boolean shouldShowNone() {
- // TODO(b/373390494): Ensure RoleBehavior override doesn't conflict with this.
- // mShowNone can only be true if isExclusive=true
return mShowNone;
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
index 3849a50e3..86ca8e2ce 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
@@ -32,6 +32,14 @@ import java.util.List;
public interface RoleBehavior {
/**
+ * @see Role#getExclusivity()
+ */
+ @Nullable
+ default Integer getExclusivity() {
+ return null;
+ }
+
+ /**
* @see Role#onRoleAddedAsUser(UserHandle, Context)
*/
default void onRoleAddedAsUser(@NonNull Role role, @NonNull UserHandle user,
diff --git a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
index bc7562c11..a5ac5700e 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.role.controller.service;
+import android.annotation.UserIdInt;
import android.app.role.RoleControllerService;
import android.app.role.RoleManager;
import android.content.Context;
@@ -49,11 +50,21 @@ public class RoleControllerServiceImpl extends RoleControllerService {
private static final boolean DEBUG = false;
+ public static volatile SetActiveUserForRoleMethod sSetActiveUserForRoleMethod;
private UserHandle mUser;
private Context mContext;
private RoleManager mUserRoleManager;
+ /** Method for setting active user from role controller */
+ public interface SetActiveUserForRoleMethod {
+ /**
+ * Sets user as active for the given role.
+ * @see RoleManager#setActiveUserForRole(String, UserHandle, int)
+ */
+ void setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId, int flags);
+ }
+
public RoleControllerServiceImpl() {}
public RoleControllerServiceImpl(@NonNull UserHandle user, @NonNull Context context) {
@@ -232,6 +243,11 @@ public class RoleControllerServiceImpl extends RoleControllerService {
boolean added = false;
if (role.isExclusive()) {
+ if (role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ sSetActiveUserForRoleMethod.setActiveUserForRole(roleName, mUser.getIdentifier(),
+ flags);
+ }
+
List<String> currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
int currentPackageNamesSize = currentPackageNames.size();
for (int i = 0; i < currentPackageNamesSize; i++) {
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
new file mode 100644
index 000000000..f8a8502cd
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.os.Build;
+
+import androidx.annotation.ChecksSdkIntAtLeast;
+
+import com.android.modules.utils.build.SdkLevel;
+
+/** Util class for getting shared feature flag check logic. */
+public final class RoleFlags {
+ private RoleFlags() { /* cannot be instantiated */ }
+
+ /**
+ * Returns whether profile group exclusive roles are available. Profile exclusive roles are
+ * available on B+
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static boolean isProfileGroupExclusivityAvailable() {
+ // TODO(b/372743073): change to isAtLeastB once available
+ return SdkLevel.isAtLeastV() && com.android.permission.flags.Flags.crossUserRoleEnabled();
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java
new file mode 100644
index 000000000..05d69e998
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.compat;
+
+import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
+import android.permission.flags.Flags;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Helper AppOpsManager compat class
+ */
+public class AppOpsManagerCompat {
+
+ private AppOpsManagerCompat() {}
+
+ /**
+ * For platform version <= V, call the deprecated unsafeCheckOpRawNoThrow. For newer platforms,
+ * call the new API checkOpRawNoThrow.
+ *
+ * @return the raw mode of the op
+ */
+ // TODO: b/379749734
+ @SuppressWarnings("deprecation")
+ @SuppressLint("NewApi")
+ public static int checkOpRawNoThrow(@NonNull AppOpsManager appOpsManager, @NonNull String op,
+ int uid, @NonNull String packageName) {
+ if (Flags.checkOpOverloadApiEnabled()) {
+ return appOpsManager.checkOpRawNoThrow(op, uid, packageName, null);
+ } else {
+ return appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
index 1e44f16bd..3202f5b69 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.data
import android.app.AppOpsManager
import android.app.Application
import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.compat.AppOpsManagerCompat
/**
* A LiveData which represents the appop state
@@ -36,13 +37,13 @@ private constructor(
private val app: Application,
private val packageName: String,
private val op: String,
- private val uid: Int
+ private val uid: Int,
) : SmartUpdateMediatorLiveData<Int>() {
private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
override fun onUpdate() {
- value = appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName)
+ value = AppOpsManagerCompat.checkOpRawNoThrow(appOpsManager, op, uid, packageName)
}
override fun onActive() {
@@ -62,7 +63,7 @@ private constructor(
PermissionControllerApplication.get(),
key.first,
key.second,
- key.third
+ key.third,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
index 4a2d3b68a..2b6d9728e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
@@ -28,6 +28,7 @@ import android.app.Application
import android.os.Build
import android.os.UserHandle
import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.compat.AppOpsManagerCompat
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import kotlinx.coroutines.Job
@@ -46,7 +47,7 @@ object FullStoragePermissionAppsLiveData :
val packageName: String,
val user: UserHandle,
val isLegacy: Boolean,
- val isGranted: Boolean
+ val isGranted: Boolean,
)
init {
@@ -88,7 +89,7 @@ object FullStoragePermissionAppsLiveData :
fun getFullStorageStateForPackage(
appOpsManager: AppOpsManager,
packageInfo: LightPackageInfo,
- userHandle: UserHandle? = null
+ userHandle: UserHandle? = null,
): FullStoragePackageState? {
val sdk = packageInfo.targetSdkVersion
val user = userHandle ?: UserHandle.getUserHandleForUid(packageInfo.uid)
@@ -97,29 +98,31 @@ object FullStoragePermissionAppsLiveData :
packageInfo.packageName,
user,
isLegacy = true,
- isGranted = true
+ isGranted = true,
)
} else if (
sdk <= Build.VERSION_CODES.Q &&
- appOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManagerCompat.checkOpRawNoThrow(
+ appOpsManager,
OPSTR_LEGACY_STORAGE,
packageInfo.uid,
- packageInfo.packageName
+ packageInfo.packageName,
) == MODE_ALLOWED
) {
return FullStoragePackageState(
packageInfo.packageName,
user,
isLegacy = true,
- isGranted = true
+ isGranted = true,
)
}
if (MANAGE_EXTERNAL_STORAGE in packageInfo.requestedPermissions) {
val mode =
- appOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManagerCompat.checkOpRawNoThrow(
+ appOpsManager,
OPSTR_MANAGE_EXTERNAL_STORAGE,
packageInfo.uid,
- packageInfo.packageName
+ packageInfo.packageName,
)
val granted =
mode == MODE_ALLOWED ||
@@ -130,7 +133,7 @@ object FullStoragePermissionAppsLiveData :
packageInfo.packageName,
user,
isLegacy = false,
- isGranted = granted
+ isGranted = granted,
)
}
return null
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index a4f629d80..c1479caf2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -46,7 +46,6 @@ import android.app.KeyguardManager;
import android.app.ecm.EnhancedConfirmationManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -195,7 +194,7 @@ public class GrantPermissionsActivity extends SettingsActivity
/** A list of permissions requested on an app's behalf by the system. Usually Implicitly
* requested, although this isn't necessarily always the case.
*/
- private List<String> mSystemRequestedPermissions = new ArrayList<>();
+ private final List<String> mSystemRequestedPermissions = new ArrayList<>();
/** A copy of the list of permissions originally requested in the intent to this activity */
private String[] mOriginalRequestedPermissions = new String[0];
@@ -209,7 +208,7 @@ public class GrantPermissionsActivity extends SettingsActivity
* A list of other GrantPermissionActivities for the same package which passed their list of
* permissions to this one. They need to be informed when this activity finishes.
*/
- private List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
+ private final List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
/** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */
private boolean mDelegated;
@@ -235,7 +234,7 @@ public class GrantPermissionsActivity extends SettingsActivity
private PackageManager mPackageManager;
- private ActivityResultLauncher<Intent> mShowWarningDialog =
+ private final ActivityResultLauncher<Intent> mShowWarningDialog =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
@@ -284,7 +283,7 @@ public class GrantPermissionsActivity extends SettingsActivity
return;
}
try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(mTargetPackage, 0);
+ mPackageManager.getPackageInfo(mTargetPackage, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Unable to get package info for the calling package.", e);
finishAfterTransition();
@@ -314,20 +313,23 @@ public class GrantPermissionsActivity extends SettingsActivity
.getPackageManager();
}
- // When the dialog is streamed to a remote device, verify requested permissions are all
- // device aware and target device is the same as the remote device. Otherwise show a
- // warning dialog.
+ // When the permission grant dialog is streamed to a virtual device, and when requested
+ // permissions include both device-aware permissions and non-device aware permissions,
+ // device-aware permissions will use virtual device id and non-device aware permissions
+ // will use default device id for granting. If flag is not enabled, we would show a
+ // warning dialog for this use case.
if (getDeviceId() != ContextCompat.DEVICE_ID_DEFAULT) {
boolean showWarningDialog = mTargetDeviceId != getDeviceId();
for (String permission : mRequestedPermissions) {
- if (!MultiDeviceUtils.isPermissionDeviceAware(
- getApplicationContext(), mTargetDeviceId, permission)) {
+ if (!MultiDeviceUtils.isPermissionDeviceAware(getApplicationContext(),
+ mTargetDeviceId, permission)) {
showWarningDialog = true;
+ break;
}
}
- if (showWarningDialog) {
+ if (showWarningDialog && !Flags.allowHostPermissionDialogsOnVirtualDevices()) {
mShowWarningDialog.launch(
new Intent(this, PermissionDialogStreamingBlockedActivity.class));
return;
@@ -1115,9 +1117,17 @@ public class GrantPermissionsActivity extends SettingsActivity
if ((mDelegated || (mViewModel != null && mViewModel.shouldReturnPermissionState()))
&& mTargetPackage != null) {
+ PackageManager defaultDevicePackageManager = SdkLevel.isAtLeastV()
+ && mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT
+ ? createDeviceContext(ContextCompat.DEVICE_ID_DEFAULT).getPackageManager()
+ : mPackageManager;
+ PackageManager targetDevicePackageManager = mPackageManager;
for (int i = 0; i < resultPermissions.length; i++) {
- grantResults[i] =
- mPackageManager.checkPermission(resultPermissions[i], mTargetPackage);
+ String permission = resultPermissions[i];
+ PackageManager pm = MultiDeviceUtils.isPermissionDeviceAware(
+ getApplicationContext(), mTargetDeviceId, permission)
+ ? targetDevicePackageManager : defaultDevicePackageManager;
+ grantResults[i] = pm.checkPermission(resultPermissions[i], mTargetPackage);
}
} else {
grantResults = new int[0];
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index 0a01929e6..1e5b96c2e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -29,6 +29,7 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.admin.DevicePolicyManager
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED
@@ -41,6 +42,8 @@ import android.os.Build
import android.os.Bundle
import android.os.Process
import android.permission.PermissionManager
+import android.permission.flags.Flags
+import android.util.ArrayMap
import android.util.Log
import androidx.core.util.Consumer
import androidx.lifecycle.ViewModel
@@ -116,6 +119,7 @@ import com.android.permissioncontroller.permission.utils.SafetyNetLogger
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils
+import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils.isPermissionDeviceAware
/**
* ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by the
@@ -153,6 +157,21 @@ class GrantPermissionsViewModel(
} else {
null
}
+ private val permissionGroupToDeviceIdMap: Map<String, Int> =
+ if (SdkLevel.isAtLeastV() && Flags.allowHostPermissionDialogsOnVirtualDevices()) {
+ requestedPermissions
+ .filter({ PermissionMapping.getGroupOfPlatformPermission(it) != null })
+ .associateBy({ PermissionMapping.getGroupOfPlatformPermission(it)!! }, {
+ if (isPermissionDeviceAware(
+ app.applicationContext,
+ deviceId,
+ it
+ )
+ ) deviceId else Context.DEVICE_ID_DEFAULT
+ })
+ } else {
+ ArrayMap()
+ }
private val dpm = app.getSystemService(DevicePolicyManager::class.java)!!
private val permissionPolicy = dpm.getPermissionPolicy(null)
private val groupStates = mutableMapOf<String, GroupState>()
@@ -314,7 +333,8 @@ class GrantPermissionsViewModel(
}
val getLiveDataFun = { groupName: String ->
- LightAppPermGroupLiveData[packageName, groupName, user, deviceId]
+ LightAppPermGroupLiveData[packageName, groupName, user,
+ permissionGroupToDeviceIdMap.get(groupName) ?: deviceId]
}
setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun)
}
@@ -398,7 +418,8 @@ class GrantPermissionsViewModel(
safetyLabel,
groupState.group.permGroupName
),
- deviceId
+ permissionGroupToDeviceIdMap.get(groupState.group.permGroupName)
+ ?: deviceId
)
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
index f94999626..6dabe8ab7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
@@ -119,7 +119,7 @@ class NumCustomPermGroupsWithPackagesLiveData() : SmartUpdateMediatorLiveData<In
/**
* A LiveData that tracks the names of the platform-defined permission groups, such that at least
* one of the permissions in the group has been requested at runtime by at least one non-system
- * application or has been pregranted to a non-system application.
+ * application.
*
* @param app The current application of the fragment
*/
@@ -130,10 +130,7 @@ class UsedStandardPermGroupNamesLiveData(private val app: Application) :
permGroups ->
if (permGroups.values.any { it != null }) {
value =
- permGroups
- .filterValues { it != null && it.nonSystemUserSetOrPreGranted > 0 }
- .keys
- .toList()
+ permGroups.filterValues { it != null && it.nonSystemTotal > 0 }.keys.toList()
}
}
}
@@ -145,8 +142,7 @@ class UsedStandardPermGroupNamesLiveData(private val app: Application) :
/**
* A LiveData that tracks the names of the platform-defined permission groups, such that none of the
- * the permissions in the group has been requested at runtime by any non-system application nor has
- * been pregranted to any non-system application.
+ * the permissions in the group has been requested at runtime by any non-system application.
*
* @param app The current application of the fragment
*/
@@ -155,11 +151,7 @@ class UnusedStandardPermGroupNamesLiveData(private val app: Application) :
init {
addSource(PermGroupsPackagesUiInfoLiveData(app, StandardPermGroupNamesLiveData)) {
permGroups ->
- value =
- permGroups
- .filterValues { it != null && it.nonSystemUserSetOrPreGranted == 0 }
- .keys
- .toList()
+ value = permGroups.filterValues { it != null && it.nonSystemTotal == 0 }.keys.toList()
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
index 50a19e571..1498b91b6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
@@ -22,7 +22,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
-import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
@@ -43,6 +42,7 @@ import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipTo
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl
import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
@@ -58,9 +58,8 @@ fun WearGrantPermissionsScreen(
val locationVisibilities = viewModel.locationVisibilitiesLiveData.observeAsState(emptyList())
val preciseLocationChecked = viewModel.preciseLocationCheckedLiveData.observeAsState(false)
val buttonVisibilities = viewModel.buttonVisibilitiesLiveData.observeAsState(emptyList())
- val useMaterial3Controls = Flags.wearComposeMaterial3()
val materialUIVersion =
- if (useMaterial3Controls) {
+ if (ResourceHelper.material3Enabled) {
MATERIAL3
} else {
MATERIAL2_5
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
index e0adf1265..1d660ca35 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
@@ -16,12 +16,16 @@
package com.android.permissioncontroller.permission.ui.wear.elements.material3
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredSizeIn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.Hyphens
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonColors
import androidx.wear.compose.material3.ButtonDefaults
@@ -79,9 +83,9 @@ fun WearPermissionButton(
}
@Composable
-private fun WearPermissionButtonInternal(
- label: String,
+internal fun WearPermissionButtonInternal(
modifier: Modifier = Modifier,
+ label: String? = null,
iconBuilder: WearPermissionIconBuilder? = null,
labelMaxLines: Int? = null,
secondaryLabel: String? = null,
@@ -89,18 +93,31 @@ private fun WearPermissionButtonInternal(
onClick: () -> Unit,
enabled: Boolean = true,
colors: ButtonColors = ButtonDefaults.filledTonalButtonColors(),
+ contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
+ requiresMinimumHeight: Boolean = true,
) {
+ val minHeight: Dp =
+ if (requiresMinimumHeight) {
+ 0.dp
+ } else {
+ 1.dp
+ }
val iconParam: (@Composable BoxScope.() -> Unit)? = iconBuilder?.let { { it.build() } }
-
- val labelParam: (@Composable RowScope.() -> Unit) = {
- Text(
- text = label,
- modifier = Modifier.fillMaxWidth(),
- maxLines = labelMaxLines ?: LocalTextConfiguration.current.maxLines,
- style =
- LocalTextStyle.current.copy(fontWeight = FontWeight.W600, hyphens = Hyphens.Auto),
- )
- }
+ val labelParam: (@Composable RowScope.() -> Unit)? =
+ label?.let {
+ {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = labelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ style =
+ LocalTextStyle.current.copy(
+ fontWeight = FontWeight.W600,
+ hyphens = Hyphens.Auto,
+ ),
+ )
+ }
+ }
val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
secondaryLabel?.let {
@@ -115,11 +132,12 @@ private fun WearPermissionButtonInternal(
Button(
icon = iconParam,
- label = labelParam,
+ label = labelParam ?: {},
secondaryLabel = secondaryLabelParam,
enabled = enabled,
onClick = onClick,
- modifier = modifier.fillMaxWidth(),
+ modifier = modifier.requiredSizeIn(minHeight = minHeight).fillMaxWidth(),
+ contentPadding = contentPadding,
colors = colors,
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
index 65a85db7e..b7521d073 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
@@ -23,8 +23,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
-import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.IconButtonDefaults
import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter
/**
@@ -54,7 +54,7 @@ class WearPermissionIconBuilder private constructor() {
var contentDescription: String? = null
private set
- var modifier: Modifier = Modifier.size(ButtonDefaults.IconSize)
+ var modifier: Modifier = Modifier.size(IconButtonDefaults.LargeIconSize)
private set
var tint: Color = Color.Unspecified
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt
index 807c93370..10125c873 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt
@@ -16,18 +16,14 @@
package com.android.permissioncontroller.permission.ui.wear.elements.material3
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
-import androidx.wear.compose.material3.Text
import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
+/** This component is creates a transparent styled button to use as a list footer. */
@Composable
fun WearPermissionListFooter(
materialUIVersion: WearPermissionMaterialUIVersion,
@@ -42,26 +38,14 @@ fun WearPermissionListFooter(
onClick = onClick,
)
} else {
- WearPermissionListFooterInternal(label, iconBuilder, onClick)
- }
-}
-
-@Composable
-private fun WearPermissionListFooterInternal(
- label: String,
- iconBuilder: WearPermissionIconBuilder?,
- onClick: () -> Unit,
-) {
- val footerTextComposable: (@Composable RowScope.() -> Unit) = {
- Text(modifier = Modifier.fillMaxWidth(), text = label, maxLines = Int.MAX_VALUE)
+ WearPermissionButtonInternal(
+ iconBuilder = iconBuilder,
+ secondaryLabel = label,
+ secondaryLabelMaxLines = Int.MAX_VALUE,
+ onClick = onClick,
+ contentPadding = PaddingValues(0.dp),
+ colors = ButtonDefaults.childButtonColors(),
+ requiresMinimumHeight = false,
+ )
}
- Button(
- icon = { iconBuilder?.build() },
- label = {},
- secondaryLabel = footerTextComposable,
- enabled = true,
- onClick = onClick,
- modifier = Modifier.requiredHeightIn(min = 1.dp).fillMaxWidth(),
- colors = ButtonDefaults.childButtonColors(),
- )
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt
index 3c2c38578..c7ed0958c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt
@@ -16,6 +16,7 @@
package com.android.permissioncontroller.permission.ui.wear.theme
import android.content.Context
+import android.os.SystemProperties
import androidx.annotation.ColorRes
import androidx.annotation.DimenRes
import androidx.annotation.DoNotInline
@@ -23,6 +24,14 @@ import androidx.annotation.StringRes
import androidx.compose.ui.graphics.Color
internal object ResourceHelper {
+
+ private const val MATERIAL3_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"
+
+ val material3Enabled: Boolean
+ get() {
+ return SystemProperties.getBoolean(MATERIAL3_ENABLED_SYSPROP, false)
+ }
+
@DoNotInline
fun getColor(context: Context, @ColorRes id: Int): Color? {
return try {
@@ -45,7 +54,7 @@ internal object ResourceHelper {
@DoNotInline
fun getDimen(context: Context, @DimenRes id: Int): Float? {
return try {
- context.resources.getDimension(id)
+ context.resources.getDimension(id) / context.resources.displayMetrics.density
} catch (e: Exception) {
null
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
index adf179be6..8823bee07 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
@@ -30,7 +30,6 @@ import androidx.wear.compose.material.Colors
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Typography
import androidx.wear.compose.material3.MaterialTheme as Material3Theme
-import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
@@ -55,7 +54,8 @@ fun WearPermissionTheme(
WearPermissionLegacyTheme(content)
} else {
// Whether we are ready to use material3 for any screen.
- val useBridgedTheme = Flags.wearComposeMaterial3()
+ val useBridgedTheme = ResourceHelper.material3Enabled
+
// Material3 UI controls are still being used in the screen that the theme is applied
if (version == MATERIAL3) {
val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index e5de63f32..3d3b47272 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -126,6 +126,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
+import java.util.function.Supplier;
public final class Utils {
@@ -1566,18 +1567,40 @@ public final class Utils {
public static String getEnterpriseString(@NonNull Context context,
@NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) {
return SdkLevel.isAtLeastT()
- ? getUpdatableEnterpriseString(
- context, updatableStringId, defaultStringId, formatArgs)
+ ? getUpdatableEnterpriseString(context, updatableStringId,
+ () -> context.getString(defaultStringId, formatArgs), formatArgs)
: context.getString(defaultStringId, formatArgs);
}
+ /**
+ * Selects the appropriate enterprise string for the provided resource ID and a fallback string
+ */
+ @NonNull
+ public static String getEnterpriseString(@NonNull Context context,
+ @NonNull String updatableStringId, @NonNull String defaultString) {
+ return SdkLevel.isAtLeastT()
+ ? getUpdatableEnterpriseString(context, updatableStringId, () -> defaultString)
+ : defaultString;
+ }
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@NonNull
private static String getUpdatableEnterpriseString(@NonNull Context context,
- @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) {
+ @NonNull String updatableStringId, @NonNull Supplier<String> defaultStringLoader,
+ @NonNull Object... formatArgs) {
DevicePolicyManager dpm = getSystemServiceSafe(context, DevicePolicyManager.class);
- return dpm.getResources().getString(updatableStringId, () -> context.getString(
- defaultStringId, formatArgs), formatArgs);
+ return dpm.getResources().getString(updatableStringId, defaultStringLoader, formatArgs);
+ }
+
+ /**
+ * Returns the profile label from the {@link UserManager} for the provided profile
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
+ public static String getProfileLabel(@NonNull UserHandle profile, @NonNull Context context) {
+ Context profileContext = context.createContextAsUser(profile, 0);
+ UserManager profileUserManager = profileContext.getSystemService(UserManager.class);
+ return profileUserManager.getProfileLabel();
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
index 0b96eb8ba..48472bc5e 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -36,6 +37,7 @@ import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.role.utils.PackageUtils;
@@ -145,15 +147,25 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
addMoreDefaultAppsPreference(preferenceScreen, oldPreferences, context);
addManageDomainUrlsPreference(preferenceScreen, oldPreferences, context);
if (hasWorkProfile && !workRoleItems.isEmpty()) {
+ String defaultWorkTitle;
+ if (SdkLevel.isAtLeastV() && Flags.useProfileLabelsForDefaultAppSectionTitles()) {
+ defaultWorkTitle = Utils.getProfileLabel(mViewModel.getWorkProfile(), context);
+ } else {
+ defaultWorkTitle = context.getString(R.string.default_apps_for_work);
+ }
String workTitle = Utils.getEnterpriseString(context,
- DefaultAppSettings.WORK_PROFILE_DEFAULT_APPS_TITLE,
- R.string.default_apps_for_work);
+ DefaultAppSettings.WORK_PROFILE_DEFAULT_APPS_TITLE, defaultWorkTitle);
addPreferenceCategory(oldWorkPreferenceCategory, PREFERENCE_KEY_WORK_CATEGORY,
workTitle, preferenceScreen, workRoleItems, oldWorkPreferences, this,
mViewModel.getWorkProfile(), context);
}
if (hasPrivateProfile && !privateRoleItems.isEmpty()) {
- String privateTitle = context.getString(R.string.default_apps_for_private_profile);
+ String privateTitle;
+ if (SdkLevel.isAtLeastV() && Flags.useProfileLabelsForDefaultAppSectionTitles()) {
+ privateTitle = Utils.getProfileLabel(mViewModel.getPrivateProfile(), context);
+ } else {
+ privateTitle = context.getString(R.string.default_apps_for_private_profile);
+ }
addPreferenceCategory(oldPrivatePreferenceCategory, PREFERENCE_KEY_PRIVATE_CATEGORY,
privateTitle, preferenceScreen, privateRoleItems, oldPrivatePreferences, this,
mViewModel.getPrivateProfile(), context);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
index ee8fe2545..aa9b31e0d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
@@ -30,7 +30,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
@@ -40,6 +39,7 @@ import com.android.permissioncontroller.permission.ui.wear.elements.material3.We
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionListFooter
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlStyle
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
@@ -78,10 +78,8 @@ fun WearRequestRoleScreen(
helper.initializeSelectedPackageName()
}
}
-
- val useMaterial3Controls = Flags.wearComposeMaterial3()
val materialUIVersion =
- if (useMaterial3Controls) {
+ if (ResourceHelper.material3Enabled) {
MATERIAL3
} else {
MATERIAL2_5
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java
index 3ab4faa66..54cb86ff3 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java
@@ -26,7 +26,7 @@ import android.view.ViewGroup;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
+import androidx.preference.SwitchPreferenceCompat;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel;
@@ -38,7 +38,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsVie
* method will not register any changes while it appears disabled.
*/
@RequiresApi(TIRAMISU)
-public class ClickableDisabledSwitchPreference extends SwitchPreference {
+public class ClickableDisabledSwitchPreference extends SwitchPreferenceCompat {
private boolean mAppearDisabled;
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
index 7622270b9..88759797e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
@@ -52,6 +52,7 @@ import androidx.preference.PreferenceViewHolder;
import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
+import com.android.settingslib.widget.GroupSectionDividerMixin;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.shape.AbsoluteCornerSize;
@@ -62,7 +63,8 @@ import java.util.Objects;
/** A preference that displays a card representing a {@link SafetyCenterIssue}. */
@RequiresApi(TIRAMISU)
-public class IssueCardPreference extends Preference implements ComparablePreference {
+public class IssueCardPreference extends Preference
+ implements ComparablePreference, GroupSectionDividerMixin {
public static final String TAG = IssueCardPreference.class.getSimpleName();
@@ -101,12 +103,13 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- holder.itemView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId());
+ View issueCardView = holder.itemView.requireViewById(R.id.issue_card);
+ issueCardView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId());
int topMargin = getTopMargin(mPositionInCardList, getContext());
- MarginLayoutParams layoutParams = (MarginLayoutParams) holder.itemView.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) issueCardView.getLayoutParams();
if (layoutParams.topMargin != topMargin) {
layoutParams.topMargin = topMargin;
- holder.itemView.setLayoutParams(layoutParams);
+ issueCardView.setLayoutParams(layoutParams);
}
// Set default group visibility in case view is being reused
@@ -202,19 +205,20 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
private void configureSafetyProtectionView(PreferenceViewHolder holder) {
View safetyProtectionSectionView =
holder.findViewById(R.id.issue_card_protected_by_android);
+ View issueCard = holder.findViewById(R.id.issue_card);
if (safetyProtectionSectionView.getVisibility() == View.GONE) {
- holder.itemView.setPaddingRelative(
- holder.itemView.getPaddingStart(),
- holder.itemView.getPaddingTop(),
- holder.itemView.getPaddingEnd(),
+ issueCard.setPaddingRelative(
+ issueCard.getPaddingStart(),
+ issueCard.getPaddingTop(),
+ issueCard.getPaddingEnd(),
/* bottom= */ getContext()
.getResources()
.getDimensionPixelSize(R.dimen.sc_card_margin_bottom));
} else {
- holder.itemView.setPaddingRelative(
- holder.itemView.getPaddingStart(),
- holder.itemView.getPaddingTop(),
- holder.itemView.getPaddingEnd(),
+ issueCard.setPaddingRelative(
+ issueCard.getPaddingStart(),
+ issueCard.getPaddingTop(),
+ issueCard.getPaddingEnd(),
/* bottom= */ 0);
}
}
@@ -427,9 +431,7 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
TypedValue buttonThemeValue = new TypedValue();
mContext.getTheme()
.resolveAttribute(
- R.attr.scActionButtonTheme,
- buttonThemeValue,
- /* resolveRefs= */ false);
+ R.attr.scActionButtonTheme, buttonThemeValue, /* resolveRefs= */ false);
mContextThemeWrapper = new ContextThemeWrapper(context, buttonThemeValue.data);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt
index a63e19984..5c86b4515 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt
@@ -24,6 +24,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
import com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView
+import com.android.settingslib.widget.GroupSectionDividerMixin
/** A preference that displays a card linking to a list of more {@link SafetyCenterIssue}. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@@ -34,8 +35,8 @@ internal class MoreIssuesCardPreference(
private var newMoreIssuesCardData: MoreIssuesCardData,
private val dismissedOnly: Boolean,
val isStaticHeader: Boolean,
- private val onClickListener: () -> Unit
-) : Preference(context), ComparablePreference {
+ private val onClickListener: () -> Unit,
+) : Preference(context), ComparablePreference, GroupSectionDividerMixin {
init {
layoutResource = R.layout.preference_more_issues_card
@@ -44,11 +45,12 @@ internal class MoreIssuesCardPreference(
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
- val issueHeaderView = holder.itemView as MoreIssuesHeaderView
+ val issueHeaderView =
+ holder.itemView.requireViewById<MoreIssuesHeaderView>(R.id.more_issues_card)
if (isStaticHeader) {
issueHeaderView.showStaticHeader(
context.getString(R.string.safety_center_dismissed_issues_card_title),
- newMoreIssuesCardData.severityLevel
+ newMoreIssuesCardData.severityLevel,
)
} else {
issueHeaderView.showExpandableHeader(
@@ -62,7 +64,7 @@ internal class MoreIssuesCardPreference(
}
),
overrideChevronIconResId,
- onClickListener
+ onClickListener,
)
}
}
@@ -94,5 +96,5 @@ internal class MoreIssuesCardPreference(
internal data class MoreIssuesCardData(
val severityLevel: Int,
val hiddenIssueCount: Int,
- val isExpanded: Boolean
+ val isExpanded: Boolean,
)
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
index 57e4175ca..c5287af53 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
@@ -28,11 +28,12 @@ import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
import com.android.permissioncontroller.R
import com.android.permissioncontroller.safetycenter.SafetyCenterConstants
+import com.android.settingslib.widget.GroupSectionDividerMixin
/** A preference that displays the Security and Privacy brand name on a Safety Center subpage. */
@RequiresApi(UPSIDE_DOWN_CAKE)
internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet) :
- Preference(context, attrs) {
+ Preference(context, attrs), GroupSectionDividerMixin {
init {
setLayoutResource(R.layout.preference_brand_chip)
@@ -67,7 +68,7 @@ internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet)
fun closeSubpage(
fragmentActivity: FragmentActivity,
fragmentContext: Context,
- sessionId: Long
+ sessionId: Long,
) {
val openedFromHomepage =
fragmentActivity
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
index c6f2d146f..04206479f 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
@@ -29,6 +29,8 @@ import static com.android.permissioncontroller.safetycenter.SafetyCenterConstant
import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PRIVATE_PROFILE_SUFFIX;
import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.WORK_PROFILE_SUFFIX;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActionBar;
import android.content.Intent;
import android.content.res.Configuration;
@@ -60,6 +62,7 @@ import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel.Pref;
import com.android.settingslib.activityembedding.ActivityEmbeddingUtils;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.settingslib.widget.SettingsThemeHelper;
import java.util.List;
import java.util.Objects;
@@ -78,10 +81,10 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
private static final String EXTRA_PREVENT_TRAMPOLINE_TO_SETTINGS =
"com.android.permissioncontroller.safetycenter.extra.PREVENT_TRAMPOLINE_TO_SETTINGS";
- private SafetyCenterManager mSafetyCenterManager;
+ @Nullable private SafetyCenterManager mSafetyCenterManager;
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSafetyCenterManager = getSystemService(SafetyCenterManager.class);
@@ -93,18 +96,25 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
return;
}
+ if (SettingsThemeHelper.isExpressiveTheme(this)) {
+ // Setting a theme programmatically causes standard preferences to display weirdly.
+ // See b/377519324.
+ setTheme(R.style.Theme_SafetyCenterExpressive);
+ }
+
Fragment frag;
+ Intent intent = getIntent();
final boolean maybeOpenSubpage =
SafetyCenterUiFlags.getShowSubpages()
- && getIntent().getAction().equals(ACTION_SAFETY_CENTER);
- if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) {
- String groupId = getIntent().getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID);
+ && Objects.equals(intent.getAction(), ACTION_SAFETY_CENTER);
+ if (maybeOpenSubpage && intent.hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) {
+ String groupId = intent.getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID);
frag = openRelevantSubpage(groupId);
- } else if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) {
- String preferenceKey = getIntent().getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY);
+ } else if (maybeOpenSubpage && intent.hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) {
+ String preferenceKey = intent.getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY);
String groupId = getParentGroupId(preferenceKey);
frag = openRelevantSubpage(groupId);
- } else if (getIntent().getAction().equals(PRIVACY_CONTROLS_ACTION)) {
+ } else if (Objects.equals(intent.getAction(), PRIVACY_CONTROLS_ACTION)) {
setTitle(R.string.privacy_controls_title);
frag = PrivacyControlsFragment.newInstance();
} else {
@@ -306,7 +316,8 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
return PRIVACY_SOURCES_GROUP_ID;
}
- SafetyCenterConfig safetyCenterConfig = mSafetyCenterManager.getSafetyCenterConfig();
+ SafetyCenterConfig safetyCenterConfig =
+ requireNonNull(mSafetyCenterManager).getSafetyCenterConfig();
String[] splitKey;
if (preferenceKey.endsWith(PERSONAL_PROFILE_SUFFIX)) {
splitKey = preferenceKey.split("_" + PERSONAL_PROFILE_SUFFIX);
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index efbd57080..ed6bc382c 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -56,6 +56,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.safetycenter.internaldata.SafetyCenterBundles;
import com.android.safetycenter.resources.SafetyCenterResourcesApk;
+import com.android.settingslib.widget.SettingsThemeHelper;
import kotlin.Unit;
@@ -121,12 +122,16 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
mEntriesGroup = getPreferenceScreen().findPreference(ENTRIES_GROUP_KEY);
mStaticEntriesGroup = getPreferenceScreen().findPreference(STATIC_ENTRIES_GROUP_KEY);
+ Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY);
+ if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
+ getPreferenceScreen().removePreference(spacerPreference);
+ }
+
if (mIsQuickSettingsFragment) {
getPreferenceScreen().removePreference(mEntriesGroup);
mEntriesGroup = null;
getPreferenceScreen().removePreference(mStaticEntriesGroup);
mStaticEntriesGroup = null;
- Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY);
getPreferenceScreen().removePreference(spacerPreference);
}
getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus);
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
index 9feecf5d4..04503de5e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
@@ -22,7 +22,6 @@ import android.safetycenter.SafetyCenterErrorDetails
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModelProvider
-import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.RecyclerView
import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
@@ -33,10 +32,12 @@ import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterVi
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
import com.android.safetycenter.resources.SafetyCenterResourcesApk
+import com.android.settingslib.widget.SettingsBasePreferenceFragment
+import com.android.settingslib.widget.SettingsThemeHelper
/** A base fragment that represents a page in Safety Center. */
@RequiresApi(TIRAMISU)
-abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
+abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() {
lateinit var safetyCenterViewModel: SafetyCenterViewModel
lateinit var sameTaskSourceIds: List<String>
@@ -51,12 +52,17 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
override fun onCreateAdapter(
preferenceScreen: PreferenceScreen
- ): RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ ): RecyclerView.Adapter<out RecyclerView.ViewHolder> {
/* The scroll-to-result functionality for settings search is currently implemented only for
* subpages i.e. non expand-and-collapse type entries. Hence, we check that the flag is
* enabled before using an adapter that does the highlighting and scrolling. */
- val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
- if (SafetyCenterUiFlags.getShowSubpages()) {
+ val adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder> =
+ if (
+ SafetyCenterUiFlags.getShowSubpages() &&
+ !SettingsThemeHelper.isExpressiveTheme(requireContext())
+ ) {
+ // TODO: b/378433878 - Create highlight adapter for settings expressive theme, which
+ // has a different base class.
highlightManager.createAdapter(preferenceScreen)
} else {
super.onCreateAdapter(preferenceScreen)
@@ -80,7 +86,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
safetyCenterViewModel =
ViewModelProvider(
requireActivity(),
- LiveSafetyCenterViewModelFactory(requireActivity().getApplication())
+ LiveSafetyCenterViewModelFactory(requireActivity().getApplication()),
)
.get(SafetyCenterViewModel::class.java)
safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? ->
@@ -177,7 +183,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
safetyCenterViewModel.interactionLogger.recordForIssue(
Action.SAFETY_CENTER_VIEWED,
maybeIssue,
- isDismissed = false
+ isDismissed = false,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java
index 2ad282449..d9f45cc08 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java
@@ -24,7 +24,9 @@ import android.permission.PermissionManager;
import androidx.fragment.app.FragmentActivity;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.settingslib.widget.SettingsThemeHelper;
/** Activity for the Safety Center Quick Settings Activity */
public class SafetyCenterQsActivity extends FragmentActivity {
@@ -39,6 +41,12 @@ public class SafetyCenterQsActivity extends FragmentActivity {
return;
}
+ if (SettingsThemeHelper.isExpressiveTheme(this)) {
+ // Safe to set expressive theme here since QS doesn't display vanilla preferences.
+ // See b/377519324.
+ setTheme(R.style.Theme_SafetyCenterQsExpressive);
+ }
+
configureFragment();
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
index fdade2189..2e5ba49ae 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
@@ -28,6 +28,7 @@ import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreferenc
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.settingslib.widget.FooterPreference
+import com.android.settingslib.widget.SettingsThemeHelper
/** A fragment that represents a generic subpage in Safety Center. */
@RequiresApi(UPSIDE_DOWN_CAKE)
@@ -45,15 +46,16 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
setPreferencesFromResource(R.xml.safety_center_subpage, rootKey)
sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!!
- subpageBrandChip = getPreferenceScreen().findPreference(BRAND_CHIP_KEY)!!
- subpageIllustration = getPreferenceScreen().findPreference(ILLUSTRATION_KEY)!!
- subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)!!
- subpageEntryGroup = getPreferenceScreen().findPreference(ENTRY_GROUP_KEY)!!
- subpageFooter = getPreferenceScreen().findPreference(FOOTER_KEY)!!
+ subpageBrandChip = preferenceScreen.findPreference(BRAND_CHIP_KEY)!!
+ subpageIllustration = preferenceScreen.findPreference(ILLUSTRATION_KEY)!!
+ subpageIssueGroup = preferenceScreen.findPreference(ISSUE_GROUP_KEY)!!
+ subpageEntryGroup = preferenceScreen.findPreference(ENTRY_GROUP_KEY)!!
+ subpageFooter = preferenceScreen.findPreference(FOOTER_KEY)!!
subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId)
setupIllustration()
setupFooter()
+ maybeRemoveSpacer()
prerenderCurrentSafetyCenterData()
}
@@ -80,7 +82,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
return
}
- requireActivity().setTitle(entryGroup.title)
+ requireActivity().title = entryGroup.title
updateSafetyCenterIssues(uiData)
updateSafetyCenterEntries(entryGroup)
}
@@ -91,7 +93,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme)
if (drawable == null) {
Log.w(TAG, "$sourceGroupId doesn't have any matching illustration")
- subpageIllustration.setVisible(false)
+ subpageIllustration.isVisible = false
}
subpageIllustration.illustrationDrawable = drawable
@@ -102,12 +104,19 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName)
if (footerText.isEmpty()) {
Log.w(TAG, "$sourceGroupId doesn't have any matching footer")
- subpageFooter.setVisible(false)
+ subpageFooter.isVisible = false
}
// footer is ordered last by default
// in order to keep a spacer after the footer, footer needs to be the second from last
- subpageFooter.setOrder(Int.MAX_VALUE - 2)
- subpageFooter.setSummary(footerText)
+ subpageFooter.order = Int.MAX_VALUE - 2
+ subpageFooter.summary = footerText
+ }
+
+ private fun maybeRemoveSpacer() {
+ if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
+ val spacerPreference = preferenceScreen.findPreference<SpacerPreference>(SPACER_KEY)!!
+ preferenceScreen.removePreference(spacerPreference)
+ }
}
private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) {
@@ -131,7 +140,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
subpageIssues,
subpageDismissedIssues,
uiData.resolvedIssues,
- requireActivity().getTaskId()
+ requireActivity().taskId,
)
}
@@ -145,23 +154,24 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
PendingIntentSender.getTaskIdForEntry(
entry.id,
sameTaskSourceIds,
- requireActivity()
+ requireActivity(),
),
entry,
- safetyCenterViewModel
+ safetyCenterViewModel,
)
)
}
}
companion object {
- private val TAG: String = SafetyCenterSubpageFragment::class.java.simpleName
- private const val BRAND_CHIP_KEY: String = "subpage_brand_chip"
- private const val ILLUSTRATION_KEY: String = "subpage_illustration"
- private const val ISSUE_GROUP_KEY: String = "subpage_issue_group"
- private const val ENTRY_GROUP_KEY: String = "subpage_entry_group"
- private const val FOOTER_KEY: String = "subpage_footer"
- private const val SOURCE_GROUP_ID_KEY: String = "source_group_id"
+ private val TAG = SafetyCenterSubpageFragment::class.java.simpleName
+ private const val BRAND_CHIP_KEY = "subpage_brand_chip"
+ private const val ILLUSTRATION_KEY = "subpage_illustration"
+ private const val ISSUE_GROUP_KEY = "subpage_issue_group"
+ private const val ENTRY_GROUP_KEY = "subpage_entry_group"
+ private const val FOOTER_KEY = "subpage_footer"
+ private const val SPACER_KEY = "subpage_spacer"
+ private const val SOURCE_GROUP_ID_KEY = "source_group_id"
/** Creates an instance of SafetyCenterSubpageFragment with the arguments set */
@JvmStatic
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt
index 5acb27131..3976fa7e3 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt
@@ -25,11 +25,12 @@ import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
+import com.android.settingslib.widget.GroupSectionDividerMixin
/** A preference that displays the illustration on a Safety Center subpage. */
@RequiresApi(UPSIDE_DOWN_CAKE)
internal class SafetyIllustrationPreference(context: Context, attrs: AttributeSet) :
- Preference(context, attrs) {
+ Preference(context, attrs), GroupSectionDividerMixin {
init {
layoutResource = R.layout.preference_illustration
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
index 811841845..abf159955 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
@@ -40,6 +40,7 @@ import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.permissioncontroller.safetycenter.ui.view.StatusCardView;
+import com.android.settingslib.widget.GroupSectionDividerMixin;
import kotlin.Pair;
@@ -48,7 +49,8 @@ import java.util.Objects;
/** Preference which displays a visual representation of {@link SafetyCenterStatus}. */
@RequiresApi(TIRAMISU)
-public class SafetyStatusPreference extends Preference implements ComparablePreference {
+public class SafetyStatusPreference extends Preference
+ implements ComparablePreference, GroupSectionDividerMixin {
private static final String TAG = "SafetyStatusPreference";
@@ -82,7 +84,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
}
Context context = getContext();
- StatusCardView statusCardView = (StatusCardView) holder.itemView;
+ StatusCardView statusCardView = holder.itemView.requireViewById(R.id.status_card);
+
configureButtons(context, statusCardView);
statusCardView
.getTitleAndSummaryContainerView()
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt
index 030b67be9..7f619d1ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt
@@ -55,6 +55,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) :
}
private var maxKnownToolbarHeight = 0
+
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val spacer = holder.itemView
@@ -74,7 +75,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) :
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int
+ oldBottom: Int,
) {
adjustHeight(spacer)
}
diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp
index 60b35e80f..4cd9e0e6f 100644
--- a/PermissionController/tests/inprocess/Android.bp
+++ b/PermissionController/tests/inprocess/Android.bp
@@ -50,6 +50,9 @@ android_test {
"compatibility-device-util-axt",
"kotlin-test",
"permission-test-util-lib",
+ // This may result in two flag libs being included. This should only be used for Flag
+ //string referencing for test annotations.
+ "com.android.permission.flags-aconfig-java-export",
],
data: [
diff --git a/SafetyCenter/Resources/res/values-fa-v35/strings.xml b/SafetyCenter/Resources/res/values-fa-v35/strings.xml
index 878c10bb4..40072433d 100644
--- a/SafetyCenter/Resources/res/values-fa-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-fa-v35/strings.xml
@@ -21,7 +21,7 @@
<string name="cellular_network_security_summary" msgid="7319307247487475572">"نوع شبکه، رمزگذاری، کنترل‌های اعلان"</string>
<string name="biometrics_title_for_private_profile" msgid="542819107383037820"></string>
<string name="privacy_title" msgid="7047524783080782769">"حریم خصوصی"</string>
- <string name="privacy_sources_title" msgid="309304028326660956">"تنظیمات حریم خصوصی"</string>
+ <string name="privacy_sources_title" msgid="309304028326660956">"کنترل‌های حریم خصوصی"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"اجازه‌ها، کنترل‌ها"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
<string name="private_space_title" msgid="6158245041481535879">"فضای خصوصی"</string>
diff --git a/SafetyCenter/Resources/res/values-fa/strings.xml b/SafetyCenter/Resources/res/values-fa/strings.xml
index bdf2063f1..01835a8e5 100644
--- a/SafetyCenter/Resources/res/values-fa/strings.xml
+++ b/SafetyCenter/Resources/res/values-fa/strings.xml
@@ -33,9 +33,9 @@
<string name="permission_manager_title" msgid="5277347862821255015">"مدیر اجازه‌ها"</string>
<string name="permission_manager_summary" msgid="8099852107340970790">"کنترل دسترسی برنامه به داده‌های شما"</string>
<string name="permission_manager_search_terms" msgid="2895147613099694722">"اجازه‌ها، مدیر اجازه‌ها"</string>
- <string name="privacy_controls_title" msgid="5322875777945432395">"تنظیمات حریم خصوصی"</string>
+ <string name="privacy_controls_title" msgid="5322875777945432395">"کنترل‌های حریم خصوصی"</string>
<string name="privacy_controls_summary" msgid="2402066941190435424">"کنترل دسترسی دستگاه به میکروفون، دوربین، و غیره"</string>
- <string name="privacy_controls_search_terms" msgid="3774472175934304165">"حریم خصوصی، تنظیمات حریم خصوصی"</string>
+ <string name="privacy_controls_search_terms" msgid="3774472175934304165">"حریم خصوصی، کنترل‌های حریم خصوصی"</string>
<string name="advanced_title" msgid="8745436380690561172">"تنظیمات بیشتر"</string>
<string name="advanced_security_title" msgid="1126833338772188155">"تنظیمات ایمنی بیشتر"</string>
<string name="advanced_security_summary" msgid="6172253327022425123">"رمزگذاری، اطلاعات اعتباری، و غیره"</string>
diff --git a/SafetyCenter/Resources/res/values-hy-v35/strings.xml b/SafetyCenter/Resources/res/values-hy-v35/strings.xml
index dd5c9e01b..d5d9f39c4 100644
--- a/SafetyCenter/Resources/res/values-hy-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-hy-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"Գաղտնիության կարգավորումներ"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"Թույլտվություններ, կարգավորումներ"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"Անձնական տարածք"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"Մասնավոր տարածք"</string>
<string name="private_space_summary" msgid="529869826714610294">"Կարգավորեք մասնավոր տարածքը և ավելին"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"Անձնական տարածք"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"Մասնավոր տարածք"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-ja/strings.xml b/SafetyCenter/Resources/res/values-ja/strings.xml
index b31b1690a..6e67998f4 100644
--- a/SafetyCenter/Resources/res/values-ja/strings.xml
+++ b/SafetyCenter/Resources/res/values-ja/strings.xml
@@ -30,9 +30,9 @@
<string name="permission_usage_title" msgid="3633779688945350407">"プライバシー ダッシュボード"</string>
<string name="permission_usage_summary" msgid="5323079206029964468">"権限を最近使用したアプリが表示されます"</string>
<string name="permission_usage_search_terms" msgid="3852343592870257104">"プライバシー, プライバシー ダッシュボード"</string>
- <string name="permission_manager_title" msgid="5277347862821255015">"権限マネージャ"</string>
+ <string name="permission_manager_title" msgid="5277347862821255015">"権限マネージャー"</string>
<string name="permission_manager_summary" msgid="8099852107340970790">"アプリのデータアクセスを管理します"</string>
- <string name="permission_manager_search_terms" msgid="2895147613099694722">"権限, 権限マネージャ"</string>
+ <string name="permission_manager_search_terms" msgid="2895147613099694722">"権限, 権限マネージャー"</string>
<string name="privacy_controls_title" msgid="5322875777945432395">"プライバシー管理"</string>
<string name="privacy_controls_summary" msgid="2402066941190435424">"マイク、カメラなどへのデバイス アクセスを管理します"</string>
<string name="privacy_controls_search_terms" msgid="3774472175934304165">"プライバシー, プライバシー管理"</string>
diff --git a/SafetyCenter/Resources/res/values-ka-v35/strings.xml b/SafetyCenter/Resources/res/values-ka-v35/strings.xml
index d5a444deb..6d434bca0 100644
--- a/SafetyCenter/Resources/res/values-ka-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-ka-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"კონფიდენციალურობის მართვის პარამეტრები"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"ნებართვები, მართვის საშუალებები"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"პირადი სივრცე"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"კერძო სივრცე"</string>
<string name="private_space_summary" msgid="529869826714610294">"დააყენეთ პირადი სივრცე და ა.შ."</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"პირადი სივრცე"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"კერძო სივრცე"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-kn-v35/strings.xml b/SafetyCenter/Resources/res/values-kn-v35/strings.xml
index 339e522d9..c054b2fa1 100644
--- a/SafetyCenter/Resources/res/values-kn-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-kn-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"ಗೌಪ್ಯತೆ ನಿಯಂತ್ರಣಗಳು"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"ಅನುಮತಿಗಳು, ನಿಯಂತ್ರಣಗಳು"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"ಖಾಸಗಿ ಸ್ಪೇಸ್"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್"</string>
<string name="private_space_summary" msgid="529869826714610294">"ಖಾಸಗಿ ಸ್ಪೇಸ್ ಅನ್ನು ಸೆಟಪ್ ಮಾಡಿ ಹಾಗೂ ಇನ್ನಷ್ಟನ್ನು ಮಾಡಿ"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"ಖಾಸಗಿ ಸ್ಪೇಸ್"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-lv-v35/strings.xml b/SafetyCenter/Resources/res/values-lv-v35/strings.xml
index f26fcccc3..e3cf556bf 100644
--- a/SafetyCenter/Resources/res/values-lv-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-lv-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"Konfidencialitātes vadīklas"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"Atļaujas, vadīklas"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"Privātā mape"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"Privātā telpa"</string>
<string name="private_space_summary" msgid="529869826714610294">"Privātās mapes iestatīšana un citas iespējas"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"Privātā mape"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"Privātā telpa"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-si-v35/strings.xml b/SafetyCenter/Resources/res/values-si-v35/strings.xml
index bcff52b2a..41025a3a4 100644
--- a/SafetyCenter/Resources/res/values-si-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-si-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"පෞද්ගලිකත්ව පාලන"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"අවසර, පාලන"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"පෞද්ගලික ඉඩ"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"රහසිගත අවකාශය"</string>
<string name="private_space_summary" msgid="529869826714610294">"පෞද්ගලික ඉඩ, සහ තවත් දේ පිහිටුවන්න"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"පෞද්ගලික ඉඩ"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"රහසිගත අවකාශය"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-sv-v35/strings.xml b/SafetyCenter/Resources/res/values-sv-v35/strings.xml
index d1f44114e..974916040 100644
--- a/SafetyCenter/Resources/res/values-sv-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-sv-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"Integritetsinställningar"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"Behörigheter och inställningar"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"Privat rum"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"Privat utrymme"</string>
<string name="private_space_summary" msgid="529869826714610294">"Ställ in privat rum med mera"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"Privat rum"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"Privat utrymme"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-tr-v35/strings.xml b/SafetyCenter/Resources/res/values-tr-v35/strings.xml
index 89a3a3e0c..038bd9680 100644
--- a/SafetyCenter/Resources/res/values-tr-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-tr-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"Gizlilik denetimleri"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"İzinler, denetimler"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"Gizli Alan"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"Özel Alan"</string>
<string name="private_space_summary" msgid="529869826714610294">"Gizli alan yapılandırma ve daha fazlası"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"Gizli Alan"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"Özel Alan"</string>
</resources>
diff --git a/SafetyCenter/Resources/res/values-ur-v35/strings.xml b/SafetyCenter/Resources/res/values-ur-v35/strings.xml
index d4184bda3..d21e73368 100644
--- a/SafetyCenter/Resources/res/values-ur-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-ur-v35/strings.xml
@@ -24,7 +24,7 @@
<string name="privacy_sources_title" msgid="309304028326660956">"رازداری سے متعلق کنٹرولز"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"اجازتیں، کنٹرولز"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
- <string name="private_space_title" msgid="6158245041481535879">"نجی اسپیس"</string>
+ <string name="private_space_title" msgid="6158245041481535879">"پرائیویٹ اسپیس"</string>
<string name="private_space_summary" msgid="529869826714610294">"نجی اسپیس اور بھی بہت کچھ سیٹ اپ کریں"</string>
- <string name="private_space_search_terms" msgid="4820808478299116258">"نجی اسپیس"</string>
+ <string name="private_space_search_terms" msgid="4820808478299116258">"پرائیویٹ اسپیس"</string>
</resources>
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
index e15887576..2f6bbf154 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -6,6 +6,7 @@ package android.app.ecm {
method @NonNull public android.content.Intent createRestrictedSettingDialogIntent(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isRestricted(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isUnknownCallOngoing();
method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public void setClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
field public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG";
}
@@ -37,6 +38,7 @@ package android.app.role {
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @Nullable @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_ROLE_HOLDERS, android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, conditional=true) public android.os.UserHandle getActiveUserForRole(@NonNull String);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) public String getDefaultApplication(@NonNull String);
method @Deprecated @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
@@ -48,6 +50,7 @@ package android.app.role {
method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_ROLE_HOLDERS, android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, conditional=true) public void setActiveUserForRole(@NonNull String, @NonNull android.os.UserHandle, int);
method @RequiresPermission(android.Manifest.permission.BYPASS_ROLE_QUALIFICATION) public void setBypassingRoleQualification(boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) public void setDefaultApplication(@NonNull String, @Nullable String, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @FlaggedApi("android.permission.flags.system_server_role_controller_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void setRoleFallbackEnabled(@NonNull String, boolean);
@@ -55,6 +58,7 @@ package android.app.role {
field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
field public static final String ROLE_DEVICE_POLICY_MANAGEMENT = "android.app.role.DEVICE_POLICY_MANAGEMENT";
field public static final String ROLE_FINANCED_DEVICE_KIOSK = "android.app.role.FINANCED_DEVICE_KIOSK";
+ field @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY = "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY";
field public static final String ROLE_SYSTEM_ACTIVITY_RECOGNIZER = "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER";
field public static final String ROLE_SYSTEM_CALL_STREAMING = "android.app.role.SYSTEM_CALL_STREAMING";
field public static final String ROLE_SYSTEM_SUPERVISION = "android.app.role.SYSTEM_SUPERVISION";
diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
index 74062165e..8eaa9354d 100644
--- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
+++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
@@ -20,6 +20,7 @@ import static android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_AC
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -35,8 +36,6 @@ import android.os.RemoteException;
import android.permission.flags.Flags;
import android.util.ArraySet;
-import androidx.annotation.NonNull;
-
import java.lang.annotation.Retention;
/**
@@ -329,6 +328,23 @@ public final class EnhancedConfirmationManager {
}
/**
+ * Returns whether the enhanced confirmation system thinks a call with an unknown party is
+ * occurring
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+ @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES)
+ public boolean isUnknownCallOngoing() {
+ try {
+ return mService.isUntrustedCallOngoing();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets an intent that will open the "Restricted setting" dialog for the specified package
* and setting.
*
diff --git a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl
index 5149daa49..833485890 100644
--- a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl
+++ b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl
@@ -30,4 +30,7 @@ interface IEnhancedConfirmationManager {
boolean isClearRestrictionAllowed(in String packageName, int userId);
void setClearRestrictionAllowed(in String packageName, int userId);
+
+ boolean isUntrustedCallOngoing();
+
}
diff --git a/framework-s/java/android/app/role/IRoleManager.aidl b/framework-s/java/android/app/role/IRoleManager.aidl
index 522967630..cd0079e08 100644
--- a/framework-s/java/android/app/role/IRoleManager.aidl
+++ b/framework-s/java/android/app/role/IRoleManager.aidl
@@ -45,6 +45,10 @@ interface IRoleManager {
void setDefaultApplicationAsUser(in String roleName, in String packageName, int flags,
int userId, in RemoteCallback callback);
+ int getActiveUserForRoleAsUser(in String roleName, int userId);
+
+ void setActiveUserForRoleAsUser(in String roleName, int activeUserId, int flags, int userId);
+
void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId);
void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener,
diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java
index 4b8c9b388..6f62fdd76 100644
--- a/framework-s/java/android/app/role/RoleManager.java
+++ b/framework-s/java/android/app/role/RoleManager.java
@@ -40,6 +40,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.permission.flags.Flags;
+import android.permission.internal.compat.UserHandleCompat;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -211,6 +212,16 @@ public final class RoleManager {
"android.app.role.SYSTEM_CALL_STREAMING";
/**
+ * The name of the role used for testing cross-user roles.
+ *
+ * @hide
+ */
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SystemApi
+ public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY =
+ "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY";
+
+ /**
* @hide
*/
@IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP })
@@ -574,6 +585,87 @@ public final class RoleManager {
}
}
+ /**
+ * Get the {@link UserHandle} of the user who that is the active user for the specified role.
+ * <p>
+ * Only profile-group exclusive roles can be used with this method, and they will
+ * have one active user within a profile group.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL} and one of
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} or
+ * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}.
+ *
+ * @param roleName the name of the role to get the active user for
+ *
+ * @return a {@link UserHandle} of the active user for the specified role
+ *
+ * @see #setActiveUserForRole(String, UserHandle, int)
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS},
+ conditional = true)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Nullable
+ public UserHandle getActiveUserForRole(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ int userId = mService.getActiveUserForRoleAsUser(roleName,
+ mContext.getUser().getIdentifier());
+ return userId == UserHandleCompat.USER_NULL ? null : UserHandle.of(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set a specific user as active user for a role.
+ * <p>
+ * Only profile-group exclusive roles can be used with this method, and they will have
+ * one active user within a profile group.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL} and one of
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} or
+ * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}.
+ *
+ * @param roleName the name of the role to set the active user for
+ * @param user the user to set as active user for specified role
+ * @param flags optional behavior flags
+ *
+ * @see #getActiveUserForRole(String)
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS},
+ conditional = true)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ // The user handle parameter is a value to be set by this method, while the context user of the
+ // operation is indeed read from the context
+ @SuppressLint("UserHandle")
+ public void setActiveUserForRole(
+ @NonNull String roleName, @NonNull UserHandle user, @ManageHoldersFlags int flags) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(user, "user cannot be null");
+ try {
+ mService.setActiveUserForRoleAsUser(roleName, user.getIdentifier(), flags,
+ mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
private static RemoteCallback createRemoteCallback(@NonNull Executor executor,
@NonNull Consumer<Boolean> callback) {
diff --git a/service/java/com/android/permission/compat/UserHandleCompat.java b/framework-s/java/android/permission/internal/compat/UserHandleCompat.java
index 1b3ebb8d6..8a3ec444d 100644
--- a/service/java/com/android/permission/compat/UserHandleCompat.java
+++ b/framework-s/java/android/permission/internal/compat/UserHandleCompat.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permission.compat;
+package android.permission.internal.compat;
import android.annotation.UserIdInt;
import android.os.UserHandle;
diff --git a/service/java/com/android/permission/compat/package-info.java b/framework-s/java/android/permission/internal/compat/package-info.java
index c89cc8eab..b78aac878 100644
--- a/service/java/com/android/permission/compat/package-info.java
+++ b/framework-s/java/android/permission/internal/compat/package-info.java
@@ -19,4 +19,4 @@
* TODO(b/146466118) remove this javadoc tag
*/
@android.annotation.Hide
-package com.android.permission.compat;
+package android.permission.internal.compat;
diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt
index 30fbab484..a3db89370 100644
--- a/service/api/system-server-current.txt
+++ b/service/api/system-server-current.txt
@@ -1,4 +1,18 @@
// Signature format: 2.0
+package com.android.ecm {
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public class EnhancedConfirmationCallTrackerService extends android.telecom.InCallService {
+ ctor public EnhancedConfirmationCallTrackerService();
+ }
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public interface EnhancedConfirmationManagerLocal {
+ method public void addOngoingCall(@NonNull android.telecom.Call);
+ method public void clearOngoingCalls();
+ method public void removeOngoingCall(@NonNull String);
+ }
+
+}
+
package com.android.permission.persistence {
public interface RuntimePermissionsPersistence {
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 4d4d6e050..ef6971b11 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -10,6 +10,10 @@ rule android.companion.virtualdevice.flags.*FeatureFlags* com.android.permission
rule android.companion.virtualdevice.flags.FeatureFlags* com.android.permission.jarjar.@0
rule android.companion.virtualdevice.flags.FeatureFlags com.android.permission.jarjar.@0
rule android.companion.virtualdevice.flags.Flags com.android.permission.jarjar.@0
+rule android.content.pm.*FeatureFlags* com.android.permission.jarjar.@0
+rule android.content.pm.FeatureFlags* com.android.permission.jarjar.@0
+rule android.content.pm.FeatureFlags com.android.permission.jarjar.@0
+rule android.content.pm.Flags com.android.permission.jarjar.@0
rule android.os.*FeatureFlags* com.android.permission.jarjar.@0
rule android.os.FeatureFlags* com.android.permission.jarjar.@0
rule android.os.FeatureFlags com.android.permission.jarjar.@0
diff --git a/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java
new file mode 100644
index 000000000..407d56f70
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+import android.telecom.InCallService;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * @hide
+ *
+ * This InCallService tracks called (both incoming and outgoing), and sends their information to the
+ * EnhancedConfirmationService
+ *
+ **/
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+public class EnhancedConfirmationCallTrackerService extends InCallService {
+ private EnhancedConfirmationManagerLocal mEnhancedConfirmationManagerLocal;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (Flags.enhancedConfirmationInCallApisEnabled()) {
+ mEnhancedConfirmationManagerLocal =
+ LocalManagerRegistry.getManager(EnhancedConfirmationManagerLocal.class);
+ }
+ }
+
+ @Override
+ public void onCallAdded(@Nullable Call call) {
+ if (mEnhancedConfirmationManagerLocal == null || call == null) {
+ return;
+ }
+
+ mEnhancedConfirmationManagerLocal.addOngoingCall(call);
+ }
+
+ @Override
+ public void onCallRemoved(@Nullable Call call) {
+ if (mEnhancedConfirmationManagerLocal == null || call == null) {
+ return;
+ }
+
+ mEnhancedConfirmationManagerLocal.removeOngoingCall(call.getDetails().getId());
+ }
+
+ /**
+ * When unbound, we should assume all calls have finished. Notify the system of such.
+ */
+ public boolean onUnbind(@Nullable Intent intent) {
+ if (mEnhancedConfirmationManagerLocal != null) {
+ mEnhancedConfirmationManagerLocal.clearOngoingCalls();
+ }
+ return super.onUnbind(intent);
+ }
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java
new file mode 100644
index 000000000..483071716
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+
+/**
+ * @hide
+ *
+ * In-process API for the Enhanced Confirmation Service
+ */
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+public interface EnhancedConfirmationManagerLocal {
+ /**
+ * Inform the enhanced confirmation service of an ongoing call
+ *
+ * @param call The call to potentially track
+ *
+ */
+ void addOngoingCall(@NonNull Call call);
+
+ /**
+ * Inform the enhanced confirmation service that a call has ended
+ *
+ * @param callId The ID of the call to stop tracking
+ *
+ */
+ void removeOngoingCall(@NonNull String callId);
+
+ /**
+ * Informs the enhanced confirmation service it should clear out any ongoing calls
+ */
+ void clearOngoingCalls();
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java
new file mode 100644
index 000000000..a5c6d3c36
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.NonNull;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+
+/** @hide */
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+class EnhancedConfirmationManagerLocalImpl implements EnhancedConfirmationManagerLocal {
+
+ private final EnhancedConfirmationService mService;
+
+ EnhancedConfirmationManagerLocalImpl(EnhancedConfirmationService service) {
+ if (Flags.enhancedConfirmationInCallApisEnabled()) {
+ mService = service;
+ } else {
+ mService = null;
+ }
+ }
+
+ @Override
+ public void addOngoingCall(@NonNull Call call) {
+ if (mService != null) {
+ mService.addOngoingCall(call);
+ }
+ }
+
+ @Override
+ public void removeOngoingCall(@NonNull String callId) {
+ if (mService != null) {
+ mService.removeOngoingCall(callId);
+ }
+ }
+
+ @Override
+ public void clearOngoingCalls() {
+ if (mService != null) {
+ mService.clearOngoingCalls();
+ }
+ }
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java
index 708884e85..dde9fe2fd 100644
--- a/service/java/com/android/ecm/EnhancedConfirmationService.java
+++ b/service/java/com/android/ecm/EnhancedConfirmationService.java
@@ -19,11 +19,15 @@ package com.android.ecm;
import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.ecm.EnhancedConfirmationManager;
import android.app.ecm.IEnhancedConfirmationManager;
import android.app.role.RoleManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
@@ -31,25 +35,33 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.SignedPackage;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.permission.flags.Flags;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.internal.util.Preconditions;
import com.android.permission.util.UserUtils;
+import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -66,16 +78,35 @@ import java.util.Set;
@Keep
@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SuppressLint("MissingPermission")
public class EnhancedConfirmationService extends SystemService {
private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName();
private Map<String, List<byte[]>> mTrustedPackageCertDigests;
private Map<String, List<byte[]>> mTrustedInstallerCertDigests;
+ // A map of call ID to call type
+ private final Map<String, Integer> mOngoingCalls = new ArrayMap<>();
+
+ private static final int CALL_TYPE_UNTRUSTED = 0;
+ private static final int CALL_TYPE_TRUSTED = 1;
+ private static final int CALL_TYPE_EMERGENCY = 2;
+ @IntDef(flag = true, value = {
+ CALL_TYPE_UNTRUSTED,
+ CALL_TYPE_TRUSTED,
+ CALL_TYPE_EMERGENCY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CallType {}
public EnhancedConfirmationService(@NonNull Context context) {
super(context);
+ LocalManagerRegistry.addManager(EnhancedConfirmationManagerLocal.class,
+ new EnhancedConfirmationManagerLocalImpl(this));
}
+ private ContentResolver mContentResolver;
+ private TelephonyManager mTelephonyManager;
+
@Override
public void onStart() {
Context context = getContext();
@@ -87,6 +118,8 @@ public class EnhancedConfirmationService extends SystemService {
systemConfigManager.getEnhancedConfirmationTrustedInstallers());
publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub());
+ mContentResolver = getContext().getContentResolver();
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
}
private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) {
@@ -99,6 +132,97 @@ public class EnhancedConfirmationService extends SystemService {
return trustedPackageMap;
}
+ void addOngoingCall(Call call) {
+ if (!Flags.enhancedConfirmationInCallApisEnabled()) {
+ return;
+ }
+ if (call.getDetails() == null) {
+ return;
+ }
+ mOngoingCalls.put(call.getDetails().getId(), getCallType(call));
+ }
+
+ void removeOngoingCall(String callId) {
+ if (!Flags.enhancedConfirmationInCallApisEnabled()) {
+ return;
+ }
+ Integer returned = mOngoingCalls.remove(callId);
+ if (returned == null) {
+ // TODO b/379941144: Capture a bug report whenever this happens.
+ }
+ }
+
+ void clearOngoingCalls() {
+ mOngoingCalls.clear();
+ }
+
+ private @CallType int getCallType(Call call) {
+ String number = getPhoneNumber(call);
+ try {
+ if (number != null && mTelephonyManager.isEmergencyNumber(number)) {
+ return CALL_TYPE_EMERGENCY;
+ }
+ } catch (IllegalStateException | UnsupportedOperationException e) {
+ // If either of these are thrown, the telephony service is not available on the current
+ // device, either because the device lacks telephony calling, or the telephony service
+ // is unavailable.
+ }
+ if (number != null) {
+ return hasContactWithPhoneNumber(number) ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ } else {
+ return hasContactWithDisplayName(call.getDetails().getCallerDisplayName())
+ ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ }
+ }
+
+ private String getPhoneNumber(Call call) {
+ Uri handle = call.getDetails().getHandle();
+ if (handle == null || handle.getScheme() == null) {
+ return null;
+ }
+ if (!handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
+ return null;
+ }
+ return handle.getSchemeSpecificPart();
+ }
+
+ private boolean hasContactWithPhoneNumber(String phoneNumber) {
+ if (phoneNumber == null) {
+ return false;
+ }
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(phoneNumber));
+ String[] projection = new String[]{
+ PhoneLookup.DISPLAY_NAME,
+ ContactsContract.PhoneLookup._ID
+ };
+ try (Cursor res = mContentResolver.query(uri, projection, null, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasContactWithDisplayName(String displayName) {
+ if (displayName == null) {
+ return false;
+ }
+ Uri uri = ContactsContract.Data.CONTENT_URI;
+ String[] projection = new String[]{PhoneLookup._ID};
+ String selection = StructuredName.DISPLAY_NAME + " = ?";
+ String[] selectionArgs = new String[]{displayName};
+ try (Cursor res = mContentResolver.query(uri, projection, selection, selectionArgs, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasCallOfType(@CallType int callType) {
+ for (int ongoingCallType : mOngoingCalls.values()) {
+ if (ongoingCallType == callType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private class Stub extends IEnhancedConfirmationManager.Stub {
/** A map of ECM states to their corresponding app op states */
@@ -227,8 +351,20 @@ public class EnhancedConfirmationService extends SystemService {
}
}
+ @Override
+ public boolean isUntrustedCallOngoing() {
+ enforcePermissions("isUntrustedCallOngoing",
+ UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier());
+ if (hasCallOfType(CALL_TYPE_EMERGENCY)) {
+ // If we have an emergency call, return false always.
+ return false;
+ }
+ return hasCallOfType(CALL_TYPE_UNTRUSTED);
+ }
+
private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, methodName, mContext);
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, methodName, mContext);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, methodName);
}
@@ -322,6 +458,7 @@ public class EnhancedConfirmationService extends SystemService {
return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ @SuppressLint("WrongConstant")
private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState,
@UserIdInt int userId) throws NameNotFoundException {
int packageUid = getPackageUid(packageName, userId);
diff --git a/service/java/com/android/permission/util/UserUtils.java b/service/java/com/android/permission/util/UserUtils.java
index 639c7aacb..c69afb199 100644
--- a/service/java/com/android/permission/util/UserUtils.java
+++ b/service/java/com/android/permission/util/UserUtils.java
@@ -17,21 +17,22 @@
package com.android.permission.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.internal.compat.UserHandleCompat;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permission.compat.UserHandleCompat;
import com.android.permission.flags.Flags;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
/** Utility class to deal with Android users. */
public final class UserUtils {
@@ -42,11 +43,12 @@ public final class UserUtils {
public static void enforceCrossUserPermission(
@UserIdInt int userId,
boolean allowAll,
+ boolean enforceForProfileGroup,
@NonNull String message,
@NonNull Context context) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandleCompat.getUserId(callingUid);
- if (userId == callingUserId) {
+ if (userId == callingUserId && !enforceForProfileGroup) {
return;
}
Preconditions.checkArgument(
@@ -55,13 +57,40 @@ public final class UserUtils {
"Invalid user " + userId);
context.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- if (callingUid != Process.SHELL_UID || userId < UserHandleCompat.USER_SYSTEM) {
+ if (callingUid != Process.SHELL_UID || userId == UserHandleCompat.USER_ALL) {
return;
}
+
+ if (enforceForProfileGroup) {
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
+ // For profileGroup exclusive roles users on BYOD are free to choose personal o
+ // work profile app regardless of DISALLOW_DEBUGGING_FEATURES
+ return;
+ }
+
+ Context userContext = UserUtils.getUserContext(userId, context);
+ List<UserHandle> profiles = getUserProfiles(userContext, true);
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ int profileId = profiles.get(i).getIdentifier();
+ if (profileId == callingUserId) {
+ continue;
+ }
+ enforceShellRestriction(profileId, context);
+ }
+ } else {
+ enforceShellRestriction(userId, context);
+ }
+ }
+
+ private static void enforceShellRestriction(int userId, @NonNull Context context) {
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager.hasUserRestrictionForUser(
UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) {
- throw new SecurityException("Shell does not have permission to access user " + userId);
+ throw new SecurityException(
+ "Shell does not have permission to access user " + userId);
}
}
@@ -86,18 +115,54 @@ public final class UserUtils {
/** Returns all the enabled user profiles on the device. */
@NonNull
public static List<UserHandle> getUserProfiles(@NonNull Context context) {
+ return getUserProfiles(context, false);
+ }
+
+ /**
+ * Returns all the enabled user profiles on the device
+ *
+ * @param context the {@link Context}
+ * @param excludePrivate {@code true} to exclude private profiles from returned list of users
+ */
+ @NonNull
+ public static List<UserHandle> getUserProfiles(@NonNull Context context,
+ boolean excludePrivate) {
UserManager userManager = context.getSystemService(UserManager.class);
// This call requires the QUERY_USERS permission.
final long identity = Binder.clearCallingIdentity();
try {
- return userManager.getUserProfiles();
+ List<UserHandle> profiles = userManager.getUserProfiles();
+ if (!excludePrivate) {
+ return profiles;
+ }
+ List<UserHandle> filteredProfiles = new ArrayList<>();
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ UserHandle user = profiles.get(i);
+ if (!isPrivateProfile(user.getIdentifier(), context)) {
+ filteredProfiles.add(user);
+ }
+ }
+ return filteredProfiles;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ /**
+ * Returns the parent of a given user, or userId if it has no parent (e.g. it is the primary
+ * profile)
+ */
+ @UserIdInt
+ public static int getProfileParentIdOrSelf(@UserIdInt int userId, @NonNull Context context) {
+ UserHandle profileParent = getProfileParent(userId, context);
+ // If profile parent userhandle is null, then original user id is the parent
+ return profileParent != null ? profileParent.getIdentifier() : userId;
+ }
+
/** Returns the parent of a given user. */
- public static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) {
+ @Nullable
+ private static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) {
Context userContext = getUserContext(userId, context);
UserManager userManager = userContext.getSystemService(UserManager.class);
// This call requires the INTERACT_ACROSS_USERS permission.
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
index b8e3ad8b1..7c2ab01b1 100644
--- a/service/java/com/android/role/RoleService.java
+++ b/service/java/com/android/role/RoleService.java
@@ -46,6 +46,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.flags.Flags;
+import android.permission.internal.compat.UserHandleCompat;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -62,13 +63,16 @@ 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;
import com.android.permission.util.ForegroundThread;
import com.android.permission.util.PackageUtils;
import com.android.permission.util.ThrottledRunnable;
import com.android.permission.util.UserUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
+import com.android.role.controller.service.RoleControllerServiceImpl;
+import com.android.role.controller.util.RoleFlags;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import com.android.server.role.RoleServicePlatformHelper;
@@ -77,6 +81,7 @@ import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -114,6 +119,10 @@ public class RoleService extends SystemService implements RoleUserState.Callback
if (SdkLevel.isAtLeastV()) {
defaultApplicationRoles.add(RoleManager.ROLE_WALLET);
}
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ defaultApplicationRoles.add(
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY);
+ }
DEFAULT_APPLICATION_ROLES = defaultApplicationRoles.toArray(new String[0]);
}
@@ -165,6 +174,11 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public RoleService(@NonNull Context context) {
super(context);
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ RoleControllerServiceImpl.sSetActiveUserForRoleMethod =
+ this::setActiveUserForRoleFromController;
+ }
+
mPlatformHelper = LocalManagerRegistry.getManager(RoleServicePlatformHelper.class);
RoleControllerManager.initializeRemoteServiceComponentName(context);
@@ -462,12 +476,100 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}
}
+ private void enforceProfileGroupExclusiveRole(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgument(isProfileGroupExclusiveRole(roleName, getContext()),
+ roleName + " is not a profile-group exclusive role");
+ }
+
+ /**
+ * Returns whether the given role has profile group exclusivity
+ *
+ * @param roleName The name of role to check
+ * @param context The context
+ * @return {@code true} if the role is profile group exclusive, {@code false} otherwise
+ */
+ private static boolean isProfileGroupExclusiveRole(String roleName, Context context) {
+ if (!RoleFlags.isProfileGroupExclusivityAvailable()) {
+ return false;
+ }
+ Role role = Roles.get(context).get(roleName);
+ return role != null && role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP;
+ }
+
+ private void setActiveUserForRoleFromController(@NonNull String roleName, @UserIdInt int userId,
+ @RoleManager.ManageHoldersFlags int flags) {
+ setActiveUserForRoleAsUserInternal(roleName, userId, flags, false, userId);
+ }
+
+ private void setActiveUserForRoleAsUserInternal(@NonNull String roleName,
+ @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags,
+ boolean clearRoleHoldersForActiveUser, @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setActiveUserForRoleAsUser not available");
+ enforceProfileGroupExclusiveRole(roleName);
+
+ if (!UserUtils.isUserExistent(userId, getContext())) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ if (!UserUtils.isUserExistent(activeUserId, getContext())) {
+ Log.e(LOG_TAG, "user " + activeUserId + " does not exist");
+ return;
+ }
+ if (UserUtils.isPrivateProfile(activeUserId, getContext())) {
+ Log.e(LOG_TAG, "Cannot set private profile " + activeUserId + " as active user"
+ + " for role");
+ return;
+ }
+ Context userContext = UserUtils.getUserContext(userId, getContext());
+ List<UserHandle> profiles = UserUtils.getUserProfiles(userContext, true);
+ if (!profiles.contains(UserHandle.of(activeUserId))) {
+ Log.e(LOG_TAG, "User " + activeUserId + " is not in the same profile-group as "
+ + userId);
+ return;
+ }
+
+ int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext());
+ RoleUserState userState = getOrCreateUserState(profileParentId);
+
+ if (!userState.setActiveUserForRole(roleName, activeUserId)) {
+ Log.i(LOG_TAG, "User " + activeUserId + " is already the active user for role");
+ return;
+ }
+
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ int profilesUserId = profiles.get(i).getIdentifier();
+ if (!clearRoleHoldersForActiveUser && profilesUserId == activeUserId) {
+ continue;
+ }
+ final AndroidFuture<Void> future = new AndroidFuture<>();
+ final RemoteCallback callback = new RemoteCallback(result -> {
+ boolean successful = result != null;
+ if (successful) {
+ future.complete(null);
+ } else {
+ future.completeExceptionally(new RuntimeException());
+ }
+ });
+ getOrCreateController(profilesUserId)
+ .onClearRoleHolders(roleName, flags, callback);
+ try {
+ future.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(LOG_TAG, "Exception while clearing role holders for non-active user: "
+ + profilesUserId, e);
+ }
+ }
+ }
+
private class Stub extends IRoleManager.Stub {
@Override
public boolean isRoleAvailableAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleAvailableAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleAvailableAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -483,7 +585,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@UserIdInt int userId) {
mAppOpsManager.checkPackage(getCallingUid(), packageName);
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleHeldAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleHeldAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -502,8 +605,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@NonNull
@Override
public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getRoleHoldersAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
@@ -525,8 +628,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
@NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "addRoleHolderAsUser",
- getContext());
+ boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ enforceForProfileGroup, "addRoleHolderAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -546,8 +650,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
@NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "removeRoleHolderAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "removeRoleHolderAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -568,8 +672,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void clearRoleHoldersAsUser(@NonNull String roleName,
@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
@NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "clearRoleHoldersAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "clearRoleHoldersAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -587,7 +691,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
@Nullable
public String getDefaultApplicationAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "getDefaultApplicationAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getDefaultApplicationAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -612,8 +717,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void setDefaultApplicationAsUser(@NonNull String roleName,
@Nullable String packageName, @RoleManager.ManageHoldersFlags int flags,
@UserIdInt int userId, @NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "setDefaultApplicationAsUser",
- getContext());
+ boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ enforceForProfileGroup, "setDefaultApplicationAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -635,10 +741,49 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}
@Override
+ public int getActiveUserForRoleAsUser(@NonNull String roleName, @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "getActiveUserForRoleAsUser not available");
+ enforceProfileGroupExclusiveRole(roleName);
+
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ true, "getActiveUserForRole", getContext());
+ if (!UserUtils.isUserExistent(userId, getContext())) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return UserHandleCompat.USER_NULL;
+ }
+
+ enforceCallingOrSelfAnyPermissions(new String[] {
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS,
+ Manifest.permission.MANAGE_ROLE_HOLDERS
+ }, "getActiveUserForRole");
+
+ int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext());
+ RoleUserState userState = getOrCreateUserState(profileParentId);
+ return userState.getActiveUserForRole(roleName);
+ }
+
+ @Override
+ public void setActiveUserForRoleAsUser(@NonNull String roleName,
+ @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags,
+ @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setActiveUserForRoleAsUser not available");
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ true, "setActiveUserForRole", getContext());
+ enforceCallingOrSelfAnyPermissions(new String[] {
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS,
+ Manifest.permission.MANAGE_ROLE_HOLDERS
+ }, "setActiveUserForRoleAsUser");
+ setActiveUserForRoleAsUserInternal(roleName, activeUserId, flags, true, userId);
+ }
+
+ @Override
public void addOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, true,
- "addOnRoleHoldersChangedListenerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true,
+ /* enforceForProfileGroup= */ false, "addOnRoleHoldersChangedListenerAsUser",
+ getContext());
if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId,
getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -658,7 +803,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, true,
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true,
+ /* enforceForProfileGroup= */ false,
"removeOnRoleHoldersChangedListenerAsUser", getContext());
if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId,
getContext())) {
@@ -711,7 +857,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean isRoleFallbackEnabledAsUser(@NonNull String roleName,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleFallbackEnabledAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleFallbackEnabledAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -729,7 +876,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void setRoleFallbackEnabledAsUser(@NonNull String roleName, boolean fallbackEnabled,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "setRoleFallbackEnabledAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "setRoleFallbackEnabledAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -747,7 +895,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void setRoleNamesFromControllerAsUser(@NonNull List<String> roleNames,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "setRoleNamesFromControllerAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "setRoleNamesFromControllerAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -766,8 +915,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean addRoleHolderFromControllerAsUser(@NonNull String roleName,
@NonNull String packageName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "addRoleHolderFromControllerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "addRoleHolderFromControllerAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -786,8 +936,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean removeRoleHolderFromControllerAsUser(@NonNull String roleName,
@NonNull String packageName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "removeRoleHolderFromControllerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "removeRoleHolderFromControllerAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -806,8 +957,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public List<String> getHeldRolesFromControllerAsUser(@NonNull String packageName,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "getHeldRolesFromControllerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getHeldRolesFromControllerAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
@@ -916,7 +1068,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public String getSmsRoleHolder(int userId) {
final Context context = getContext();
- UserUtils.enforceCrossUserPermission(userId, false, "getSmsRoleHolder", context);
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getSmsRoleHolder", context);
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return null;
@@ -940,7 +1093,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public String getEmergencyRoleHolder(int userId) {
final Context context = getContext();
- UserUtils.enforceCrossUserPermission(userId, false, "getEmergencyRoleHolder", context);
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getEmergencyRoleHolder", context);
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return null;
@@ -966,8 +1120,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean isRoleVisibleAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleVisibleAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleVisibleAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -984,8 +1138,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean isApplicationVisibleForRoleAsUser(@NonNull String roleName,
@NonNull String packageName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "isApplicationVisibleForRoleAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isApplicationVisibleForRoleAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -1042,6 +1197,20 @@ public class RoleService extends SystemService implements RoleUserState.Callback
return true;
}
}
+
+ private void enforceCallingOrSelfAnyPermissions(@NonNull String[] permissions,
+ @NonNull String message) {
+ for (String permission : permissions) {
+ if (getContext().checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ throw new SecurityException(message + ": Neither user " + Binder.getCallingUid()
+ + " nor current process has at least one of" + Arrays.toString(permissions)
+ + ".");
+ }
}
private class Local implements RoleManagerLocal {
diff --git a/service/java/com/android/role/RoleShellCommand.java b/service/java/com/android/role/RoleShellCommand.java
index 808a64cb4..538e62f47 100644
--- a/service/java/com/android/role/RoleShellCommand.java
+++ b/service/java/com/android/role/RoleShellCommand.java
@@ -22,11 +22,11 @@ import android.app.role.IRoleManager;
import android.os.Build;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.permission.internal.compat.UserHandleCompat;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.BasicShellCommandHandler;
-import com.android.permission.compat.UserHandleCompat;
import java.io.PrintWriter;
import java.util.List;
@@ -87,6 +87,10 @@ class RoleShellCommand extends BasicShellCommandHandler {
return runClearRoleHolders();
case "set-bypassing-role-qualification":
return runSetBypassingRoleQualification();
+ case "get-active-user-for-role":
+ return runGetActiveUserForRole();
+ case "set-active-user-for-role":
+ return runSetActiveUserForRole();
default:
return handleDefaultCommands(cmd);
}
@@ -162,6 +166,27 @@ class RoleShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int runGetActiveUserForRole() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+
+ int activeUserId = mRoleManager.getActiveUserForRoleAsUser(roleName, userId);
+ if (activeUserId != UserHandleCompat.USER_NULL) {
+ getOutPrintWriter().println(activeUserId);
+ }
+ return 0;
+ }
+
+ private int runSetActiveUserForRole() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+ int activeUserId = Integer.parseInt(getNextArgRequired());
+ int flags = getFlagsMaybe();
+
+ mRoleManager.setActiveUserForRoleAsUser(roleName, activeUserId, flags, userId);
+ return 0;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -174,6 +199,8 @@ class RoleShellCommand extends BasicShellCommandHandler {
pw.println(" remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
pw.println(" clear-role-holders [--user USER_ID] ROLE [FLAGS]");
pw.println(" set-bypassing-role-qualification true|false");
+ pw.println(" get-active-user-for-role [--user USER_ID] ROLE");
+ pw.println(" set-active-user-for-role [--user USER_ID] ROLE ACTIVE_USER_ID [FLAGS]");
pw.println();
}
}
diff --git a/service/java/com/android/role/RoleUserState.java b/service/java/com/android/role/RoleUserState.java
index cda7fcfa8..c94b58826 100644
--- a/service/java/com/android/role/RoleUserState.java
+++ b/service/java/com/android/role/RoleUserState.java
@@ -24,6 +24,7 @@ import android.annotation.WorkerThread;
import android.os.Build;
import android.os.Handler;
import android.os.UserHandle;
+import android.permission.internal.compat.UserHandleCompat;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -428,6 +429,42 @@ class RoleUserState {
}
/**
+ * Return the active user for the role
+ *
+ * @param roleName the name of the role to get the active user for
+ */
+ public int getActiveUserForRole(@NonNull String roleName) {
+ synchronized (mLock) {
+ return mActiveUserIds.getOrDefault(roleName, UserHandleCompat.USER_NULL);
+ }
+ }
+
+ /**
+ * Set the active user for the role
+ *
+ * @param roleName the name of the role to set the active user for
+ * @param userId User id to set as active for this role
+ * @return whether any changes were made
+ */
+ public boolean setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId) {
+ if (!com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ return false;
+ }
+ synchronized (mLock) {
+ Integer currentActiveUserId = mActiveUserIds.get(roleName);
+ // If we have pre-existing roles that weren't profile group exclusive and don't have an
+ // active user, ensure we set and write value, and return modified, otherwise other
+ // users might not have role holder revoked.
+ if (currentActiveUserId != null && currentActiveUserId == userId) {
+ return false;
+ }
+ mActiveUserIds.put(roleName, userId);
+ scheduleWriteFileLocked();
+ return true;
+ }
+ }
+
+ /**
* Schedule writing the state to file.
*/
@GuardedBy("mLock")
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index 250be5f25..6df4184e1 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -694,7 +694,8 @@ public final class SafetyCenterService extends SystemService {
/** Enforces cross user permission and returns whether the user is valid. */
private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) {
UserUtils.enforceCrossUserPermission(
- userId, /* allowAll= */ false, message, getContext());
+ userId, /* allowAll= */ false, /* enforceForProfileGroup= */ false, message,
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.w(
TAG,
diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java
index a78113b04..1f5258437 100644
--- a/service/java/com/android/safetycenter/UserProfileGroup.java
+++ b/service/java/com/android/safetycenter/UserProfileGroup.java
@@ -21,16 +21,12 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Binder;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.internal.compat.UserHandleCompat;
import android.util.Log;
-import androidx.annotation.Nullable;
-
-import com.android.permission.compat.UserHandleCompat;
import com.android.permission.util.UserUtils;
import java.lang.annotation.Retention;
@@ -135,11 +131,7 @@ public final class UserProfileGroup {
public static UserProfileGroup fromUser(Context context, @UserIdInt int userId) {
Context userContext = UserUtils.getUserContext(userId, context);
List<UserHandle> userProfiles = UserUtils.getUserProfiles(userContext);
- UserHandle profileParent = UserUtils.getProfileParent(userId, userContext);
- int profileParentUserId = userId;
- if (profileParent != null) {
- profileParentUserId = profileParent.getIdentifier();
- }
+ int profileParentUserId = UserUtils.getProfileParentIdOrSelf(userId, userContext);
int[] managedProfilesUserIds = new int[userProfiles.size()];
int[] managedRunningProfilesUserIds = new int[userProfiles.size()];
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index b10928320..6aaf3da8c 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -35,17 +35,6 @@
</issue>
<issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
- errorLine1=" return UserHandle.of(userId).getUid(appId);"
- errorLine2=" ~~~~~~">
- <location
- file="packages/modules/Permission/service/java/com/android/permission/compat/UserHandleCompat.java"
- line="57"
- column="38"/>
- </issue>
-
- <issue
id="FlaggedApi"
message="Method `RolesState()` is a flagged API and should be inside an `if (Flags.systemServerRoleControllerEnabled())` check (or annotate the surrounding method `writeFile` with `@FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED) to transfer requirement to caller`)"
errorLine1=" roles = new RolesState(mVersion, packagesHash,"
diff --git a/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
index fcdccd87a..f4bed4ada 100644
--- a/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
+++ b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
@@ -95,8 +95,7 @@ public class BackgroundPermissionsTest {
verifyBackgroundPermissionsProperties("android");
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
- codeName = "VanillaIceCream")
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
@RequiresFlagsEnabled({Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED})
@Test
@AppModeFull(reason = "Instant apps cannot read properties of other packages")
diff --git a/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java
index 1b3f65ee6..fc5ea3a44 100644
--- a/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java
+++ b/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java
@@ -155,23 +155,6 @@ public final class NfcPermissionTest {
}
/**
- * Verifies that isTagIntentAppPreferenceSupported() requires Permission.
- * <p>
- * Requires Permission: {@link android.Manifest.permission.WRITE_SECURE_SETTINGS}.
- */
- @Test
- @AppModeFull
- public void testIsTagIntentAppPreferenceSupported() {
- try {
- mNfcAdapter.isTagIntentAppPreferenceSupported();
- fail("mNfcAdapter.isTagIntentAppPreferenceSupported() did not throw SecurityException"
- + " as expected");
- } catch (SecurityException se) {
- // Expected Exception
- }
- }
-
- /**
* Verifies that getTagIntentAppPreferenceForUser() requires Permission.
* <p>
* Requires Permission: {@link android.Manifest.permission.WRITE_SECURE_SETTINGS}.
diff --git a/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index 776a1065e..f2d59cbe7 100755
--- a/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -45,9 +45,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
+import android.health.connect.HealthPermissions;
import android.os.Build;
import android.permission.PermissionManager;
import android.permission.PermissionManager.SplitPermissionInfo;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SdkSuppress;
@@ -56,6 +62,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiLevelUtil;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +78,9 @@ public class SplitPermissionsSystemTest {
private List<SplitPermissionInfo> mSplitPermissions;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void before() {
Context context = InstrumentationRegistry.getContext();
@@ -87,9 +97,14 @@ public class SplitPermissionsSystemTest {
for (SplitPermissionInfo split : mSplitPermissions) {
String splitPermission = split.getSplitPermission();
- boolean isAndroid = splitPermission.startsWith("android");
- if (!isAndroid) {
+ // Due to limitation with accessing flag values in tests, BODY_SENSORS relevant splits
+ // are handled in its dedicated tests.
+ boolean shouldSkip =
+ !splitPermission.startsWith("android")
+ || splitPermission.equals(BODY_SENSORS)
+ || splitPermission.equals(BODY_SENSORS_BACKGROUND);
+ if (shouldSkip) {
continue;
}
@@ -149,9 +164,6 @@ public class SplitPermissionsSystemTest {
case BLUETOOTH_SCAN:
assertSplit(split, Build.VERSION_CODES.S, BLUETOOTH, BLUETOOTH_ADMIN);
break;
- case BODY_SENSORS:
- assertSplit(split, Build.VERSION_CODES.TIRAMISU, BODY_SENSORS_BACKGROUND);
- break;
case ACCESS_MEDIA_LOCATION:
case READ_MEDIA_IMAGES:
case READ_MEDIA_VIDEO:
@@ -160,7 +172,56 @@ public class SplitPermissionsSystemTest {
}
}
- assertEquals(24, seenSplits.size());
+ assertEquals(23, seenSplits.size());
+ }
+
+ @RequiresFlagsDisabled({Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED})
+ @Test
+ public void
+ validateBodySensors_beforeGranularHealthPermissions_isSplitToBodySensorsBackground() {
+ assumeTrue(ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q));
+
+ mSplitPermissions.stream()
+ .filter(split -> split.getSplitPermission().equals(BODY_SENSORS))
+ .findFirst()
+ .ifPresent(
+ split ->
+ assertSplit(
+ split,
+ Build.VERSION_CODES.TIRAMISU,
+ BODY_SENSORS_BACKGROUND));
+ }
+
+ @RequiresFlagsEnabled({Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED})
+ @Test
+ public void validateBodySensors_afterGranularHealthPermissions_isSplitToReadHeartRate() {
+ // TODO: Change this to Baklava when available.
+ assumeTrue(ApiLevelUtil.isAtLeast(36));
+
+ SplitPermissionInfo legacyBodySensorPermissionInfo = null;
+ SplitPermissionInfo readHeartRatePermissionInfo = null;
+ SplitPermissionInfo bodySensorBackgroundPermissionInfo = null;
+ for (SplitPermissionInfo split : mSplitPermissions) {
+ if (split.getSplitPermission().equals(BODY_SENSORS)
+ && split.getNewPermissions().contains(BODY_SENSORS_BACKGROUND)) {
+ legacyBodySensorPermissionInfo = split;
+ } else if (split.getSplitPermission().equals(BODY_SENSORS)
+ && split.getNewPermissions().contains(HealthPermissions.READ_HEART_RATE)) {
+ readHeartRatePermissionInfo = split;
+ } else if (split.getSplitPermission().equals(BODY_SENSORS_BACKGROUND)) {
+ bodySensorBackgroundPermissionInfo = split;
+ }
+ }
+ // Assert BODY_SENSORS is split to BODY_SENSORS_BACKGROUND and READ_HEART_RATE.
+ assertSplit(
+ legacyBodySensorPermissionInfo,
+ Build.VERSION_CODES.TIRAMISU,
+ BODY_SENSORS_BACKGROUND);
+ assertSplit(readHeartRatePermissionInfo, HealthPermissions.READ_HEART_RATE);
+ // Assert BODY_SENSORS_BACKGROUND is split to READ_HEALTH_DATA_IN_BACKGROUND.
+ assertSplit(
+ bodySensorBackgroundPermissionInfo,
+ HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
}
private void assertSplit(SplitPermissionInfo split, int targetSdk, String... permission) {
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
index f0a0e3fc1..687234582 100644
--- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
@@ -21,6 +21,7 @@ import android.app.Instrumentation
import android.companion.virtual.VirtualDeviceManager
import android.companion.virtual.VirtualDeviceParams
import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT
import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA
import android.content.ComponentName
import android.content.Intent
@@ -39,7 +40,9 @@ import android.permissionmultidevice.cts.UiAutomatorUtils.click
import android.permissionmultidevice.cts.UiAutomatorUtils.findTextForView
import android.permissionmultidevice.cts.UiAutomatorUtils.waitFindObject
import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
+import android.provider.Settings
import android.view.Display
import android.virtualdevice.cts.common.VirtualDeviceRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -47,7 +50,6 @@ import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.SystemUtil
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@@ -146,6 +148,7 @@ class DeviceAwarePermissionGrantTest {
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
)
+ @RequiresFlagsDisabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
@Test
fun onRemoteDevice_requestPermissionForHostDevice_shouldShowWarningDialog() {
requestPermissionOnDevice(virtualDisplay.display.displayId, DEVICE_ID_DEFAULT)
@@ -156,6 +159,32 @@ class DeviceAwarePermissionGrantTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
+ Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES
+ )
+ @Test
+ fun onRemoteDevice_requestPermissionForHostDevice_shouldGrantPermission() {
+ // Create a virtual device with default policy, so that camera permission request will
+ // correspond to default device camera access.
+ virtualDevice =
+ virtualDeviceRule.createManagedVirtualDevice(
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_DEFAULT)
+ .build()
+ )
+ testGrantPermissionForDevice(
+ virtualDisplay.display.displayId,
+ virtualDevice.deviceId,
+ true,
+ Settings.Global.getString(defaultDeviceContext.contentResolver,
+ Settings.Global.DEVICE_NAME),
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnRemoteDevice = false
+ )
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
)
@Test
@@ -199,7 +228,7 @@ class DeviceAwarePermissionGrantTest {
TestConstants.PERMISSION_RESULT_KEY_PERMISSIONS
)
)
- .isEqualTo(arrayOf(DEVICE_AWARE_PERMISSION))
+ .isEqualTo(arrayOf(PERMISSION))
assertThat(
grantPermissionResult.getIntArray(TestConstants.PERMISSION_RESULT_KEY_GRANT_RESULTS)
)
@@ -237,7 +266,7 @@ class DeviceAwarePermissionGrantTest {
private fun assertPermissionMessageContainsDeviceName(displayId: Int, deviceName: String) {
waitFindObject(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
val text = findTextForView(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
- Truth.assertThat(text).contains(deviceName)
+ assertThat(text).contains(deviceName)
}
private fun assertAppHasPermissionForDevice(deviceId: Int, expectPermissionGranted: Boolean) {
@@ -245,7 +274,7 @@ class DeviceAwarePermissionGrantTest {
defaultDeviceContext
.createDeviceContext(deviceId)
.packageManager
- .checkPermission(DEVICE_AWARE_PERMISSION, APP_PACKAGE_NAME)
+ .checkPermission(PERMISSION, APP_PACKAGE_NAME)
if (expectPermissionGranted) {
Assert.assertEquals(PackageManager.PERMISSION_GRANTED, checkPermissionResult)
@@ -269,7 +298,7 @@ class DeviceAwarePermissionGrantTest {
"com.android.permissioncontroller:id/permission_allow_foreground_only_button"
const val DEVICE_ID_DEFAULT = 0
const val PERSISTENT_DEVICE_ID_DEFAULT = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
- const val DEVICE_AWARE_PERMISSION = Manifest.permission.CAMERA
+ const val PERMISSION = Manifest.permission.CAMERA
const val TIMEOUT = 5000L
private const val DISPLAY_HEIGHT = 1920
private const val DISPLAY_WIDTH = 1080
diff --git a/tests/cts/permissionpolicy/res/raw/OWNERS b/tests/cts/permissionpolicy/res/raw/OWNERS
index 6e1a91b88..4f0306f55 100644
--- a/tests/cts/permissionpolicy/res/raw/OWNERS
+++ b/tests/cts/permissionpolicy/res/raw/OWNERS
@@ -1,6 +1,5 @@
hackbod@google.com
patb@google.com
-yamasani@google.com
michaelwr@google.com
narayan@google.com
roosa@google.com
diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
index 067364ac4..5df9c944e 100644
--- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -898,13 +898,26 @@
android:featureFlag="android.provider.user_keys" />
<!-- Allows an application to set default account for new contacts.
- <p> This permission is only granted to system applications fulfilling the Contacts app role.
+ <p>This permission is only granted to system applications fulfilling the Contacts app role.
<p>Protection level: internal|role
@SystemApi
@hide
-->
<permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|role"
+ android:featureFlag="!android.provider.new_default_account_api_enabled"/>
+
+ <!-- Allows an application to set default account for new contacts.
+ <p>This permission is only granted to system applications fulfilling the Contacts app role
+ and the application with known signers.
+ <p>Protection level: internal|role|knownSigner
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
+ android:protectionLevel="internal|role|knownSigner"
+ android:knownCerts="@array/config_setContactsDefaultAccountKnownSigners"
+ android:featureFlag="android.provider.new_default_account_api_enabled"/>
<!-- ====================================================================== -->
<!-- Permissions for accessing user's calendar -->
@@ -3955,7 +3968,7 @@
@FlaggedApi("android.security.aapm_api")
@SystemApi
@hide -->
- <permission android:name="android.permission.SET_ADVANCED_PROTECTION_MODE"
+ <permission android:name="android.permission.MANAGE_ADVANCED_PROTECTION_MODE"
android:protectionLevel="signature|privileged"
android:featureFlag="android.security.aapm_api"/>
@@ -3965,6 +3978,37 @@
android:protectionLevel="normal"
android:featureFlag="android.security.aapm_api"/>
+ <!-- Allows an application to read the state of the IntrusionDetectionService
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.READ_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.READ_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+ <!-- Allows an application to change the state of the IntrusionDetectionService
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.MANAGE_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.MANAGE_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+ <!-- Must be required by any IntrusionDetectionEventTransportService to ensure that
+ only the system can bind to it.
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE"
+ android:featureFlag="android.security.afl_api"/>
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup|knownSigner"
@@ -4108,6 +4152,52 @@
android:protectionLevel="signature" />
<!-- ================================== -->
+ <!-- Permissions associated with picture profiles and processing -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- @FlaggedApi(android.media.tv.flags.Flags.apply_picture_profiles)
+ Allows an app to apply a {@link MediaQualityManager.PictureProfile} to a layer via
+ {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and, additionally, system apps via
+ {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+ -->
+ <permission android:name="android.permission.APPLY_PICTURE_PROFILE"
+ android:protectionLevel="normal"
+ android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+ <!-- @hide
+ Allows MediaQualityManager to observe any {@link MediaQualityManager.PictureProfile}
+ applied to any layer in the system by apps via
+ {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and by system apps via
+ {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+ -->
+ <permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+ <!--
+ @SystemApi
+ @FlaggedApi("android.media.tv.flags.media_quality_fw")
+ Allows an application to access its picture profile from the media quality database.
+ <p> Protection level: signature|privileged|vendor privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+ <!--
+ @SystemApi
+ @FlaggedApi("android.media.tv.flags.media_quality_fw")
+ Allows an application to access its sound profile from the media quality database.
+ <p> Protection level: signature|privileged|vendor privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+ <!-- ================================== -->
<!-- Permissions affecting the display of other applications -->
<!-- ================================== -->
<eat-comment />
@@ -4812,16 +4902,16 @@
android:protectionLevel="signature|privileged|role"
android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
- <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+ <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_WRITE_SYSTEM_PREFERENCE_PERMISSION_ENABLED)
Allows an application to access the Settings Preference services to write settings
values exposed by the system Settings app and system apps that contribute settings surfaced
in the Settings app.
<p>This allows the calling application to write settings values
through the host application, agnostic of underlying storage.
- <p>Protection Level: signature|privileged|appop - appop to be added in followup -->
+ <p>Protection Level: signature|privileged|appop -->
<permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES"
- android:protectionLevel="signature|privileged"
- android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+ android:protectionLevel="signature|privileged|appop"
+ android:featureFlag="com.android.settingslib.flags.write_system_preference_permission_enabled" />
<!-- ========================================= -->
<!-- Permissions for special development tools -->
@@ -5611,6 +5701,17 @@
<permission android:name="android.permission.LOCK_DEVICE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi
+ @FlaggedApi(android.security.Flags.FLAG_SECURE_LOCKDOWN)
+ Allows an application to lock down the device into an enhanced security state.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_SECURE_LOCK_DEVICE"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.security.secure_lockdown" />
+
<!-- @SystemApi Allows low-level access to setting the orientation (actually
rotation) of the screen.
<p>Not for use by third-party applications.
@@ -6221,6 +6322,15 @@
<permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"
android:protectionLevel="signature|privileged|role" />
+ <!-- @SystemApi Allows an application to bypass concurrency restrictions while
+ recording audio. For example, apps with this permission can continue to record
+ while a voice call is active.</p>
+ @FlaggedApi(android.media.audio.Flags.FLAG_CONCURRENT_AUDIO_RECORD_BYPASS_PERMISSION)
+ @hide -->
+ <permission android:name="android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION"
+ android:featureFlag="android.media.audio.concurrent_audio_record_bypass_permission"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows an application to capture audio for hotword detection.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -7664,7 +7774,31 @@
<!-- @SystemApi Allows an application to access shared libraries.
@hide -->
<permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer"
+ android:featureFlag="!android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to access shared libraries.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
+ android:protectionLevel="signature|installer|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Permission held by the system to allow binding to the dependency installer role
+ holder.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.BIND_DEPENDENCY_INSTALLER"
+ android:protectionLevel="signature"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to install shared libraries of types
+ {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE}.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
<!-- Allows an app to log compat change usage.
@hide <p>Not for use by third-party applications.</p> -->
@@ -7739,6 +7873,15 @@
<permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
android:protectionLevel="signature|role"/>
+ <!-- Allows an application to create displays that mirror other displays' content.
+ <p>Not for use by third-party applications.
+ <p>Protection level: internal|role
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE)
+ @hide @SystemApi -->
+ <permission android:name="android.permission.ADD_MIRROR_DISPLAY"
+ android:protectionLevel="internal|role"
+ android:featureFlag="android.companion.virtualdevice.flags.enable_limited_vdm_role" />
+
<!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|role" />
@@ -8367,58 +8510,23 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
- <!-- @SystemApi
- @FlaggedApi("android.content.pm.verification_service")
- Allows app to be the verification agent to verify packages.
+ <!-- Allows app to enter trade-in-mode.
<p>Protection level: signature|privileged
@hide
-->
- <permission android:name="android.permission.VERIFICATION_AGENT"
+ <permission android:name="android.permission.ENTER_TRADE_IN_MODE"
android:protectionLevel="signature|privileged"
- android:featureFlag="android.content.pm.verification_service"/>
+ android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" />
<!-- @SystemApi
- @FlaggedApi("android.content.pm.verification_service")
- Must be required by a privileged {@link android.content.pm.verify.pkg.VerifierService}
- to ensure that only the system can bind to it.
- This permission should not be held by anything other than the system.
- <p>Not for use by third-party applications. </p>
+ @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS)
+ Ability to read program metadata and attach dynamic instrumentation.
<p>Protection level: signature
@hide
-->
- <permission android:name="android.permission.BIND_VERIFICATION_AGENT"
- android:protectionLevel="internal"
- android:featureFlag="android.content.pm.verification_service" />
-
- <!--
- @SystemApi
- @FlaggedApi("android.media.tv.flags.media_quality_fw")
- Allows an application to access its picture profile from the media quality database.
- <p> Protection level: signature|privileged|vendor privileged
- @hide
- -->
- <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
- android:protectionLevel="signature|privileged|vendorPrivileged"
- android:featureFlag="android.media.tv.flags.media_quality_fw"/>
-
- <!--
- @SystemApi
- @FlaggedApi("android.media.tv.flags.media_quality_fw")
- Allows an application to access its sound profile from the media quality database.
- <p> Protection level: signature|privileged|vendor privileged
- @hide
- -->
- <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
- android:protectionLevel="signature|privileged|vendorPrivileged"
- android:featureFlag="android.media.tv.flags.media_quality_fw"/>
-
- <!-- Allows app to enter trade-in-mode.
- <p>Protection level: signature|privileged
- @hide
- -->
- <permission android:name="android.permission.ENTER_TRADE_IN_MODE"
- android:protectionLevel="signature|privileged"
- android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" />
+ <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION"
+ android:protectionLevel="signature"
+ android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
<!-- @SystemApi
@FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
@@ -9034,6 +9142,17 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="android.app.ecm.EnhancedConfirmationCallTrackerService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:featureFlag="android.permission.flags.enhanced_confirmation_in_call_apis_enabled"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
android:permission="android.permission.BIND_INCALL_SERVICE"
android:exported="true">
diff --git a/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
index 783cd7f6b..e123f13ce 100644
--- a/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
@@ -620,4 +620,16 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_bind_app_card_provider"
android:description="@string/car_permission_desc_bind_app_card_provider" />
+ <permission
+ android:name="android.car.permission.RECORD_VEHICLE_PROPERTIES"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_record_vehicle_properties"
+ android:description="@string/car_permission_desc_record_vehicle_properties"
+ android:featureFlag="android.car.feature.car_power_cancel_shell_command" />
+ <permission
+ android:name="android.car.permission.INJECT_VEHICLE_PROPERTIES"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_inject_vehicle_properties"
+ android:description="@string/car_permission_desc_inject_vehicle_properties"
+ android:featureFlag="android.car.feature.car_power_cancel_shell_command" />
</manifest>
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
index 70832b6ba..2ce48af44 100644
--- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
@@ -59,6 +59,7 @@ import android.app.AppOpsManager.permissionToOp
import android.content.pm.PackageManager.GET_PERMISSIONS
import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP
+import android.health.connect.HealthPermissions
import android.os.Build
import android.permission.flags.Flags
import android.permission.PermissionManager
@@ -195,6 +196,18 @@ class RuntimePermissionProperties {
expectedPerms.add(RANGING)
}
- assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
+ // Separately check health permissions.
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ assertThat(expectedPerms).contains(HealthPermissions.READ_HEART_RATE);
+ assertThat(expectedPerms).contains(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+
+ // Remove these from the expected list once we've confirmed their
+ // present. These are not permissions owned by "android" so won't be
+ // in the list of platform runtime permissions.
+ expectedPerms.remove(HealthPermissions.READ_HEART_RATE);
+ expectedPerms.remove(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+ }
+
+ assertThat(platformRuntimePerms.map { it.name }).containsExactlyElementsIn(expectedPerms)
}
}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/SignaturePermissionAllowlistConfigTest.kt b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/SignaturePermissionAllowlistConfigTest.kt
index 268bbd317..e6673718f 100644
--- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/SignaturePermissionAllowlistConfigTest.kt
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/SignaturePermissionAllowlistConfigTest.kt
@@ -20,6 +20,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
+import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.CddTest
@@ -52,13 +53,16 @@ class SignaturePermissionAllowlistConfigTest {
}
}
}
- assertTrue(
- "Some platform-signed non-system packages don't have their requested platform" +
- " signature permissions allowlisted. Suggested signature permission allowlist" +
- " additions:\n\n" +
- buildSiganturePermissionAllowlist(unallowlistedPackageAndPermissions),
- unallowlistedPackageAndPermissions.isEmpty()
- )
+ // TODO(b/365777291): CDD 9.1/C-0-16 is still STRONGLY RECOMMENDED instead of MUST.
+ if (unallowlistedPackageAndPermissions.isNotEmpty()) {
+ Log.w(
+ LOG_TAG,
+ "Some platform-signed non-system packages don't have their requested platform" +
+ " signature permissions allowlisted. Suggested signature permission allowlist" +
+ " additions:\n\n" +
+ buildSiganturePermissionAllowlist(unallowlistedPackageAndPermissions),
+ )
+ }
}
private fun getSignaturePermissionAllowlist(): Map<String, Set<String>> {
@@ -107,12 +111,16 @@ class SignaturePermissionAllowlistConfigTest {
}
}
}
- assertTrue(
- "Some platform-signed non-system packages don't have their requested platform" +
- " signature permissions granted. Suggested signature permission allowlist" +
- " additions:\n\n${buildSiganturePermissionAllowlist(deniedPackageAndPermissions)}",
- deniedPackageAndPermissions.isEmpty()
- )
+ // TODO(b/365777291): CDD 9.1/C-0-16 is still STRONGLY RECOMMENDED instead of MUST.
+ if (deniedPackageAndPermissions.isNotEmpty()) {
+ Log.w(
+ LOG_TAG,
+ "Some platform-signed non-system packages don't have their requested platform" +
+ " signature permissions granted. Suggested signature permission allowlist" +
+ " additions:\n\n" +
+ buildSiganturePermissionAllowlist(deniedPackageAndPermissions),
+ )
+ }
}
private fun getPlatformSignaturePermissionNames(): List<String> =
@@ -142,4 +150,8 @@ class SignaturePermissionAllowlistConfigTest {
append(" </signature-permissions>\n")
}
}
+
+ companion object {
+ private val LOG_TAG = SignaturePermissionAllowlistConfigTest::class.java.simpleName
+ }
}
diff --git a/tests/cts/permissionui/AndroidManifest.xml b/tests/cts/permissionui/AndroidManifest.xml
index 3b80b8d8b..b5c9e2ad0 100644
--- a/tests/cts/permissionui/AndroidManifest.xml
+++ b/tests/cts/permissionui/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<application>
@@ -78,6 +79,23 @@
<meta-data android:name="android.accessibilityservice"
android:resource="@xml/test_accessibilityservice"/>
</service>
+ <service android:name=".VoipHelperTestConnectionService"
+ android:exported="true"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
+ <intent-filter>
+ <action android:name="android.telecom.ConnectionService" />
+ </intent-filter>
+ </service>
+
+ <service android:name=".EcmInCallTestInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
</application>
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
new file mode 100644
index 000000000..cbf4734d5
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest
+import android.app.Instrumentation
+import android.app.ecm.EnhancedConfirmationManager
+import android.content.ContentProviderOperation
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.permission.flags.Flags
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.provider.ContactsContract
+import android.provider.ContactsContract.CommonDataKinds
+import android.provider.ContactsContract.Data
+import android.provider.ContactsContract.RawContacts
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import java.util.concurrent.Callable
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * This test verifies the behavior of the Enhanced Confirmation Manager APIs that deal with unknown
+ * callers
+ */
+@AppModeFull(reason = "Instant apps cannot install packages")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+// @CddTest(requirement = "TBD")
+class EnhancedConfirmationInCallTest {
+ private val ecm = context.getSystemService(EnhancedConfirmationManager::class.java)!!
+ private val packageManager = context.packageManager
+ private val addedContacts = mutableMapOf<String, List<Uri>>()
+
+ @JvmField
+ @Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun assumeNotAutoOrTv() {
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+ }
+
+ companion object {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.targetContext
+ private lateinit var voipService: VoipCallHelper
+
+ @JvmStatic
+ @BeforeClass
+ fun setupVoipService() {
+ voipService = VoipCallHelper(context)
+ voipService.registerPhoneAccount()
+ }
+
+ @JvmStatic
+ @AfterClass
+ fun tearDownVoipService() {
+ voipService.removePhoneAccount()
+ }
+
+ const val CONTACT_DISPLAY_NAME = "Alice Bobson"
+ const val NON_CONTACT_DISPLAY_NAME = "Eve McEve"
+ const val CONTACT_PHONE_NUMBER = "8888888888"
+ const val NON_CONTACT_PHONE_NUMBER = "1111111111"
+ }
+
+ private fun addContact(displayName: String, phoneNumber: String) {
+ runWithShellPermissionIdentity {
+ val ops: ArrayList<ContentProviderOperation> = ArrayList()
+ ops.add(
+ ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
+ .withValue(RawContacts.ACCOUNT_TYPE, "test type")
+ .withValue(RawContacts.ACCOUNT_NAME, "test account")
+ .build()
+ )
+ ops.add(
+ ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, 0)
+ .withValue(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
+ .build()
+ )
+ ops.add(
+ ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, 0)
+ .withValue(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(CommonDataKinds.Phone.NUMBER, phoneNumber)
+ .build()
+ )
+ val results = context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
+ val resultsForDisplayName = mutableListOf<Uri>()
+ results.forEach { resultsForDisplayName.add(it.uri!!) }
+ addedContacts[displayName] = resultsForDisplayName
+ }
+ }
+
+ private fun removeContact(displayName: String) {
+ runWithShellPermissionIdentity {
+ var totalRowsRemoved = 0
+ for (data in addedContacts[displayName] ?: emptyList()) {
+ totalRowsRemoved += context.contentResolver.delete(data, null)
+ }
+ // There are multiple contacts tables, and removing from the raw_contacts table
+ // can cause row removals from the data table, so we may get some uris that don't
+ // report a delete, but we should get at least one, and not more than the number of uris
+ Assert.assertNotEquals(
+ "Expected at least one contact row to be removed",
+ 0,
+ totalRowsRemoved,
+ )
+ Assert.assertTrue(
+ "Unexpectedly large number of contact rows removed",
+ totalRowsRemoved <= (addedContacts[displayName]?.size ?: 0),
+ )
+ addedContacts.remove(displayName)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ voipService.endCallAndWaitForInactive()
+ addedContacts.keys.forEach { removeContact(it) }
+ }
+
+ private fun getInUnknownCallState(): Boolean {
+ return callWithShellPermissionIdentity { ecm.isUnknownCallOngoing }
+ }
+
+ @Test
+ fun testCannotReadOngoingState_WithoutPermission() {
+ try {
+ ecm.isUnknownCallOngoing
+ Assert.fail()
+ } catch (expected: SecurityException) {
+ Assert.assertTrue(
+ expected.message?.contains(
+ Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES
+ ) == true
+ )
+ }
+
+ val unexpectedException =
+ callWithShellPermissionIdentity(
+ Callable {
+ try {
+ ecm.isUnknownCallOngoing
+ null
+ } catch (unexpected: SecurityException) {
+ // Catching the exception, because exceptions thrown inside
+ // run/callWithShellPermissionIdentity are obscured by the rethrow
+ // from run/call.
+ unexpected
+ }
+ },
+ Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES,
+ )
+ Assert.assertNull(unexpectedException)
+ }
+
+ @Test
+ fun testIncomingCall_NonContact() {
+ voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, NON_CONTACT_PHONE_NUMBER)
+ Assert.assertTrue(getInUnknownCallState())
+ voipService.endCallAndWaitForInactive()
+ Assert.assertFalse(getInUnknownCallState())
+ }
+
+ @Test
+ fun testIncomingCall_Contact_DisplayNameMatches_PhoneNotGiven() {
+ addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ // If no phone number is given, the display name will be checked
+ voipService.createCallAndWaitForActive(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ Assert.assertFalse(getInUnknownCallState())
+ voipService.endCallAndWaitForInactive()
+ Assert.assertFalse(getInUnknownCallState())
+ }
+
+ @Test
+ fun testIncomingCall_Contact_PhoneNumberMatches() {
+ addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ // If the phone number matches, the display name is not checked
+ voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ Assert.assertFalse(getInUnknownCallState())
+ voipService.endCallAndWaitForInactive()
+ Assert.assertFalse(getInUnknownCallState())
+ }
+
+ @Test
+ fun testCall_DoesntBecomeTrustedIfCallerAddedDuringCall() {
+ val tempContactDisplay = "TEMP CONTACT"
+ val tempContactPhone = "999-999-9999"
+ voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone)
+ addContact(tempContactDisplay, tempContactPhone)
+ // State should not be recomputed just because the contact is newly added
+ Assert.assertTrue(getInUnknownCallState())
+ voipService.endCallAndWaitForInactive()
+ voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone)
+ // A new call should recognize our contact, and mark the call as trusted
+ Assert.assertFalse(getInUnknownCallState())
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
index d509add3a..da70fc186 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
@@ -17,15 +17,22 @@
package android.permissionui.cts
import android.os.Build
+import android.permission.flags.Flags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.filters.FlakyTest
import androidx.test.filters.SdkSuppress
import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
/** Runtime permission behavior tests for permission splits. */
@FlakyTest
class PermissionSplitTest : BaseUsePermissionTest() {
+
+ @Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
fun assumeNotTv() {
assumeFalse(isTv)
@@ -56,6 +63,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
fun testBodySensorSplit() {
installPackage(APP_APK_PATH_31)
@@ -63,6 +71,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
fun testBodySensorSplit32() {
installPackage(APP_APK_PATH_32)
@@ -70,6 +79,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
fun testBodySensorNonSplit() {
installPackage(APP_APK_PATH_LATEST)
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt b/tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt
new file mode 100644
index 000000000..480d7bff3
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.content.ComponentName
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.os.Process
+import android.permissionui.cts.VoipCallHelper.Companion.EXTRA_DISPLAY_NAME
+import android.permissionui.cts.VoipCallHelper.Companion.awaitingCallStateLatch
+import android.permissionui.cts.VoipCallHelper.Companion.currentActiveConnection
+import android.telecom.Connection
+import android.telecom.ConnectionRequest
+import android.telecom.ConnectionService
+import android.telecom.DisconnectCause
+import android.telecom.PhoneAccount
+import android.telecom.PhoneAccountHandle
+import android.telecom.TelecomManager
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Assert
+
+/** A helper class which can register a phone account, and make/end VOIP phone calls */
+class VoipCallHelper(val context: Context) {
+ private val telecomManager by lazy { context.getSystemService(TelecomManager::class.java) }
+ private lateinit var phoneAccount: PhoneAccount
+ private val accountHandle =
+ PhoneAccountHandle(
+ ComponentName(context, VoipHelperTestConnectionService::class.java),
+ "cts-voip-helper-test",
+ Process.myUserHandle(),
+ )
+
+ init {
+ registerPhoneAccount()
+ }
+
+ companion object {
+ var currentActiveConnection: VoIPConnection? = null
+ var awaitingCallStateLatch: CallPlacedLatch? = null
+
+ const val EXTRA_DISPLAY_NAME = "display_name"
+ const val CUSTOM_ADDRESS_SCHEMA = "custom_schema"
+ const val CALL_STATE_WAIT_MS = 1000L
+ const val CALL_TIMEOUT_MS = 10000L
+ }
+
+ fun registerPhoneAccount() {
+ val phoneAccountBuilder = PhoneAccount.builder(accountHandle, "CTS VOIP HELPER")
+ phoneAccountBuilder.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ // see b/343674176. Some OEMs expect the PhoneAccount.getExtras() to be non-null
+ val defaultBundle = Bundle()
+ phoneAccountBuilder.setExtras(defaultBundle)
+
+ // build and register the PhoneAccount via the Platform API
+ phoneAccount = phoneAccountBuilder.build()
+ telecomManager.registerPhoneAccount(phoneAccount)
+ }
+
+ fun removePhoneAccount() {
+ telecomManager.unregisterPhoneAccount(phoneAccount.accountHandle)
+ }
+
+ fun createCallAndWaitForActive(displayName: String?, phoneNumber: String?) {
+ val extras = Bundle()
+
+ val phoneUri =
+ if (phoneNumber != null) {
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null)
+ } else {
+ // If we don't have a phone number, provide a custom address URI, like many VOIP
+ // apps that aren't tied to a phone number do
+ Uri.fromParts(CUSTOM_ADDRESS_SCHEMA, "custom_address", null)
+ }
+ if (displayName != null) {
+ extras.putString(EXTRA_DISPLAY_NAME, displayName)
+ }
+ extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, phoneUri)
+ awaitingCallStateLatch = CallPlacedLatch(phoneUri, displayName)
+ telecomManager.addNewIncomingCall(phoneAccount.accountHandle, extras)
+ Assert.assertTrue(
+ "Timed out waiting for call to start",
+ awaitingCallStateLatch!!.await(CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+ )
+ // TODO b/379941144: Replace wait with waiting until a test InCallService gets a callback
+ Thread.sleep(CALL_STATE_WAIT_MS)
+ }
+
+ fun endCallAndWaitForInactive() {
+ currentActiveConnection?.let { connection ->
+ connection.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
+ connection.destroy()
+ // TODO b/379941144: Replace wait with waiting until a test InCallService gets a
+ // callback
+ Thread.sleep(CALL_STATE_WAIT_MS)
+ }
+ currentActiveConnection = null
+ }
+}
+
+class CallPlacedLatch(val address: Uri?, val displayName: String?) : CountDownLatch(1) {
+ fun nameAndNumberMatch(connection: Connection): Boolean {
+ return connection.address == address && connection.callerDisplayName == displayName
+ }
+}
+
+class VoIPConnection : Connection() {
+ init {
+ setConnectionProperties(PROPERTY_SELF_MANAGED)
+ setAudioModeIsVoip(true)
+ setActive()
+ }
+
+ override fun onShowIncomingCallUi() {
+ super.onShowIncomingCallUi()
+ setActive()
+ currentActiveConnection = this
+ if (awaitingCallStateLatch?.nameAndNumberMatch(this) == true) {
+ awaitingCallStateLatch?.countDown()
+ }
+ }
+}
+
+class VoipHelperTestConnectionService : ConnectionService() {
+ override fun onCreateOutgoingConnection(
+ connectionManagerPhoneAccount: PhoneAccountHandle,
+ request: ConnectionRequest,
+ ): Connection {
+ return createConnection(request)
+ }
+
+ override fun onCreateIncomingConnection(
+ connectionManagerPhoneAccount: PhoneAccountHandle?,
+ request: ConnectionRequest?,
+ ): Connection {
+ return createConnection(request)
+ }
+
+ private fun createConnection(request: ConnectionRequest?): Connection {
+ val connection = VoIPConnection()
+ if (request?.extras?.containsKey(EXTRA_DISPLAY_NAME) == true) {
+ connection.setCallerDisplayName(
+ request.extras.getString(EXTRA_DISPLAY_NAME),
+ TelecomManager.PRESENTATION_ALLOWED,
+ )
+ connection.setAddress(
+ request.extras.getParcelable(
+ TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+ Uri::class.java,
+ ),
+ TelecomManager.PRESENTATION_ALLOWED,
+ )
+ }
+ return connection
+ }
+}
diff --git a/tests/cts/role/Android.bp b/tests/cts/role/Android.bp
index 5751aaada..9f1e6cff6 100644
--- a/tests/cts/role/Android.bp
+++ b/tests/cts/role/Android.bp
@@ -30,10 +30,12 @@ android_test {
static_libs: [
"android.permission.flags-aconfig-java-export",
"androidx.test.rules",
+ "com.android.permission.flags-aconfig-java-export",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"Harrier",
"bedstead-multiuser",
+ "flag-junit",
"platform-test-annotations",
"truth",
],
diff --git a/tests/cts/role/AndroidTest.xml b/tests/cts/role/AndroidTest.xml
index bfdcf2829..73f23dd1b 100644
--- a/tests/cts/role/AndroidTest.xml
+++ b/tests/cts/role/AndroidTest.xml
@@ -24,6 +24,7 @@
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user_on_secondary_display" />
<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" />
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
index e3bf054b0..9f89140d7 100644
--- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
@@ -104,6 +104,8 @@ public class RoleManagerTest {
private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
private static final String ROLE_PHONE_NAME = RoleManager.ROLE_DIALER;
private static final String ROLE_SMS_NAME = RoleManager.ROLE_SMS;
+ private static final String PROFILE_GROUP_EXCLUSIVE_ROLE_NAME =
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY;
private static final String ROLE_SHORT_LABEL = "Browser app";
private static final String APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk";
@@ -1349,6 +1351,51 @@ public class RoleManagerTest {
});
}
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ public void cannotGetActiveUserForRoleWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, ()->
+ sRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ public void cannotGetActiveUserForNonProfileGroupExclusiveRole() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.getActiveUserForRole(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER)));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ public void cannotSetActiveUserForRoleWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, ()->
+ sRoleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ Process.myUserHandle(), 0));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ public void cannotSetActiveUserForNonProfileGroupExclusiveRole() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setActiveUserForRole(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, Process.myUserHandle(),
+ 0)));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ public void setAndGetActiveUserForRole() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ Process.myUserHandle(), 0);
+ assertThat(sRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME))
+ .isEqualTo(Process.myUserHandle());
+ });
+ }
+
@NonNull
private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
diff --git a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
index 83d4f78ad..f615f0f4b 100644
--- a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
+++ b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
@@ -19,15 +19,19 @@ package android.app.role.cts
import android.app.role.RoleManager
import android.os.Build
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SdkSuppress
import androidx.test.runner.AndroidJUnit4
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.permission.flags.Flags
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,6 +47,8 @@ class RoleShellCommandTest {
private var roleHolder: String? = null
private var wasBypassingRoleQualification: Boolean = false
+ @get:Rule val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
public fun setUp() {
saveRoleHolder()
@@ -156,6 +162,28 @@ class RoleShellCommandTest {
assertThat(isBypassingRoleQualification()).isFalse()
}
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun setActiveUserForProfileGroupExclusiveRoleAsUser() {
+ val activeUser = userId
+ setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, activeUser)
+
+ val currentActiveUserId = getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ assertThat(currentActiveUserId).isEqualTo(activeUser)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun setActiveUserForNonProfileGroupExclusiveRoleThenFails() {
+ assertThrows(AssertionError::class.java) { setActiveUserForRole(ROLE_NAME, userId) }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun getActiveUserForNonProfileGroupExclusiveRoleThenFails() {
+ assertThrows(AssertionError::class.java) { getActiveUserForRole(ROLE_NAME) }
+ }
+
private fun addRoleHolder(packageName: String = APP_PACKAGE_NAME) {
runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME $packageName")
}
@@ -204,8 +232,22 @@ class RoleShellCommandTest {
callWithShellPermissionIdentity { roleManager.setBypassingRoleQualification(value) }
}
+ private fun getActiveUserForRole(roleName: String): Int? {
+ return runShellCommandOrThrow("cmd role get-active-user-for-role --user $userId $roleName")
+ .trim()
+ .toIntOrNull()
+ }
+
+ private fun setActiveUserForRole(roleName: String, activeUserId: Int) {
+ runShellCommandOrThrow(
+ "cmd role set-active-user-for-role --user $userId $roleName $activeUserId"
+ )
+ }
+
companion object {
private const val ROLE_NAME = RoleManager.ROLE_BROWSER
+ private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME =
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY
private const val APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk"
private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
private const val APP_CLONE_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestAppClone.apk"
diff --git a/tests/cts/rolemultiuser/Android.bp b/tests/cts/rolemultiuser/Android.bp
new file mode 100644
index 000000000..7de55fc1b
--- /dev/null
+++ b/tests/cts/rolemultiuser/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsRoleMultiUserTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "bedstead-flags",
+ "bedstead-multiuser",
+ "com.android.permission.flags-aconfig-java-export",
+ "ctstestrunner-axt",
+ "Harrier",
+ "flag-junit",
+ "Nene",
+ "truth",
+ ],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-permission",
+ ],
+
+ data: [
+ ":CtsRoleMultiUserTestApp",
+ ],
+}
diff --git a/tests/cts/rolemultiuser/AndroidManifest.xml b/tests/cts/rolemultiuser/AndroidManifest.xml
new file mode 100644
index 000000000..1524c5703
--- /dev/null
+++ b/tests/cts/rolemultiuser/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.rolemultiuser.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.app.rolemultiuser.cts"
+ android:label="CTS multi-user tests of android.app.role">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/rolemultiuser/AndroidTest.xml b/tests/cts/rolemultiuser/AndroidTest.xml
new file mode 100644
index 000000000..15c34f54a
--- /dev/null
+++ b/tests/cts/rolemultiuser/AndroidTest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Config for CTS role multi-user test cases">
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_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="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.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsRoleMultiUserTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts-role" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts-role"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsRoleMultiUserTestApp.apk->/data/local/tmp/cts-role/CtsRoleMultiUserTestApp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.app.rolemultiuser.cts" />
+ <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+</configuration>
diff --git a/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp
new file mode 100644
index 000000000..71ccd0e59
--- /dev/null
+++ b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRoleMultiUserTestApp",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+}
diff --git a/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml
new file mode 100644
index 000000000..eea3be741
--- /dev/null
+++ b/tests/cts/rolemultiuser/CtsRoleMultiUserTestApp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.rolemultiuser.cts.app">
+
+ <application android:label="CtsRoleMultiUserTestApp" />
+</manifest>
diff --git a/tests/cts/rolemultiuser/OWNERS b/tests/cts/rolemultiuser/OWNERS
new file mode 100644
index 000000000..fb6099cf7
--- /dev/null
+++ b/tests/cts/rolemultiuser/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/cts/rolemultiuser/TEST_MAPPING b/tests/cts/rolemultiuser/TEST_MAPPING
new file mode 100644
index 000000000..323e3094c
--- /dev/null
+++ b/tests/cts/rolemultiuser/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "postsubmit": [
+ {
+ "name": "CtsRoleMultiUserTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
+ }
+ ]
+}
diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
new file mode 100644
index 000000000..6c9cdfcb8
--- /dev/null
+++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.rolemultiuser.cts
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.os.Build
+import android.os.Process
+import androidx.test.filters.SdkSuppress
+import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile
+import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile
+import com.android.bedstead.enterprise.workProfile
+import com.android.bedstead.flags.annotations.RequireFlagsEnabled
+import com.android.bedstead.harrier.BedsteadJUnit4
+import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser
+import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser
+import com.android.bedstead.multiuser.annotations.RequireRunNotOnSecondaryUser
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser
+import com.android.bedstead.multiuser.privateProfile
+import com.android.bedstead.multiuser.secondaryUser
+import com.android.bedstead.nene.TestApis.context
+import com.android.bedstead.nene.TestApis.permissions
+import com.android.bedstead.nene.TestApis.users
+import com.android.bedstead.nene.types.OptionalBoolean
+import com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL
+import com.android.bedstead.permissions.CommonPermissions.MANAGE_DEFAULT_APPLICATIONS
+import com.android.bedstead.permissions.CommonPermissions.MANAGE_ROLE_HOLDERS
+import com.android.bedstead.permissions.annotations.EnsureDoesNotHavePermission
+import com.android.bedstead.permissions.annotations.EnsureHasPermission
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@RunWith(BedsteadJUnit4::class)
+class RoleManagerMultiUserTest {
+ @Before
+ @Throws(java.lang.Exception::class)
+ fun setUp() {
+ installAppForAllUsers()
+ }
+
+ @After
+ @Throws(java.lang.Exception::class)
+ fun tearDown() {
+ uninstallAppForAllUsers()
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotGetActiveUserForNonCrossUserRole() {
+ assertThrows(IllegalArgumentException::class.java) {
+ roleManager.getActiveUserForRole(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @Test
+ @Throws(Exception::class)
+ fun cannotGetActiveUserForRoleWithoutInteractAcrossUserPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS, MANAGE_DEFAULT_APPLICATIONS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotGetActiveUserForRoleWithoutManageRoleAndManageDefaultApplicationsPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForNonCrossUserRole() {
+ assertThrows(IllegalArgumentException::class.java) {
+ roleManager.setActiveUserForRole(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER,
+ Process.myUserHandle(),
+ 0,
+ )
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleWithoutInteractAcrossUserPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ Process.myUserHandle(),
+ 0,
+ )
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS, MANAGE_DEFAULT_APPLICATIONS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleWithoutManageRoleAndManageDefaultApplicationsPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ Process.myUserHandle(),
+ 0,
+ )
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleToNonExistentUser() {
+ val targetActiveUser = users().nonExisting().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasPrivateProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleToPrivateProfileUser() {
+ val targetActiveUser = deviceState.privateProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasAdditionalUser(installInstrumentedApp = OptionalBoolean.TRUE)
+ @EnsureHasSecondaryUser
+ @RequireRunNotOnSecondaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleToUserNotInProfileGroup() {
+ val targetActiveUser = deviceState.secondaryUser().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetCurrentUserWithManageRoleHoldersPermission() {
+ assumeFalse(
+ "setActiveUser not supported for private profile",
+ users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME,
+ )
+
+ val targetActiveUser = users().current().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetCurrentUserWithManageDefaultApplicationPermission() {
+ assumeFalse(
+ "setActiveUser not supported for private profile",
+ users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME,
+ )
+
+ val targetActiveUser = users().current().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetWorkProfile() {
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotAddRoleHolderAsUserForProfileExclusiveRoleWithoutInteractAcrossUserPermission() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ // setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
+ // state and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ assertThrows(SecurityException::class.java) {
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ }
+ assertThat(
+ roleManager.getRoleHoldersAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ targetActiveUser,
+ )
+ )
+ .isEmpty()
+
+ // getActiveUserForRole is used to ensure addRoleHolderAsUser didn't set active user, and
+ // requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserSetsPrimaryUserAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ roleManager
+ .getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser)
+ .first()
+ )
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserSetsWorkProfileAsActive() {
+ // Set other user as active
+ val initialUser = users().main()!!.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ roleManager
+ .getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser)
+ .first()
+ )
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetDefaultApplicationForProfileExclusiveRoleWithoutInteractAcrossUserPermission() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ // setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
+ // state and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+
+ val future = CallbackFuture()
+ assertThrows(SecurityException::class.java) {
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ }
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)).isNull()
+
+ // getActiveUserForRole is used to ensure setDefaultApplication didn't set active user,
+ // and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationSetsPrimaryUserAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val future = CallbackFuture()
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(users().current().userHandle())
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationSetsWorkProfileAsActive() {
+ // Set other user as active
+ val initialUser = users().main()!!.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val future = CallbackFuture()
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(APP_PACKAGE_NAME)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.workProfile().userHandle())
+ }
+
+ @Throws(java.lang.Exception::class)
+ private fun installAppForAllUsers() {
+ SystemUtil.runShellCommandOrThrow("pm install -r --user all $APP_APK_PATH")
+ }
+
+ private fun uninstallAppForAllUsers() {
+ SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME")
+ }
+
+ class CallbackFuture : CompletableFuture<Boolean?>(), Consumer<Boolean?> {
+ override fun accept(successful: Boolean?) {
+ complete(successful)
+ }
+ }
+
+ companion object {
+ private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong()
+ private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME =
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY
+ private const val PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE"
+ private const val APP_APK_PATH: String =
+ "/data/local/tmp/cts-role/CtsRoleMultiUserTestApp.apk"
+ private const val APP_PACKAGE_NAME: String = "android.app.rolemultiuser.cts.app"
+ private val context: Context = context().instrumentedContext()
+ private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)
+
+ @JvmField @ClassRule @Rule val deviceState = DeviceState()
+ }
+}
diff --git a/tests/functional/safetycenter/singleuser/AndroidTest.xml b/tests/functional/safetycenter/singleuser/AndroidTest.xml
index 3aa173508..af040eb6f 100644
--- a/tests/functional/safetycenter/singleuser/AndroidTest.xml
+++ b/tests/functional/safetycenter/singleuser/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- TODO(b/379928062): Ensure device not on lockscreen. Reassess when keyguard bug is
+ closed -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
index 912ea44ad..f6bef747d 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
@@ -17,6 +17,7 @@
package com.android.safetycenter.testing
import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.annotation.TargetApi
import android.app.job.JobInfo
@@ -532,7 +533,7 @@ object SafetyCenterFlags {
}
private fun writeDeviceConfigProperty(name: String, stringValue: String?) {
- callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG) {
+ callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
val valueWasSet =
DeviceConfig.setProperty(
NAMESPACE_PRIVACY,