summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Richard MacGregor <rmacgregor@google.com> 2022-12-06 11:00:43 -0800
committer Richard MacGregor <rmacgregor@google.com> 2022-12-14 12:10:50 -0800
commiteb9878bf5cd830edead959556fcc66ce94d64d57 (patch)
treee85589f0fb7ad2d407c173b6789e48e4092d2b74
parente08832c5a30e7742b425efbc5c9514097c7ddb53 (diff)
Add installsource live data and hook up store link
Bug: 260144598 Test: atest PermissionRationalePermissionGrantDialogTest Test: atest PermissionRationaleTest Change-Id: Ie5de608ece58507b49873386f38a8f385d70b643
-rw-r--r--PermissionController/res/values-v34/strings.xml2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt98
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelLiveData.kt)80
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt30
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt34
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt26
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt45
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java111
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt34
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java1
11 files changed, 413 insertions, 52 deletions
diff --git a/PermissionController/res/values-v34/strings.xml b/PermissionController/res/values-v34/strings.xml
index 412467779..09f2e17a3 100644
--- a/PermissionController/res/values-v34/strings.xml
+++ b/PermissionController/res/values-v34/strings.xml
@@ -37,7 +37,7 @@
may share for app functionality, analytics, developer communications, advertising or marketing,
fraud prevention, security, and compliance, personalization, account management. This may be
updated in the future." [CHAR LIMIT=300] -->
- <string name="permission_rationale_purpose_message">This app developer stated to <annotation id="link"><xliff:g id="install_source" example="App Store">%1$s</xliff:g></annotation> that it may share for <xliff:g id="purpose_list" example="purpose 1, purpose 2, purpose 3">%2$s</xliff:g>\nThis may be updated in the future.</string>
+ <string name="permission_rationale_purpose_message">This app developer stated to <annotation id="link"><annotation id="install_source" example="App Store">%1$s</annotation></annotation> that it may share for <annotation id="purpose_list" example="purpose 1, purpose 2, purpose 3">%2$s</annotation>\nThis may be updated in the future.</string>
<!-- TODO(b/259279178): update with finalized permission rationale strings -->
<!-- Message shown to the user letting them know that data will be shared and for which
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt
new file mode 100644
index 000000000..543d8eae2
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.Context
+import android.content.pm.InstallSourceInfo
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.util.Log
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.model.livedatatypes.LightInstallSourceInfo
+import com.android.permissioncontroller.permission.model.livedatatypes.LightInstallSourceInfo.Companion.UNKNOWN_INSTALL_SOURCE
+import com.android.permissioncontroller.permission.utils.Utils
+import kotlinx.coroutines.Job
+
+/**
+ * [LightInstallSourceInfo] [LiveData] for the specified package
+ *
+ * @param app current Application
+ * @param packageName name of the package to get InstallSourceInfo for
+ * @param user The user of the package
+ */
+class LightInstallSourceInfoLiveData
+private constructor(
+ private val app: Application,
+ private val packageName: String,
+ private val user: UserHandle
+) : SmartAsyncMediatorLiveData<LightInstallSourceInfo>(),
+ PackageBroadcastReceiver.PackageBroadcastListener {
+
+ override fun onActive() {
+ super.onActive()
+ PackageBroadcastReceiver.addChangeCallback(packageName, this)
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ PackageBroadcastReceiver.removeChangeCallback(packageName, this)
+ }
+
+ /**
+ * Callback from the PackageBroadcastReceiver
+ *
+ * @param packageName the name of the package which was updated.
+ */
+ override fun onPackageUpdate(packageName: String) {
+ update()
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ if (job.isCancelled) {
+ return
+ }
+
+ val lightInstallSourceInfo: LightInstallSourceInfo =
+ try {
+ val userContext = Utils.getUserContext(app, user)
+ LightInstallSourceInfo(
+ getInstallSourceInfo(userContext, packageName).installingPackageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(LOG_TAG, "InstallSourceInfo for $packageName not found")
+ SafetyLabelInfoLiveData.invalidateSingle(packageName to user)
+ UNKNOWN_INSTALL_SOURCE
+ }
+ postValue(lightInstallSourceInfo)
+ }
+
+ companion object :
+ DataRepositoryForPackage<Pair<String, UserHandle>, LightInstallSourceInfoLiveData>() {
+ private val LOG_TAG = LightInstallSourceInfoLiveData::class.java.simpleName
+
+ override fun newValue(key: Pair<String, UserHandle>): LightInstallSourceInfoLiveData {
+ return LightInstallSourceInfoLiveData(
+ PermissionControllerApplication.get(), key.first, key.second)
+ }
+
+ /** Returns the [InstallSourceInfo] for the given package */
+ @Throws(PackageManager.NameNotFoundException::class)
+ private fun getInstallSourceInfo(context: Context, packageName: String): InstallSourceInfo {
+ return context.packageManager.getInstallSourceInfo(packageName)
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt
index 9dc16e306..c66e7a7d6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt
@@ -161,6 +161,8 @@ object PackageBroadcastReceiver : BroadcastReceiver() {
HibernationSettingStateLiveData.invalidateAllForPackage(packageName)
LightAppPermGroupLiveData.invalidateAllForPackage(packageName)
AppPermGroupUiInfoLiveData.invalidateAllForPackage(packageName)
+ SafetyLabelInfoLiveData.invalidateAllForPackage(packageName)
+ LightInstallSourceInfoLiveData.invalidateAllForPackage(packageName)
}
}
@@ -176,4 +178,4 @@ object PackageBroadcastReceiver : BroadcastReceiver() {
*/
fun onPackageUpdate(packageName: String)
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt
index 48909384f..64bd21e13 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt
@@ -19,55 +19,93 @@ package com.android.permissioncontroller.permission.data
import android.app.Application
import android.content.pm.PackageManager
import android.os.PersistableBundle
+import android.os.UserHandle
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.model.livedatatypes.SafetyLabelInfo
import com.android.permissioncontroller.permission.utils.KotlinUtils.isPlaceholderSafetyLabelDataEnabled
import kotlinx.coroutines.Job
/**
- * SafetyLabel LiveData for the specified package
+ * [SafetyLabelInfo] [LiveData] for the specified package
*
* @param app current Application
* @param packageName name of the package to get SafetyLabel information for
+ * @param user The user of the package
*/
-class SafetyLabelLiveData
-private constructor(private val app: Application, private val packageName: String) :
- SmartAsyncMediatorLiveData<SafetyLabel>() {
+class SafetyLabelInfoLiveData
+private constructor(
+ private val app: Application,
+ private val packageName: String,
+ private val user: UserHandle
+) :
+ SmartAsyncMediatorLiveData<SafetyLabelInfo>(),
+ PackageBroadcastReceiver.PackageBroadcastListener {
+
+ private val lightInstallSourceInfoLiveData = LightInstallSourceInfoLiveData[packageName, user]
+
+ init {
+ addSource(lightInstallSourceInfoLiveData) { update() }
+
+ update()
+ }
+
+ override fun onActive() {
+ super.onActive()
+ PackageBroadcastReceiver.addChangeCallback(packageName, this)
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ PackageBroadcastReceiver.removeChangeCallback(packageName, this)
+ }
+
+ /**
+ * Callback from the PackageBroadcastReceiver
+ *
+ * @param packageName the name of the package which was updated.
+ */
+ override fun onPackageUpdate(packageName: String) {
+ update()
+ }
override suspend fun loadDataAndPostValue(job: Job) {
if (job.isCancelled) {
return
}
- if (!isPermissionRationaleEnabled()) {
- postValue(null)
+ if (lightInstallSourceInfoLiveData.isStale) {
return
}
- if (packageName.isEmpty()) {
- postValue(null)
+ // TODO(b/261607291): Add support preinstall apps that provide SafetyLabel. Installing
+ // package is null until updated from an app store
+ val installSourcePackageName = lightInstallSourceInfoLiveData.value?.installingPackageName
+ if (installSourcePackageName == null) {
+ postValue(SafetyLabelInfo.UNAVAILABLE)
return
}
- val safetyLabel: SafetyLabel? =
+ val safetyLabelInfo: SafetyLabelInfo =
try {
- val metadataBundle: PersistableBundle? = getInstallMetadataBundle()
- SafetyLabel.getSafetyLabelFromMetadata(metadataBundle)
+ val metadataBundle: PersistableBundle? = getAppMetadata()
+ SafetyLabelInfo(
+ SafetyLabel.getSafetyLabelFromMetadata(metadataBundle),
+ installSourcePackageName)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(LOG_TAG, "SafetyLabel for $packageName not found")
- invalidateSingle(packageName)
- null
+ invalidateSingle(packageName to user)
+ SafetyLabelInfo.UNAVAILABLE
}
- postValue(safetyLabel)
+ postValue(safetyLabelInfo)
}
// TODO(b/257293222): Update when hooking up PackageManager APIs
- private fun getInstallMetadataBundle(): PersistableBundle? {
+ private fun getAppMetadata(): PersistableBundle? {
return if (isPlaceholderSafetyLabelDataEnabled()) {
placeholderMetadataBundle()
} else {
@@ -106,11 +144,13 @@ private constructor(private val app: Application, private val packageName: Strin
}
}
- companion object : DataRepositoryForPackage<String, SafetyLabelLiveData>() {
- private val LOG_TAG = SafetyLabelLiveData::class.java.simpleName
+ companion object : DataRepositoryForPackage<Pair<String, UserHandle>, SafetyLabelInfoLiveData>(
+ ) {
+ private val LOG_TAG = SafetyLabelInfoLiveData::class.java.simpleName
- override fun newValue(key: String): SafetyLabelLiveData {
- return SafetyLabelLiveData(PermissionControllerApplication.get(), key)
+ override fun newValue(key: Pair<String, UserHandle>): SafetyLabelInfoLiveData {
+ return SafetyLabelInfoLiveData(PermissionControllerApplication.get(), key.first,
+ key.second)
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt
new file mode 100644
index 000000000..68fdf8739
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.model.livedatatypes
+
+/**
+ * A lighter version of the system's InstallSourceInfo class, containing select information about
+ * the install source.
+ *
+ * @param installingPackageName The package name of the install source (usually the app store)
+ */
+class LightInstallSourceInfo(val installingPackageName: String?) {
+
+ companion object {
+ val UNKNOWN_INSTALL_SOURCE = LightInstallSourceInfo(null)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt
new file mode 100644
index 000000000..2c26ad0d4
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.model.livedatatypes
+
+import com.android.permission.safetylabel.SafetyLabel
+
+/**
+ * A wrapping class for [SafetyLabel] class that includes the install source package name
+ *
+ * @param safetyLabel The resulting [SafetyLabel], or null if none found
+ * @param installSourcePackageName The package name of the install source for the APK and safety
+ * label(usually the app store)
+ */
+class SafetyLabelInfo(val safetyLabel: SafetyLabel?, val installSourcePackageName: String?) {
+
+ companion object {
+ /** Default definition of unavailable or no safety label found */
+ val UNAVAILABLE = SafetyLabelInfo(null, null)
+ }
+}
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 a942343af..a474bab7f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -74,7 +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.SafetyLabelInfoLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
@@ -152,11 +152,12 @@ 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 safetyLabelInfoLiveData = SafetyLabelInfoLiveData[packageName, user]
private val dpm = app.getSystemService(DevicePolicyManager::class.java)!!
private val permissionPolicy = dpm.getPermissionPolicy(null)
private val permGroupsToSkip = mutableListOf<String>()
private var groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>()
+ private val permissionRationaleEnabled: Boolean by lazy { isPermissionRationaleEnabled() }
private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null
private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier {
@@ -202,10 +203,14 @@ class GrantPermissionsViewModel(
private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
+ // TODO(b/260873483): only query safety label for supported permission groups. should only
+ // query location, but currently queries for all groups
init {
addSource(packagePermissionsLiveData) { onPackageLoaded() }
addSource(packageInfoLiveData) { onPackageLoaded() }
- addSource(safetyLabelLiveData) { onPackageLoaded() }
+ if (permissionRationaleEnabled) {
+ addSource(safetyLabelInfoLiveData) { onPackageLoaded() }
+ }
// Load package state, if available
onPackageLoaded()
@@ -214,10 +219,17 @@ class GrantPermissionsViewModel(
private fun onPackageLoaded() {
if (packageInfoLiveData.isStale ||
packagePermissionsLiveData.isStale ||
- safetyLabelLiveData.isStale) {
+ (permissionRationaleEnabled && safetyLabelInfoLiveData.isStale)) {
return
}
+ safetyLabel =
+ if (permissionRationaleEnabled) {
+ safetyLabelInfoLiveData.value?.safetyLabel
+ } else {
+ null
+ }
+
val groups = packagePermissionsLiveData.value
val pI = packageInfoLiveData.value
if (groups == null || groups.isEmpty() || pI == null) {
@@ -235,8 +247,6 @@ class GrantPermissionsViewModel(
return
}
- safetyLabel = safetyLabelLiveData.value
-
val allAffectedPermissions = requestedPermissions.toMutableSet()
for (requestedPerm in requestedPermissions) {
allAffectedPermissions.addAll(computeAffectedPermissions(requestedPerm, groups))
@@ -610,9 +620,7 @@ class GrantPermissionsViewModel(
safetyLabel: SafetyLabel?,
groupState: GroupState
): Boolean {
- if (!isPermissionRationaleEnabled() ||
- safetyLabel == null ||
- safetyLabel.dataLabel.dataShared.isEmpty()) {
+ if (safetyLabel == null || safetyLabel.dataLabel.dataShared.isEmpty()) {
return false
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
index c88e8a7b6..eb9c5bad8 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
@@ -17,8 +17,10 @@
package com.android.permissioncontroller.permission.ui.model.v34
import android.app.Application
+import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.os.Process
import android.util.Log
import androidx.core.util.Consumer
import androidx.lifecycle.ViewModel
@@ -27,8 +29,12 @@ 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.permission.data.SafetyLabelLiveData
+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.SafetyLabelInfo.Companion.UNAVAILABLE
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getAppStoreIntent
import com.android.permissioncontroller.permission.utils.SafetyLabelPermissionMapping
/**
@@ -49,7 +55,8 @@ class PermissionRationaleViewModel(
private val sessionId: Long,
private val storedState: Bundle?
) : ViewModel() {
- private val safetyLabelLiveData = SafetyLabelLiveData[packageName]
+ private val user = Process.myUserHandle()
+ private val safetyLabelInfoLiveData = SafetyLabelInfoLiveData[packageName, user]
var activityResultCallback: Consumer<Intent>? = null
@@ -59,7 +66,8 @@ class PermissionRationaleViewModel(
*/
data class PermissionRationaleInfo(
val groupName: String,
- val installSourceName: String?,
+ val installSourcePackageName: String?,
+ val installSourceLabel: CharSequence?,
val purposeSet: Set<Int>
)
@@ -68,29 +76,39 @@ class PermissionRationaleViewModel(
object : SmartUpdateMediatorLiveData<PermissionRationaleInfo>() {
init {
- addSource(safetyLabelLiveData) { onUpdate() }
+ addSource(safetyLabelInfoLiveData) { onUpdate() }
// Load package state, if available
onUpdate()
}
override fun onUpdate() {
- if (safetyLabelLiveData.isStale) {
+ if (safetyLabelInfoLiveData.isStale) {
return
}
- val safetyLabel = safetyLabelLiveData.value
- if (safetyLabel == null) {
+ val safetyLabelInfo = safetyLabelInfoLiveData.value
+ val safetyLabel = safetyLabelInfo?.safetyLabel
+
+ if (safetyLabelInfo == null ||
+ safetyLabelInfo == UNAVAILABLE ||
+ safetyLabel == null) {
Log.e(LOG_TAG, "Safety label for $packageName not found")
value = null
return
}
- // TODO(b/260144598): link to app store
+ val installSourcePackageName = safetyLabelInfo.installSourcePackageName
+ val installSourceLabel: CharSequence? =
+ installSourcePackageName?.let {
+ KotlinUtils.getPackageLabel(app, it, Process.myUserHandle())
+ }
+
value =
PermissionRationaleInfo(
permissionGroupName,
- null,
+ installSourcePackageName,
+ installSourceLabel,
getSafetyLabelSharingPurposesForGroup(safetyLabel, permissionGroupName))
}
@@ -124,6 +142,15 @@ class PermissionRationaleViewModel(
}
}
+ fun canLinkToAppStore(context: Context, installSourcePackageName: String): Boolean {
+ return getAppStoreIntent(context, installSourcePackageName, packageName) != null
+ }
+
+ fun sendToAppStore(context: Context, installSourcePackageName: String) {
+ val storeIntent = getAppStoreIntent(context, installSourcePackageName, packageName)
+ context.startActivity(storeIntent)
+ }
+
companion object {
private val LOG_TAG = PermissionRationaleViewModel::class.java.simpleName
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
index b81c802da..819291d50 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
@@ -33,6 +33,9 @@ import android.icu.lang.UCharacter;
import android.icu.text.ListFormatter;
import android.os.Build;
import android.os.Bundle;
+import android.text.Annotation;
+import android.text.SpannableStringBuilder;
+import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -57,6 +60,7 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Random;
@@ -73,13 +77,34 @@ public class PermissionRationaleActivity extends SettingsActivity implements
private static final String KEY_SESSION_ID = PermissionRationaleActivity.class.getName()
+ "_SESSION_ID";
+ /**
+ * [Annotation] key for span annotations replacement within the permission rationale purposes
+ * string resource
+ */
+ public static final String ANNOTATION_ID_KEY = "id";
+ /**
+ * [Annotation] id value for span annotations replacement of link annotations within the
+ * permission rationale purposes string resource
+ */
+ public static final String LINK_ANNOTATION_ID = "link";
+ /**
+ * [Annotation] id value for span annotations replacement of install source annotations within
+ * the permission rationale purposes string resource
+ */
+ public static final String INSTALL_SOURCE_ANNOTATION_ID = "install_source";
+ /**
+ * [Annotation] id value for span annotations replacement of purpose list annotations within
+ * the permission rationale purposes string resource
+ */
+ public static final String PURPOSE_LIST_ANNOTATION_ID = "purpose_list";
+
/** Unique Id of a request. Inherited from GrantPermissionDialog if provide via intent extra */
private long mSessionId;
/** Package that shall have permissions granted */
private String mTargetPackage;
/** The permission group that initiated the permission rationale details activity */
private String mPermissionGroupName;
- /** The permission rationale info resulting from the specified permisison and group */
+ /** The permission rationale info resulting from the specified permission and group */
private PermissionRationaleInfo mPermissionRationaleInfo;
private PermissionRationaleViewHandler mViewHandler;
@@ -93,6 +118,14 @@ public class PermissionRationaleActivity extends SettingsActivity implements
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ if (!KotlinUtils.INSTANCE.isPermissionRationaleEnabled()) {
+ Log.e(
+ LOG_TAG,
+ "Permission rationale feature disabled");
+ finishAfterTransition();
+ return;
+ }
+
if (icicle == null) {
mSessionId =
getIntent().getLongExtra(Constants.EXTRA_SESSION_ID, new Random().nextLong());
@@ -235,9 +268,6 @@ public class PermissionRationaleActivity extends SettingsActivity implements
}
private void showPermissionRationale() {
- String groupName = mPermissionRationaleInfo.getGroupName();
- String installSourceName = mPermissionRationaleInfo.getInstallSourceName();
-
List<String> purposesList =
new ArrayList<>(mPermissionRationaleInfo.getPurposeSet().size());
for (@Purpose int purpose : mPermissionRationaleInfo.getPurposeSet()) {
@@ -247,20 +277,25 @@ public class PermissionRationaleActivity extends SettingsActivity implements
// TODO(b/260144215): update purposes join based on l18n feedback
String purposesString = ListFormatter.getInstance().format(purposesList);
- // TODO(b/260144598): link to app store
+ String installSourcePackageName = mPermissionRationaleInfo.getInstallSourcePackageName();
+ CharSequence installSourceLabel = mPermissionRationaleInfo.getInstallSourceLabel();
CharSequence purposeMessage;
- if (installSourceName == null || installSourceName.isEmpty()) {
- purposeMessage =
- getString(R.string.permission_rationale_purpose_default_source_message,
- purposesString);
+ if (installSourcePackageName == null || installSourcePackageName.length() == 0
+ || installSourceLabel == null || installSourceLabel.length() == 0) {
+ purposeMessage = getString(
+ R.string.permission_rationale_purpose_default_source_message,
+ purposesString);
} else {
purposeMessage =
- getString(R.string.permission_rationale_purpose_message,
- installSourceName,
- purposesString);
+ createPurposeMessageWithLink(
+ getText(R.string.permission_rationale_purpose_message),
+ installSourceLabel,
+ purposesString,
+ getLinkToAppStore(installSourcePackageName));
}
// TODO(b/260144330): link to permission settings
+ String groupName = mPermissionRationaleInfo.getGroupName();
String permissionGroupLabel =
KotlinUtils.INSTANCE.getPermGroupLabel(this, groupName).toString();
CharSequence settingsMessage =
@@ -306,4 +341,56 @@ public class PermissionRationaleActivity extends SettingsActivity implements
throw new IllegalArgumentException("Invalid purpose: " + purpose);
}
}
+
+ private CharSequence createPurposeMessageWithLink(
+ CharSequence purposeText,
+ CharSequence installSourceLabel,
+ CharSequence purposes,
+ ClickableSpan link) {
+ SpannableStringBuilder text = SpannableStringBuilder.valueOf(purposeText);
+ Annotation[] annotations = text.getSpans(0, text.length(), Annotation.class);
+ // Sort the annotations in reverse order.
+ Arrays.sort(annotations, (a, b) -> text.getSpanStart(b) - text.getSpanStart(a));
+ SpannableStringBuilder messageWithSpan = new SpannableStringBuilder(text);
+ for (android.text.Annotation annotation : annotations) {
+ if (!annotation.getKey().equals(ANNOTATION_ID_KEY)) {
+ continue;
+ }
+
+ int spanStart = text.getSpanStart(annotation);
+ int spanEnd = text.getSpanEnd(annotation);
+ messageWithSpan.removeSpan(annotation);
+
+ switch (annotation.getValue()) {
+ case INSTALL_SOURCE_ANNOTATION_ID:
+ messageWithSpan.replace(spanStart, spanEnd, installSourceLabel);
+ break;
+ case LINK_ANNOTATION_ID:
+ messageWithSpan.setSpan(link, spanStart, spanEnd, 0);
+ break;
+ case PURPOSE_LIST_ANNOTATION_ID:
+ messageWithSpan.replace(spanStart, spanEnd, purposes);
+ break;
+ default:
+ continue;
+ }
+ }
+ return messageWithSpan;
+ }
+
+ private ClickableSpan getLinkToAppStore(String installSourcePackageName) {
+ boolean canLinkToAppStore = mViewModel
+ .canLinkToAppStore(PermissionRationaleActivity.this, installSourcePackageName);
+ if (!canLinkToAppStore) {
+ return null;
+ }
+ return new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ // TODO(b/259961958): metrics for click events
+ mViewModel.sendToAppStore(PermissionRationaleActivity.this,
+ installSourcePackageName);
+ }
+ };
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index a7af4948d..fad90fc13 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -48,6 +48,7 @@ import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
+import android.content.pm.ResolveInfo
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
@@ -1391,6 +1392,39 @@ object KotlinUtils {
val permissions = HealthConnectManager.getHealthPermissions(context)
PermissionMapping.addHealthPermissionsToPlatform(permissions)
}
+
+ /**
+ * Returns an [Intent] to the installer app store for a given package name, or {@code null} if
+ * none found
+ */
+ fun getAppStoreIntent(
+ context: Context,
+ installerPackageName: String?,
+ packageName: String?
+ ): Intent? {
+ val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO)
+ .setPackage(installerPackageName)
+ val result: Intent? = resolveActivityForIntent(context, intent)
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ return result
+ }
+ return null
+ }
+
+ /**
+ * Verify that a component that supports the intent with action and return a new intent with
+ * same action and resolved class name set. Returns null if no activity resolution.
+ */
+ private fun resolveActivityForIntent(context: Context, intent: Intent): Intent? {
+ val result: ResolveInfo? = context.packageManager.resolveActivity(intent, 0)
+ return if (result != null) {
+ Intent(intent.action)
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name)
+ } else {
+ null
+ }
+ }
}
/**
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java b/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java
index 214b2db54..822e9c4fe 100644
--- a/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java
+++ b/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java
@@ -34,6 +34,7 @@ public class SafetyLabel {
/** Returns {@link SafetyLabel} created by parsing a metadata {@link PersistableBundle} */
@Nullable
public static SafetyLabel getSafetyLabelFromMetadata(@Nullable PersistableBundle bundle) {
+ // TODO(b/261069412): add versioning and nonnull empty/invalid cases
if (bundle == null) {
return null;
}