From c712a571dc67105f27239faa6f03d2aee3836162 Mon Sep 17 00:00:00 2001 From: Yahav Nussbaum Date: Mon, 29 Jan 2024 12:23:28 +0000 Subject: [PATCH] Introduce Powered Off Finding API Bug: 307898240 Test: atest CtsNearbyFastPairTestCases Test: atest NearbyIntegrationPrivilegedTests Test: atest NearbyIntegrationUntrustedTests Change-Id: I57b37ea796c0791d72fec931f731a0143e816dac --- framework-t/Android.bp | 3 + framework-t/api/system-current.txt | 6 + nearby/framework/Android.bp | 1 + .../java/android/nearby/NearbyManager.java | 168 ++++++++++++++++++ nearby/tests/cts/fastpair/Android.bp | 1 + .../android/nearby/cts/NearbyManagerTest.java | 99 +++++++++++ .../privileged/NearbyManagerTest.kt | 46 +++++ .../untrusted/NearbyManagerTest.kt | 44 ++++- 8 files changed, 366 insertions(+), 2 deletions(-) diff --git a/framework-t/Android.bp b/framework-t/Android.bp index c31dcf5d9c..beba1e8e29 100644 --- a/framework-t/Android.bp +++ b/framework-t/Android.bp @@ -34,6 +34,7 @@ java_defaults { name: "enable-framework-connectivity-t-targets", enabled: true, } + // The above defaults can be used to disable framework-connectivity t // targets while minimizing merge conflicts in the build rules. @@ -116,6 +117,7 @@ java_library { "framework-bluetooth", "framework-wifi", "framework-connectivity-pre-jarjar", + "framework-location.stubs.module_lib", ], visibility: ["//packages/modules/Connectivity:__subpackages__"], } @@ -138,6 +140,7 @@ java_sdk_library { "sdk_module-lib_current_framework-connectivity", ], libs: [ + "framework-location.stubs.module_lib", "sdk_module-lib_current_framework-connectivity", ], permitted_packages: [ diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt index 3513573839..d0e3879e4c 100644 --- a/framework-t/api/system-current.txt +++ b/framework-t/api/system-current.txt @@ -59,11 +59,17 @@ package android.nearby { } public class NearbyManager { + method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getPoweredOffFindingMode(); method public void queryOffloadCapability(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingEphemeralIds(@NonNull java.util.List); + method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingMode(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopScan(@NonNull android.nearby.ScanCallback); + field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1; // 0x1 + field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2; // 0x2 + field @FlaggedApi("com.android.nearby.flags.powered_off_finding") public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0; // 0x0 } public final class OffloadCapability implements android.os.Parcelable { diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp index 4bb9efdb81..b6e5a551e9 100644 --- a/nearby/framework/Android.bp +++ b/nearby/framework/Android.bp @@ -49,6 +49,7 @@ java_library { "androidx.annotation_annotation", "framework-annotations-lib", "framework-bluetooth", + "framework-location.stubs.module_lib", ], static_libs: [ "modules-utils-preconditions", diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java index 00f1c38923..cae653d889 100644 --- a/nearby/framework/java/android/nearby/NearbyManager.java +++ b/nearby/framework/java/android/nearby/NearbyManager.java @@ -18,6 +18,7 @@ package android.nearby; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -25,9 +26,12 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.bluetooth.BluetoothManager; import android.content.Context; +import android.location.LocationManager; import android.nearby.aidl.IOffloadCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; @@ -37,6 +41,7 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; +import java.util.List; import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.Executor; @@ -75,8 +80,51 @@ public class NearbyManager { int ERROR = 2; } + /** + * Return value of {@link #getPoweredOffFindingMode()} when this powered off finding is not + * supported the device. + */ + @FlaggedApi("com.android.nearby.flags.powered_off_finding") + public static final int POWERED_OFF_FINDING_MODE_UNSUPPORTED = 0; + + /** + * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link + * #setPoweredOffFindingMode(int)} when powered off finding is supported but disabled. The + * device will not start to advertise when powered off. + */ + @FlaggedApi("com.android.nearby.flags.powered_off_finding") + public static final int POWERED_OFF_FINDING_MODE_DISABLED = 1; + + /** + * Return value of {@link #getPoweredOffFindingMode()} and argument of {@link + * #setPoweredOffFindingMode(int)} when powered off finding is enabled. The device will start to + * advertise when powered off. + */ + @FlaggedApi("com.android.nearby.flags.powered_off_finding") + public static final int POWERED_OFF_FINDING_MODE_ENABLED = 2; + + /** + * Powered off finding modes. + * + * @hide + */ + @IntDef( + prefix = {"POWERED_OFF_FINDING_MODE"}, + value = { + POWERED_OFF_FINDING_MODE_UNSUPPORTED, + POWERED_OFF_FINDING_MODE_DISABLED, + POWERED_OFF_FINDING_MODE_ENABLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PoweredOffFindingMode {} + private static final String TAG = "NearbyManager"; + private static final int POWERED_OFF_FINDING_EID_LENGTH = 20; + + private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY = + "ro.bluetooth.finder.supported"; + /** * TODO(b/286137024): Remove this when CTS R5 is rolled out. * Whether allows Fast Pair to scan. @@ -456,4 +504,124 @@ public class NearbyManager { "successfully %s Fast Pair scan", enable ? "enables" : "disables")); } + /** + * Sets the precomputed EIDs for advertising when the phone is powered off. The Bluetooth + * controller will store these EIDs in its memory, and will start advertising them in Find My + * Device network EID frames when powered off, only if the powered off finding mode was + * previously enabled by calling {@link #setPoweredOffFindingMode(int)}. + * + *

The EIDs are cryptographic ephemeral identifiers that change periodically, based on the + * Android clock at the time of the shutdown. They are used as the public part of asymmetric key + * pairs. Members of the Find My Device network can use them to encrypt the location of where + * they sight the advertising device. Only someone in possession of the private key (the device + * owner or someone that the device owner shared the key with) can decrypt this encrypted + * location. + * + *

Android will typically call this method during the shutdown process. Even after the + * method was called, it is still possible to call {#link setPoweredOffFindingMode() to disable + * the advertisement, for example to temporarily disable it for a single shutdown. + * + *

If called more than once, the EIDs of the most recent call overrides the EIDs from any + * previous call. + * + * @throws IllegalArgumentException if the length of one of the EIDs is not 20 bytes + */ + @FlaggedApi("com.android.nearby.flags.powered_off_finding") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void setPoweredOffFindingEphemeralIds(@NonNull List eids) { + Objects.requireNonNull(eids); + if (!isPoweredOffFindingSupported()) { + throw new UnsupportedOperationException( + "Powered off finding is not supported on this device"); + } + List ephemeralIdList = eids.stream().map( + eid -> { + Preconditions.checkArgument(eid.length == POWERED_OFF_FINDING_EID_LENGTH); + PoweredOffFindingEphemeralId ephemeralId = new PoweredOffFindingEphemeralId(); + ephemeralId.bytes = eid; + return ephemeralId; + }).toList(); + try { + mService.setPoweredOffFindingEphemeralIds(ephemeralIdList); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** + * Turns the powered off finding on or off. Power off finding will operate only if this method + * was called at least once since boot, and the value of the argument {@code + * poweredOffFindinMode} was {@link #POWERED_OFF_FINDING_MODE_ENABLED} the last time the method + * was called. + * + *

When an Android device with the powered off finding feature is turned off (either as part + * of a normal shutdown or due to dead battery), its Bluetooth chip starts to advertise Find My + * Device network EID frames with the EID payload that were provided by the last call to {@link + * #setPoweredOffFindingEphemeralIds(List)}. These EIDs can be sighted by other Android devices + * in BLE range that are part of the Find My Device network. The Android sighters use the EID to + * encrypt the location of the Android device and upload it to the server, in a way that only + * the owner of the advertising device, or people that the owner shared their encryption key + * with, can decrypt the location. + * + * @param poweredOffFindingMode {@link #POWERED_OFF_FINDING_MODE_ENABLED} or {@link + * #POWERED_OFF_FINDING_MODE_DISABLED} + * + * @throws IllegalStateException if called with {@link #POWERED_OFF_FINDING_MODE_ENABLED} when + * Bluetooth or location services are disabled + */ + @FlaggedApi("com.android.nearby.flags.powered_off_finding") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void setPoweredOffFindingMode(@PoweredOffFindingMode int poweredOffFindingMode) { + Preconditions.checkArgument( + poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED + || poweredOffFindingMode == POWERED_OFF_FINDING_MODE_DISABLED, + "invalid poweredOffFindingMode"); + if (!isPoweredOffFindingSupported()) { + throw new UnsupportedOperationException( + "Powered off finding is not supported on this device"); + } + if (poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED) { + Preconditions.checkState(areLocationAndBluetoothEnabled(), + "Location services and Bluetooth must be on"); + } + try { + mService.setPoweredOffModeEnabled( + poweredOffFindingMode == POWERED_OFF_FINDING_MODE_ENABLED); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the state of the powered off finding feature. + * + *

{@link #POWERED_OFF_FINDING_MODE_UNSUPPORTED} if the feature is not supported by the + * device, {@link #POWERED_OFF_FINDING_MODE_DISABLED} if this was the last value set by {@link + * #setPoweredOffFindingMode(int)} or if no value was set since boot, {@link + * #POWERED_OFF_FINDING_MODE_ENABLED} if this was the last value set by {@link + * #setPoweredOffFindingMode(int)} + */ + @FlaggedApi("com.android.nearby.flags.powered_off_finding") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public @PoweredOffFindingMode int getPoweredOffFindingMode() { + if (!isPoweredOffFindingSupported()) { + return POWERED_OFF_FINDING_MODE_UNSUPPORTED; + } + try { + return mService.getPoweredOffModeEnabled() + ? POWERED_OFF_FINDING_MODE_ENABLED : POWERED_OFF_FINDING_MODE_DISABLED; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private boolean isPoweredOffFindingSupported() { + return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY)); + } + + private boolean areLocationAndBluetoothEnabled() { + return mContext.getSystemService(BluetoothManager.class).getAdapter().isEnabled() + && mContext.getSystemService(LocationManager.class).isLocationEnabled(); + } } diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp index 4309d7ed25..560c4df72a 100644 --- a/nearby/tests/cts/fastpair/Android.bp +++ b/nearby/tests/cts/fastpair/Android.bp @@ -33,6 +33,7 @@ android_test { "framework-bluetooth.stubs.module_lib", "framework-configinfrastructure", "framework-connectivity-t.impl", + "framework-location.stubs.module_lib", ], srcs: ["src/**/*.java"], test_suites: [ diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java index bc9691da33..832ac038bf 100644 --- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java +++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java @@ -25,12 +25,14 @@ import static android.nearby.ScanCallback.ERROR_UNSUPPORTED; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; import android.app.UiAutomation; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.bluetooth.cts.BTAdapterUtils; import android.content.Context; +import android.location.LocationManager; import android.nearby.BroadcastCallback; import android.nearby.BroadcastRequest; import android.nearby.NearbyDevice; @@ -42,6 +44,8 @@ import android.nearby.PrivateCredential; import android.nearby.ScanCallback; import android.nearby.ScanRequest; import android.os.Build; +import android.os.Process; +import android.os.UserHandle; import android.provider.DeviceConfig; import androidx.annotation.NonNull; @@ -50,6 +54,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SdkSuppress; +import com.android.compatibility.common.util.SystemUtil; import com.android.modules.utils.build.SdkLevel; import org.junit.Before; @@ -57,6 +62,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -189,6 +195,92 @@ public class NearbyManagerTest { mScanCallback.onError(ERROR_UNSUPPORTED); } + @Test + public void testsetPoweredOffFindingEphemeralIds() { + // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used. + assumeTrue(SdkLevel.isAtLeastV()); + // Only test supporting devices. + if (mNearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return; + + mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20], new byte[20])); + } + + @Test + public void testsetPoweredOffFindingEphemeralIds_noPrivilegedPermission() { + // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used. + assumeTrue(SdkLevel.isAtLeastV()); + // Only test supporting devices. + if (mNearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return; + + mUiAutomation.dropShellPermissionIdentity(); + + assertThrows(SecurityException.class, + () -> mNearbyManager.setPoweredOffFindingEphemeralIds(List.of(new byte[20]))); + } + + + @Test + public void testSetAndGetPoweredOffFindingMode_enabled() { + // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used. + assumeTrue(SdkLevel.isAtLeastV()); + // Only test supporting devices. + if (mNearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return; + + enableLocation(); + // enableLocation() has dropped shell permission identity. + mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED); + + mNearbyManager.setPoweredOffFindingMode( + NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED); + assertThat(mNearbyManager.getPoweredOffFindingMode()) + .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED); + } + + @Test + public void testSetAndGetPoweredOffFindingMode_disabled() { + // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used. + assumeTrue(SdkLevel.isAtLeastV()); + // Only test supporting devices. + if (mNearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return; + + mNearbyManager.setPoweredOffFindingMode( + NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED); + assertThat(mNearbyManager.getPoweredOffFindingMode()) + .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED); + } + + @Test + public void testSetPoweredOffFindingMode_noPrivilegedPermission() { + // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used. + assumeTrue(SdkLevel.isAtLeastV()); + // Only test supporting devices. + if (mNearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return; + + enableLocation(); + mUiAutomation.dropShellPermissionIdentity(); + + assertThrows(SecurityException.class, () -> mNearbyManager + .setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED)); + } + + @Test + public void testGetPoweredOffFindingMode_noPrivilegedPermission() { + // Replace with minSdkVersion when Build.VERSION_CODES.VANILLA_ICE_CREAM can be used. + assumeTrue(SdkLevel.isAtLeastV()); + // Only test supporting devices. + if (mNearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return; + + mUiAutomation.dropShellPermissionIdentity(); + + assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode()); + } + private void enableBluetooth() { BluetoothManager manager = mContext.getSystemService(BluetoothManager.class); BluetoothAdapter bluetoothAdapter = manager.getAdapter(); @@ -197,6 +289,13 @@ public class NearbyManagerTest { } } + private void enableLocation() { + LocationManager locationManager = mContext.getSystemService(LocationManager.class); + UserHandle user = Process.myUserHandle(); + SystemUtil.runWithShellPermissionIdentity( + mUiAutomation, () -> locationManager.setLocationEnabledForUser(true, user)); + } + private static class OffloadCallback implements Consumer { @Override public void accept(OffloadCapability aBoolean) { diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt index 506b4e2e9f..b949720c3d 100644 --- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt +++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt @@ -29,6 +29,7 @@ import android.nearby.ScanRequest import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -96,4 +97,49 @@ class NearbyManagerTest { ) nearbyManager.stopBroadcast(broadcastCallback) } + + /** Verify privileged app can set powered off finding ephemeral IDs without exception. */ + @Test + fun testNearbyManagerSetPoweredOffFindingEphemeralIds_fromPrivilegedApp_succeed() { + val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager + // Only test supporting devices. + if (nearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return + + val eid = ByteArray(20) + + nearbyManager.setPoweredOffFindingEphemeralIds(listOf(eid)) + } + + /** + * Verifies that [NearbyManager.setPoweredOffFindingEphemeralIds] checkes the ephemeral ID + * length. + */ + @Test + fun testNearbyManagerSetPoweredOffFindingEphemeralIds_wrongSize_throwsException() { + val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager + // Only test supporting devices. + if (nearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return + + assertThrows(IllegalArgumentException::class.java) { + nearbyManager.setPoweredOffFindingEphemeralIds(listOf(ByteArray(21))) + } + assertThrows(IllegalArgumentException::class.java) { + nearbyManager.setPoweredOffFindingEphemeralIds(listOf(ByteArray(19))) + } + } + + /** Verify privileged app can set and get powered off finding mode without exception. */ + @Test + fun testNearbyManagerSetGetPoweredOffMode_fromPrivilegedApp_succeed() { + val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager + // Only test supporting devices. + if (nearbyManager.getPoweredOffFindingMode() + == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) return + + nearbyManager.setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED) + assertThat(nearbyManager.getPoweredOffFindingMode()) + .isEqualTo(NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED) + } } diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt index 7bf9f63dfa..015d022494 100644 --- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt +++ b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt @@ -30,12 +30,12 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.LogcatWaitMixin import com.google.common.truth.Truth.assertThat +import java.time.Duration +import java.util.Calendar import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import java.time.Duration -import java.util.Calendar @RunWith(AndroidJUnit4::class) class NearbyManagerTest { @@ -151,6 +151,46 @@ class NearbyManagerTest { ).isTrue() } + /** + * Verify untrusted app can't set powered off finding ephemeral IDs because it needs + * BLUETOOTH_PRIVILEGED permission which is not for use by third-party applications. + */ + @Test + fun testNearbyManagerSetPoweredOffFindingEphemeralIds_fromUnTrustedApp_throwsException() { + val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager + val eid = ByteArray(20) + + assertThrows(SecurityException::class.java) { + nearbyManager.setPoweredOffFindingEphemeralIds(listOf(eid)) + } + } + + /** + * Verify untrusted app can't set powered off finding mode because it needs BLUETOOTH_PRIVILEGED + * permission which is not for use by third-party applications. + */ + @Test + fun testNearbyManagerSetPoweredOffFindingMode_fromUnTrustedApp_throwsException() { + val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager + + assertThrows(SecurityException::class.java) { + nearbyManager.setPoweredOffFindingMode(NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED) + } + } + + /** + * Verify untrusted app can't get powered off finding mode because it needs BLUETOOTH_PRIVILEGED + * permission which is not for use by third-party applications. + */ + @Test + fun testNearbyManagerGetPoweredOffFindingMode_fromUnTrustedApp_throwsException() { + val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager + + assertThrows(SecurityException::class.java) { + nearbyManager.getPoweredOffFindingMode() + } + } + companion object { private val WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT = Duration.ofSeconds(5) }