diff options
author | 2022-11-15 13:59:24 -0800 | |
---|---|---|
committer | 2022-11-18 19:52:04 -0800 | |
commit | e9a22f288c23ab7fc0230273ee34207462b6b292 (patch) | |
tree | 7dabd20eba9ed59fdef27da94c2ac37dced944e0 | |
parent | a0f5b20a4adfb88f93974e788392fc4836fb743c (diff) |
Add permission rationale to permission grant dialog
Initial permission rationale UX, link to more info in follow up
Bug: 258557390
Test: atest PermissionRationalePermissionGrantDialogTest
Change-Id: If31fc9198ecc5c571a14179a945afeea310699bc
23 files changed, 491 insertions, 22 deletions
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp index c254036f0..ca4d8ceeb 100644 --- a/PermissionController/Android.bp +++ b/PermissionController/Android.bp @@ -140,6 +140,7 @@ android_app { "modules-utils-build_system", "safety-center-resources-lib", "lottie", + "safety-label" ], proto: { diff --git a/PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml b/PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml new file mode 100644 index 000000000..b76b68c5e --- /dev/null +++ b/PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners android:radius="16dp"/> + <stroke android:width="1dp" android:color="?androidprv:attr/textColorSecondaryInverse" /> +</shape>
\ No newline at end of file diff --git a/PermissionController/res/drawable/ic_more_info_arrow.xml b/PermissionController/res/drawable/ic_more_info_arrow.xml new file mode 100644 index 000000000..73eb5ccfc --- /dev/null +++ b/PermissionController/res/drawable/ic_more_info_arrow.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="48dp" + android:height="48dp" android:viewportWidth="48" android:viewportHeight="48" + android:autoMirrored="true" android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/white" + android:pathData="M18.75,36 L16.6,33.85 26.5,23.95 16.6,14.05 18.75,11.9 30.8,23.95Z"/> +</vector>
\ No newline at end of file diff --git a/PermissionController/res/drawable/ic_shield_exclamation_outline.xml b/PermissionController/res/drawable/ic_shield_exclamation_outline.xml new file mode 100644 index 000000000..5785babf9 --- /dev/null +++ b/PermissionController/res/drawable/ic_shield_exclamation_outline.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="48dp" + android:height="48dp" android:viewportWidth="48" android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/white" + android:pathData="M24,31.3Q24.7,31.3 25.2,30.8Q25.7,30.3 25.7,29.6Q25.7,28.9 25.2,28.4Q24.7,27.9 24,27.9Q23.3,27.9 22.8,28.4Q22.3,28.9 22.3,29.6Q22.3,30.3 22.8,30.8Q23.3,31.3 24,31.3ZM22.5,24.6H25.5V14.25H22.5ZM24,43.95Q17,42.2 12.5,35.825Q8,29.45 8,21.85V9.95L24,3.95L40,9.95V21.85Q40,29.45 35.5,35.825Q31,42.2 24,43.95ZM24,40.85Q29.75,38.95 33.375,33.675Q37,28.4 37,21.85V12.05L24,7.15L11,12.05V21.85Q11,28.4 14.625,33.675Q18.25,38.95 24,40.85ZM24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Z"/> +</vector> diff --git a/PermissionController/res/layout/grant_permissions.xml b/PermissionController/res/layout/grant_permissions.xml index ed1873519..ca0706171 100644 --- a/PermissionController/res/layout/grant_permissions.xml +++ b/PermissionController/res/layout/grant_permissions.xml @@ -62,6 +62,29 @@ </LinearLayout> + <!-- permission rationale --> + <LinearLayout + android:id="@+id/permission_rationale_container" + style="@style/PermissionGrantPermissionRationaleContent"> + + <ImageView + android:id="@+id/permission_rationale_icon" + android:importantForAccessibility="no" + android:src="@drawable/ic_shield_exclamation_outline" + style="@style/PermissionGrantPermissionRationaleIcon" /> + + <TextView + android:id="@+id/permission_rationale_message" + style="@style/PermissionGrantPermissionRationaleMessage" /> + + <ImageView + android:id="@+id/permission_rationale_more_info_icon" + android:importantForAccessibility="no" + android:src="@drawable/ic_more_info_arrow" + style="@style/PermissionGrantPermissionRationaleMoreInfoIcon" /> + + </LinearLayout> + <!-- location (precise/approximate) animations --> <LinearLayout android:layout_width="match_parent" diff --git a/PermissionController/res/layout/grant_permissions_material3.xml b/PermissionController/res/layout/grant_permissions_material3.xml index 843ea9bb7..9c3ab6eef 100644 --- a/PermissionController/res/layout/grant_permissions_material3.xml +++ b/PermissionController/res/layout/grant_permissions_material3.xml @@ -63,6 +63,29 @@ </LinearLayout> + <!-- permission rationale --> + <LinearLayout + android:id="@+id/permission_rationale_container" + style="@style/PermissionGrantPermissionRationaleContent"> + + <ImageView + android:id="@+id/permission_rationale_icon" + android:importantForAccessibility="no" + android:src="@drawable/ic_shield_exclamation_outline" + style="@style/PermissionGrantPermissionRationaleIcon" /> + + <TextView + android:id="@+id/permission_rationale_message" + style="@style/PermissionGrantPermissionRationaleMessage" /> + + <ImageView + android:id="@+id/permission_rationale_more_info_icon" + android:importantForAccessibility="no" + android:src="@drawable/ic_more_info_arrow" + style="@style/PermissionGrantPermissionRationaleMoreInfoIcon" /> + + </LinearLayout> + <!-- location (precise/approximate) animations --> <LinearLayout android:layout_width="match_parent" diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml index 6e7973fb1..ed8dc8969 100644 --- a/PermissionController/res/values/overlayable.xml +++ b/PermissionController/res/values/overlayable.xml @@ -61,6 +61,12 @@ <item type="style" name="PermissionGrantButtonAllowOneTime" /> <item type="style" name="PermissionGrantButtonDeny" /> <item type="style" name="PermissionGrantButtonNoUpgrade" /> + + <item type="style" name="PermissionGrantPermissionRationaleContent" /> + <item type="style" name="PermissionGrantPermissionRationaleIcon" /> + <item type="style" name="PermissionGrantPermissionRationaleMessage" /> + <item type="style" name="PermissionGrantPermissionRationaleMoreInfoIcon" /> + <!-- END PERMISSION GRANT DIALOG --> diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index d16c095e7..b92cc4286 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -1713,4 +1713,13 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo <!-- Summary for toggle controlling whether to show the first letter while typing passwords. [CHAR LIMIT=NONE] --> <string name="show_password_summary">Display characters briefly as you type</string> + <!-- TODO(b/259279178): update with finalized permission rationale strings --> + <!-- Template for the permission rationale message when an app requests a permission. Third + parties are other organizations outside of the app developer. These could be companies or even + governmental organizations. But because we aren't able to be inclusive of all possibilities, + phrasing should be as generic as possible while still helping users understand they aren't just + sharing data with the developer company. [CHAR LIMIT=100] --> + <string name="permission_rationale_message_template">This app stated it may share + <xliff:g id="permission_name" example="location">%s</xliff:g> data with third parties</string> + </resources> diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml index 42f4cc76f..60f39874b 100644 --- a/PermissionController/res/values/styles.xml +++ b/PermissionController/res/values/styles.xml @@ -183,6 +183,44 @@ <item name="android:background">?android:attr/selectableItemBackground</item> </style> + <style name="PermissionGrantPermissionRationaleContent"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginStart">24dp</item> + <item name="android:layout_marginEnd">24dp</item> + <item name="android:layout_marginBottom">24dp</item> + <item name="android:padding">12dp</item> + <item name="android:orientation">horizontal</item> + <item name="android:background">@drawable/grant_dialog_permission_rationale_background</item> + </style> + + <style name="PermissionGrantPermissionRationaleIcon"> + <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:tint">?android:attr/textColorSecondary</item> + </style> + + <style name="PermissionGrantPermissionRationaleMessage" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:layout_width">0dp</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_weight">1</item> + <item name="android:layout_marginStart">12dp</item> + <item name="android:layout_marginEnd">12dp</item> + <item name="android:textSize">14sp</item> + </style> + + <style name="PermissionGrantPermissionRationaleMoreInfoIcon"> + <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:tint">?android:attr/textColorSecondary</item> + </style> + <!-- for use in overlays --> <style name="PermissionGrantButtonAllow" parent="@style/PermissionGrantButton"></style> diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelLiveData.kt new file mode 100644 index 000000000..48909384f --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelLiveData.kt @@ -0,0 +1,116 @@ +/* + * 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.data + +import android.app.Application +import android.content.pm.PackageManager +import android.os.PersistableBundle +import android.util.Log +import com.android.permission.safetylabel.DataCategoryConstants +import com.android.permission.safetylabel.DataLabelConstants +import com.android.permission.safetylabel.DataTypeConstants +import com.android.permission.safetylabel.SafetyLabel +import com.android.permissioncontroller.PermissionControllerApplication +import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissionRationaleEnabled +import com.android.permissioncontroller.permission.utils.KotlinUtils.isPlaceholderSafetyLabelDataEnabled +import kotlinx.coroutines.Job + +/** + * SafetyLabel LiveData for the specified package + * + * @param app current Application + * @param packageName name of the package to get SafetyLabel information for + */ +class SafetyLabelLiveData +private constructor(private val app: Application, private val packageName: String) : + SmartAsyncMediatorLiveData<SafetyLabel>() { + + override suspend fun loadDataAndPostValue(job: Job) { + if (job.isCancelled) { + return + } + + if (!isPermissionRationaleEnabled()) { + postValue(null) + return + } + + if (packageName.isEmpty()) { + postValue(null) + return + } + + val safetyLabel: SafetyLabel? = + try { + val metadataBundle: PersistableBundle? = getInstallMetadataBundle() + SafetyLabel.getSafetyLabelFromMetadata(metadataBundle) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(LOG_TAG, "SafetyLabel for $packageName not found") + invalidateSingle(packageName) + null + } + postValue(safetyLabel) + } + + // TODO(b/257293222): Update when hooking up PackageManager APIs + private fun getInstallMetadataBundle(): PersistableBundle? { + return if (isPlaceholderSafetyLabelDataEnabled()) { + placeholderMetadataBundle() + } else { + null + } + } + + // TODO(b/257293222): Remove when hooking up PackageManager APIs + private fun placeholderMetadataBundle(): PersistableBundle { + val approximateLocationBundle = PersistableBundle().apply { + putIntArray( + "purposes", + (1..7).toList().toIntArray()) + } + + val locationBundle = PersistableBundle().apply { + putPersistableBundle( + DataTypeConstants.LOCATION_APPROX_LOCATION, + approximateLocationBundle) + } + + val dataSharedBundle = PersistableBundle().apply { + putPersistableBundle(DataCategoryConstants.CATEGORY_LOCATION, locationBundle) + } + + val dataLabelBundle = PersistableBundle().apply { + putPersistableBundle(DataLabelConstants.DATA_USAGE_SHARED, dataSharedBundle) + } + + val safetyLabelBundle = PersistableBundle().apply { + putPersistableBundle("data_labels", dataLabelBundle) + } + + return PersistableBundle().apply { + putPersistableBundle("safety_labels", safetyLabelBundle) + } + } + + companion object : DataRepositoryForPackage<String, SafetyLabelLiveData>() { + private val LOG_TAG = SafetyLabelLiveData::class.java.simpleName + + override fun newValue(key: String): SafetyLabelLiveData { + return SafetyLabelLiveData(PermissionControllerApplication.get(), key) + } + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index 35501aaed..37acc9ef0 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -41,6 +41,7 @@ import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Icon; +import android.icu.lang.UCharacter; import android.os.Bundle; import android.os.Process; import android.text.Annotation; @@ -487,6 +488,16 @@ public class GrantPermissionsActivity extends SettingsActivity setTitle(message); } + CharSequence permissionRationaleMessage = null; + if (info.getShowPermissionRationale()) { + String permissionGroupLabel = + KotlinUtils.INSTANCE.getPermGroupLabel(this, info.getGroupName()) + .toString(); + + permissionRationaleMessage = getString(R.string.permission_rationale_message_template, + UCharacter.toLowerCase(permissionGroupLabel)); + } + ArrayList<Integer> idxs = new ArrayList<>(); mButtonVisibilities = new boolean[info.getButtonVisibilities().size()]; for (int i = 0; i < info.getButtonVisibilities().size(); i++) { @@ -502,7 +513,8 @@ public class GrantPermissionsActivity extends SettingsActivity } mViewHandler.updateUi(info.getGroupName(), mTotalRequests, mCurrentRequestIdx, icon, - message, detailMessage, mButtonVisibilities, mLocationVisibilities); + message, detailMessage, permissionRationaleMessage, mButtonVisibilities, + mLocationVisibilities); if (showingNewGroup) { mCurrentRequestIdx++; } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java index 78caebc21..03f6d604c 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java @@ -85,11 +85,15 @@ public interface GrantPermissionsViewHandler { * @param message the message to display the user * @param detailMessage another message to display to the user. This clarifies "message" in more * detail + * @param permissionRationaleMessage another message to display to the user. This message lets + * users know developer stated data usages for the requested + * permission * @param buttonVisibilities visibilities for each button * @param locationVisibilities visibilities for location options */ void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, - CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities, + CharSequence message, CharSequence detailMessage, + CharSequence permissionRationaleMessage, boolean[] buttonVisibilities, boolean[] locationVisibilities); /** diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java index 194faff6f..6b09921cb 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java @@ -91,8 +91,11 @@ public class GrantPermissionsAutoViewHandler implements GrantPermissionsViewHand @Override public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, - CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities, + CharSequence message, CharSequence detailMessage, + CharSequence permissionRationaleMessage, boolean[] buttonVisibilities, boolean[] locationVisibilities) { + // permissionRationaleMessage ignored by auto + mGroupName = groupName; mGroupCount = groupCount; mGroupIndex = groupIndex; 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 af6d922ba..e39a8fbad 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt @@ -95,6 +95,7 @@ class GrantPermissionsViewHandlerImpl( private var groupIcon: Icon? = null private var groupMessage: CharSequence? = null private var detailMessage: CharSequence? = null + private var permissionRationaleMessage: CharSequence? = null private val buttonVisibilities = BooleanArray(NEXT_BUTTON) { false } private val locationVisibilities = BooleanArray(NEXT_LOCATION_DIALOG) { false } private var selectedPrecision: Int = 0 @@ -110,6 +111,8 @@ class GrantPermissionsViewHandlerImpl( private var iconView: ImageView? = null private var messageView: TextView? = null private var detailMessageView: TextView? = null + private var permissionRationaleView: View? = null + private var permissionRationaleMessageView: TextView? = null private var buttons: Array<Button?> = arrayOfNulls(NEXT_BUTTON) private var locationViews: Array<View?> = arrayOfNulls(NEXT_LOCATION_DIALOG) private var rootView: ViewGroup? = null @@ -121,6 +124,8 @@ class GrantPermissionsViewHandlerImpl( arguments.putParcelable(ARG_GROUP_ICON, groupIcon) arguments.putCharSequence(ARG_GROUP_MESSAGE, groupMessage) arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, detailMessage) + arguments.putCharSequence(ARG_GROUP_PERMISSION_RATIONALE_MESSAGE, + permissionRationaleMessage) arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, buttonVisibilities) arguments.putBooleanArray(ARG_DIALOG_LOCATION_VISIBILITIES, locationVisibilities) arguments.putInt(ARG_DIALOG_SELECTED_PRECISION, selectedPrecision) @@ -133,6 +138,8 @@ class GrantPermissionsViewHandlerImpl( groupCount = savedInstanceState.getInt(ARG_GROUP_COUNT) groupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX) detailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE) + permissionRationaleMessage = + savedInstanceState.getCharSequence(ARG_GROUP_PERMISSION_RATIONALE_MESSAGE) setButtonVisibilities(savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES)) setLocationVisibilities(savedInstanceState.getBooleanArray( ARG_DIALOG_LOCATION_VISIBILITIES)) @@ -142,14 +149,15 @@ class GrantPermissionsViewHandlerImpl( } override fun updateUi( - groupName: String, + groupName: String?, groupCount: Int, groupIndex: Int, icon: Icon?, message: CharSequence?, detailMessage: CharSequence?, - buttonVisibilities: BooleanArray, - locationVisibilities: BooleanArray + permissionRationaleMessage: CharSequence?, + buttonVisibilities: BooleanArray?, + locationVisibilities: BooleanArray? ) { this.groupName = groupName @@ -158,6 +166,7 @@ class GrantPermissionsViewHandlerImpl( groupIcon = icon groupMessage = message this.detailMessage = detailMessage + this.permissionRationaleMessage = permissionRationaleMessage setButtonVisibilities(buttonVisibilities) setLocationVisibilities(locationVisibilities) @@ -170,6 +179,7 @@ class GrantPermissionsViewHandlerImpl( private fun updateAll() { updateDescription() updateDetailDescription() + updatePermissionRationale() updateButtons() updateLocationVisibilities() @@ -212,6 +222,10 @@ class GrantPermissionsViewHandlerImpl( detailMessageView!!.movementMethod = LinkMovementMethod.getInstance() iconView = rootView.findViewById(R.id.permission_icon) + permissionRationaleView = rootView.findViewById(R.id.permission_rationale_container) + permissionRationaleMessageView = rootView.findViewById(R.id.permission_rationale_message) + permissionRationaleView!!.setOnClickListener(this) + val buttons = arrayOfNulls<Button>(NEXT_BUTTON) val numButtons = BUTTON_RES_ID_TO_NUM.size() for (i in 0 until numButtons) { @@ -305,6 +319,16 @@ class GrantPermissionsViewHandlerImpl( } } + private fun updatePermissionRationale() { + val message = permissionRationaleMessage + if (message == null || message.isEmpty()) { + permissionRationaleView!!.visibility = View.GONE + } else { + permissionRationaleMessageView!!.text = message + permissionRationaleView!!.visibility = View.VISIBLE + } + } + private fun updateButtons() { for (i in 0 until BUTTON_RES_ID_TO_NUM.size()) { val pos = BUTTON_RES_ID_TO_NUM.valueAt(i) @@ -412,6 +436,11 @@ class GrantPermissionsViewHandlerImpl( override fun onClick(view: View) { val id = view.id + if (id == R.id.permission_rationale_container) { + // TODO(b/256913489): Implement Permission rationale details activity + return + } + if (id == R.id.permission_location_accuracy_radio_fine) { (locationViews[FINE_RADIO_BUTTON] as RadioButton).isChecked = true selectedPrecision = FINE_RADIO_BUTTON @@ -515,6 +544,7 @@ class GrantPermissionsViewHandlerImpl( } companion object { + private val TAG = GrantPermissionsViewHandlerImpl::class.java.simpleName const val ARG_GROUP_NAME = "ARG_GROUP_NAME" const val ARG_GROUP_COUNT = "ARG_GROUP_COUNT" @@ -522,6 +552,8 @@ class GrantPermissionsViewHandlerImpl( const val ARG_GROUP_ICON = "ARG_GROUP_ICON" const val ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE" private const val ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE" + private const val ARG_GROUP_PERMISSION_RATIONALE_MESSAGE = + "ARG_GROUP_PERMISSION_RATIONALE_MESSAGE" private const val ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES" private const val ARG_DIALOG_LOCATION_VISIBILITIES = "ARG_DIALOG_LOCATION_VISIBILITIES" private const val ARG_DIALOG_SELECTED_PRECISION = "ARG_DIALOG_SELECTED_PRECISION" 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 ecfea63d3..c6c5cef8e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt @@ -46,6 +46,10 @@ import androidx.core.util.Consumer import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider 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.permission.safetylabel.SafetyLabel import com.android.permissioncontroller.Constants import com.android.permissioncontroller.DeviceUtils import com.android.permissioncontroller.PermissionControllerApplication @@ -70,6 +74,7 @@ import com.android.permissioncontroller.auto.DrivingDecisionReminderService import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData +import com.android.permissioncontroller.permission.data.SafetyLabelLiveData import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData import com.android.permissioncontroller.permission.data.get import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup @@ -116,8 +121,12 @@ import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity. import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils import com.android.permissioncontroller.permission.utils.KotlinUtils import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision +import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions +import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled +import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissionRationaleEnabled 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 @@ -142,6 +151,7 @@ class GrantPermissionsViewModel( private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName private val user = Process.myUserHandle() private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user] + private val safetyLabelLiveData = SafetyLabelLiveData[packageName] private val dpm = app.getSystemService(DevicePolicyManager::class.java)!! private val permissionPolicy = dpm.getPermissionPolicy(null) private val permGroupsToSkip = mutableListOf<String>() @@ -154,6 +164,7 @@ class GrantPermissionsViewModel( } private lateinit var packageInfo: LightPackageInfo + private var safetyLabel: SafetyLabel? = null // All permissions that could possibly be affected by the provided requested permissions, before // filtering system fixed, auto grant, etc. @@ -174,7 +185,8 @@ class GrantPermissionsViewModel( val message: RequestMessage = RequestMessage.FG_MESSAGE, val detailMessage: RequestMessage = RequestMessage.NO_MESSAGE, val sendToSettingsImmediately: Boolean = false, - val openPhotoPicker: Boolean = false + val openPhotoPicker: Boolean = false, + val showPermissionRationale: Boolean = false ) { val groupName = groupInfo.name } @@ -192,12 +204,16 @@ class GrantPermissionsViewModel( init { addSource(packagePermissionsLiveData) { onPackageLoaded() } addSource(packageInfoLiveData) { onPackageLoaded() } + addSource(safetyLabelLiveData) { onPackageLoaded() } + // Load package state, if available onPackageLoaded() } private fun onPackageLoaded() { - if (packageInfoLiveData.isStale || packagePermissionsLiveData.isStale) { + if (packageInfoLiveData.isStale || + packagePermissionsLiveData.isStale || + safetyLabelLiveData.isStale) { return } @@ -218,6 +234,8 @@ class GrantPermissionsViewModel( return } + safetyLabel = safetyLabelLiveData.value + val allAffectedPermissions = requestedPermissions.toMutableSet() for (requestedPerm in requestedPermissions) { allAffectedPermissions.addAll(computeAffectedPermissions(requestedPerm, groups)) @@ -553,7 +571,9 @@ class GrantPermissionsViewModel( buttonVisibilities, locationVisibilities, message, - detailMessage)) + detailMessage, + showPermissionRationale = shouldShowPermissionRationale( + safetyLabel, groupState))) } sortPermissionGroups(requestInfos) @@ -585,6 +605,41 @@ class GrantPermissionsViewModel( } } + private fun shouldShowPermissionRationale( + safetyLabel: SafetyLabel?, + groupState: GroupState + ): Boolean { + if (!isPermissionRationaleEnabled() || + safetyLabel == null || + safetyLabel.dataLabel.dataShared.isEmpty()) { + return false + } + + val groupName = groupState.group.permGroupName + 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 + } + /** * Converts a list of LightAppPermGroups into a list of GroupStates */ @@ -769,10 +824,9 @@ class GrantPermissionsViewModel( !isSplitGroupLowerGrant(group)) { if (group.permissions[perm]?.isGrantedIncludingAppOp == false) { if (isBackground) { - KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm)) + grantBackgroundRuntimePermissions(app, group, listOf(perm)) } else { - KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm), - group.isOneTime) + grantForegroundRuntimePermissions(app, group, listOf(perm), group.isOneTime) } KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false, filterPermissions = listOf(perm)) @@ -830,9 +884,9 @@ class GrantPermissionsViewModel( if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission( app, perm, user.identifier)) { if (isBackground) { - KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm)) + grantBackgroundRuntimePermissions(app, group, listOf(perm)) } else { - KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm)) + grantForegroundRuntimePermissions(app, group, listOf(perm)) } KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true, FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false, @@ -1033,11 +1087,11 @@ class GrantPermissionsViewModel( PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED } if (groupState.isBackground) { - KotlinUtils.grantBackgroundRuntimePermissions(app, groupState.group, + grantBackgroundRuntimePermissions(app, groupState.group, groupState.affectedPermissions) } else { if (affectedForegroundPermissions == null) { - KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group, + grantForegroundRuntimePermissions(app, groupState.group, groupState.affectedPermissions, isOneTime) // This prevents weird flag state when app targetSDK switches from S+ to R- if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) { @@ -1045,7 +1099,7 @@ class GrantPermissionsViewModel( app, groupState.group, true) } } else { - val newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, + val newGroup = grantForegroundRuntimePermissions(app, groupState.group, affectedForegroundPermissions, isOneTime) if (!isOneTime || newGroup.isOneTime) { KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, newGroup, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java index 3c7a90b5b..45dd4c1f2 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java @@ -146,8 +146,11 @@ public final class GrantPermissionsViewHandlerImpl implements GrantPermissionsVi @Override public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, - CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities, + CharSequence message, CharSequence detailMessage, + CharSequence permissionRationaleMessage, boolean[] buttonVisibilities, boolean[] locationVisibilities) { + // permissionRationaleMessage ignored by television + // TODO: Handle detailMessage mGroupName = groupName; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java index 1c0af63cc..ccd1ed42e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java @@ -96,8 +96,11 @@ public final class GrantPermissionsWearViewHandler implements GrantPermissionsVi @Override public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon, - CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities, + CharSequence message, CharSequence detailMessage, + CharSequence permissionRationaleMessage, boolean[] buttonVisibilities, boolean[] locationVisibilities) { + // permissionRationaleMessage ignored by wear + // TODO: Handle detailMessage boolean showDoNotAsk = buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON]; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt index 6bf2ac7b6..f6693a7eb 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt @@ -138,6 +138,13 @@ object KotlinUtils { /** Whether to show 7-day toggle in privacy hub. */ private const val PRIVACY_DASHBOARD_7_DAY_TOGGLE = "privacy_dashboard_7_day_toggle" + /** Whether to show permission rationale in permission settings and grant dialog. */ + private const val PRIVACY_PERMISSION_RATIONALE_ENABLED = "privacy_permission_rationale_enabled" + + /** Whether to placeholder safety label data in permission settings and grant dialog. */ + private const val PRIVACY_PLACEHOLDER_SAFETY_LABEL_DATA_ENABLED = + "privacy_placeholder_safety_label_data_enabled" + /** Default location precision */ private const val PROPERTY_LOCATION_PRECISION = "location_precision" @@ -247,6 +254,27 @@ object KotlinUtils { PROPERTY_PHOTO_PICKER_PROMPT_ENABLED, false) } + /* + * Whether we should enable the permission rationale in permission settings and grant dialog + * + * @return whether the flag is enabled + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake") + fun isPermissionRationaleEnabled(): Boolean { + return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + PRIVACY_PERMISSION_RATIONALE_ENABLED, false) + } + + /** + * Whether we should use placeholder safety label data + * + * @return whether the flag is enabled + */ + fun isPlaceholderSafetyLabelDataEnabled(): Boolean { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + PRIVACY_PLACEHOLDER_SAFETY_LABEL_DATA_ENABLED, false) + } + /** * Given a Map, and a List, determines which elements are in the list, but not the map, and * vice versa. Used primarily for determining which liveDatas are already being watched, and diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt new file mode 100644 index 000000000..5fd852bb6 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt @@ -0,0 +1,44 @@ +/* + * 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.utils + +import android.Manifest +import com.android.permission.safetylabel.DataCategoryConstants + +/** + * This file contains the canonical mapping of permission and permission group to Safety Label + * categories and types used in the Permission settings screens and grant dialog. It also includes + * methods related to that mapping. + */ +object SafetyLabelPermissionMapping { + + /** + * Get the Safety Label categories pertaining to a specified permission group. + * + * @param groupName the permission group name + * + * @return The categories or an empty list if the group does not have supported and mapped group + * to safety label category + */ + fun getCategoriesForPermissionGroup(groupName: String): List<String> { + return if (groupName == Manifest.permission_group.LOCATION) { + listOf(DataCategoryConstants.CATEGORY_LOCATION) + } else { + emptyList() + } + } +} diff --git a/PermissionController/tests/mocking/Android.bp b/PermissionController/tests/mocking/Android.bp index 2d4e6803e..e6074e335 100644 --- a/PermissionController/tests/mocking/Android.bp +++ b/PermissionController/tests/mocking/Android.bp @@ -108,6 +108,7 @@ android_test { "modules-utils-build_system", "safety-center-resources-lib", "safety-center-internal-data", + "safety-label", "lottie", "androidx.test.rules", diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java b/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java index 68af27470..564d5479f 100644 --- a/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java +++ b/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java @@ -32,7 +32,7 @@ import java.util.Map; * {@link DataCategory} */ public class DataLabel { - @VisibleForTesting public static final String KEY_DATA_LABEL = "data_labels"; + @VisibleForTesting static final String KEY_DATA_LABEL = "data_labels"; private final Map<String, DataCategory> mDataCollected; private final Map<String, DataCategory> mDataShared; diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataType.java b/SafetyLabel/java/com/android/permission/safetylabel/DataType.java index abd69ca6c..cc63b07ac 100644 --- a/SafetyLabel/java/com/android/permission/safetylabel/DataType.java +++ b/SafetyLabel/java/com/android/permission/safetylabel/DataType.java @@ -35,9 +35,9 @@ import java.util.Set; * metadata related to the data usage purpose. */ public class DataType { + @VisibleForTesting static final String KEY_PURPOSES = "purposes"; @VisibleForTesting static final String KEY_USER_CONTROL = "user_control"; @VisibleForTesting static final String KEY_EPHEMERAL = "ephemeral"; - @VisibleForTesting static final String KEY_PURPOSES = "purposes"; @Purpose private final Set<Integer> mPurposeSet; private final Boolean mUserControl; diff --git a/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java b/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java index a91ff901f..214b2db54 100644 --- a/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java +++ b/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java @@ -24,7 +24,7 @@ import androidx.annotation.VisibleForTesting; /** Safety Label representation containing zero or more {@link DataCategory} for data shared */ public class SafetyLabel { - @VisibleForTesting public static final String KEY_SAFETY_LABEL = "safety_label"; + @VisibleForTesting static final String KEY_SAFETY_LABEL = "safety_labels"; private final DataLabel mDataLabel; private SafetyLabel(@NonNull DataLabel dataLabel) { |