Merge changes I57b37ea7,I00772535 into main

* changes:
  Introduce Powered Off Finding API
  Add Powered Off Finding operations to NearbyService
This commit is contained in:
Yahav Nussbaum 2024-02-13 14:34:22 +00:00 • committed by Gerrit Code Review
commit c412fd3987
12 changed files with 470 additions and 3 deletions

View file

@ -118,6 +118,7 @@ java_library {
"framework-bluetooth", "framework-bluetooth",
"framework-wifi", "framework-wifi",
"framework-connectivity-pre-jarjar", "framework-connectivity-pre-jarjar",
"framework-location.stubs.module_lib",
], ],
visibility: ["//packages/modules/Connectivity:__subpackages__"], visibility: ["//packages/modules/Connectivity:__subpackages__"],
} }
@ -140,6 +141,7 @@ java_sdk_library {
"sdk_module-lib_current_framework-connectivity", "sdk_module-lib_current_framework-connectivity",
], ],
libs: [ libs: [
"framework-location.stubs.module_lib",
"sdk_module-lib_current_framework-connectivity", "sdk_module-lib_current_framework-connectivity",
], ],
permitted_packages: [ permitted_packages: [

View file

@ -59,11 +59,17 @@ package android.nearby {
} }
public class NearbyManager { 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<android.nearby.OffloadCapability>); method public void queryOffloadCapability(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.nearby.OffloadCapability>);
method @FlaggedApi("com.android.nearby.flags.powered_off_finding") @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setPoweredOffFindingEphemeralIds(@NonNull java.util.List<byte[]>);
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_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_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_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); 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 { public final class OffloadCapability implements android.os.Parcelable {

View file

@ -50,6 +50,7 @@ java_library {
"androidx.annotation_annotation", "androidx.annotation_annotation",
"framework-annotations-lib", "framework-annotations-lib",
"framework-bluetooth", "framework-bluetooth",
"framework-location.stubs.module_lib",
], ],
static_libs: [ static_libs: [
"modules-utils-preconditions", "modules-utils-preconditions",

View file

@ -20,6 +20,7 @@ import android.nearby.IBroadcastListener;
import android.nearby.IScanListener; import android.nearby.IScanListener;
import android.nearby.BroadcastRequestParcelable; import android.nearby.BroadcastRequestParcelable;
import android.nearby.ScanRequest; import android.nearby.ScanRequest;
import android.nearby.PoweredOffFindingEphemeralId;
import android.nearby.aidl.IOffloadCallback; import android.nearby.aidl.IOffloadCallback;
/** /**
@ -40,4 +41,10 @@ interface INearbyManager {
void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag); void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag);
void queryOffloadCapability(in IOffloadCallback callback) ; void queryOffloadCapability(in IOffloadCallback callback) ;
void setPoweredOffFindingEphemeralIds(in List<PoweredOffFindingEphemeralId> eids);
void setPoweredOffModeEnabled(boolean enabled);
boolean getPoweredOffModeEnabled();
} }

View file

@ -18,6 +18,7 @@ package android.nearby;
import android.Manifest; import android.Manifest;
import android.annotation.CallbackExecutor; import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
@ -25,9 +26,12 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.SystemApi; import android.annotation.SystemApi;
import android.annotation.SystemService; import android.annotation.SystemService;
import android.bluetooth.BluetoothManager;
import android.content.Context; import android.content.Context;
import android.location.LocationManager;
import android.nearby.aidl.IOffloadCallback; import android.nearby.aidl.IOffloadCallback;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
@ -37,6 +41,7 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -75,8 +80,51 @@ public class NearbyManager {
int ERROR = 2; 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 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. * TODO(b/286137024): Remove this when CTS R5 is rolled out.
* Whether allows Fast Pair to scan. * Whether allows Fast Pair to scan.
@ -456,4 +504,124 @@ public class NearbyManager {
"successfully %s Fast Pair scan", enable ? "enables" : "disables")); "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)}.
*
* <p>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.
*
* <p>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.
*
* <p>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<byte[]> eids) {
Objects.requireNonNull(eids);
if (!isPoweredOffFindingSupported()) {
throw new UnsupportedOperationException(
"Powered off finding is not supported on this device");
}
List<PoweredOffFindingEphemeralId> 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.
*
* <p>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.
*
* <p>{@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();
}
} }

View file

@ -0,0 +1,26 @@
/*
* 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.nearby;
/**
* Find My Device network ephemeral ID for powered off finding.
*
* @hide
*/
parcelable PoweredOffFindingEphemeralId {
byte[20] bytes;
}

View file

@ -35,12 +35,14 @@ import android.nearby.IBroadcastListener;
import android.nearby.INearbyManager; import android.nearby.INearbyManager;
import android.nearby.IScanListener; import android.nearby.IScanListener;
import android.nearby.NearbyManager; import android.nearby.NearbyManager;
import android.nearby.PoweredOffFindingEphemeralId;
import android.nearby.ScanRequest; import android.nearby.ScanRequest;
import android.nearby.aidl.IOffloadCallback; import android.nearby.aidl.IOffloadCallback;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.injector.Injector; import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.managers.BluetoothFinderManager;
import com.android.server.nearby.managers.BroadcastProviderManager; import com.android.server.nearby.managers.BroadcastProviderManager;
import com.android.server.nearby.managers.DiscoveryManager; import com.android.server.nearby.managers.DiscoveryManager;
import com.android.server.nearby.managers.DiscoveryProviderManager; import com.android.server.nearby.managers.DiscoveryProviderManager;
@ -50,6 +52,8 @@ import com.android.server.nearby.util.identity.CallerIdentity;
import com.android.server.nearby.util.permissions.BroadcastPermissions; import com.android.server.nearby.util.permissions.BroadcastPermissions;
import com.android.server.nearby.util.permissions.DiscoveryPermissions; import com.android.server.nearby.util.permissions.DiscoveryPermissions;
import java.util.List;
/** Service implementing nearby functionality. */ /** Service implementing nearby functionality. */
public class NearbyService extends INearbyManager.Stub { public class NearbyService extends INearbyManager.Stub {
public static final String TAG = "NearbyService"; public static final String TAG = "NearbyService";
@ -79,6 +83,7 @@ public class NearbyService extends INearbyManager.Stub {
}; };
private final DiscoveryManager mDiscoveryProviderManager; private final DiscoveryManager mDiscoveryProviderManager;
private final BroadcastProviderManager mBroadcastProviderManager; private final BroadcastProviderManager mBroadcastProviderManager;
private final BluetoothFinderManager mBluetoothFinderManager;
public NearbyService(Context context) { public NearbyService(Context context) {
mContext = context; mContext = context;
@ -90,6 +95,7 @@ public class NearbyService extends INearbyManager.Stub {
mNearbyConfiguration.refactorDiscoveryManager() mNearbyConfiguration.refactorDiscoveryManager()
? new DiscoveryProviderManager(context, mInjector) ? new DiscoveryProviderManager(context, mInjector)
: new DiscoveryProviderManagerLegacy(context, mInjector); : new DiscoveryProviderManagerLegacy(context, mInjector);
mBluetoothFinderManager = new BluetoothFinderManager();
} }
@VisibleForTesting @VisibleForTesting
@ -148,6 +154,30 @@ public class NearbyService extends INearbyManager.Stub {
mDiscoveryProviderManager.queryOffloadCapability(callback); mDiscoveryProviderManager.queryOffloadCapability(callback);
} }
@Override
public void setPoweredOffFindingEphemeralIds(List<PoweredOffFindingEphemeralId> eids) {
// Permissions check
enforceBluetoothPrivilegedPermission(mContext);
mBluetoothFinderManager.sendEids(eids);
}
@Override
public void setPoweredOffModeEnabled(boolean enabled) {
// Permissions check
enforceBluetoothPrivilegedPermission(mContext);
mBluetoothFinderManager.setPoweredOffFinderMode(enabled);
}
@Override
public boolean getPoweredOffModeEnabled() {
// Permissions check
enforceBluetoothPrivilegedPermission(mContext);
return mBluetoothFinderManager.getPoweredOffFinderMode();
}
/** /**
* Called by the service initializer. * Called by the service initializer.
* *

View file

@ -0,0 +1,41 @@
/*
* 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 com.android.server.nearby.managers;
import android.nearby.PoweredOffFindingEphemeralId;
import java.util.List;
/** Connects to {@link IBluetoothFinder} HAL and invokes its API. */
// A placeholder implementation until the HAL API can be used.
public class BluetoothFinderManager {
private boolean mPoweredOffFindingModeEnabled = false;
/** An empty implementation of the corresponding HAL API call. */
public void sendEids(List<PoweredOffFindingEphemeralId> eids) {}
/** A placeholder implementation of the corresponding HAL API call. */
public void setPoweredOffFinderMode(boolean enable) {
mPoweredOffFindingModeEnabled = enable;
}
/** A placeholder implementation of the corresponding HAL API call. */
public boolean getPoweredOffFinderMode() {
return mPoweredOffFindingModeEnabled;
}
}

View file

@ -34,6 +34,7 @@ android_test {
"framework-bluetooth.stubs.module_lib", "framework-bluetooth.stubs.module_lib",
"framework-configinfrastructure", "framework-configinfrastructure",
"framework-connectivity-t.impl", "framework-connectivity-t.impl",
"framework-location.stubs.module_lib",
], ],
srcs: ["src/**/*.java"], srcs: ["src/**/*.java"],
test_suites: [ test_suites: [

View file

@ -25,12 +25,14 @@ import static android.nearby.ScanCallback.ERROR_UNSUPPORTED;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation; import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
import android.bluetooth.cts.BTAdapterUtils; import android.bluetooth.cts.BTAdapterUtils;
import android.content.Context; import android.content.Context;
import android.location.LocationManager;
import android.nearby.BroadcastCallback; import android.nearby.BroadcastCallback;
import android.nearby.BroadcastRequest; import android.nearby.BroadcastRequest;
import android.nearby.NearbyDevice; import android.nearby.NearbyDevice;
@ -42,6 +44,8 @@ import android.nearby.PrivateCredential;
import android.nearby.ScanCallback; import android.nearby.ScanCallback;
import android.nearby.ScanRequest; import android.nearby.ScanRequest;
import android.os.Build; import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -50,6 +54,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress; import androidx.test.filters.SdkSuppress;
import com.android.compatibility.common.util.SystemUtil;
import com.android.modules.utils.build.SdkLevel; import com.android.modules.utils.build.SdkLevel;
import org.junit.Before; import org.junit.Before;
@ -57,6 +62,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -189,6 +195,92 @@ public class NearbyManagerTest {
mScanCallback.onError(ERROR_UNSUPPORTED); 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() { private void enableBluetooth() {
BluetoothManager manager = mContext.getSystemService(BluetoothManager.class); BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
BluetoothAdapter bluetoothAdapter = manager.getAdapter(); 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<OffloadCapability> { private static class OffloadCallback implements Consumer<OffloadCapability> {
@Override @Override
public void accept(OffloadCapability aBoolean) { public void accept(OffloadCapability aBoolean) {

View file

@ -29,6 +29,7 @@ import android.nearby.ScanRequest
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -96,4 +97,49 @@ class NearbyManagerTest {
) )
nearbyManager.stopBroadcast(broadcastCallback) 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)
}
} }

View file

@ -30,12 +30,12 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.LogcatWaitMixin import androidx.test.uiautomator.LogcatWaitMixin
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.time.Duration
import java.util.Calendar
import org.junit.Assert.assertThrows import org.junit.Assert.assertThrows
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.time.Duration
import java.util.Calendar
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class NearbyManagerTest { class NearbyManagerTest {
@ -151,6 +151,46 @@ class NearbyManagerTest {
).isTrue() ).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 { companion object {
private val WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT = Duration.ofSeconds(5) private val WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT = Duration.ofSeconds(5)
} }