Loading framework/tests/util/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ java_library { srcs: [ "src/**/*.java", "src/BlockingBluetoothAdapter.kt", "src/Permissions.kt", ], sdk_version: "test_current", visibility: [ Loading framework/tests/util/src/BlockingBluetoothAdapter.kt +5 −33 Original line number Diff line number Diff line Loading @@ -17,13 +17,13 @@ package android.bluetooth.test_utils import android.Manifest.permission.BLUETOOTH_CONNECT import android.Manifest.permission.BLUETOOTH_PRIVILEGED import android.app.UiAutomation import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED import android.bluetooth.BluetoothAdapter.STATE_BLE_ON import android.bluetooth.BluetoothAdapter.STATE_OFF import android.bluetooth.BluetoothAdapter.STATE_ON import android.bluetooth.BluetoothManager import android.bluetooth.test_utils.Permissions.withPermissions import android.content.BroadcastReceiver import android.content.Context import android.content.Intent Loading Loading @@ -53,7 +53,6 @@ object BlockingBluetoothAdapter { @JvmStatic val adapter = context.getSystemService(BluetoothManager::class.java).getAdapter() private val state = AdapterStateListener(context, adapter) private val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() // BLE_START_TIMEOUT_DELAY + BREDR_START_TIMEOUT_DELAY + (10 seconds of additional delay) private val stateChangeTimeout = 18.seconds Loading @@ -69,7 +68,7 @@ object BlockingBluetoothAdapter { throw IllegalStateException("Invalid call to enableBLE while current state is: $state") } Log.d(TAG, "Call to enableBLE") if (!withPermission(BLUETOOTH_CONNECT).use { adapter.enableBLE() }) { if (!withPermissions(BLUETOOTH_CONNECT).use { adapter.enableBLE() }) { Log.e(TAG, "enableBLE: Failed") return false } Loading @@ -83,7 +82,7 @@ object BlockingBluetoothAdapter { throw IllegalStateException("Invalid call to disableBLE while current state is: $state") } Log.d(TAG, "Call to disableBLE") if (!withPermission(BLUETOOTH_CONNECT).use { adapter.disableBLE() }) { if (!withPermissions(BLUETOOTH_CONNECT).use { adapter.disableBLE() }) { Log.e(TAG, "disableBLE: Failed") return false } Loading @@ -99,7 +98,7 @@ object BlockingBluetoothAdapter { } Log.d(TAG, "Call to enable") if ( !withPermission(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { !withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { @Suppress("DEPRECATION") adapter.enable() } ) { Loading @@ -118,7 +117,7 @@ object BlockingBluetoothAdapter { } Log.d(TAG, "Call to disable($persist)") if ( !withPermission(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { !withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { adapter.disable(persist) } ) { Loading @@ -129,33 +128,6 @@ object BlockingBluetoothAdapter { state.wasDisabled = true return state.waitForStateWithTimeout(stateChangeTimeout, STATE_OFF) } private fun restorePermissions(permissions: Set<String>) { if (UiAutomation.ALL_PERMISSIONS.equals(permissions)) { uiAutomation.adoptShellPermissionIdentity() } else { uiAutomation.adoptShellPermissionIdentity(*permissions.map { it }.toTypedArray()) } } private fun replacePermissionsWith(vararg newPermissions: String): Set<String> { val currentPermissions = uiAutomation.getAdoptedShellPermissions() if (newPermissions.size == 0) { // Throw even if the code support it as we are not expecting this by design throw IllegalArgumentException("Invalid permissions replacement with no permissions.") } uiAutomation.adoptShellPermissionIdentity(*newPermissions) return currentPermissions } // Set permissions to be used as long as the resource is open. // Restore initial permissions after closing resource. private fun withPermission( vararg newPermissions: String, ): AutoCloseable { val savedPermissions = replacePermissionsWith(*newPermissions) return AutoCloseable { restorePermissions(savedPermissions) } } } private class AdapterStateListener(context: Context, private val adapter: BluetoothAdapter) { Loading framework/tests/util/src/Permissions.kt 0 → 100644 +83 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.bluetooth.test_utils import android.app.UiAutomation import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import org.junit.Assert.assertThrows private const val TAG: String = "Permissions" object Permissions { private val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() public interface PermissionContext : AutoCloseable { // Override AutoCloseable method to silent the requirement on Exception override fun close() } /** * Set permissions to be used as long as the resource is open. Restore initial permissions after * closing resource. * * @param newPermissions Permissions to hold when using resource. You need to specify at least 1 */ @JvmStatic fun withPermissions(vararg newPermissions: String): PermissionContext { val savedPermissions = replacePermissionsWith(*newPermissions) return object : PermissionContext { override fun close() { restorePermissions(savedPermissions) } } } @JvmStatic fun enforceEachPermissions(action: () -> Any, newPermissions: List<String>) { if (newPermissions.size < 2) { throw IllegalArgumentException("Not supported for less than 2 permissions") } newPermissions.forEach { val permissionsSet = newPermissions.toMutableSet() permissionsSet.remove(it) withPermissions(*arrayOf(*permissionsSet.toTypedArray())).use { assertThrows(SecurityException::class.java, { action() }) } } } private fun restorePermissions(permissions: Set<String>) { if (UiAutomation.ALL_PERMISSIONS.equals(permissions)) { uiAutomation.adoptShellPermissionIdentity() } else { uiAutomation.adoptShellPermissionIdentity(*permissions.map { it }.toTypedArray()) } Log.d(TAG, "Restored ${permissions}") } private fun replacePermissionsWith(vararg newPermissions: String): Set<String> { val currentPermissions = uiAutomation.getAdoptedShellPermissions() if (newPermissions.size == 0) { // Throw even if the code support it as we are not expecting this by design throw IllegalArgumentException("Invalid permissions replacement with no permissions.") } uiAutomation.adoptShellPermissionIdentity(*newPermissions) Log.d(TAG, "Replaced ${currentPermissions} with ${newPermissions.toSet()}") return currentPermissions } } Loading
framework/tests/util/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ java_library { srcs: [ "src/**/*.java", "src/BlockingBluetoothAdapter.kt", "src/Permissions.kt", ], sdk_version: "test_current", visibility: [ Loading
framework/tests/util/src/BlockingBluetoothAdapter.kt +5 −33 Original line number Diff line number Diff line Loading @@ -17,13 +17,13 @@ package android.bluetooth.test_utils import android.Manifest.permission.BLUETOOTH_CONNECT import android.Manifest.permission.BLUETOOTH_PRIVILEGED import android.app.UiAutomation import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED import android.bluetooth.BluetoothAdapter.STATE_BLE_ON import android.bluetooth.BluetoothAdapter.STATE_OFF import android.bluetooth.BluetoothAdapter.STATE_ON import android.bluetooth.BluetoothManager import android.bluetooth.test_utils.Permissions.withPermissions import android.content.BroadcastReceiver import android.content.Context import android.content.Intent Loading Loading @@ -53,7 +53,6 @@ object BlockingBluetoothAdapter { @JvmStatic val adapter = context.getSystemService(BluetoothManager::class.java).getAdapter() private val state = AdapterStateListener(context, adapter) private val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() // BLE_START_TIMEOUT_DELAY + BREDR_START_TIMEOUT_DELAY + (10 seconds of additional delay) private val stateChangeTimeout = 18.seconds Loading @@ -69,7 +68,7 @@ object BlockingBluetoothAdapter { throw IllegalStateException("Invalid call to enableBLE while current state is: $state") } Log.d(TAG, "Call to enableBLE") if (!withPermission(BLUETOOTH_CONNECT).use { adapter.enableBLE() }) { if (!withPermissions(BLUETOOTH_CONNECT).use { adapter.enableBLE() }) { Log.e(TAG, "enableBLE: Failed") return false } Loading @@ -83,7 +82,7 @@ object BlockingBluetoothAdapter { throw IllegalStateException("Invalid call to disableBLE while current state is: $state") } Log.d(TAG, "Call to disableBLE") if (!withPermission(BLUETOOTH_CONNECT).use { adapter.disableBLE() }) { if (!withPermissions(BLUETOOTH_CONNECT).use { adapter.disableBLE() }) { Log.e(TAG, "disableBLE: Failed") return false } Loading @@ -99,7 +98,7 @@ object BlockingBluetoothAdapter { } Log.d(TAG, "Call to enable") if ( !withPermission(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { !withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { @Suppress("DEPRECATION") adapter.enable() } ) { Loading @@ -118,7 +117,7 @@ object BlockingBluetoothAdapter { } Log.d(TAG, "Call to disable($persist)") if ( !withPermission(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { !withPermissions(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use { adapter.disable(persist) } ) { Loading @@ -129,33 +128,6 @@ object BlockingBluetoothAdapter { state.wasDisabled = true return state.waitForStateWithTimeout(stateChangeTimeout, STATE_OFF) } private fun restorePermissions(permissions: Set<String>) { if (UiAutomation.ALL_PERMISSIONS.equals(permissions)) { uiAutomation.adoptShellPermissionIdentity() } else { uiAutomation.adoptShellPermissionIdentity(*permissions.map { it }.toTypedArray()) } } private fun replacePermissionsWith(vararg newPermissions: String): Set<String> { val currentPermissions = uiAutomation.getAdoptedShellPermissions() if (newPermissions.size == 0) { // Throw even if the code support it as we are not expecting this by design throw IllegalArgumentException("Invalid permissions replacement with no permissions.") } uiAutomation.adoptShellPermissionIdentity(*newPermissions) return currentPermissions } // Set permissions to be used as long as the resource is open. // Restore initial permissions after closing resource. private fun withPermission( vararg newPermissions: String, ): AutoCloseable { val savedPermissions = replacePermissionsWith(*newPermissions) return AutoCloseable { restorePermissions(savedPermissions) } } } private class AdapterStateListener(context: Context, private val adapter: BluetoothAdapter) { Loading
framework/tests/util/src/Permissions.kt 0 → 100644 +83 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.bluetooth.test_utils import android.app.UiAutomation import android.util.Log import androidx.test.platform.app.InstrumentationRegistry import org.junit.Assert.assertThrows private const val TAG: String = "Permissions" object Permissions { private val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() public interface PermissionContext : AutoCloseable { // Override AutoCloseable method to silent the requirement on Exception override fun close() } /** * Set permissions to be used as long as the resource is open. Restore initial permissions after * closing resource. * * @param newPermissions Permissions to hold when using resource. You need to specify at least 1 */ @JvmStatic fun withPermissions(vararg newPermissions: String): PermissionContext { val savedPermissions = replacePermissionsWith(*newPermissions) return object : PermissionContext { override fun close() { restorePermissions(savedPermissions) } } } @JvmStatic fun enforceEachPermissions(action: () -> Any, newPermissions: List<String>) { if (newPermissions.size < 2) { throw IllegalArgumentException("Not supported for less than 2 permissions") } newPermissions.forEach { val permissionsSet = newPermissions.toMutableSet() permissionsSet.remove(it) withPermissions(*arrayOf(*permissionsSet.toTypedArray())).use { assertThrows(SecurityException::class.java, { action() }) } } } private fun restorePermissions(permissions: Set<String>) { if (UiAutomation.ALL_PERMISSIONS.equals(permissions)) { uiAutomation.adoptShellPermissionIdentity() } else { uiAutomation.adoptShellPermissionIdentity(*permissions.map { it }.toTypedArray()) } Log.d(TAG, "Restored ${permissions}") } private fun replacePermissionsWith(vararg newPermissions: String): Set<String> { val currentPermissions = uiAutomation.getAdoptedShellPermissions() if (newPermissions.size == 0) { // Throw even if the code support it as we are not expecting this by design throw IllegalArgumentException("Invalid permissions replacement with no permissions.") } uiAutomation.adoptShellPermissionIdentity(*newPermissions) Log.d(TAG, "Replaced ${currentPermissions} with ${newPermissions.toSet()}") return currentPermissions } }