diff options
18 files changed, 400 insertions, 119 deletions
diff --git a/PermissionController/res/values-v34/styles.xml b/PermissionController/res/values-v34/styles.xml index f427d1a8e..cc119a331 100644 --- a/PermissionController/res/values-v34/styles.xml +++ b/PermissionController/res/values-v34/styles.xml @@ -149,6 +149,7 @@ <item name="android:gravity">center</item> <item name="android:insetTop">6dp</item> <item name="android:insetBottom">6dp</item> + <item name="android:minWidth">48dp</item> <item name="android:minHeight">48dp</item> <item name="android:paddingTop">8dp</item> <item name="android:paddingBottom">8dp</item> diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index d225d296a..dc884e810 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -68,12 +68,8 @@ </permission-set> <permission-set name="sensors"> - <permission name="android.permission.BODY_SENSORS"/> - <permission name="android.permission.BODY_SENSORS_BACKGROUND" minSdkVersion="33"/> - <permission name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" - minSdkVersion="34"/> - <permission name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND" - minSdkVersion="34"/> + <permission name="android.permission.BODY_SENSORS" /> + <permission name="android.permission.BODY_SENSORS_BACKGROUND" minSdkVersion="33" /> </permission-set> <permission-set name="storage"> diff --git a/PermissionController/src/com/android/permissioncontroller/Constants.java b/PermissionController/src/com/android/permissioncontroller/Constants.java index e16f8d758..3995b608e 100644 --- a/PermissionController/src/com/android/permissioncontroller/Constants.java +++ b/PermissionController/src/com/android/permissioncontroller/Constants.java @@ -312,4 +312,19 @@ public class Constants { @RequiresApi(Build.VERSION_CODES.TIRAMISU) public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; + + /** + * Extra used by Settings to indicate an Intent should be treated as if opened directly by + * Settings app itself. + */ + public static final String EXTRA_FROM_SETTINGS = "is_from_settings_homepage"; + + /** + * Extra used by Settings to indicate an Intent should be treated as if opened by a slice + * within Settings. + * + * <p>Slices are opened within settings by firing a PendingIntent, so we can use this extra to + * allow the same UX path to be taken as for slices. + */ + public static final String EXTRA_IS_FROM_SLICE = "is_from_slice"; } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java index 82620058d..001520c1b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java @@ -34,6 +34,7 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Process; @@ -50,6 +51,7 @@ import android.util.Xml; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto; import com.android.permissioncontroller.PermissionControllerStatsLog; @@ -777,6 +779,7 @@ public final class PermissionControllerServiceImpl extends PermissionControllerL } @Override + @RequiresApi(Build.VERSION_CODES.TIRAMISU) public void onRevokeSelfPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull Runnable callback) { PackageInfo pkgInfo = getPkgInfo(packageName); 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 91b6de077..cc44a400b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt @@ -85,6 +85,7 @@ import com.android.permissioncontroller.permission.utils.PermissionMapping.getPa import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils import com.android.permissioncontroller.permission.utils.navigateSafe +import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils import com.android.settingslib.RestrictedLockUtils import java.util.Random import kotlin.collections.component1 @@ -206,7 +207,7 @@ class AppPermissionViewModel( return } - value = PermissionMapping.getSafetyLabelSharingPurposesForGroup( + value = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup( safetyLabel, permGroupName).any() } } 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 3a689fd72..c6d89876b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt @@ -130,6 +130,7 @@ import com.android.permissioncontroller.permission.utils.PermissionMapping import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils +import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils /** * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by @@ -626,7 +627,7 @@ class GrantPermissionsViewModel( return false } - val purposes = PermissionMapping.getSafetyLabelSharingPurposesForGroup(safetyLabel, + val purposes = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup(safetyLabel, permissionGroupName) return purposes.isNotEmpty() } 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 3d205a270..561a8eef2 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 @@ -46,7 +46,7 @@ import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity. import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity import com.android.permissioncontroller.permission.utils.KotlinUtils import com.android.permissioncontroller.permission.utils.KotlinUtils.getAppStoreIntent -import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils import com.android.settingslib.HelpUtils /** @@ -124,7 +124,7 @@ class PermissionRationaleViewModel( KotlinUtils.getPackageLabel(app, it, Process.myUserHandle()) } - val purposes = PermissionMapping.getSafetyLabelSharingPurposesForGroup( + val purposes = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup( safetyLabelInfo.safetyLabel, permissionGroupName) if (value == null) { logPermissionRationaleDialogViewed(purposes) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt index 1d73ac959..a9d8c4e12 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt @@ -304,18 +304,6 @@ object KotlinUtils { PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH, false) } - /** - * The minimum amount of time to wait, after scheduling the safety label changes job, before - * the job actually runs for the first time. - */ - @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake") - fun getSafetyLabelChangesJobDelayMillis(): Long { - return DeviceConfig.getLong( - DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS, - Duration.ofMinutes(30).toMillis()) - } - /** How often the safety label changes job will run. */ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake") fun getSafetyLabelChangesJobIntervalMillis(): Long { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt index c4355b1e5..b06a09b28 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt @@ -26,11 +26,7 @@ import android.util.Log import com.android.modules.utils.build.SdkLevel import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup -import com.android.permission.safetylabel.DataCategory import com.android.permission.safetylabel.DataCategoryConstants -import com.android.permission.safetylabel.DataType -import com.android.permission.safetylabel.DataTypeConstants -import com.android.permission.safetylabel.SafetyLabel /** * This file contains the canonical mapping of permission to permission group, used in the @@ -187,13 +183,6 @@ object PermissionMapping { Manifest.permission_group.SENSORS } - if (SdkLevel.isAtLeastU()) { - PLATFORM_PERMISSIONS[Utils.BODY_SENSORS_WRIST_TEMPERATURE] = - Manifest.permission_group.SENSORS - PLATFORM_PERMISSIONS[Utils.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND] = - Manifest.permission_group.SENSORS - } - for ((permission, permissionGroup) in PLATFORM_PERMISSIONS) { PLATFORM_PERMISSION_GROUPS.getOrPut(permissionGroup) { mutableListOf() }.add(permission) } @@ -390,38 +379,6 @@ object PermissionMapping { return AppOpsManager.opToPermission(opName)?.let { getGroupOfPlatformPermission(it) } } - /* - * Get the sharing purposes for a SafetyLabel related to a specific permission group. - */ - @JvmStatic - fun getSafetyLabelSharingPurposesForGroup( - safetyLabel: SafetyLabel, - groupName: String - ): Set<Int> { - val purposeSet = mutableSetOf<Int>() - val categoriesForPermission = getDataCategoriesForPermissionGroup(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()) { - purposeSet.addAll(dataType.purposeSet) - } - } - } - - return purposeSet - } - /** * Get the SafetyLabel categories pertaining to a specified permission group. * diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index b0aaac9ea..d4354bd72 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -76,7 +76,6 @@ import android.hardware.SensorPrivacyManager; import android.os.Binder; import android.os.Build; import android.os.Parcelable; -import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -110,8 +109,6 @@ import com.android.permissioncontroller.permission.model.AppPermissionGroup; import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup; import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo; -import kotlin.Triple; - import java.lang.annotation.Retention; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -123,6 +120,8 @@ import java.util.Locale; import java.util.Random; import java.util.Set; +import kotlin.Triple; + public final class Utils { @Retention(SOURCE) @@ -253,12 +252,6 @@ public final class Utils { private static final String SYSTEM_VISUAL_INTELLIGENCE = "android.app.role.SYSTEM_VISUAL_INTELLIGENCE"; - public static final String BODY_SENSORS_WRIST_TEMPERATURE = - "android.permission.BODY_SENSORS_WRIST_TEMPERATURE"; - - public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = - "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"; - // TODO: theianchen Using hardcoded values here as a WIP solution for now. private static final String[] EXEMPTED_ROLES = { SYSTEM_AMBIENT_AUDIO_INTELLIGENCE, @@ -1069,38 +1062,6 @@ public final class Utils { } /** - * Checks whether a package has an active one-time permission according to the system server's - * flags - * - * @param context the {@code Context} to retrieve {@code PackageManager} - * @param packageName The package to check for - * @return Whether a package has an active one-time permission - */ - public static boolean hasOneTimePermissions(Context context, String packageName) { - String[] permissions; - PackageManager pm = context.getPackageManager(); - try { - permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) - .requestedPermissions; - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package"); - return false; - } - if (permissions == null) { - return false; - } - for (String permissionName : permissions) { - if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle()) - & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0 - && pm.checkPermission(permissionName, packageName) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - } - return false; - } - - /** * Returns a random session ID value that's guaranteed to not be {@code INVALID_SESSION_ID}. * * @return A valid session ID. diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java index e9d68c9c6..9b7927c1d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java @@ -53,13 +53,6 @@ public final class AdminRestrictedPermissionsUtils { if (SdkLevel.isAtLeastT()) { ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); } - // New U permissions - do not add unless running on U and above. - if (SdkLevel.isAtLeastU()) { - ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add( - Utils.BODY_SENSORS_WRIST_TEMPERATURE); - ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add( - Utils.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); - } } /** diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/v34/SafetyLabelUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/v34/SafetyLabelUtils.kt new file mode 100644 index 000000000..5dbe203f9 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/v34/SafetyLabelUtils.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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.v34 + +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.utils.PermissionMapping + +object SafetyLabelUtils { + /* + * Get the sharing purposes for a SafetyLabel related to a specific permission group. + */ + @JvmStatic + fun getSafetyLabelSharingPurposesForGroup( + safetyLabel: SafetyLabel, + groupName: String + ): Set<Int> { + val purposeSet = mutableSetOf<Int>() + val categoriesForPermission = PermissionMapping + .getDataCategoriesForPermissionGroup(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()) { + purposeSet.addAll(dataType.purposeSet) + } + } + } + + return purposeSet + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt index 11881e3ab..4ba685589 100644 --- a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt +++ b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt @@ -420,7 +420,7 @@ class AccessibilitySourceService( // Start this Settings activity using the same UX that settings slices uses. This allows // settings to correctly support 2-pane layout with as-best-as-possible transition // animation. - intent.putExtra(EXTRA_IS_FROM_SLICE, true) + intent.putExtra(Constants.EXTRA_IS_FROM_SLICE, true) return PendingIntent.getActivity( context, 0, @@ -649,7 +649,6 @@ class AccessibilitySourceService( private const val PROPERTY_SC_ACCESSIBILITY_JOB_INTERVAL_MILLIS = "sc_accessibility_job_interval_millis" private val DEFAULT_SC_ACCESSIBILITY_JOB_INTERVAL_MILLIS = TimeUnit.DAYS.toMillis(1) - private val EXTRA_IS_FROM_SLICE = "is_from_slice" private val sourceStateChanged = SafetyEvent.Builder( SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt new file mode 100644 index 000000000..305dcdfb7 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 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.util + +import com.android.permissioncontroller.permission.utils.ArrayUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ArrayUtilsTest { + @Test + fun appendString_appendToNull_returnsArrayWithString() { + assertThat(ArrayUtils.appendString(null, TEST_STRING)) + .isEqualTo(arrayOf(TEST_STRING)) + } + + @Test + fun appendString_appendToNull_returnsArrayWithNull() { + val result = ArrayUtils.appendString(null, null) + assertThat(result.size).isEqualTo(1) + assertThat(result[0]).isEqualTo(null) + } + + @Test + fun appendString_duplicatedString_returnsArray() { + val cur = arrayOf("a", "b", TEST_STRING) + assertThat(ArrayUtils.appendString(cur, TEST_STRING)).isEqualTo(cur) + } + + @Test + fun appendString_appendNull_returnsArray() { + val cur = arrayOf("a", "b", null) + assertThat(ArrayUtils.appendString(cur, null)).isEqualTo(cur) + } + + @Test + fun appendString_appendToEmptyArray_returnsArrayWithNewString() { + val cur = arrayOf<String>() + val new = arrayOf(TEST_STRING) + assertThat(ArrayUtils.appendString(cur, TEST_STRING)).isEqualTo(new) + } + + @Test + fun appendString_appendNullToEmptyArray_returnsArrayWithNewString() { + val cur = arrayOf<String>() + val result = ArrayUtils.appendString(cur, null) + assertThat(result.size).isEqualTo(1) + assertThat(result[0]).isNull() + } + + @Test + fun appendString_appendNewString() { + val cur = arrayOf("old test") + val new = arrayOf("old test", TEST_STRING) + assertThat(ArrayUtils.appendString(cur, TEST_STRING)).isEqualTo(new) + } + + companion object { + private const val TEST_STRING = "test" + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt new file mode 100644 index 000000000..8f54da579 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2023 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.util + +import android.Manifest.permission.READ_MEDIA_IMAGES +import android.Manifest.permission.READ_MEDIA_VIDEO +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_SHOW_APP_INFO +import android.content.Intent.EXTRA_PACKAGE_NAME +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.permissioncontroller.permission.utils.KotlinUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.argThat +import org.mockito.Mockito.mock +import kotlin.test.assertFailsWith +import org.mockito.Mockito.`when` as whenever + +/** + * Unit tests for [KotlinUtils]. + */ +@RunWith(AndroidJUnit4::class) +class KotlinUtilsTest { + private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext + + @Test + fun convertToBitmap_argb888BitmapDrawable_returnsSameBitmap() { + val bitmap = Bitmap.createBitmap(/* width= */ 5, /* height= */ 10, Bitmap.Config.ARGB_8888) + val drawable = BitmapDrawable(targetContext.resources, bitmap) + + assertThat(KotlinUtils.convertToBitmap(drawable).sameAs(bitmap)).isTrue() + } + + @Test + fun convertToBitmap_noIntrinsicSize_throws() { + val drawable = FakeDrawable(intrinsicSize = 0) + assertFailsWith<IllegalArgumentException> { KotlinUtils.convertToBitmap(drawable) } + } + + class FakeDrawable(private val intrinsicSize: Int) : Drawable() { + override fun getIntrinsicWidth() = intrinsicSize + override fun getIntrinsicHeight() = intrinsicSize + + override fun draw(canvas: Canvas) = Unit // no-op + override fun getOpacity() = throw UnsupportedOperationException() + override fun setAlpha(alpha: Int) = throw UnsupportedOperationException() + override fun setColorFilter(colorFilter: ColorFilter?) = + throw UnsupportedOperationException() + } + + @Test + fun getAppStoreIntent_returnsResolvedIntent() { + val installerPackage = "installer" + val installerActivity = "activity" + val appPackage = "app" + val mockContext = mock(Context::class.java) + val mockPackageManager = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(mockPackageManager) + val installerIntent = Intent(ACTION_SHOW_APP_INFO).setPackage(installerPackage) + whenever( + mockPackageManager.resolveActivity( + argThat { intent -> intent.filterEquals(installerIntent) }, /* flags= */ anyInt())) + .thenReturn(ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = installerPackage + name = installerActivity + } + }) + + val intent = KotlinUtils.getAppStoreIntent(mockContext, installerPackage, appPackage) + + assertThat(intent).isNotNull() + with (intent!!) { + assertThat(action).isEqualTo(ACTION_SHOW_APP_INFO) + assertThat(component?.packageName).isEqualTo(installerPackage) + assertThat(component?.className).isEqualTo(installerActivity) + } + } + + @Test + fun getAppStoreIntent_returnsAppPackageInExtras() { + val appPackage = "app" + val mockContext = mock(Context::class.java) + val mockPackageManager = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(mockPackageManager) + whenever( + mockPackageManager.resolveActivity(any(), /* flags= */ anyInt())) + .thenReturn(ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = "" + name = "" + } + }) + + val intent = KotlinUtils.getAppStoreIntent(mockContext, "com.installer", appPackage) + + assertThat(intent).isNotNull() + assertThat(intent?.extras?.getString(EXTRA_PACKAGE_NAME)).isEqualTo(appPackage) + } + + @Test + fun getAppStoreIntent_returnsNullWhenInstallerNotResolved() { + val mockContext = mock(Context::class.java) + whenever(mockContext.packageManager).thenReturn(mock(PackageManager::class.java)) + // Un-stubbed activity resolution will return null. + + assertThat(KotlinUtils.getAppStoreIntent(mockContext, "com.installer", "com.app")).isNull() + } + + @Test + fun getMimeTypeForPermissions_onlyReadMediaImages_returnsImage() { + assertThat(KotlinUtils.getMimeTypeForPermissions(listOf(READ_MEDIA_IMAGES, "read memes"))) + .isEqualTo("image/*") + } + + @Test + fun getMimeTypeForPermissions_onlyReadMediaVideo_returnsVideo() { + assertThat(KotlinUtils.getMimeTypeForPermissions(listOf("write memes", READ_MEDIA_VIDEO))) + .isEqualTo("video/*") + } + + @Test + fun getMimeTypeForPermissions_bothReadMediaPermissions_returnsNull() { + assertThat( + KotlinUtils.getMimeTypeForPermissions(listOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))) + .isNull() + } + + @Test + fun getMimeTypeForPermissions_noReadMediaPermissions_returnsNull() { + assertThat(KotlinUtils.getMimeTypeForPermissions(listOf("amazing permission"))).isNull() + } + + @Test + fun getMimeTypeForPermissions_emptyList_returnsNull() { + assertThat(KotlinUtils.getMimeTypeForPermissions(emptyList())).isNull() + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt new file mode 100644 index 000000000..64a13df60 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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.util + +import android.Manifest +import android.app.AppOpsManager +import android.health.connect.HealthPermissions +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PermissionMappingTest { + @Test + fun testGetPlatformPermissionGroupForOp_healthPermissionGroup() { + assertThat(PermissionMapping.getPlatformPermissionGroupForOp( + AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA + )).isEqualTo(HealthPermissions.HEALTH_PERMISSION_GROUP) + } + + @Test + fun testGetPlatformPermissionGroupForOp_microphone() { + assertThat(PermissionMapping.getPlatformPermissionGroupForOp( + AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE + )).isEqualTo(Manifest.permission_group.MICROPHONE) + } + + @Test + fun testGetPlatformPermissionGroupForOp_camera() { + assertThat( + PermissionMapping.getPlatformPermissionGroupForOp(AppOpsManager.OPSTR_PHONE_CALL_CAMERA) + ).isEqualTo(Manifest.permission_group.CAMERA) + } + + @Test + fun testGetPlatformPermissionGroupForOp_readContacts() { + assertThat( + PermissionMapping.getPlatformPermissionGroupForOp(AppOpsManager.OPSTR_READ_CONTACTS) + ).isEqualTo( + PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.READ_CONTACTS) + ) + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt index 9cd6da588..15218024e 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt @@ -45,6 +45,7 @@ import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.utils.Utils import com.android.permissioncontroller.privacysources.WorkPolicyInfo import com.google.common.truth.Truth.assertThat +import org.junit.Ignore import org.junit.Test import kotlin.test.assertFailsWith @@ -78,7 +79,7 @@ class UtilsTest { fun getAbsoluteTimeString_previousDateTime_returnsDateFormatString() { val lastAccessTime = 1680739840723L val time = Utils.getAbsoluteTimeString(context, lastAccessTime) - assertThat(time).isEqualTo("Apr 5, 2023") + assertThat(time == "Apr 5, 2023" || time == "Apr 6, 2023").isTrue() } @Test @@ -107,6 +108,7 @@ class UtilsTest { } @Test + @Ignore("b/277782895") fun getEnterpriseString() { assertThat(Utils.getEnterpriseString(context, WorkPolicyInfo.WORK_POLICY_TITLE, R.string.work_policy_title)).isInstanceOf(String::class.java) @@ -184,7 +186,7 @@ class UtilsTest { @Test fun getPermissionLastAccessSummaryTimestamp_sensorDataPermission_lastAccessSummaryTimestampIsLast7Days() { val result = Utils.getPermissionLastAccessSummaryTimestamp( - System.currentTimeMillis() - 6 * 24 * 60 * 60 * 1000, context, LOCATION) + System.currentTimeMillis() - 5 * 24 * 60 * 60 * 1000, context, LOCATION) assertThat(result.first).isNotEmpty() assertThat(result.second).isEqualTo(Utils.LAST_7D_SENSOR) assertThat(result.third).isNotEmpty() diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java index 5d03fa299..5d2d1ca88 100644 --- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java +++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java @@ -597,7 +597,7 @@ public final class SafetyCenterDataFactory { } PendingIntent entryPendingIntent = safetySourceStatus.getPendingIntent(); boolean enabled = safetySourceStatus.isEnabled() && !inQuietMode; - if (entryPendingIntent == null) { + if (entryPendingIntent == null || inQuietMode) { entryPendingIntent = mPendingIntentFactory.getPendingIntent( safetySource.getId(), @@ -809,7 +809,15 @@ public final class SafetyCenterDataFactory { mSafetyCenterDataManager.getSafetySourceDataInternal(key)); boolean inQuietMode = isUserManaged && !isManagedUserRunning; if (safetySourceStatus != null) { - PendingIntent pendingIntent = safetySourceStatus.getPendingIntent(); + PendingIntent pendingIntent = + inQuietMode + ? mPendingIntentFactory.getPendingIntent( + safetySource.getId(), + safetySource.getIntentAction(), + safetySource.getPackageName(), + userId, + true) + : safetySourceStatus.getPendingIntent(); if (pendingIntent == null) { // TODO(b/222838784): Decide strategy for static entries when the intent is // null. |