diff options
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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> să-ți acceseze activitatea fizică?"</string> <string name="permgrouprequest_device_aware_activityRecognition" msgid="1243869530588745374">"Permiți ca <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> să acceseze activitatea ta fizică de pe <b><xliff:g id="DEVICE_NAME">%2$s</xliff:g></b>?"</string> - <string name="permgrouprequest_camera" msgid="5123097035410002594">"Permiți ca <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> să fotografieze și să înregistreze video?"</string> + <string name="permgrouprequest_camera" msgid="5123097035410002594">"Permiți ca <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> să fotografieze și să înregistreze videoclipuri?"</string> <string name="permgrouprequest_device_aware_camera" msgid="5340173564041615494">"Permiți ca <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> să facă fotografii și să înregistreze videoclipuri pe <b><xliff:g id="DEVICE_NAME">%2$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> కోసం లొకేషన్ యాక్సెస్ను మార్చాలా?"</string> <string name="permgroupupgraderequest_device_aware_location" msgid="1812338666887726191">"<b><xliff:g id="DEVICE_NAME">%2$s</xliff:g></b>లో <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>కు సంబంధించిన లొకేషన్ యాక్సెస్ను మార్చాలా?"</string> <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"మీరు యాప్ ఉపయోగించనప్పుడు కూడా ఈ యాప్ మీ లొకేషన్ను ఎప్పటికప్పుడు యాక్సెస్ చేయాలని అనుకుంటోంది."<annotation id="link">"సెట్టింగ్లలో అనుమతించండి."</annotation></string> - <string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"సమీప పరికరాల సంబంధిత స్థానాన్ని కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి \"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b>\"ను అనుమతించాలా?"</string> + <string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"సమీప పరికరాల సంబంధిత పొజిషన్ను కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి \"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b>\"ను అనుమతించాలా?"</string> <string name="permgrouprequest_device_aware_nearby_devices" msgid="5293478278408567442">"<b><xliff:g id="DEVICE_NAME">%2$s</xliff:g></b>లో సమీప పరికరాలు కనుగొని, కనెక్ట్ అయి, వాటి దూరం అంచనా వేసేలా <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>ను అనుమతించాలా?"</string> - <string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"సమీప పరికరాల సంబంధిత స్థానాన్ని కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>ను అనుమతించాలా? "<annotation id="link">"సెట్టింగ్లలో అనుమతించండి."</annotation></string> + <string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"సమీప పరికరాల సంబంధిత పొజిషన్ను కనుగొనడానికి, కనెక్ట్ చేయడానికి అలాగే నిర్ణయించడానికి <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>ను అనుమతించాలా? "<annotation id="link">"సెట్టింగ్లలో అనుమతించండి."</annotation></string> <string name="permgrouprequest_fineupgrade" msgid="2334242928821697672">"<xliff:g id="APP_NAME"><b>%1$s</b></xliff:g>కు సంబంధించిన లొకేషన్ యాక్సెస్ను సుమారు నుండి ఖచ్చితమైనదిగా మార్చాలా?"</string> <string name="permgrouprequest_device_aware_fineupgrade" msgid="4453775952305587571">"<b><xliff:g id="DEVICE_NAME">%2$s</xliff:g></b>లో <b><xliff:g id="APP_NAME"><b>%1$s</b></xliff:g></b> లొకేషన్ యాక్సెస్ను రమారమి నుండి ఖచ్చితమైన లొకేషన్కు మార్చాలా?"</string> <string name="permgrouprequest_coarselocation" msgid="7244605063736425232">"ఈ పరికరానికి సంబంధించి సుమారుగా ఉన్న లొకేషన్ను యాక్సెస్ చేయడానికి <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>ని అనుమతించాలా?"</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, |