diff options
author | 2022-12-15 00:17:02 +0000 | |
---|---|---|
committer | 2023-01-07 02:34:30 +0000 | |
commit | f3d89f40901f46d6d643cfc54280a9767c757437 (patch) | |
tree | fd5b60b3cfcc867a63d3773af790dac2a8c3ee20 | |
parent | c2309b65ca868c5003cb3eb6aa34b274ffa90789 (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
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 |