summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author fayey <fayey@google.com> 2022-12-15 00:17:02 +0000
committer fayey <fayey@google.com> 2023-01-07 02:34:30 +0000
commitf3d89f40901f46d6d643cfc54280a9767c757437 (patch)
treefd5b60b3cfcc867a63d3773af790dac2a8c3ee20
parentc2309b65ca868c5003cb3eb6aa34b274ffa90789 (diff)
Add safety label into App Permission layout.
This change adds the safety label and displays the permission rationale dialog when the label title is clicked. The view can be navigated to by Settings->Location->App Location permissioins->Your app or Settings->Security & privacy->Privacy->Permission manager->Location->Your app Click on the "Your $permissionGroupName may be shared" will display the permission rationale dialog. Screenshot verification in comments. Test: atest CtsPermission3TestCases Bug: 258556840 Change-Id: I579000d956907eb2fcc71ec1678917f56b2ac809
-rw-r--r--PermissionController/res/layout/app_permission.xml38
-rw-r--r--PermissionController/res/values-v34/strings.xml9
-rw-r--r--PermissionController/res/values/overlayable.xml7
-rw-r--r--PermissionController/res/values/styles.xml49
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java42
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt67
6 files changed, 210 insertions, 2 deletions
diff --git a/PermissionController/res/layout/app_permission.xml b/PermissionController/res/layout/app_permission.xml
index 3f9260740..8341102a3 100644
--- a/PermissionController/res/layout/app_permission.xml
+++ b/PermissionController/res/layout/app_permission.xml
@@ -38,6 +38,44 @@
<LinearLayout
style="@style/AppPermissionSelection">
+ <LinearLayout
+ android:id="@+id/app_permission_rationale_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/AppPermissionRationaleContainer">
+ <TextView
+ android:id="@+id/app_permission_rationale_message"
+ style="@style/AppPermissionMessage" />
+
+ <LinearLayout
+ android:id="@+id/app_permission_rationale_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/AppPermissionRationaleContent" >
+
+ <ImageView
+ android:id="@+id/app_permission_rationale_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_shield_exclamation_outline"
+ style="@style/AppPermissionRationaleIcon" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/AppPermissionRationaleTextContent">
+ <TextView
+ android:duplicateParentState="true"
+ android:id="@+id/app_permission_rationale_title"
+ style="@style/AppPermissionRationaleTitle" />
+ <TextView
+ android:duplicateParentState="true"
+ android:id="@+id/app_permission_rationale_subtitle"
+ style="@style/AppPermissionRationaleSubtitle" />
+ </LinearLayout>
+
+ </LinearLayout>
+ </LinearLayout>
+
<TextView
android:id="@+id/permission_message"
style="@style/AppPermissionMessage" />
diff --git a/PermissionController/res/values-v34/strings.xml b/PermissionController/res/values-v34/strings.xml
index adc5df27b..36e0e6e40 100644
--- a/PermissionController/res/values-v34/strings.xml
+++ b/PermissionController/res/values-v34/strings.xml
@@ -127,6 +127,15 @@
[CHAR LIMIT=50] -->
<string name="permission_rational_purpose_account_management">account management</string>
+ <!-- Text for an app's permission rationale header [CHAR LIMIT=60]-->
+ <string name="app_permission_rationale_message">Data privacy</string>
+
+ <!-- Text for linking to an app's location permission rationale dialog. [CHAR LIMIT=60] -->
+ <string name="app_location_permission_rationale_title">Your location may be shared</string>
+
+ <!-- Description for the app's location permission rationale dialog content. [CHAR LIMIT=60] -->
+ <string name="app_location_permission_rationale_subtitle">This app declared it may share your location with third parties</string>
+
<!-- Permission Rationale - End -->
<!-- Safety Label Change Notifications Start -->
diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml
index 6f7d5a8ac..61ee28fb2 100644
--- a/PermissionController/res/values/overlayable.xml
+++ b/PermissionController/res/values/overlayable.xml
@@ -127,6 +127,13 @@
<item type="style" name="LargeHeaderLink" />
<item type="style" name="LargeHeaderDivider" />
+ <item type="style" name="AppPermissionRationaleContainer"/>
+ <item type="style" name="AppPermissionRationaleContent"/>
+ <item type="style" name="AppPermissionRationaleTextContent"/>
+ <item type="style" name="AppPermissionRationaleTitle"/>
+ <item type="style" name="AppPermissionRationaleSubtitle"/>
+ <item type="style" name="AppPermissionRationaleIcon"/>
+
<item type="style" name="AppPermissionSelection" />
<item type="style" name="AppPermissionMessage" />
<item type="style" name="AppPermissionRadioButton" />
diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml
index 1dcc97b9f..39d515951 100644
--- a/PermissionController/res/values/styles.xml
+++ b/PermissionController/res/values/styles.xml
@@ -563,6 +563,55 @@
<item name="android:textDirection">locale</item>
</style>
+ <!-- APP PERMISSION RATIONALE CONTAINER -->
+ <style name="AppPermissionRationaleContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <style name="AppPermissionRationaleContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:orientation">horizontal</item>
+ </style>
+
+ <style name="AppPermissionRationaleTextContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginStart">16dp</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+
+ <style name="AppPermissionRationaleTitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="AppPermissionRationaleSubtitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="AppPermissionRationaleIcon">
+ <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">centerInside</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ </style>
+ <!-- END APP PERMISSION RATIONALE CONTAINER -->
+
<style name="AppPermissionRadioButton"
parent="@android:style/Widget.DeviceDefault.CompoundButton.RadioButton">
<item name="android:layout_width">match_parent</item>
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
index 6821b0cd7..320067959 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
@@ -73,6 +73,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState;
+import com.android.permissioncontroller.permission.model.livedatatypes.SafetyLabelInfo;
import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs;
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
@@ -105,6 +106,8 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
static final String GRANT_CATEGORY = "grant_category";
private @NonNull AppPermissionViewModel mViewModel;
+ private @NonNull ViewGroup mAppPermissionRationaleContainer;
+ private @NonNull ViewGroup mAppPermissionRationaleContent;
private @NonNull RadioButton mAllowButton;
private @NonNull RadioButton mAllowAlwaysButton;
private @NonNull RadioButton mAllowForegroundButton;
@@ -192,6 +195,10 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
getActivity().getApplication(), mPackageName, mPermGroupName, mUser, mSessionId);
mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class);
Handler delayHandler = new Handler(Looper.getMainLooper());
+ if (KotlinUtils.INSTANCE.isPermissionRationaleEnabled()) {
+ mViewModel.getSafetyLabelInfoLiveData().observe(this,
+ this::showPermissionRationaleDialog);
+ }
mViewModel.getButtonStateLiveData().observe(this, buttonState -> {
if (mIsInitialLoad) {
setRadioButtonsState(buttonState);
@@ -284,14 +291,47 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
TextView storageFooter = root.requireViewById(R.id.footer_storage_special_app_access);
storageFooter.setVisibility(View.GONE);
}
+ mAppPermissionRationaleContainer =
+ root.requireViewById(R.id.app_permission_rationale_container);
+ mAppPermissionRationaleContent =
+ root.requireViewById(R.id.app_permission_rationale_content);
+ if (!KotlinUtils.INSTANCE.isPermissionRationaleEnabled()) {
+ hidePermissionRationaleContainer();
+ } else {
+ setPermissionRationaleContainer(root, context);
+ }
getActivity().setTitle(
getPreferenceManager().getContext().getString(R.string.app_permission_title,
mPermGroupLabel));
-
return root;
}
+ private void setPermissionRationaleContainer(View root, Context context) {
+ ((TextView) root.requireViewById(R.id.app_permission_rationale_message)).setText(
+ context.getString(R.string.app_permission_rationale_message));
+ ((TextView) root.requireViewById(R.id.app_permission_rationale_title)).setText(
+ context.getString(R.string.app_location_permission_rationale_title));
+ ((TextView) root.requireViewById(R.id.app_permission_rationale_subtitle)).setText(
+ context.getString(R.string.app_location_permission_rationale_subtitle));
+ }
+
+ private void showPermissionRationaleDialog(@Nullable SafetyLabelInfo safetyLabelInfo) {
+ if (safetyLabelInfo == null
+ || !mViewModel.shouldShowPermissionRationale(safetyLabelInfo, mPermGroupName)) {
+ hidePermissionRationaleContainer();
+ } else {
+ mAppPermissionRationaleContainer.setVisibility(View.VISIBLE);
+ mAppPermissionRationaleContent.setOnClickListener((v) -> {
+ mViewModel.showPermissionRationaleActivity(getActivity(), mPermGroupName);
+ });
+ }
+ }
+
+ private void hidePermissionRationaleContainer() {
+ mAppPermissionRationaleContainer.setVisibility(View.GONE);
+ }
+
private void setBottomLinkState(TextView view, String caller, String action) {
if ((Objects.equals(caller, AppPermissionGroupsFragment.class.getName())
&& action.equals(Intent.ACTION_MANAGE_APP_PERMISSIONS))
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
index 0a56095e5..40ef3d701 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
@@ -22,6 +22,7 @@ import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
import android.Manifest.permission_group.READ_MEDIA_VISUAL
import android.annotation.SuppressLint
+import android.app.Activity
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
@@ -46,6 +47,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.android.modules.utils.build.SdkLevel
+import com.android.permission.safetylabel.DataCategory
+import com.android.permission.safetylabel.DataType
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_VIEWED
@@ -53,10 +58,12 @@ import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
+import com.android.permissioncontroller.permission.data.SafetyLabelInfoLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
+import com.android.permissioncontroller.permission.model.livedatatypes.SafetyLabelInfo
import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl
import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs
@@ -69,11 +76,14 @@ import com.android.permissioncontroller.permission.ui.model.AppPermissionViewMod
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY_FOREGROUND
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.LOCATION_ACCURACY
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.SELECT_PHOTOS
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision
import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissionRationaleEnabled
import com.android.permissioncontroller.permission.utils.LocationUtils
import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.SafetyLabelPermissionMapping
import com.android.permissioncontroller.permission.utils.SafetyNetLogger
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.navigateSafe
@@ -102,11 +112,12 @@ class AppPermissionViewModel(
companion object {
private val LOG_TAG = AppPermissionViewModel::class.java.simpleName
-
private const val DEVICE_PROFILE_ROLE_PREFIX = "android.app.role"
const val PHOTO_PICKER_REQUEST_CODE = 1
}
+ val safetyLabelInfoLiveData = SafetyLabelInfoLiveData[packageName, user]
+
interface ConfirmDialogShowingFragment {
fun showConfirmDialog(
changeRequest: ChangeRequest,
@@ -545,6 +556,60 @@ class AppPermissionViewModel(
return true
}
+ // TODO(b/264309196): extract should show permission rationale logic as a util method.
+ fun shouldShowPermissionRationale(
+ safetyLabelInfo: SafetyLabelInfo,
+ groupName: String
+ ): Boolean {
+ val safetyLabel = safetyLabelInfo.safetyLabel
+ if (safetyLabel == null) {
+ return false
+ }
+ if (safetyLabel.dataLabel.dataShared.isEmpty()) {
+ return false
+ }
+ 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()) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * 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 (!isPermissionRationaleEnabled()) {
+ 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)
+ }
+ activity.startActivity(intent)
+ }
+
/**
* Navigate to either the App Permission Groups screen, or the Permission Apps Screen.
* @param fragment The current fragment