diff options
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 |