summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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