| /* |
| * Copyright (C) 2021 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.permission.cts; |
| |
| import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; |
| import static android.Manifest.permission.ACCESS_FINE_LOCATION; |
| import static android.Manifest.permission.BLUETOOTH_CONNECT; |
| import static android.Manifest.permission.BLUETOOTH_SCAN; |
| import static android.permission.cts.PermissionUtils.grantPermission; |
| import static android.permission.cts.PermissionUtils.install; |
| import static android.permission.cts.PermissionUtils.revokePermission; |
| import static android.permission.cts.PermissionUtils.uninstallApp; |
| |
| import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothManager; |
| import android.bluetooth.cts.BTAdapterUtils; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.platform.test.annotations.AppModeFull; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.SdkSuppress; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.compatibility.common.util.CddTest; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| /** |
| * Tests the behavior of the |
| * {@link android.Manifest.permission_group#NEARBY_DEVICES} permission group |
| * under various permutations of grant states. |
| */ |
| @RunWith(AndroidJUnit4.class) |
| @AppModeFull |
| @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S") |
| public class NearbyDevicesPermissionTest { |
| private static final String TEST_APP_PKG = "android.permission.cts.appthatrequestpermission"; |
| private static final String TEST_APP_AUTHORITY = "appthatrequestpermission"; |
| private static final String DISAVOWAL_APP_PKG = "android.permission.cts.appneverforlocation"; |
| |
| private static final String TMP_DIR = "/data/local/tmp/cts/permissions/"; |
| private static final String APK_BLUETOOTH_30 = TMP_DIR |
| + "CtsAppThatRequestsBluetoothPermission30.apk"; |
| private static final String APK_BLUETOOTH_31 = TMP_DIR |
| + "CtsAppThatRequestsBluetoothPermission31.apk"; |
| private static final String APK_BLUETOOTH_NEVER_FOR_LOCATION_31 = TMP_DIR |
| + "CtsAppThatRequestsBluetoothPermissionNeverForLocation31.apk"; |
| private static final String APK_BLUETOOTH_NEVER_FOR_LOCATION_NO_PROVIDER = TMP_DIR |
| + "CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider.apk"; |
| |
| private enum Result { |
| UNKNOWN, EXCEPTION, EMPTY, FILTERED, FULL |
| } |
| |
| private Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); |
| private BluetoothAdapter mBluetoothAdapter; |
| private boolean mBluetoothAdapterWasEnabled; |
| |
| @Before |
| public void enableBluetooth() { |
| assumeTrue(supportsBluetooth()); |
| mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter(); |
| mBluetoothAdapterWasEnabled = mBluetoothAdapter.isEnabled(); |
| assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext)); |
| enableTestMode(); |
| } |
| |
| @After |
| public void disableBluetooth() { |
| assumeTrue(supportsBluetooth()); |
| disableTestMode(); |
| if (!mBluetoothAdapterWasEnabled) { |
| assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext)); |
| } |
| } |
| |
| @Before |
| @After |
| public void uninstallTestApp() { |
| uninstallApp(TEST_APP_PKG); |
| uninstallApp(DISAVOWAL_APP_PKG); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermission30_Default() throws Throwable { |
| install(APK_BLUETOOTH_30); |
| assertScanBluetoothResult(Result.EMPTY); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermission30_GrantLocation() throws Throwable { |
| install(APK_BLUETOOTH_30); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.FULL); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermission31_Default() throws Throwable { |
| install(APK_BLUETOOTH_31); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermission31_GrantNearby() throws Throwable { |
| install(APK_BLUETOOTH_31); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| assertScanBluetoothResult(Result.EMPTY); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermission31_GrantLocation() throws Throwable { |
| install(APK_BLUETOOTH_31); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermission31_GrantNearby_GrantLocation() throws Throwable { |
| install(APK_BLUETOOTH_31); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.FULL); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermissionNeverForLocation31_Default() throws Throwable { |
| install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermissionNeverForLocation31_GrantNearby() throws Throwable { |
| install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| assertScanBluetoothResult(Result.FILTERED); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermissionNeverForLocation31_GrantLocation() throws Throwable { |
| install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| } |
| |
| @Test |
| @CddTest(requirement="7.4.3/C-6-1") |
| public void testRequestBluetoothPermissionNeverForLocation31_GrantNearby_GrantLocation() |
| throws Throwable { |
| install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.FILTERED); |
| } |
| |
| @Test |
| public void testRequestBluetoothPermission31_OnBehalfOfDisavowingApp() throws Throwable { |
| install(APK_BLUETOOTH_31); |
| install(APK_BLUETOOTH_NEVER_FOR_LOCATION_NO_PROVIDER); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| grantPermission(DISAVOWAL_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(DISAVOWAL_APP_PKG, BLUETOOTH_SCAN); |
| assertScanBluetoothResult("PROXY", Result.FILTERED); |
| } |
| |
| /** |
| * Verify that a legacy app that was unable to interact with Bluetooth |
| * devices is still unable to interact with them after updating to a modern |
| * SDK; they'd always need to involve the user to gain permissions. |
| */ |
| @Test |
| public void testRequestBluetoothPermission_Default_Upgrade() throws Throwable { |
| install(APK_BLUETOOTH_30); |
| assertScanBluetoothResult(Result.EMPTY); |
| |
| // Upgrading to target a new SDK level means they need to explicitly |
| // request the new runtime permission; by default it's denied |
| install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| |
| // If the user does grant it, they can scan again |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| assertScanBluetoothResult(Result.FILTERED); |
| } |
| |
| /** |
| * Verify that a legacy app that was able to interact with Bluetooth devices |
| * is still able to interact with them after updating to a modern SDK. |
| */ |
| @Test |
| public void testRequestBluetoothPermission_GrantLocation_Upgrade() throws Throwable { |
| install(APK_BLUETOOTH_30); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.FULL); |
| |
| // Upgrading to target a new SDK level means they still have the access |
| // they enjoyed as a legacy app |
| install(APK_BLUETOOTH_31); |
| assertScanBluetoothResult(Result.FULL); |
| } |
| |
| /** |
| * Verify that downgrading an app doesn't gain them any access to Bluetooth |
| * scan results; they'd always need to involve the user to gain permissions. |
| */ |
| @Test |
| public void testRequestBluetoothPermission_Downgrade() throws Throwable { |
| install(APK_BLUETOOTH_31); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION); |
| grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION); |
| assertScanBluetoothResult(Result.FULL); |
| |
| // Revoking nearby permission means modern app can't scan |
| revokePermission(TEST_APP_PKG, BLUETOOTH_CONNECT); |
| revokePermission(TEST_APP_PKG, BLUETOOTH_SCAN); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| |
| // And if they attempt to downgrade, confirm that they can't obtain the |
| // split-permission grant from the older non-runtime permissions |
| install(APK_BLUETOOTH_30); |
| assertScanBluetoothResult(Result.EXCEPTION); |
| } |
| |
| private void assertScanBluetoothResult(Result expected) { |
| assertScanBluetoothResult(null, expected); |
| } |
| |
| private void assertScanBluetoothResult(String arg, Result expected) { |
| SystemClock.sleep(1000); // Wait for location permissions to propagate |
| final ContentResolver resolver = InstrumentationRegistry.getTargetContext() |
| .getContentResolver(); |
| final Bundle res = resolver.call(TEST_APP_AUTHORITY, "", arg, null); |
| Result actual = Result.values()[res.getInt(Intent.EXTRA_INDEX)]; |
| assertEquals(expected, actual); |
| } |
| |
| private boolean supportsBluetooth() { |
| return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); |
| } |
| |
| private void enableTestMode() { |
| runShellCommandOrThrow("dumpsys activity service" |
| + " com.android.bluetooth.btservice.AdapterService set-test-mode enabled"); |
| } |
| |
| private void disableTestMode() { |
| runShellCommandOrThrow("dumpsys activity service" |
| + " com.android.bluetooth.btservice.AdapterService set-test-mode disabled"); |
| } |
| } |