summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java83
-rw-r--r--PermissionController/tests/permissionui/Android.bp2
-rw-r--r--PermissionController/tests/permissionui/AndroidTest.xml4
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml25
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml25
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt73
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt8
9 files changed, 284 insertions, 4 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index 38495f3b3..327142896 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -39,6 +39,7 @@ import static android.content.Intent.EXTRA_REASON;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -78,6 +79,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
import android.health.connect.HealthConnectManager;
+import android.health.connect.HealthPermissions;
import android.os.Binder;
import android.os.Build;
import android.os.Parcelable;
@@ -1107,17 +1109,31 @@ public final class Utils {
return false;
}
+ // Always show Fitness&Wellness chip on Wear.
+ if (Flags.replaceBodySensorPermissionEnabled()
+ && pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ return true;
+ }
+
// Check in permission is already granted as we should not hide it in the UX at that point.
List<String> grantedPermissions = packageInfo.getGrantedPermissions();
for (PermissionInfo permission : permissions) {
boolean isCurrentlyGranted = grantedPermissions.contains(permission.name);
if (isCurrentlyGranted) {
- Log.d(LOG_TAG, "At least one Health permission group permission is granted, "
+ Log.d(
+ LOG_TAG,
+ "At least one Health permission group permission is granted, "
+ "show permission group entry");
return true;
}
}
+ // When none health permission is granted, exempt health permission view usage intent filter
+ // if all the requested health permissions are from permission splits.
+ if (isRequestFromSplitHealthPermission(packageInfo)) {
+ return true;
+ }
+
Intent viewUsageIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE);
viewUsageIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS);
viewUsageIntent.setPackage(packageInfo.getPackageName());
@@ -1132,6 +1148,71 @@ public final class Utils {
}
/**
+ * Returns true if the request is being made as the result of a split health permission from
+ * BODY_SENSORS call.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static boolean isRequestFromSplitHealthPermission(LightPackageInfo packageInfo) {
+ // Sdk check to make sure HealthConnectManager.isHealthPermission() is supported.
+ if (!SdkLevel.isAtLeastU() || !Flags.replaceBodySensorPermissionEnabled()) {
+ return false;
+ }
+
+ PermissionControllerApplication app = PermissionControllerApplication.get();
+ PackageManager pm = app.getPackageManager();
+ String packageName = packageInfo.getPackageName();
+ UserHandle user = UserHandle.getUserHandleForUid(packageInfo.getUid());
+ Context context = Utils.getUserContext(app, user);
+
+ List<String> requestedHealthPermissions = new ArrayList<>();
+ for (String permission : packageInfo.getRequestedPermissions()) {
+ if (HealthConnectManager.isHealthPermission(context, permission)) {
+ requestedHealthPermissions.add(permission);
+ }
+ }
+
+ // Split permission only applies to READ_HEART_RATE.
+ if (!requestedHealthPermissions.contains(HealthPermissions.READ_HEART_RATE)) {
+ return false;
+ }
+
+ // If there are other health permissions (other than READ_HEALTH_DATA_IN_BACKGROUND)
+ // don't consider this a pure split-permission request.
+ if (requestedHealthPermissions.size() > 2) {
+ return false;
+ }
+
+ boolean isBackgroundPermissionRequested =
+ requestedHealthPermissions.contains(
+ HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+ // If there are two health permissions declared, make sure the other is
+ // READ_HEALTH_DATA_IN_BACKGROUND.
+ if (requestedHealthPermissions.size() == 2 && !isBackgroundPermissionRequested) {
+ return false;
+ }
+
+ // If READ_HEALTH_DATA_IN_BACKGROUND is requested, check permission flag to see if is from
+ // split permission.
+ if (isBackgroundPermissionRequested) {
+ int readHealthDataInBackgroundFlag =
+ pm.getPermissionFlags(
+ HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, packageName, user);
+ if (!isFromSplitPermission(readHealthDataInBackgroundFlag)) {
+ return false;
+ }
+ }
+
+ // Check READ_HEART_RATE permission flag to see if is from split permission.
+ int readHeartRateFlag =
+ pm.getPermissionFlags(HealthPermissions.READ_HEART_RATE, packageName, user);
+ return isFromSplitPermission(readHeartRateFlag);
+ }
+
+ private static boolean isFromSplitPermission(int permissionFlag) {
+ return (permissionFlag & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0;
+ }
+
+ /**
* Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
*
* @param context the context to get the shared preferences
diff --git a/PermissionController/tests/permissionui/Android.bp b/PermissionController/tests/permissionui/Android.bp
index e0e8fed10..8cc91bd99 100644
--- a/PermissionController/tests/permissionui/Android.bp
+++ b/PermissionController/tests/permissionui/Android.bp
@@ -72,6 +72,8 @@ android_test {
":PermissionUiUseAdditionalPermissionApp",
":PermissionUiUseTwoAdditionalPermissionsApp",
":PermissionUiReadCalendarPermissionApp",
+ ":PermissionUiUseLegacyBodySensorsPermissionApp",
+ ":PermissionUiUseReadHeartRatePermissionApp",
],
per_testcase_directory: true,
}
diff --git a/PermissionController/tests/permissionui/AndroidTest.xml b/PermissionController/tests/permissionui/AndroidTest.xml
index a4aa03abe..9cadbd12f 100644
--- a/PermissionController/tests/permissionui/AndroidTest.xml
+++ b/PermissionController/tests/permissionui/AndroidTest.xml
@@ -57,6 +57,10 @@
value="/data/local/tmp/pc-permissionui/PermissionUiUseTwoAdditionalPermissionsApp.apk" />
<option name="push-file" key="PermissionUiReadCalendarPermissionApp.apk"
value="/data/local/tmp/pc-permissionui/PermissionUiReadCalendarPermissionApp.apk" />
+ <option name="push-file" key="PermissionUiUseLegacyBodySensorsPermissionApp.apk"
+ value="/data/local/tmp/pc-permissionui/PermissionUiUseLegacyBodySensorsPermissionApp.apk" />
+ <option name="push-file" key="PermissionUiUseReadHeartRatePermissionApp.apk"
+ value="/data/local/tmp/pc-permissionui/PermissionUiUseReadHeartRatePermissionApp.apk" />
</target_preparer>
<!-- Uninstall test-apps -->
diff --git a/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp
new file mode 100644
index 000000000..eb0e2d0fc
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2025 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Permission_PermissionController_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: [
+ "packages_modules_Permission_PermissionController_license",
+ ],
+}
+
+android_test_helper_app {
+ name: "PermissionUiUseLegacyBodySensorsPermissionApp",
+
+ srcs: ["src/**/*.kt"],
+
+ sdk_version: "34",
+}
diff --git a/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml
new file mode 100644
index 000000000..fac299b3e
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permissioncontroller.tests.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
+
+ <application />
+</manifest>
diff --git a/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp
new file mode 100644
index 000000000..ccc9c9636
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2025 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Permission_PermissionController_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: [
+ "packages_modules_Permission_PermissionController_license",
+ ],
+}
+
+android_test_helper_app {
+ name: "PermissionUiUseReadHeartRatePermissionApp",
+
+ srcs: ["src/**/*.kt"],
+
+ sdk_version: "34",
+}
diff --git a/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml
new file mode 100644
index 000000000..278af1987
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permissioncontroller.tests.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" />
+
+ <application />
+</manifest>
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
index b2d47a7d7..d4d4be6ec 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
@@ -16,10 +16,16 @@
package com.android.permissioncontroller.permissionui.ui
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Build
+import android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -28,7 +34,9 @@ import com.android.compatibility.common.util.UiAutomatorUtils2.waitUntilObjectGo
import com.android.permissioncontroller.permissionui.wakeUpScreen
import org.junit.After
import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,10 +49,17 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
+
+ @Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private lateinit var context: Context
+
@Before fun assumeNotTelevision() = assumeFalse(isTelevision)
@Before
- fun wakeScreenUp() {
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().context
+
wakeUpScreen()
}
@@ -52,8 +67,10 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
fun uninstallTestApp() {
uninstallTestApps()
}
+
@Test
- fun usedHealthConnectPermissionsAreListed() {
+ fun usedHealthConnectPermissionsAreListed_handHeldDevices() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
installTestAppThatUsesHealthConnectPermission()
startManageAppPermissionsActivity()
@@ -62,7 +79,8 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
}
@Test
- fun invalidUngrantedUsedHealthConnectPermissionsAreNotListed() {
+ fun invalidUngrantedUsedHealthConnectPermissionsAreNotListed_handHeldDevices() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
installInvalidTestAppThatUsesHealthConnectPermission()
startManageAppPermissionsActivity()
@@ -70,6 +88,54 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
waitUntilObjectGone(By.text(HEALTH_CONNECT_LABEL), TIMEOUT_SHORT)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_wearDevices_requestLegacyBodySensorsUngranted_fitnessAndWellnessShowsUp() {
+ assumeTrue(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesLegacyBodySensorsPermissions()
+
+ startManageAppPermissionsActivity()
+
+ eventually { waitFindObject(By.text(FITNESS_AND_WELLNESS_LABEL)) }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_wearDevices_requestReadHeartRateUngranted_fitnessAndWellnessShowsUp() {
+ assumeTrue(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesReadHeartRatePermissions()
+
+ startManageAppPermissionsActivity()
+
+ eventually { waitFindObject(By.text(FITNESS_AND_WELLNESS_LABEL)) }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_handHeldDevices_requestLegacyBodySensorsUngranted_healthConnectShowsUp() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesLegacyBodySensorsPermissions()
+
+ startManageAppPermissionsActivity()
+
+ eventually { waitFindObject(By.text(HEALTH_CONNECT_LABEL)) }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_handHeldDevices_requestReadHeartRateUngranted_healthConnectNotShowsUp() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesReadHeartRatePermissions()
+
+ startManageAppPermissionsActivity()
+
+ waitUntilObjectGone(By.text(HEALTH_CONNECT_LABEL), TIMEOUT_SHORT)
+ }
+
private fun startManageAppPermissionsActivity() {
runWithShellPermissionIdentity {
instrumentationContext.startActivity(
@@ -83,6 +149,7 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
}
companion object {
+ private const val FITNESS_AND_WELLNESS_LABEL = "Fitness and wellness"
// Health connect label uses a non breaking space
private const val HEALTH_CONNECT_LABEL = "Health\u00A0Connect"
private const val HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED =
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
index 7227ea3d8..8eef11e73 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
@@ -36,6 +36,10 @@ private const val TWO_ADDITIONAL_PERM_USER_APK =
"$APK_DIRECTORY/PermissionUiUseTwoAdditionalPermissionsApp.apk"
private const val ADDITIONAL_PERM_DEFINER_APK =
"$APK_DIRECTORY/PermissionUiDefineAdditionalPermissionApp.apk"
+private const val LEGACY_BODY_SENSORS_APK =
+ "$APK_DIRECTORY/PermissionUiUseLegacyBodySensorsPermissionApp.apk"
+private const val READ_HEART_RATE_APK =
+ "$APK_DIRECTORY/PermissionUiUseReadHeartRatePermissionApp.apk"
// All 4 of the AppThatUses_X_Permission(s) applications share the same package name.
private const val PERM_DEFINER_PACKAGE =
@@ -69,6 +73,10 @@ fun installTestAppThatUsesTwoAdditionalPermissions() = install(TWO_ADDITIONAL_PE
fun installTestAppThatDefinesAdditionalPermissions() = install(ADDITIONAL_PERM_DEFINER_APK)
+fun installTestAppThatUsesLegacyBodySensorsPermissions() = install(LEGACY_BODY_SENSORS_APK)
+
+fun installTestAppThatUsesReadHeartRatePermissions() = install(READ_HEART_RATE_APK)
+
fun uninstallTestApps() {
uninstallApp(PERM_USER_PACKAGE)
uninstallApp(PERM_DEFINER_PACKAGE)