summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Richard MacGregor <rmacgregor@google.com> 2022-11-17 16:24:19 -0800
committer Richard MacGregor <rmacgregor@google.com> 2022-12-07 17:42:50 +0000
commit2ef3f709ece0ed759dcda3ef53ac7a9bcb36e87b (patch)
tree326ad119e4694aa92212de35bf78158805aff867
parent04e240e492440c40668b49246c55f7bf4fcaaea8 (diff)
Add initial PermissionRationaleActivity
Activity shows details about permission data usage by the app NO_IFTTT=initial ifchange addition Bug: 256913489 Test: atest PermissionRationaleTest Test: atest PermissionRationalePermissionGrantDialogTest Change-Id: I092f6a5ecd1e6bc41d083456b20b017adb6519a1
-rw-r--r--PermissionController/AndroidManifest.xml11
-rw-r--r--PermissionController/res/drawable-v34/ic_business.xml10
-rw-r--r--PermissionController/res/drawable-v34/ic_collections_bookmark.xml10
-rw-r--r--PermissionController/res/drawable-v34/ic_gear.xml13
-rw-r--r--PermissionController/res/drawable-v34/ic_info.xml10
-rw-r--r--PermissionController/res/layout-v34/permission_rationale.xml147
-rw-r--r--PermissionController/res/values-v34/strings.xml132
-rw-r--r--PermissionController/res/values/overlayable.xml20
-rw-r--r--PermissionController/res/values/styles.xml111
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt204
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt146
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java309
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt97
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java18
20 files changed, 1303 insertions, 3 deletions
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index 004d87064..a5d912ae7 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -249,6 +249,17 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity"
+ android:configChanges="keyboardHidden|screenSize"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:theme="@style/GrantPermissions.FilterTouches"
+ android:visibleToInstantApps="true"
+ android:inheritShowWhenLocked="true"
+ android:hardwareAccelerated="false"
+ android:canDisplayOnRemoteDevices="false">
+ </activity>
+
<activity android:name="com.android.permissioncontroller.permission.ui.ManagePermissionsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_permissions"
diff --git a/PermissionController/res/drawable-v34/ic_business.xml b/PermissionController/res/drawable-v34/ic_business.xml
new file mode 100644
index 000000000..23ee37d0e
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_business.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,7L12,3L2,3v18h20L22,7L12,7zM6,19L4,19v-2h2v2zM6,15L4,15v-2h2v2zM6,11L4,11L4,9h2v2zM6,7L4,7L4,5h2v2zM10,19L8,19v-2h2v2zM10,15L8,15v-2h2v2zM10,11L8,11L8,9h2v2zM10,7L8,7L8,5h2v2zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2L12,9h8v10zM18,11h-2v2h2v-2zM18,15h-2v2h2v-2z"/>
+</vector>
diff --git a/PermissionController/res/drawable-v34/ic_collections_bookmark.xml b/PermissionController/res/drawable-v34/ic_collections_bookmark.xml
new file mode 100644
index 000000000..29f9e569c
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_collections_bookmark.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L8,16L8,4h5v7l2.5,-1.88L18,11L18,4h2v12z"/>
+</vector>
diff --git a/PermissionController/res/drawable-v34/ic_gear.xml b/PermissionController/res/drawable-v34/ic_gear.xml
new file mode 100644
index 000000000..958284d4c
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_gear.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46 0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19c-0.59,-0.45 -0.74,-1.26 -0.37,-1.88l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91c-0.1,0.68 -0.72,1.22 -1.46,1.22zM10.62,20.25h2.76l0.37,-2.55 0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34 2.38,0.96 1.38,-2.4 -2.03,-1.58 0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78s-0.03,-0.53 -0.06,-0.78l-0.07,-0.56 2.03,-1.58 -1.39,-2.4 -2.39,0.96 -0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77l-0.52,-0.22 -0.37,-2.55h-2.76l-0.37,2.55 -0.53,0.21c-0.44,0.19 -0.88,0.44 -1.34,0.79l-0.45,0.33 -2.38,-0.95 -1.39,2.39 2.03,1.58 -0.07,0.56c-0.03,0.26 -0.06,0.53 -0.06,0.79s0.02,0.53 0.06,0.78l0.07,0.56 -2.03,1.58 1.38,2.4 2.39,-0.96 0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22 0.38,2.55z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
diff --git a/PermissionController/res/drawable-v34/ic_info.xml b/PermissionController/res/drawable-v34/ic_info.xml
new file mode 100644
index 000000000..35f7f5f61
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_info.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/PermissionController/res/layout-v34/permission_rationale.xml b/PermissionController/res/layout-v34/permission_rationale.xml
new file mode 100644
index 000000000..c3b782d75
--- /dev/null
+++ b/PermissionController/res/layout-v34/permission_rationale.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ ~ A lot of content in this file is identical to grant_permissions.xml
+ ~ Be sure to update both files when making changes.
+ -->
+
+<!-- In (hopefully very rare) case dialog is too high: allow scrolling -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/PermissionRationaleScrollView">
+
+ <LinearLayout
+ android:id="@+id/grant_singleton"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ style="@style/PermissionRationaleSingleton">
+
+ <!-- The dialog -->
+ <LinearLayout
+ android:id="@+id/grant_dialog"
+ android:theme="@style/Theme.PermissionGrantDialog"
+ android:importantForAccessibility="no"
+ style="@style/PermissionRationaleDialog">
+
+ <LinearLayout
+ android:id="@+id/content_container"
+ style="@style/PermissionRationaleContent">
+
+ <LinearLayout
+ style="@style/PermissionRationaleTitleContainer">
+
+ <ImageView
+ android:id="@+id/permission_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_shield_exclamation_outline"
+ style="@style/PermissionRationaleTitleIcon" />
+
+ <TextView
+ android:id="@+id/permission_rationale_title"
+ android:text="@string/permission_rationale_title"
+ style="@style/PermissionRationaleTitleMessage" />
+
+ </LinearLayout>
+
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/purpose_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_info"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/purpose_title"
+ android:text="@string/permission_rationale_purpose_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ <TextView
+ android:id="@+id/purpose_message"
+ android:text="@string/permission_rationale_purpose_message"
+ style="@style/PermissionRationaleSectionMessage" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/third_party_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_business"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/third_party_title"
+ android:text="@string/permission_rationale_thirdparty_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ <TextView
+ android:id="@+id/third_party_message"
+ android:text="@string/permission_rationale_thirdparty_message"
+ style="@style/PermissionRationaleSectionMessage" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/settings_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_gear"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/settings_title"
+ android:text="@string/permission_rationale_permission_settings_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ <TextView
+ android:id="@+id/settings_message"
+ android:text="@string/permission_rationale_permission_settings_message"
+ style="@style/PermissionRationaleSectionMessage" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/learn_more_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_collections_bookmark"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/learn_more_message"
+ android:text="@string/permission_rationale_permission_learn_more_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout style="@style/PermissionRationaleButtonContainer">
+ <!-- TODO(b/260269197): update back button style -->
+ <Button
+ android:id="@+id/back_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="36dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="6dp"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:text="@string/back" />
+ </LinearLayout>
+
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/PermissionController/res/values-v34/strings.xml b/PermissionController/res/values-v34/strings.xml
new file mode 100644
index 000000000..412467779
--- /dev/null
+++ b/PermissionController/res/values-v34/strings.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Permission Rationale - Start -->
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title message shown for Permission Rationale dialog [CHAR LIMIT=50] -->
+ <string name="permission_rationale_title">More about allowing this access</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale data sharing purposes section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_purpose_title">Why the app may share data?</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user letting them know that data will be shared and for which
+ purposes. It will also display where the information was provided from via the install_source
+ placeholder, this will map to app store labels/names. Purposes will be comma delimited and are
+ one or many of the following: "app functionality", "analytics", "developer communications",
+ "advertising or marketing", "fraud prevention, security, and compliance", "personalization",
+ "account management". Example usage as follows: "This app developer stated to App Store that it
+ may share for app functionality, analytics, developer communications, advertising or marketing,
+ fraud prevention, security, and compliance, personalization, account management. This may be
+ updated in the future." [CHAR LIMIT=300] -->
+ <string name="permission_rationale_purpose_message">This app developer stated to <annotation id="link"><xliff:g id="install_source" example="App Store">%1$s</xliff:g></annotation> that it may share for <xliff:g id="purpose_list" example="purpose 1, purpose 2, purpose 3">%2$s</xliff:g>\nThis may be updated in the future.</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user letting them know that data will be shared and for which
+ purposes. It will also display that the information was provided via the device manufacturer
+ (company responsible for making and released the device). Purposes will be comma delimited and
+ are one or many of the following: "app functionality", "analytics", "developer communications",
+ "advertising or marketing", "fraud prevention, security, and compliance", "personalization",
+ "account management". Example usage as follows: "This app developer stated to the device
+ manufacturer that it may share for app functionality, analytics, developer communications,
+ advertising or marketing, fraud prevention, security, and compliance, personalization, account
+ management. This may be updated in the future." [CHAR LIMIT=300] -->
+ <string name="permission_rationale_purpose_default_source_message">This app developer stated to the device manufacturer that it may share for <xliff:g id="purpose_list" example="purpose 1, purpose 2, purpose 3">%1$s</xliff:g>\nThis may be updated in the future.</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale third party description section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_thirdparty_title">What are third parties?</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user that explains what a "third party" is [CHAR LIMIT=100] -->
+ <string name="permission_rationale_thirdparty_message">Third parties are other companies or organizations</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale permission settings section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_permission_settings_title">Change or revoke access any time</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user letting them where to change permission settings in the future [CHAR LIMIT=100] -->
+ <string name="permission_rationale_permission_settings_message">Go to your <annotation id="link">privacy settings</annotation>, under <xliff:g id="permission_name" example="location">%1$s</xliff:g></string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale "learn more about data sharing" section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_permission_learn_more_title"><annotation id="link">Learn more</annotation> about data sharing</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for app functionality. This may be updated in the future."
+ [CHAR LIMIT=30] -->
+ <string name="permission_rational_purpose_app_functionality">app functionality</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for analytics. This may be updated in the future."
+ [CHAR LIMIT=30] -->
+ <string name="permission_rational_purpose_analytics">analytics</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for developer communications. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_developer_communications">developer communications</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for advertising or marketing. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_advertising">advertising or marketing</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for fraud prevention, security, and compliance. This may be
+ updated in the future." [CHAR LIMIT=75] -->
+ <string name="permission_rational_purpose_fraud_prevention_security">fraud prevention, security, and compliance</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for personalization. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_personalization">personalization</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for account management. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_account_management">account management</string>
+
+ <!-- Permission Rationale - End -->
+
+</resources>
diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml
index ed8dc8969..6f7d5a8ac 100644
--- a/PermissionController/res/values/overlayable.xml
+++ b/PermissionController/res/values/overlayable.xml
@@ -69,6 +69,26 @@
<!-- END PERMISSION GRANT DIALOG -->
+ <!-- START PERMISSION RATIONALE DIALOG -->
+
+ <item type="style" name="PermissionRationaleScrollView" />
+ <item type="style" name="PermissionRationaleSingleton" />
+ <item type="style" name="PermissionRationaleDialog" />
+ <item type="style" name="PermissionRationaleContent" />
+
+ <item type="style" name="PermissionRationaleTitleContainer" />
+ <item type="style" name="PermissionRationaleTitleIcon" />
+ <item type="style" name="PermissionRationaleTitleMessage" />
+
+ <item type="style" name="PermissionRationaleSectionOuterContainer" />
+ <item type="style" name="PermissionRationaleSectionIcon" />
+ <item type="style" name="PermissionRationaleSectionInnerContainer" />
+ <item type="style" name="PermissionRationaleSectionTitle" />
+ <item type="style" name="PermissionRationaleSectionMessage" />
+
+ <item type="style" name="PermissionRationaleButtonContainer" />
+
+ <!-- END PERMISSION RATIONALE DIALOG -->
<!-- START PERMISSION REVIEW SCREEN -->
<item type="style" name="PermissionReview" />
diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml
index 60f39874b..041747ac3 100644
--- a/PermissionController/res/values/styles.xml
+++ b/PermissionController/res/values/styles.xml
@@ -198,7 +198,7 @@
<item name="android:layout_width">20dp</item>
<item name="android:layout_height">20dp</item>
<item name="android:layout_gravity">start|center_vertical</item>
- <item name="android:scaleType">fitCenter</item>
+ <item name="android:scaleType">centerInside</item>
<item name="android:tint">?android:attr/textColorSecondary</item>
</style>
@@ -217,7 +217,7 @@
<item name="android:layout_width">20dp</item>
<item name="android:layout_height">20dp</item>
<item name="android:layout_gravity">end|center_vertical</item>
- <item name="android:scaleType">fitCenter</item>
+ <item name="android:scaleType">centerInside</item>
<item name="android:tint">?android:attr/textColorSecondary</item>
</style>
@@ -262,6 +262,113 @@
<!-- END PERMISSION GRANT DIALOG -->
+ <!-- START PERMISSION RATIONALE DIALOG -->
+
+ <style name="PermissionRationaleScrollView">
+ <item name="android:scrollbars">none</item>
+ <item name="android:fillViewport">true</item>
+ <item name="android:clipChildren">false</item>
+ </style>
+
+ <style name="PermissionRationaleSingleton">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="PermissionRationaleDialog">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">?android:attr/windowBackground</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:showDividers">middle</item>
+ </style>
+
+ <style name="PermissionRationaleContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:paddingTop">24dp</item>
+ <item name="android:paddingBottom">18dp</item>
+ <item name="android:paddingStart">24dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ </style>
+
+ <style name="PermissionRationaleTitleContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="PermissionRationaleTitleIcon">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_marginBottom">16dp</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ <item name="android:scaleType">centerInside</item>
+ </style>
+
+ <style name="PermissionRationaleTitleMessage"
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="PermissionRationaleSectionOuterContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:layout_marginTop">16dp</item>
+ </style>
+
+ <style name="PermissionRationaleSectionIcon">
+ <item name="android:layout_width">20dp</item>
+ <item name="android:layout_height">20dp</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ <item name="android:scaleType">centerInside</item>
+ </style>
+
+ <style name="PermissionRationaleSectionInnerContainer">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_marginStart">16dp</item>
+ </style>
+
+ <style name="PermissionRationaleSectionTitle"
+ parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineHeight">20sp</item>
+ <item name="android:lineSpacingMultiplier">1.25</item>
+ </style>
+
+ <style name="PermissionRationaleSectionMessage"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">4dp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineHeight">20sp</item>
+ <item name="android:lineSpacingMultiplier">1.25</item>
+ </style>
+
+ <style name="PermissionRationaleButtonContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">32dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:gravity">end</item>
+ </style>
+
+ <!-- END PERMISSION RATIONALE DIALOG -->
+
<!-- START PERMISSION REVIEW SCREEN -->
<style name="PermissionReview">
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index 37acc9ef0..393ed8194 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -527,6 +527,7 @@ public class GrantPermissionsActivity extends SettingsActivity
}
}
+ // LINT.IfChange(dispatchTouchEvent)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
View rootView = getWindow().getDecorView();
@@ -544,6 +545,7 @@ public class GrantPermissionsActivity extends SettingsActivity
}
return super.dispatchTouchEvent(ev);
}
+ // LINT.ThenChange(PermissionRationaleActivity.java:dispatchTouchEvent)
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
@@ -618,6 +620,11 @@ public class GrantPermissionsActivity extends SettingsActivity
}
@Override
+ public void onPermissionRationaleClicked(String groupName) {
+ mViewModel.showPermissionRationaleActivity(this, groupName);
+ }
+
+ @Override
public void onBackPressed() {
if (mViewHandler == null) {
return;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java
index 03f6d604c..6aaa836b7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java
@@ -57,6 +57,8 @@ public interface GrantPermissionsViewHandler {
void onPermissionGrantResult(String groupName, List<String> affectedForegroundPermissions,
@Result int result);
+
+ void onPermissionRationaleClicked(String groupName);
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
index e39a8fbad..883ff86d8 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
@@ -437,7 +437,7 @@ class GrantPermissionsViewHandlerImpl(
val id = view.id
if (id == R.id.permission_rationale_container) {
- // TODO(b/256913489): Implement Permission rationale details activity
+ resultListener.onPermissionRationaleClicked(groupName)
return
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
new file mode 100644
index 000000000..10cf03793
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.handheld.v34
+
+import android.app.Activity
+import android.os.Build
+import android.os.Bundle
+import android.text.method.LinkMovementMethod
+import android.transition.ChangeBounds
+import android.transition.TransitionManager
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.animation.AnimationUtils
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.Companion.CANCELLED
+
+/**
+ * Handheld implementation of [PermissionRationaleViewHandler]. Used for managing the presentation
+ * and user interaction of the "permission rationale" user interface.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class PermissionRationaleViewHandlerImpl(
+ private val mActivity: Activity,
+ private val resultListener: PermissionRationaleViewHandler.ResultListener
+) : PermissionRationaleViewHandler, OnClickListener {
+
+ private var groupName: String? = null
+ private var purposeMessage: CharSequence? = null
+ private var settingsMessage: CharSequence? = null
+ private var learnMoreMessage: CharSequence? = null
+
+ private var rootView: ViewGroup? = null
+ private var purposeMessageView: TextView? = null
+ private var settingsMessageView: TextView? = null
+ private var learnMoreMessageView: TextView? = null
+ private var backButton: Button? = null
+
+ override fun saveInstanceState(outState: Bundle) {
+ outState.putString(ARG_GROUP_NAME, groupName)
+ outState.putCharSequence(ARG_PURPOSE_MESSAGE, purposeMessage)
+ outState.putCharSequence(ARG_SETTINGS_MESSAGE, settingsMessage)
+ outState.putCharSequence(ARG_LEARN_MORE_MESSAGE, learnMoreMessage)
+ }
+
+ override fun loadInstanceState(savedInstanceState: Bundle) {
+ groupName = savedInstanceState.getString(ARG_GROUP_NAME)
+ purposeMessage = savedInstanceState.getCharSequence(ARG_PURPOSE_MESSAGE)
+ settingsMessage = savedInstanceState.getCharSequence(ARG_SETTINGS_MESSAGE)
+ learnMoreMessage = savedInstanceState.getCharSequence(ARG_LEARN_MORE_MESSAGE)
+ }
+
+ override fun updateUi(
+ groupName: String,
+ purposeMessage: CharSequence,
+ settingsMessage: CharSequence,
+ learnMoreMessage: CharSequence
+ ) {
+ this.groupName = groupName
+ this.purposeMessage = purposeMessage
+ this.settingsMessage = settingsMessage
+ this.learnMoreMessage = learnMoreMessage
+
+ // If view already created, update all children
+ if (rootView != null) {
+ updateAll()
+ }
+ }
+
+ private fun updateAll() {
+ updatePurposeMessage()
+ updateSettingsMessage()
+ updateLearnMoreMessage()
+
+ // Animate change in size
+ // Grow or shrink the content container to size of new content
+ val growShrinkToNewContentSize = ChangeBounds()
+ growShrinkToNewContentSize.duration = ANIMATION_DURATION_MILLIS
+ growShrinkToNewContentSize.interpolator = AnimationUtils.loadInterpolator(mActivity,
+ android.R.interpolator.fast_out_slow_in)
+ TransitionManager.beginDelayedTransition(rootView, growShrinkToNewContentSize)
+ }
+
+ override fun createView(): View {
+ // Make this activity be Non-IME target to prevent hiding keyboard flicker when it show up.
+ mActivity.window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
+
+ val rootView = LayoutInflater.from(mActivity)
+ .inflate(R.layout.permission_rationale, null) as ViewGroup
+
+ // Uses the vertical gravity of the PermissionGrantSingleton style to position the window
+ val gravity = rootView.requireViewById<LinearLayout>(R.id.grant_singleton).gravity
+ val verticalGravity = Gravity.VERTICAL_GRAVITY_MASK and gravity
+ mActivity.window.setGravity(Gravity.CENTER_HORIZONTAL or verticalGravity)
+
+ // Cancel dialog
+ rootView.findViewById<View>(R.id.grant_singleton)!!.setOnClickListener(this)
+ // Swallow click event
+ rootView.findViewById<View>(R.id.grant_dialog)!!.setOnClickListener(this)
+
+ purposeMessageView = rootView.findViewById(R.id.purpose_message)
+ purposeMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+
+ settingsMessageView = rootView.findViewById(R.id.settings_message)
+ settingsMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+
+ learnMoreMessageView = rootView.findViewById(R.id.learn_more_message)
+ learnMoreMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+
+ backButton = rootView.findViewById<Button>(R.id.back_button)
+ backButton!!.setOnClickListener(this)
+
+ this.rootView = rootView
+
+ // If ui model present, update all children
+ if (groupName != null) {
+ updateAll()
+ }
+
+ return rootView
+ }
+
+ override fun onClick(view: View) {
+ val id = view.id
+
+ if (id == R.id.grant_singleton) {
+ onCancelled()
+ return
+ }
+
+ if (id == R.id.back_button) {
+ onCancelled()
+ }
+ }
+
+ override fun onBackPressed() {
+ onCancelled()
+ }
+
+ override fun onCancelled() {
+ resultListener.onPermissionRationaleResult(groupName, CANCELLED)
+ }
+
+ private fun updatePurposeMessage() {
+ if (purposeMessage == null) {
+ purposeMessageView!!.visibility = View.GONE
+ } else {
+ purposeMessageView!!.text = purposeMessage
+ purposeMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ private fun updateSettingsMessage() {
+ if (settingsMessage == null) {
+ settingsMessageView!!.visibility = View.GONE
+ } else {
+ settingsMessageView!!.text = settingsMessage
+ settingsMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ private fun updateLearnMoreMessage() {
+ if (learnMoreMessage == null) {
+ learnMoreMessageView!!.visibility = View.GONE
+ } else {
+ learnMoreMessageView!!.text = learnMoreMessage
+ learnMoreMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ companion object {
+ private val TAG = PermissionRationaleViewHandlerImpl::class.java.simpleName
+
+ const val ARG_GROUP_NAME = "ARG_GROUP_NAME"
+ const val ARG_PURPOSE_MESSAGE = "ARG_PURPOSE_MESSAGE"
+ const val ARG_SETTINGS_MESSAGE = "ARG_SETTINGS_MESSAGE"
+ const val ARG_LEARN_MORE_MESSAGE = "ARG_LEARN_MORE_MESSAGE"
+
+ // Animation parameters.
+ private const val ANIMATION_DURATION_MILLIS: Long = 200
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java
new file mode 100644
index 000000000..48eb8661a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.ui.handheld.v34;
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 c6c5cef8e..0b311746f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -118,6 +118,7 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandle
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision
@@ -1345,6 +1346,26 @@ class GrantPermissionsViewModel(
}
}
+ /**
+ * Shows the Permission Rationale Dialog. For use with U+ only, otherwise no-op.
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
+ if (!SdkLevel.isAtLeastU()) {
+ return
+ }
+
+ val intent = Intent(activity, PermissionRationaleActivity::class.java).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ }
+ // TODO(b/260789748): setup similar result callback as the sendToSettingsFromLink method
+ activity.startActivity(intent)
+ }
+
private fun startAppPermissionFragment(activity: Activity, groupName: String) {
val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
new file mode 100644
index 000000000..c88e8a7b6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.model.v34
+
+import android.app.Application
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.core.util.Consumer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.permission.safetylabel.DataCategory
+import com.android.permission.safetylabel.DataType
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permission.safetylabel.SafetyLabel
+import com.android.permissioncontroller.permission.data.SafetyLabelLiveData
+import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.utils.SafetyLabelPermissionMapping
+
+/**
+ * [ViewModel] for the [PermissionRationaleActivity]. Gets all information required safety label and
+ * links required to inform user of data sharing usages by the app when granting this permission
+ *
+ * @param app: The current application
+ * @param packageName: The packageName permissions are being requested for
+ * @param permissionGroupName: The permission group requested
+ * @param sessionId: A long to identify this session
+ * @param storedState: Previous state, if this activity was stopped and is being recreated
+ */
+class PermissionRationaleViewModel(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ // TODO(b/259961958): add PermissionRationale metrics
+ private val sessionId: Long,
+ private val storedState: Bundle?
+) : ViewModel() {
+ private val safetyLabelLiveData = SafetyLabelLiveData[packageName]
+
+ var activityResultCallback: Consumer<Intent>? = null
+
+ /**
+ * A class which represents a permission rationale for permission group, and messages which
+ * should be shown with it.
+ */
+ data class PermissionRationaleInfo(
+ val groupName: String,
+ val installSourceName: String?,
+ val purposeSet: Set<Int>
+ )
+
+ /** A [LiveData] which holds the currently pending PermissionRationaleInfo */
+ val permissionRationaleInfoLiveData =
+ object : SmartUpdateMediatorLiveData<PermissionRationaleInfo>() {
+
+ init {
+ addSource(safetyLabelLiveData) { onUpdate() }
+
+ // Load package state, if available
+ onUpdate()
+ }
+
+ override fun onUpdate() {
+ if (safetyLabelLiveData.isStale) {
+ return
+ }
+
+ val safetyLabel = safetyLabelLiveData.value
+ if (safetyLabel == null) {
+ Log.e(LOG_TAG, "Safety label for $packageName not found")
+ value = null
+ return
+ }
+
+ // TODO(b/260144598): link to app store
+ value =
+ PermissionRationaleInfo(
+ permissionGroupName,
+ null,
+ getSafetyLabelSharingPurposesForGroup(safetyLabel, permissionGroupName))
+ }
+
+ private fun getSafetyLabelSharingPurposesForGroup(
+ safetyLabel: SafetyLabel,
+ groupName: String
+ ): Set<Int> {
+ val purposeSet = mutableSetOf<Int>()
+ val categoriesForPermission: List<String> =
+ SafetyLabelPermissionMapping.getCategoriesForPermissionGroup(groupName)
+ categoriesForPermission.forEach categoryLoop@{ category ->
+ val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category]
+ if (dataCategory == null) {
+ // Continue to next
+ return@categoryLoop
+ }
+ val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category)
+ typesForCategory.forEach typeLoop@{ type ->
+ val dataType: DataType? = dataCategory.dataTypes[type]
+ if (dataType == null) {
+ // Continue to next
+ return@typeLoop
+ }
+ if (dataType.purposeSet.isNotEmpty()) {
+ purposeSet.addAll(dataType.purposeSet)
+ }
+ }
+ }
+
+ return purposeSet
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = PermissionRationaleViewModel::class.java.simpleName
+ }
+}
+
+/** Factory for a [PermissionRationaleViewModel] */
+class PermissionRationaleViewModelFactory(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ private val sessionId: Long,
+ private val savedState: Bundle?
+) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return PermissionRationaleViewModel(
+ app, packageName, permissionGroupName, sessionId, savedState)
+ as T
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java
new file mode 100644
index 000000000..4e3697cfc
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.ui.model.v34;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
new file mode 100644
index 000000000..b81c802da
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.v34;
+
+import static android.content.Intent.EXTRA_PERMISSION_GROUP_NAME;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ACCOUNT_MANAGEMENT;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ADVERTISING;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ANALYTICS;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_APP_FUNCTIONALITY;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_DEVELOPER_COMMUNICATIONS;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_FRAUD_PREVENTION_SECURITY;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_PERSONALIZATION;
+import static com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.CANCELLED;
+
+import android.content.Intent;
+import android.icu.lang.UCharacter;
+import android.icu.text.ListFormatter;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.permission.safetylabel.DataPurposeConstants.Purpose;
+import com.android.permissioncontroller.Constants;
+import com.android.permissioncontroller.DeviceUtils;
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.ui.SettingsActivity;
+import com.android.permissioncontroller.permission.ui.handheld.v34.PermissionRationaleViewHandlerImpl;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModel;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModel.PermissionRationaleInfo;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModelFactory;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * An activity which displays runtime permission rationale on behalf of an app. This activity is
+ * based on GrantPermissionActivity to keep view behavior and theming consistent.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class PermissionRationaleActivity extends SettingsActivity implements
+ PermissionRationaleViewHandler.ResultListener {
+
+ private static final String LOG_TAG = PermissionRationaleActivity.class.getSimpleName();
+
+ private static final String KEY_SESSION_ID = PermissionRationaleActivity.class.getName()
+ + "_SESSION_ID";
+
+ /** Unique Id of a request. Inherited from GrantPermissionDialog if provide via intent extra */
+ private long mSessionId;
+ /** Package that shall have permissions granted */
+ private String mTargetPackage;
+ /** The permission group that initiated the permission rationale details activity */
+ private String mPermissionGroupName;
+ /** The permission rationale info resulting from the specified permisison and group */
+ private PermissionRationaleInfo mPermissionRationaleInfo;
+
+ private PermissionRationaleViewHandler mViewHandler;
+ private PermissionRationaleViewModel mViewModel;
+
+ private float mOriginalDimAmount;
+ private View mRootView;
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ if (icicle == null) {
+ mSessionId =
+ getIntent().getLongExtra(Constants.EXTRA_SESSION_ID, new Random().nextLong());
+ } else {
+ mSessionId = icicle.getLong(KEY_SESSION_ID);
+ }
+
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ mPermissionGroupName = getIntent().getStringExtra(EXTRA_PERMISSION_GROUP_NAME);
+ if (mPermissionGroupName == null) {
+ Log.e(
+ LOG_TAG,
+ "null EXTRA_PERMISSION_GROUP_NAME. Must be set for permission rationale");
+ finishAfterTransition();
+ return;
+ }
+
+ mTargetPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ if (mTargetPackage == null) {
+ Log.e(LOG_TAG, "null EXTRA_PACKAGE_NAME. Must be set for permission rationale");
+ finishAfterTransition();
+ return;
+ }
+
+ setFinishOnTouchOutside(false);
+
+ setTitle(R.string.permission_rationale_title);
+
+ if (DeviceUtils.isTelevision(this)
+ || DeviceUtils.isWear(this)
+ || DeviceUtils.isAuto(this)) {
+ finishAfterTransition();
+ } else {
+ mViewHandler = new PermissionRationaleViewHandlerImpl(this, this);
+ }
+
+ PermissionRationaleViewModelFactory factory = new PermissionRationaleViewModelFactory(
+ getApplication(), mTargetPackage, mPermissionGroupName, mSessionId, icicle);
+ mViewModel = factory.create(PermissionRationaleViewModel.class);
+ mViewModel.getPermissionRationaleInfoLiveData()
+ .observe(this, this::onPermissionRationaleInfoLoad);
+
+ mRootView = mViewHandler.createView();
+ mRootView.setVisibility(View.GONE);
+ setContentView(mRootView);
+ Window window = getWindow();
+ WindowManager.LayoutParams layoutParams = window.getAttributes();
+ mOriginalDimAmount = layoutParams.dimAmount;
+ window.setAttributes(layoutParams);
+
+ if (getResources().getBoolean(R.bool.config_useWindowBlur)) {
+ java.util.function.Consumer<Boolean> blurEnabledListener = enabled -> {
+ mViewHandler.onBlurEnabledChanged(window, enabled);
+ };
+ mRootView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ window.getWindowManager().addCrossWindowBlurEnabledListener(
+ blurEnabledListener);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ window.getWindowManager().removeCrossWindowBlurEnabledListener(
+ blurEnabledListener);
+ }
+ });
+ }
+ // Restore UI state after lifecycle events. This has to be before we show the first request,
+ // as the UI behaves differently for updates and initial creations.
+ if (icicle != null) {
+ mViewHandler.loadInstanceState(icicle);
+ } else {
+ // Do not show screen dim until data is loaded
+ window.setDimAmount(0f);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if (mViewHandler == null) {
+ return;
+ }
+
+ mViewHandler.saveInstanceState(outState);
+
+ outState.putLong(KEY_SESSION_ID, mSessionId);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mViewHandler == null) {
+ return;
+ }
+ mViewHandler.onBackPressed();
+ }
+
+ // LINT.IfChange(dispatchTouchEvent)
+ /**
+ * Used to dismiss dialog when tapping outside of dialog bounds
+ * Follows the same logic as GrantPermissionActivity
+ */
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ View rootView = getWindow().getDecorView();
+ if (rootView.getTop() != 0) {
+ // We are animating the top view, need to compensate for that in motion events.
+ ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
+ }
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ if ((x < 0) || (y < 0) || (x > (rootView.getWidth())) || (y > (rootView.getHeight()))) {
+ if (MotionEvent.ACTION_DOWN == ev.getAction()) {
+ mViewHandler.onCancelled();
+ }
+ finishAfterTransition();
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+ // LINT.ThenChange(GrantPermissionsActivity.java:dispatchTouchEvent)
+
+ @Override
+ public void onPermissionRationaleResult(@Nullable String groupName, int result) {
+ if (result == CANCELLED) {
+ finishAfterTransition();
+ }
+ }
+
+ private void onPermissionRationaleInfoLoad(PermissionRationaleInfo permissionRationaleInfo) {
+ if (permissionRationaleInfo == null) {
+ finishAfterTransition();
+ return;
+ }
+
+ mPermissionRationaleInfo = permissionRationaleInfo;
+ showPermissionRationale();
+ }
+
+ private void showPermissionRationale() {
+ String groupName = mPermissionRationaleInfo.getGroupName();
+ String installSourceName = mPermissionRationaleInfo.getInstallSourceName();
+
+ List<String> purposesList =
+ new ArrayList<>(mPermissionRationaleInfo.getPurposeSet().size());
+ for (@Purpose int purpose : mPermissionRationaleInfo.getPurposeSet()) {
+ purposesList.add(getStringForPurpose(purpose));
+ }
+
+ // TODO(b/260144215): update purposes join based on l18n feedback
+ String purposesString = ListFormatter.getInstance().format(purposesList);
+
+ // TODO(b/260144598): link to app store
+ CharSequence purposeMessage;
+ if (installSourceName == null || installSourceName.isEmpty()) {
+ purposeMessage =
+ getString(R.string.permission_rationale_purpose_default_source_message,
+ purposesString);
+ } else {
+ purposeMessage =
+ getString(R.string.permission_rationale_purpose_message,
+ installSourceName,
+ purposesString);
+ }
+
+ // TODO(b/260144330): link to permission settings
+ String permissionGroupLabel =
+ KotlinUtils.INSTANCE.getPermGroupLabel(this, groupName).toString();
+ CharSequence settingsMessage =
+ getString(R.string.permission_rationale_permission_settings_message,
+ UCharacter.toLowerCase(permissionGroupLabel));
+
+ // TODO(b/259963582): link to safety label help center article
+ CharSequence learnMoreMessage =
+ getString(R.string.permission_rationale_permission_learn_more_title);
+
+ mViewHandler.updateUi(
+ groupName,
+ purposeMessage,
+ settingsMessage,
+ learnMoreMessage
+ );
+
+ getWindow().setDimAmount(mOriginalDimAmount);
+ if (mRootView.getVisibility() == View.GONE) {
+ InputMethodManager manager = getSystemService(InputMethodManager.class);
+ manager.hideSoftInputFromWindow(mRootView.getWindowToken(), 0);
+ mRootView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private String getStringForPurpose(@Purpose int purpose) {
+ switch (purpose) {
+ case PURPOSE_APP_FUNCTIONALITY:
+ return getString(R.string.permission_rational_purpose_app_functionality);
+ case PURPOSE_ANALYTICS:
+ return getString(R.string.permission_rational_purpose_analytics);
+ case PURPOSE_DEVELOPER_COMMUNICATIONS:
+ return getString(R.string.permission_rational_purpose_developer_communications);
+ case PURPOSE_FRAUD_PREVENTION_SECURITY:
+ return getString(R.string.permission_rational_purpose_fraud_prevention_security);
+ case PURPOSE_ADVERTISING:
+ return getString(R.string.permission_rational_purpose_advertising);
+ case PURPOSE_PERSONALIZATION:
+ return getString(R.string.permission_rational_purpose_personalization);
+ case PURPOSE_ACCOUNT_MANAGEMENT:
+ return getString(R.string.permission_rational_purpose_account_management);
+ default:
+ throw new IllegalArgumentException("Invalid purpose: " + purpose);
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt
new file mode 100644
index 000000000..7b2295921
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.v34
+
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.view.Window
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+
+/**
+ * Class for managing the presentation and user interaction of the "permission rationale" user
+ * interface.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+interface PermissionRationaleViewHandler {
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(Result.CANCELLED)
+ annotation class Result {
+ companion object {
+ const val CANCELLED = -1
+ }
+ }
+
+ /**
+ * Listener interface for getting notified when the user responds to a permission rationale
+ * user action.
+ */
+ interface ResultListener {
+ fun onPermissionRationaleResult(groupName: String?, @Result result: Int)
+ }
+
+ /**
+ * Creates and returns the view hierarchy that is managed by this view handler. This must be
+ * called before [.updateUi].
+ */
+ fun createView(): View
+
+ /**
+ * Updates the view hierarchy to reflect the specified state.
+ *
+ * Note that this must be called at least once before showing the UI to the user to properly
+ * initialize the UI.
+ *
+ * @param groupName the name of the permission group
+ * @param purposeMessage the data usage purposes message to display the user
+ * @param settingsMessage the settings link message to display the user
+ * @param learnMoreMessage the more info about safety labels message to display the user
+ */
+ fun updateUi(
+ groupName: String,
+ purposeMessage: CharSequence,
+ settingsMessage: CharSequence,
+ learnMoreMessage: CharSequence
+ )
+
+ /**
+ * Called by [PermissionRationaleActivity] to save the state of this view handler to the
+ * specified bundle.
+ */
+ fun saveInstanceState(outState: Bundle)
+
+ /**
+ * Called by [PermissionRationaleActivity] to load the state of this view handler from the
+ * specified bundle.
+ */
+ fun loadInstanceState(savedInstanceState: Bundle)
+
+ /** Gives a chance for handling the back key. */
+ fun onBackPressed()
+
+ /**
+ * Handles cancel event for the permission rationale dialog.
+ */
+ fun onCancelled() {}
+
+ /**
+ * Called by [PermissionRationaleActivity] to allow the handler to update the ui when blur is
+ * enabled/disabled.
+ */
+ fun onBlurEnabledChanged(window: Window?, enabled: Boolean) {}
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java
new file mode 100644
index 000000000..0e68b52e0
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.ui.v34;