diff options
Diffstat (limited to 'tests')
4 files changed, 207 insertions, 41 deletions
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt index f52e32344..9ec09137e 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt @@ -36,6 +36,7 @@ import android.os.Build import android.os.Process import android.provider.DeviceConfig import android.provider.Settings +import android.server.wm.WindowManagerStateHelper import android.text.Spanned import android.text.style.ClickableSpan import android.util.Log @@ -63,6 +64,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before +import java.util.concurrent.TimeoutException abstract class BaseUsePermissionTest : BasePermissionTest() { companion object { @@ -247,6 +249,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { DENIED_WITH_PREJUDICE } + private val windowManagerStateHelper = WindowManagerStateHelper() + private val platformResources = context.createPackageContext("android", 0).resources private val permissionToLabelResNameMap = mapOf( @@ -674,21 +678,20 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { crossinline block: () -> Unit, ): Instrumentation.ActivityResult { // Request the permissions - lateinit var future: CompletableFuture<Instrumentation.ActivityResult> - doAndWaitForWindowTransition { - future = - startActivityForFuture( - Intent().apply { - component = - ComponentName( - APP_PACKAGE_NAME, - "$APP_PACKAGE_NAME.RequestPermissionsActivity" - ) - putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions) - putExtra("$APP_PACKAGE_NAME.ASK_TWICE", askTwice) - } - ) - } + val future = + startActivityForFuture( + Intent().apply { + component = + ComponentName( + APP_PACKAGE_NAME, + "$APP_PACKAGE_NAME.RequestPermissionsActivity" + ) + putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions) + putExtra("$APP_PACKAGE_NAME.ASK_TWICE", askTwice) + } + ) + + waitForPermissionRequestActivity() // Notification permission prompt is shown first, so get it out of the way clickNotificationPermissionRequestAllowButtonIfAvailable() @@ -698,7 +701,33 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } else { block() } - return future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + try { + return future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + } catch (e: TimeoutException) { + val uiDump = StringBuilder() + UiDumpUtils.dumpNodes(uiDump) + Log.e(LOG_TAG, "Timed out waiting for activity result, UI dump: $uiDump") + throw e + } + } + + /** + * This method waits for permission controller activity to be in a valid state, the timeout + * is 5 seconds. + */ + fun waitForPermissionRequestActivity() { + val requestPermissionIntent = Intent(PackageManager.ACTION_REQUEST_PERMISSIONS) + val componentName = + requestPermissionIntent.resolveActivity(context.packageManager) + ?: throw RuntimeException("Permission request is not handled by any activity.") + try { + windowManagerStateHelper.waitForValidState(componentName) + } catch (ex: Exception) { + // It doesn't mean a test would fail, it just meant that the test would proceed before + // waiting for permission request dialog. Permission request dialog should eventually + // come on the screen when ui-automator is trying to search for ui element. + Log.w(LOG_TAG, "Couldn't wait for permission request activity.", ex) + } } protected inline fun requestAppPermissionsAndAssertResult( diff --git a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt index 798b1942f..e1cb76171 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt @@ -123,7 +123,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase { .toString() private val cameraLabel = originalCameraLabel.lowercase() private val micLabel = originalMicLabel.lowercase() - private var wasEnabled = false private var isScreenOn = false private var screenTimeoutBeforeTest: Long = 0L private lateinit var carMicPrivacyChipId: String @@ -181,7 +180,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase { isScreenOn = true } uiDevice.findObject(By.text("Close"))?.click() - wasEnabled = setIndicatorsEnabledStateIfNeeded(true) // If the change Id is not present, then isChangeEnabled will return true. To bypass this, // the change is set to "false" if present. assumeFalse( @@ -193,23 +191,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase { install() } - private fun setIndicatorsEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean { - var currentlyEnabled = false - runWithShellPermissionIdentity { - currentlyEnabled = - DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG, true) - if (currentlyEnabled != shouldBeEnabled) { - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_PRIVACY, - INDICATORS_FLAG, - shouldBeEnabled.toString(), - false, - ) - } - } - return currentlyEnabled - } - @After fun tearDown() { // Skip the tests as Camera and Mic are not supported for visible background users. @@ -225,9 +206,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase { { assertIndicatorsShown(false, false, false) }, AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS, ) - if (!wasEnabled) { - setIndicatorsEnabledStateIfNeeded(false) - } runWithShellPermissionIdentity { Settings.System.putLong( context.contentResolver, diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt b/tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt new file mode 100644 index 000000000..59a8c21b2 --- /dev/null +++ b/tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt @@ -0,0 +1,148 @@ +/* + * 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 android.app.role.cts + +import android.app.role.RoleManager +import android.content.Context +import android.os.Build +import android.os.Process +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress +import androidx.test.platform.app.InstrumentationRegistry +import com.android.compatibility.common.util.SystemUtil +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executor +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import org.junit.After +import org.junit.Assert.fail +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests {@link RoleManager} security fixes. */ +@RunWith(AndroidJUnit4::class) +class RoleManagerSecurityTest { + private var browserRoleHolder: String? = null + + @Before + fun setUp() { + saveBrowserRoleHolder() + } + + @After + fun tearDown() { + restoreBrowserRoleHolder() + } + + private fun saveBrowserRoleHolder() { + val roleHolders: List<String> = getRoleHolders(RoleManager.ROLE_BROWSER, roleManager) + browserRoleHolder = if (roleHolders.isNotEmpty()) roleHolders[0] else null + } + + private fun restoreBrowserRoleHolder() { + browserRoleHolder?.let { packageName -> + addRoleHolderAsUser( + RoleManager.ROLE_BROWSER, + packageName, + Process.myUserHandle(), + true, + roleManager, + context.mainExecutor, + ) + } + } + + private fun getRoleHolders(roleName: String, roleManager: RoleManager): List<String> { + return SystemUtil.callWithShellPermissionIdentity { roleManager.getRoleHolders(roleName) } + } + + private fun addRoleHolderAsUser( + roleName: String, + packageName: String, + userHandle: UserHandle, + expectSuccess: Boolean, + roleManager: RoleManager, + executor: Executor, + ) { + val future = CallbackFuture() + SystemUtil.runWithShellPermissionIdentity { + roleManager.addRoleHolderAsUser(roleName, packageName, 0, userHandle, executor, future) + } + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess) + } + + @SdkSuppress( + minSdkVersion = Build.VERSION_CODES.S, + maxSdkVersion = Build.VERSION_CODES.TIRAMISU, + ) + @Test + fun cannotGetDefaultApplicationOnOlderSdk() { + assumeTrue(roleManager.isRoleAvailable(RoleManager.ROLE_BROWSER)) + try { + roleManager.getDefaultApplication(RoleManager.ROLE_BROWSER) + } catch (e: NoSuchMethodError) { + // Expected when permission module hasn't been updated + } catch (e: IllegalStateException) { + // Expected when permission module has been updated, and SDK 33 or below + } catch (e: Throwable) { + fail("Missing patch for cveBugId = [379362792]") + } + } + + @SdkSuppress( + minSdkVersion = Build.VERSION_CODES.S, + maxSdkVersion = Build.VERSION_CODES.TIRAMISU, + ) + @Test + fun cannotSetDefaultApplicationOnOlderSdk() { + assumeTrue(roleManager.isRoleAvailable(RoleManager.ROLE_BROWSER)) + val future = CallbackFuture() + try { + roleManager.setDefaultApplication( + RoleManager.ROLE_BROWSER, + APP_PACKAGE_NAME, + 0, + context.mainExecutor, + future, + ) + future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) + } catch (e: NoSuchMethodError) { + // Expected when permission module hasn't been updated + } catch (e: IllegalStateException) { + // Expected when permission module has been updated, and SDK 33 or below + } catch (e: Throwable) { + fail("Missing patch for cveBugId = [379362792]") + } + } + + private class CallbackFuture : CompletableFuture<Boolean?>(), Consumer<Boolean?> { + override fun accept(successful: Boolean?) { + complete(successful) + } + } + + companion object { + private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong() + private const val APP_PACKAGE_NAME: String = "android.app.role.cts.app" + + private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java) + } +} diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java index 5e61c66be..3bc3ff12b 100644 --- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java +++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java @@ -105,7 +105,6 @@ public class RoleManagerTest { private static final String ROLE_NAME = RoleManager.ROLE_BROWSER; private static final String ROLE_PHONE_NAME = RoleManager.ROLE_DIALER; - private static final String ROLE_SMS_NAME = RoleManager.ROLE_SMS; private static final String PROFILE_GROUP_EXCLUSIVE_ROLE_NAME = RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY; private static final String ROLE_SHORT_LABEL = "Browser app"; @@ -288,8 +287,11 @@ public class RoleManagerTest { @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO") public void requestRoleThenBlockRequestRoleDialogByRestrictedSettingDialog() throws Exception { - assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision); + assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); + assumeFalse(callWithShellPermissionIdentity( + () -> getRoleHolders(RoleManager.ROLE_SMS)).contains(APP_PACKAGE_NAME)); + // TODO: b/388960315 - Remove wait after addressing race condition runWithShellPermissionIdentity( () -> waitForEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME, @@ -298,7 +300,7 @@ public class RoleManagerTest { () -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME, AppOpsManager.MODE_ERRORED)); - requestRole(ROLE_SMS_NAME); + requestRole(RoleManager.ROLE_SMS); waitFindObject(ENHANCED_CONFIRMATION_DIALOG_SELECTOR, TIMEOUT_MILLIS); pressBack(); @@ -829,6 +831,13 @@ public class RoleManagerTest { @FlakyTest @Test public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception { + assumeFalse(callWithShellPermissionIdentity( + () -> getRoleHolders(RoleManager.ROLE_BROWSER)).contains(APP_PACKAGE_NAME)); + assumeFalse(callWithShellPermissionIdentity( + () -> getRoleHolders(RoleManager.ROLE_DIALER)).contains(APP_PACKAGE_NAME)); + assumeFalse(callWithShellPermissionIdentity( + () -> getRoleHolders(RoleManager.ROLE_SMS)).contains(APP_PACKAGE_NAME)); + sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) .addCategory(Intent.CATEGORY_DEFAULT) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)); @@ -1193,6 +1202,8 @@ public class RoleManagerTest { @Test public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception { assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); + assumeFalse(callWithShellPermissionIdentity( + () -> getRoleHolders(RoleManager.ROLE_SMS)).contains(APP_PACKAGE_NAME)); String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0); addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME); |