Merge "Change the DiscoveryProviderManager to use the Multiplexer logic" into udc-dev am: 33d8a5305d

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/22881045

Change-Id: I6980900653ebcdfc7bd82583208b047e097dbc7c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Xinyi Zhou 2023-05-03 21:47:58 +00:00 • committed by Automerger Merge Worker
commit 7faa4f5708
8 changed files with 1150 additions and 423 deletions

View file

@ -45,7 +45,9 @@ import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.FastPairManager; import com.android.server.nearby.fastpair.FastPairManager;
import com.android.server.nearby.injector.Injector; import com.android.server.nearby.injector.Injector;
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.DiscoveryProviderManager; import com.android.server.nearby.managers.DiscoveryProviderManager;
import com.android.server.nearby.managers.DiscoveryProviderManagerLegacy;
import com.android.server.nearby.presence.PresenceManager; import com.android.server.nearby.presence.PresenceManager;
import com.android.server.nearby.provider.FastPairDataProvider; import com.android.server.nearby.provider.FastPairDataProvider;
import com.android.server.nearby.util.identity.CallerIdentity; import com.android.server.nearby.util.identity.CallerIdentity;
@ -80,18 +82,21 @@ public class NearbyService extends INearbyManager.Stub {
} }
} }
}; };
private final DiscoveryProviderManager mProviderManager; private final DiscoveryManager mDiscoveryProviderManager;
private final BroadcastProviderManager mBroadcastProviderManager; private final BroadcastProviderManager mBroadcastProviderManager;
public NearbyService(Context context) { public NearbyService(Context context) {
mContext = context; mContext = context;
mInjector = new SystemInjector(context); mInjector = new SystemInjector(context);
mProviderManager = new DiscoveryProviderManager(context, mInjector);
mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector); mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector);
final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null); final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
mFastPairManager = new FastPairManager(lcw); mFastPairManager = new FastPairManager(lcw);
mPresenceManager = new PresenceManager(lcw); mPresenceManager = new PresenceManager(lcw);
mNearbyConfiguration = new NearbyConfiguration(); mNearbyConfiguration = new NearbyConfiguration();
mDiscoveryProviderManager =
mNearbyConfiguration.refactorDiscoveryManager()
? new DiscoveryProviderManager(context, mInjector)
: new DiscoveryProviderManagerLegacy(context, mInjector);
} }
@VisibleForTesting @VisibleForTesting
@ -108,7 +113,7 @@ public class NearbyService extends INearbyManager.Stub {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity); DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity);
return mProviderManager.registerScanListener(scanRequest, listener, identity); return mDiscoveryProviderManager.registerScanListener(scanRequest, listener, identity);
} }
@Override @Override
@ -119,7 +124,7 @@ public class NearbyService extends INearbyManager.Stub {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity); DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity);
mProviderManager.unregisterScanListener(listener); mDiscoveryProviderManager.unregisterScanListener(listener);
} }
@Override @Override
@ -147,7 +152,7 @@ public class NearbyService extends INearbyManager.Stub {
@Override @Override
public void queryOffloadCapability(IOffloadCallback callback) { public void queryOffloadCapability(IOffloadCallback callback) {
mProviderManager.queryOffloadCapability(callback); mDiscoveryProviderManager.queryOffloadCapability(callback);
} }
/** /**
@ -174,7 +179,7 @@ public class NearbyService extends INearbyManager.Stub {
// Initialize ContextManager for CHRE scan. // Initialize ContextManager for CHRE scan.
((SystemInjector) mInjector).initializeContextHubManager(); ((SystemInjector) mInjector).initializeContextHubManager();
} }
mProviderManager.init(); mDiscoveryProviderManager.init();
mContext.registerReceiver( mContext.registerReceiver(
mBluetoothReceiver, mBluetoothReceiver,
new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 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.IScanListener;
import android.nearby.NearbyManager;
import android.nearby.ScanRequest;
import android.nearby.aidl.IOffloadCallback;
import com.android.server.nearby.util.identity.CallerIdentity;
/**
* Interface added for flagging DiscoveryProviderManager refactor. After the
* nearby_refactor_discovery_manager flag is fully rolled out, this can be deleted.
*/
public interface DiscoveryManager {
/**
* Registers the listener in the manager and starts scan according to the requested scan mode.
*/
@NearbyManager.ScanStatus
int registerScanListener(ScanRequest scanRequest, IScanListener listener,
CallerIdentity callerIdentity);
/**
* Unregisters the listener in the manager and adjusts the scan mode if necessary afterwards.
*/
void unregisterScanListener(IScanListener listener);
/** Query offload capability in a device. */
void queryOffloadCapability(IOffloadCallback callback);
/** Called after boot completed. */
void init();
}

View file

@ -21,320 +21,74 @@ import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
import static com.android.server.nearby.NearbyService.TAG; import static com.android.server.nearby.NearbyService.TAG;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context; import android.content.Context;
import android.nearby.DataElement; import android.nearby.DataElement;
import android.nearby.IScanListener; import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable; import android.nearby.NearbyDeviceParcelable;
import android.nearby.NearbyManager; import android.nearby.NearbyManager;
import android.nearby.PresenceScanFilter; import android.nearby.PresenceScanFilter;
import android.nearby.ScanCallback;
import android.nearby.ScanFilter; import android.nearby.ScanFilter;
import android.nearby.ScanRequest; import android.nearby.ScanRequest;
import android.nearby.aidl.IOffloadCallback; import android.nearby.aidl.IOffloadCallback;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.GuardedBy; import androidx.annotation.NonNull;
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.metrics.NearbyMetrics; import com.android.server.nearby.managers.registration.DiscoveryRegistration;
import com.android.server.nearby.presence.PresenceDiscoveryResult;
import com.android.server.nearby.provider.AbstractDiscoveryProvider; import com.android.server.nearby.provider.AbstractDiscoveryProvider;
import com.android.server.nearby.provider.BleDiscoveryProvider; import com.android.server.nearby.provider.BleDiscoveryProvider;
import com.android.server.nearby.provider.ChreCommunication; import com.android.server.nearby.provider.ChreCommunication;
import com.android.server.nearby.provider.ChreDiscoveryProvider; import com.android.server.nearby.provider.ChreDiscoveryProvider;
import com.android.server.nearby.provider.PrivacyFilter;
import com.android.server.nearby.util.identity.CallerIdentity; import com.android.server.nearby.util.identity.CallerIdentity;
import com.android.server.nearby.util.permissions.DiscoveryPermissions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Set;
import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import javax.annotation.concurrent.GuardedBy;
/** Manages all aspects of discovery providers. */ /** Manages all aspects of discovery providers. */
public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Listener { public class DiscoveryProviderManager extends
ListenerMultiplexer<IScanListener, DiscoveryRegistration, MergedDiscoveryRequest> implements
AbstractDiscoveryProvider.Listener,
DiscoveryManager {
protected final Object mLock = new Object(); protected final Object mLock = new Object();
private final Context mContext;
private final BleDiscoveryProvider mBleDiscoveryProvider;
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
final ChreDiscoveryProvider mChreDiscoveryProvider; final ChreDiscoveryProvider mChreDiscoveryProvider;
private @ScanRequest.ScanMode int mScanMode; private final Context mContext;
private final BleDiscoveryProvider mBleDiscoveryProvider;
private final Injector mInjector; private final Injector mInjector;
private final Executor mExecutor;
@GuardedBy("mLock")
private Map<IBinder, ScanListenerRecord> mScanTypeScanListenerRecordMap;
@Override
public void onNearbyDeviceDiscovered(NearbyDeviceParcelable nearbyDevice) {
synchronized (mLock) {
AppOpsManager appOpsManager = Objects.requireNonNull(mInjector.getAppOpsManager());
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
if (record == null) {
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
CallerIdentity callerIdentity = record.getCallerIdentity();
if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
appOpsManager, callerIdentity)) {
Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+ "- not forwarding results");
try {
record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
}
return;
}
if (nearbyDevice.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
List<ScanFilter> presenceFilters =
record.getScanRequest().getScanFilters().stream()
.filter(
scanFilter ->
scanFilter.getType()
== SCAN_TYPE_NEARBY_PRESENCE)
.collect(Collectors.toList());
if (!presenceFilterMatches(nearbyDevice, presenceFilters)) {
Log.d(TAG, "presence filter does not match for "
+ "the scanned Presence Device");
continue;
}
}
try {
record.getScanListener()
.onDiscovered(
PrivacyFilter.filter(
record.getScanRequest().getScanType(), nearbyDevice));
NearbyMetrics.logScanDeviceDiscovered(
record.hashCode(), record.getScanRequest(), nearbyDevice);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report onDiscovered.", e);
}
}
}
}
@Override
public void onError(int errorCode) {
synchronized (mLock) {
AppOpsManager appOpsManager = Objects.requireNonNull(mInjector.getAppOpsManager());
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
if (record == null) {
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
CallerIdentity callerIdentity = record.getCallerIdentity();
if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
appOpsManager, callerIdentity)) {
Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+ "- not forwarding results");
try {
record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
}
return;
}
try {
record.getScanListener().onError(errorCode);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report onError.", e);
}
}
}
}
public DiscoveryProviderManager(Context context, Injector injector) { public DiscoveryProviderManager(Context context, Injector injector) {
Log.v(TAG, "DiscoveryProviderManager: ");
mContext = context; mContext = context;
mBleDiscoveryProvider = new BleDiscoveryProvider(mContext, injector); mBleDiscoveryProvider = new BleDiscoveryProvider(mContext, injector);
Executor executor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
mChreDiscoveryProvider = mChreDiscoveryProvider = new ChreDiscoveryProvider(mContext,
new ChreDiscoveryProvider( new ChreCommunication(injector, mContext, mExecutor), mExecutor);
mContext, new ChreCommunication(injector, mContext, executor), executor);
mScanTypeScanListenerRecordMap = new HashMap<>();
mInjector = injector; mInjector = injector;
} }
@VisibleForTesting @VisibleForTesting
DiscoveryProviderManager(Context context, Injector injector, DiscoveryProviderManager(Context context, Executor executor, Injector injector,
BleDiscoveryProvider bleDiscoveryProvider, BleDiscoveryProvider bleDiscoveryProvider,
ChreDiscoveryProvider chreDiscoveryProvider, ChreDiscoveryProvider chreDiscoveryProvider) {
Map<IBinder, ScanListenerRecord> scanTypeScanListenerRecordMap) {
mContext = context; mContext = context;
mExecutor = executor;
mInjector = injector; mInjector = injector;
mBleDiscoveryProvider = bleDiscoveryProvider; mBleDiscoveryProvider = bleDiscoveryProvider;
mChreDiscoveryProvider = chreDiscoveryProvider; mChreDiscoveryProvider = chreDiscoveryProvider;
mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap;
} }
/** Called after boot completed. */ private static boolean isChreOnly(Set<ScanFilter> scanFilters) {
public void init() {
if (mInjector.getContextHubManager() != null) {
mChreDiscoveryProvider.init();
}
mChreDiscoveryProvider.getController().setListener(this);
}
/**
* Registers the listener in the manager and starts scan according to the requested scan mode.
*/
@NearbyManager.ScanStatus
public int registerScanListener(ScanRequest scanRequest, IScanListener listener,
CallerIdentity callerIdentity) {
synchronized (mLock) {
ScanListenerDeathRecipient deathRecipient = (listener != null)
? new ScanListenerDeathRecipient(listener) : null;
IBinder listenerBinder = listener.asBinder();
if (listenerBinder != null && deathRecipient != null) {
try {
listenerBinder.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't link to scan listener's death");
}
}
if (mScanTypeScanListenerRecordMap.containsKey(listener.asBinder())) {
ScanRequest savedScanRequest =
mScanTypeScanListenerRecordMap.get(listenerBinder).getScanRequest();
if (scanRequest.equals(savedScanRequest)) {
Log.d(TAG, "Already registered the scanRequest: " + scanRequest);
return NearbyManager.ScanStatus.SUCCESS;
}
}
ScanListenerRecord scanListenerRecord =
new ScanListenerRecord(scanRequest, listener, callerIdentity, deathRecipient);
Boolean started = startProviders(scanRequest);
if (started == null) {
return NearbyManager.ScanStatus.UNKNOWN;
}
if (!started) {
return NearbyManager.ScanStatus.ERROR;
}
mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
NearbyMetrics.logScanStarted(scanListenerRecord.hashCode(), scanRequest);
if (mScanMode < scanRequest.getScanMode()) {
mScanMode = scanRequest.getScanMode();
invalidateProviderScanMode();
}
return NearbyManager.ScanStatus.SUCCESS;
}
}
/**
* Unregisters the listener in the manager and adjusts the scan mode if necessary afterwards.
*/
public void unregisterScanListener(IScanListener listener) {
IBinder listenerBinder = listener.asBinder();
synchronized (mLock) {
if (!mScanTypeScanListenerRecordMap.containsKey(listenerBinder)) {
Log.w(
TAG,
"Cannot unregister the scanRequest because the request is never "
+ "registered.");
return;
}
ScanListenerRecord removedRecord =
mScanTypeScanListenerRecordMap.remove(listenerBinder);
ScanListenerDeathRecipient deathRecipient = removedRecord.getDeathRecipient();
if (listenerBinder != null && deathRecipient != null) {
listenerBinder.unlinkToDeath(removedRecord.getDeathRecipient(), 0);
}
Log.v(TAG, "DiscoveryProviderManager unregistered scan listener.");
NearbyMetrics.logScanStopped(removedRecord.hashCode(), removedRecord.getScanRequest());
if (mScanTypeScanListenerRecordMap.isEmpty()) {
Log.v(TAG, "DiscoveryProviderManager stops provider because there is no "
+ "scan listener registered.");
stopProviders();
return;
}
// TODO(b/221082271): updates the scan with reduced filters.
// Removes current highest scan mode requested and sets the next highest scan mode.
if (removedRecord.getScanRequest().getScanMode() == mScanMode) {
Log.v(TAG, "DiscoveryProviderManager starts to find the new highest scan mode "
+ "because the highest scan mode listener was unregistered.");
@ScanRequest.ScanMode int highestScanModeRequested = ScanRequest.SCAN_MODE_NO_POWER;
// find the next highest scan mode;
for (ScanListenerRecord record : mScanTypeScanListenerRecordMap.values()) {
@ScanRequest.ScanMode int scanMode = record.getScanRequest().getScanMode();
if (scanMode > highestScanModeRequested) {
highestScanModeRequested = scanMode;
}
}
if (mScanMode != highestScanModeRequested) {
mScanMode = highestScanModeRequested;
invalidateProviderScanMode();
}
}
}
}
/**
* Query offload capability in a device.
*/
public void queryOffloadCapability(IOffloadCallback callback) {
mChreDiscoveryProvider.queryOffloadCapability(callback);
}
/**
* @return {@code null} when all providers are initializing
* {@code false} when fail to start all the providers
* {@code true} when any one of the provider starts successfully
*/
@VisibleForTesting
@Nullable
Boolean startProviders(ScanRequest scanRequest) {
if (!scanRequest.isBleEnabled()) {
Log.w(TAG, "failed to start any provider because client disabled BLE");
return false;
}
List<ScanFilter> scanFilters = getPresenceScanFilters();
boolean chreOnly = isChreOnly(scanFilters);
Boolean chreAvailable = mChreDiscoveryProvider.available();
if (chreAvailable == null) {
if (chreOnly) {
Log.w(TAG, "client wants CHRE only and Nearby service is still querying CHRE"
+ " status");
return null;
}
startBleProvider(scanFilters);
return true;
}
if (!chreAvailable) {
if (chreOnly) {
Log.w(TAG, "failed to start any provider because client wants CHRE only and CHRE"
+ " is not available");
return false;
}
startBleProvider(scanFilters);
return true;
}
if (scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
startChreProvider(scanFilters);
return true;
}
startBleProvider(scanFilters);
return true;
}
private static boolean isChreOnly(List<ScanFilter> scanFilters) {
for (ScanFilter scanFilter : scanFilters) { for (ScanFilter scanFilter : scanFilters) {
List<DataElement> dataElements = List<DataElement> dataElements =
((PresenceScanFilter) scanFilter).getExtendedProperties(); ((PresenceScanFilter) scanFilter).getExtendedProperties();
@ -355,41 +109,160 @@ public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Liste
return false; return false;
} }
private void startBleProvider(List<ScanFilter> scanFilters) { @Override
public void onNearbyDeviceDiscovered(NearbyDeviceParcelable nearbyDevice) {
synchronized (mMultiplexerLock) {
Log.d(TAG, "Found device" + nearbyDevice);
deliverToListeners(registration -> {
try {
return registration.onNearbyDeviceDiscovered(nearbyDevice);
} catch (Exception e) {
Log.w(TAG, "DiscoveryProviderManager failed to report callback.", e);
return null;
}
});
}
}
@Override
public void onError(int errorCode) {
synchronized (mMultiplexerLock) {
Log.e(TAG, "Error found during scanning.");
deliverToListeners(registration -> {
try {
return registration.reportError(errorCode);
} catch (Exception e) {
Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
return null;
}
});
}
}
/** Called after boot completed. */
public void init() {
if (mInjector.getContextHubManager() != null) {
mChreDiscoveryProvider.init();
}
mChreDiscoveryProvider.getController().setListener(this);
}
/**
* Registers the listener in the manager and starts scan according to the requested scan mode.
*/
@NearbyManager.ScanStatus
public int registerScanListener(ScanRequest scanRequest, IScanListener listener,
CallerIdentity callerIdentity) {
DiscoveryRegistration registration = new DiscoveryRegistration(this, scanRequest, listener,
mExecutor, callerIdentity, mMultiplexerLock, mInjector.getAppOpsManager());
synchronized (mMultiplexerLock) {
putRegistration(listener.asBinder(), registration);
return NearbyManager.ScanStatus.SUCCESS;
}
}
@Override
public void onRegister() {
Log.v(TAG, "Registering the DiscoveryProviderManager.");
startProviders();
}
@Override
public void onUnregister() {
Log.v(TAG, "Unregistering the DiscoveryProviderManager.");
stopProviders();
}
/**
* Unregisters the listener in the manager and adjusts the scan mode if necessary afterwards.
*/
public void unregisterScanListener(IScanListener listener) {
Log.v(TAG, "Unregister scan listener");
synchronized (mMultiplexerLock) {
removeRegistration(listener.asBinder());
}
// TODO(b/221082271): updates the scan with reduced filters.
}
/**
* Query offload capability in a device.
*/
public void queryOffloadCapability(IOffloadCallback callback) {
mChreDiscoveryProvider.queryOffloadCapability(callback);
}
/**
* @return {@code null} when all providers are initializing
* {@code false} when fail to start all the providers
* {@code true} when any one of the provider starts successfully
*/
@VisibleForTesting
@Nullable
Boolean startProviders() {
synchronized (mMultiplexerLock) {
if (!mMerged.getMediums().contains(MergedDiscoveryRequest.Medium.BLE)) {
Log.w(TAG, "failed to start any provider because client disabled BLE");
return false;
}
Set<ScanFilter> scanFilters = mMerged.getScanFilters();
boolean chreOnly = isChreOnly(scanFilters);
Boolean chreAvailable = mChreDiscoveryProvider.available();
Log.v(TAG, "startProviders: chreOnly " + chreOnly + " chreAvailable " + chreAvailable);
if (chreAvailable == null) {
if (chreOnly) {
Log.w(TAG, "client wants CHRE only and Nearby service is still querying CHRE"
+ " status");
return null;
}
startBleProvider(scanFilters);
return true;
}
if (!chreAvailable) {
if (chreOnly) {
Log.w(TAG,
"failed to start any provider because client wants CHRE only and CHRE"
+ " is not available");
return false;
}
startBleProvider(scanFilters);
return true;
}
if (mMerged.getScanTypes().contains(SCAN_TYPE_NEARBY_PRESENCE)) {
startChreProvider(scanFilters);
Log.d(TAG, "startProviders: heeey");
return true;
}
Log.d(TAG, "startProviders: geagewage");
startBleProvider(scanFilters);
return true;
}
}
@GuardedBy("mMultiplexerLock")
private void startBleProvider(Set<ScanFilter> scanFilters) {
if (!mBleDiscoveryProvider.getController().isStarted()) { if (!mBleDiscoveryProvider.getController().isStarted()) {
Log.d(TAG, "DiscoveryProviderManager starts Ble scanning."); Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
mBleDiscoveryProvider.getController().setListener(this); mBleDiscoveryProvider.getController().setListener(this);
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode); mBleDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode());
mBleDiscoveryProvider.getController().setProviderScanFilters(scanFilters); mBleDiscoveryProvider.getController().setProviderScanFilters(
new ArrayList<>(scanFilters));
mBleDiscoveryProvider.getController().start(); mBleDiscoveryProvider.getController().start();
} }
} }
@VisibleForTesting @VisibleForTesting
void startChreProvider(List<ScanFilter> scanFilters) { @GuardedBy("mMultiplexerLock")
Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning."); void startChreProvider(Collection<ScanFilter> scanFilters) {
mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters); Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning. " + mMerged);
mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode); mChreDiscoveryProvider.getController().setProviderScanFilters(new ArrayList<>(scanFilters));
mChreDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode());
mChreDiscoveryProvider.getController().start(); mChreDiscoveryProvider.getController().start();
} }
private List<ScanFilter> getPresenceScanFilters() {
synchronized (mLock) {
List<ScanFilter> scanFilters = new ArrayList();
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
List<ScanFilter> presenceFilters =
record.getScanRequest().getScanFilters().stream()
.filter(
scanFilter ->
scanFilter.getType() == SCAN_TYPE_NEARBY_PRESENCE)
.collect(Collectors.toList());
scanFilters.addAll(presenceFilters);
}
return scanFilters;
}
}
private void stopProviders() { private void stopProviders() {
stopBleProvider(); stopBleProvider();
stopChreProvider(); stopChreProvider();
@ -407,96 +280,40 @@ public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Liste
@VisibleForTesting @VisibleForTesting
void invalidateProviderScanMode() { void invalidateProviderScanMode() {
if (mBleDiscoveryProvider.getController().isStarted()) { if (mBleDiscoveryProvider.getController().isStarted()) {
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode); synchronized (mMultiplexerLock) {
mBleDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode());
}
} else { } else {
Log.d( Log.d(TAG, "Skip invalidating BleDiscoveryProvider scan mode because the provider not "
TAG, + "started.");
"Skip invalidating BleDiscoveryProvider scan mode because the provider not "
+ "started.");
} }
} }
@VisibleForTesting @Override
static boolean presenceFilterMatches( public MergedDiscoveryRequest mergeRegistrations(
NearbyDeviceParcelable device, List<ScanFilter> scanFilters) { @NonNull Collection<DiscoveryRegistration> registrations) {
if (scanFilters.isEmpty()) { MergedDiscoveryRequest.Builder builder = new MergedDiscoveryRequest.Builder();
return true; int scanMode = ScanRequest.SCAN_MODE_NO_POWER;
} for (DiscoveryRegistration registration : registrations) {
PresenceDiscoveryResult discoveryResult = PresenceDiscoveryResult.fromDevice(device); builder.addActions(registration.getActions());
for (ScanFilter scanFilter : scanFilters) { builder.addScanFilters(registration.getPresenceScanFilters());
PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter; Log.d(TAG,
if (discoveryResult.matches(presenceScanFilter)) { "mergeRegistrations: type is " + registration.getScanRequest().getScanType());
return true; builder.addScanType(registration.getScanRequest().getScanType());
if (registration.getScanRequest().isBleEnabled()) {
builder.addMedium(MergedDiscoveryRequest.Medium.BLE);
}
int requestScanMode = registration.getScanRequest().getScanMode();
if (scanMode < requestScanMode) {
scanMode = requestScanMode;
} }
} }
return false; builder.setScanMode(scanMode);
return builder.build();
} }
/** @Override
* Class to make listener unregister after the binder is dead. public void onMergedRegistrationsUpdated() {
*/ invalidateProviderScanMode();
public class ScanListenerDeathRecipient implements IBinder.DeathRecipient {
public IScanListener listener;
ScanListenerDeathRecipient(IScanListener listener) {
this.listener = listener;
}
@Override
public void binderDied() {
Log.d(TAG, "Binder is dead - unregistering scan listener");
unregisterScanListener(listener);
}
}
@VisibleForTesting
static class ScanListenerRecord {
private final ScanRequest mScanRequest;
private final IScanListener mScanListener;
private final CallerIdentity mCallerIdentity;
private final ScanListenerDeathRecipient mDeathRecipient;
ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener,
CallerIdentity callerIdentity, ScanListenerDeathRecipient deathRecipient) {
mScanListener = iScanListener;
mScanRequest = scanRequest;
mCallerIdentity = callerIdentity;
mDeathRecipient = deathRecipient;
}
IScanListener getScanListener() {
return mScanListener;
}
ScanRequest getScanRequest() {
return mScanRequest;
}
CallerIdentity getCallerIdentity() {
return mCallerIdentity;
}
ScanListenerDeathRecipient getDeathRecipient() {
return mDeathRecipient;
}
@Override
public boolean equals(Object other) {
if (other instanceof ScanListenerRecord) {
ScanListenerRecord otherScanListenerRecord = (ScanListenerRecord) other;
return Objects.equals(mScanRequest, otherScanListenerRecord.mScanRequest)
&& Objects.equals(mScanListener, otherScanListenerRecord.mScanListener);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(mScanListener, mScanRequest);
}
} }
} }

View file

@ -0,0 +1,504 @@
/*
* Copyright (C) 2023 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 static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
import static com.android.server.nearby.NearbyService.TAG;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.nearby.DataElement;
import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
import android.nearby.NearbyManager;
import android.nearby.PresenceScanFilter;
import android.nearby.ScanCallback;
import android.nearby.ScanFilter;
import android.nearby.ScanRequest;
import android.nearby.aidl.IOffloadCallback;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.metrics.NearbyMetrics;
import com.android.server.nearby.presence.PresenceDiscoveryResult;
import com.android.server.nearby.provider.AbstractDiscoveryProvider;
import com.android.server.nearby.provider.BleDiscoveryProvider;
import com.android.server.nearby.provider.ChreCommunication;
import com.android.server.nearby.provider.ChreDiscoveryProvider;
import com.android.server.nearby.provider.PrivacyFilter;
import com.android.server.nearby.util.identity.CallerIdentity;
import com.android.server.nearby.util.permissions.DiscoveryPermissions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/** Manages all aspects of discovery providers. */
public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider.Listener,
DiscoveryManager {
protected final Object mLock = new Object();
@VisibleForTesting
@Nullable
final ChreDiscoveryProvider mChreDiscoveryProvider;
private final Context mContext;
private final BleDiscoveryProvider mBleDiscoveryProvider;
private final Injector mInjector;
@ScanRequest.ScanMode
private int mScanMode;
@GuardedBy("mLock")
private final Map<IBinder, ScanListenerRecord> mScanTypeScanListenerRecordMap;
public DiscoveryProviderManagerLegacy(Context context, Injector injector) {
mContext = context;
mBleDiscoveryProvider = new BleDiscoveryProvider(mContext, injector);
Executor executor = Executors.newSingleThreadExecutor();
mChreDiscoveryProvider =
new ChreDiscoveryProvider(
mContext, new ChreCommunication(injector, mContext, executor), executor);
mScanTypeScanListenerRecordMap = new HashMap<>();
mInjector = injector;
Log.v(TAG, "DiscoveryProviderManagerLegacy: ");
}
@VisibleForTesting
DiscoveryProviderManagerLegacy(Context context, Injector injector,
BleDiscoveryProvider bleDiscoveryProvider,
ChreDiscoveryProvider chreDiscoveryProvider,
Map<IBinder, ScanListenerRecord> scanTypeScanListenerRecordMap) {
mContext = context;
mInjector = injector;
mBleDiscoveryProvider = bleDiscoveryProvider;
mChreDiscoveryProvider = chreDiscoveryProvider;
mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap;
}
private static boolean isChreOnly(List<ScanFilter> scanFilters) {
for (ScanFilter scanFilter : scanFilters) {
List<DataElement> dataElements =
((PresenceScanFilter) scanFilter).getExtendedProperties();
for (DataElement dataElement : dataElements) {
if (dataElement.getKey() != DataElement.DataType.SCAN_MODE) {
continue;
}
byte[] scanModeValue = dataElement.getValue();
if (scanModeValue == null || scanModeValue.length == 0) {
break;
}
if (Byte.toUnsignedInt(scanModeValue[0]) == ScanRequest.SCAN_MODE_CHRE_ONLY) {
return true;
}
}
}
return false;
}
@VisibleForTesting
static boolean presenceFilterMatches(
NearbyDeviceParcelable device, List<ScanFilter> scanFilters) {
if (scanFilters.isEmpty()) {
return true;
}
PresenceDiscoveryResult discoveryResult = PresenceDiscoveryResult.fromDevice(device);
for (ScanFilter scanFilter : scanFilters) {
PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter;
if (discoveryResult.matches(presenceScanFilter)) {
return true;
}
}
return false;
}
@Override
public void onNearbyDeviceDiscovered(NearbyDeviceParcelable nearbyDevice) {
synchronized (mLock) {
AppOpsManager appOpsManager = Objects.requireNonNull(mInjector.getAppOpsManager());
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
if (record == null) {
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
CallerIdentity callerIdentity = record.getCallerIdentity();
if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
appOpsManager, callerIdentity)) {
Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+ "- not forwarding results");
try {
record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
}
return;
}
if (nearbyDevice.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
List<ScanFilter> presenceFilters =
record.getScanRequest().getScanFilters().stream()
.filter(
scanFilter ->
scanFilter.getType()
== SCAN_TYPE_NEARBY_PRESENCE)
.collect(Collectors.toList());
if (!presenceFilterMatches(nearbyDevice, presenceFilters)) {
Log.d(TAG, "presence filter does not match for "
+ "the scanned Presence Device");
continue;
}
}
try {
record.getScanListener()
.onDiscovered(
PrivacyFilter.filter(
record.getScanRequest().getScanType(), nearbyDevice));
NearbyMetrics.logScanDeviceDiscovered(
record.hashCode(), record.getScanRequest(), nearbyDevice);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report onDiscovered.", e);
}
}
}
}
@Override
public void onError(int errorCode) {
synchronized (mLock) {
AppOpsManager appOpsManager = Objects.requireNonNull(mInjector.getAppOpsManager());
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
if (record == null) {
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
CallerIdentity callerIdentity = record.getCallerIdentity();
if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
appOpsManager, callerIdentity)) {
Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+ "- not forwarding results");
try {
record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
}
return;
}
try {
record.getScanListener().onError(errorCode);
} catch (RemoteException e) {
Log.w(TAG, "DiscoveryProviderManager failed to report onError.", e);
}
}
}
}
/** Called after boot completed. */
public void init() {
if (mInjector.getContextHubManager() != null) {
mChreDiscoveryProvider.init();
}
mChreDiscoveryProvider.getController().setListener(this);
}
/**
* Registers the listener in the manager and starts scan according to the requested scan mode.
*/
@NearbyManager.ScanStatus
public int registerScanListener(ScanRequest scanRequest, IScanListener listener,
CallerIdentity callerIdentity) {
synchronized (mLock) {
ScanListenerDeathRecipient deathRecipient = (listener != null)
? new ScanListenerDeathRecipient(listener) : null;
IBinder listenerBinder = listener.asBinder();
if (listenerBinder != null && deathRecipient != null) {
try {
listenerBinder.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't link to scan listener's death");
}
}
if (mScanTypeScanListenerRecordMap.containsKey(listener.asBinder())) {
ScanRequest savedScanRequest =
mScanTypeScanListenerRecordMap.get(listenerBinder).getScanRequest();
if (scanRequest.equals(savedScanRequest)) {
Log.d(TAG, "Already registered the scanRequest: " + scanRequest);
return NearbyManager.ScanStatus.SUCCESS;
}
}
ScanListenerRecord scanListenerRecord =
new ScanListenerRecord(scanRequest, listener, callerIdentity, deathRecipient);
Boolean started = startProviders(scanRequest);
if (started == null) {
return NearbyManager.ScanStatus.UNKNOWN;
}
if (!started) {
return NearbyManager.ScanStatus.ERROR;
}
mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
NearbyMetrics.logScanStarted(scanListenerRecord.hashCode(), scanRequest);
if (mScanMode < scanRequest.getScanMode()) {
mScanMode = scanRequest.getScanMode();
invalidateProviderScanMode();
}
return NearbyManager.ScanStatus.SUCCESS;
}
}
/**
* Unregisters the listener in the manager and adjusts the scan mode if necessary afterwards.
*/
public void unregisterScanListener(IScanListener listener) {
IBinder listenerBinder = listener.asBinder();
synchronized (mLock) {
if (!mScanTypeScanListenerRecordMap.containsKey(listenerBinder)) {
Log.w(
TAG,
"Cannot unregister the scanRequest because the request is never "
+ "registered.");
return;
}
ScanListenerRecord removedRecord =
mScanTypeScanListenerRecordMap.remove(listenerBinder);
ScanListenerDeathRecipient deathRecipient = removedRecord.getDeathRecipient();
if (listenerBinder != null && deathRecipient != null) {
listenerBinder.unlinkToDeath(removedRecord.getDeathRecipient(), 0);
}
Log.v(TAG, "DiscoveryProviderManager unregistered scan listener.");
NearbyMetrics.logScanStopped(removedRecord.hashCode(), removedRecord.getScanRequest());
if (mScanTypeScanListenerRecordMap.isEmpty()) {
Log.v(TAG, "DiscoveryProviderManager stops provider because there is no "
+ "scan listener registered.");
stopProviders();
return;
}
// TODO(b/221082271): updates the scan with reduced filters.
// Removes current highest scan mode requested and sets the next highest scan mode.
if (removedRecord.getScanRequest().getScanMode() == mScanMode) {
Log.v(TAG, "DiscoveryProviderManager starts to find the new highest scan mode "
+ "because the highest scan mode listener was unregistered.");
@ScanRequest.ScanMode int highestScanModeRequested = ScanRequest.SCAN_MODE_NO_POWER;
// find the next highest scan mode;
for (ScanListenerRecord record : mScanTypeScanListenerRecordMap.values()) {
@ScanRequest.ScanMode int scanMode = record.getScanRequest().getScanMode();
if (scanMode > highestScanModeRequested) {
highestScanModeRequested = scanMode;
}
}
if (mScanMode != highestScanModeRequested) {
mScanMode = highestScanModeRequested;
invalidateProviderScanMode();
}
}
}
}
/**
* Query offload capability in a device.
*/
public void queryOffloadCapability(IOffloadCallback callback) {
mChreDiscoveryProvider.queryOffloadCapability(callback);
}
/**
* @return {@code null} when all providers are initializing
* {@code false} when fail to start all the providers
* {@code true} when any one of the provider starts successfully
*/
@VisibleForTesting
@Nullable
Boolean startProviders(ScanRequest scanRequest) {
if (!scanRequest.isBleEnabled()) {
Log.w(TAG, "failed to start any provider because client disabled BLE");
return false;
}
List<ScanFilter> scanFilters = getPresenceScanFilters();
boolean chreOnly = isChreOnly(scanFilters);
Boolean chreAvailable = mChreDiscoveryProvider.available();
if (chreAvailable == null) {
if (chreOnly) {
Log.w(TAG, "client wants CHRE only and Nearby service is still querying CHRE"
+ " status");
return null;
}
startBleProvider(scanFilters);
return true;
}
if (!chreAvailable) {
if (chreOnly) {
Log.w(TAG, "failed to start any provider because client wants CHRE only and CHRE"
+ " is not available");
return false;
}
startBleProvider(scanFilters);
return true;
}
if (scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
startChreProvider(scanFilters);
return true;
}
startBleProvider(scanFilters);
return true;
}
private void startBleProvider(List<ScanFilter> scanFilters) {
if (!mBleDiscoveryProvider.getController().isStarted()) {
Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
mBleDiscoveryProvider.getController().setListener(this);
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
mBleDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
mBleDiscoveryProvider.getController().start();
}
}
@VisibleForTesting
void startChreProvider(List<ScanFilter> scanFilters) {
Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning.");
mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode);
mChreDiscoveryProvider.getController().start();
}
private List<ScanFilter> getPresenceScanFilters() {
synchronized (mLock) {
List<ScanFilter> scanFilters = new ArrayList();
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
List<ScanFilter> presenceFilters =
record.getScanRequest().getScanFilters().stream()
.filter(
scanFilter ->
scanFilter.getType() == SCAN_TYPE_NEARBY_PRESENCE)
.collect(Collectors.toList());
scanFilters.addAll(presenceFilters);
}
return scanFilters;
}
}
private void stopProviders() {
stopBleProvider();
stopChreProvider();
}
private void stopBleProvider() {
mBleDiscoveryProvider.getController().stop();
}
@VisibleForTesting
protected void stopChreProvider() {
mChreDiscoveryProvider.getController().stop();
}
@VisibleForTesting
void invalidateProviderScanMode() {
if (mBleDiscoveryProvider.getController().isStarted()) {
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
} else {
Log.d(
TAG,
"Skip invalidating BleDiscoveryProvider scan mode because the provider not "
+ "started.");
}
}
@VisibleForTesting
static class ScanListenerRecord {
private final ScanRequest mScanRequest;
private final IScanListener mScanListener;
private final CallerIdentity mCallerIdentity;
private final ScanListenerDeathRecipient mDeathRecipient;
ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener,
CallerIdentity callerIdentity, ScanListenerDeathRecipient deathRecipient) {
mScanListener = iScanListener;
mScanRequest = scanRequest;
mCallerIdentity = callerIdentity;
mDeathRecipient = deathRecipient;
}
IScanListener getScanListener() {
return mScanListener;
}
ScanRequest getScanRequest() {
return mScanRequest;
}
CallerIdentity getCallerIdentity() {
return mCallerIdentity;
}
ScanListenerDeathRecipient getDeathRecipient() {
return mDeathRecipient;
}
@Override
public boolean equals(Object other) {
if (other instanceof ScanListenerRecord) {
ScanListenerRecord otherScanListenerRecord = (ScanListenerRecord) other;
return Objects.equals(mScanRequest, otherScanListenerRecord.mScanRequest)
&& Objects.equals(mScanListener, otherScanListenerRecord.mScanListener);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(mScanListener, mScanRequest);
}
}
/**
* Class to make listener unregister after the binder is dead.
*/
public class ScanListenerDeathRecipient implements IBinder.DeathRecipient {
public IScanListener listener;
ScanListenerDeathRecipient(IScanListener listener) {
this.listener = listener;
}
@Override
public void binderDied() {
Log.d(TAG, "Binder is dead - unregistering scan listener");
unregisterScanListener(listener);
}
}
}

View file

@ -243,6 +243,11 @@ public class DiscoveryRegistration extends BinderListenerRegistration<IScanListe
} }
/** Reports an error to the client. */
public ListenerOperation<IScanListener> reportError(@ScanCallback.ErrorCode int errorCode) {
return listener -> listener.onError(errorCode);
}
@Nullable @Nullable
ListenerOperation<IScanListener> reportResult(@DiscoveryResult int result, ListenerOperation<IScanListener> reportResult(@DiscoveryResult int result,
NearbyDeviceParcelable device, @Nullable Runnable successReportCallback) { NearbyDeviceParcelable device, @Nullable Runnable successReportCallback) {
@ -250,7 +255,7 @@ public class DiscoveryRegistration extends BinderListenerRegistration<IScanListe
// NOTE: AppOps report has to be the last operation before delivering the result. Otherwise // NOTE: AppOps report has to be the last operation before delivering the result. Otherwise
// we may over-report when the discovery result doesn't end up being delivered. // we may over-report when the discovery result doesn't end up being delivered.
if (!checkIdentity()) { if (!checkIdentity()) {
return listener -> listener.onError(ScanCallback.ERROR_PERMISSION_DENIED); return reportError(ScanCallback.ERROR_PERMISSION_DENIED);
} }
return new ListenerOperation<>() { return new ListenerOperation<>() {

View file

@ -36,6 +36,7 @@ import android.app.UiAutomation;
import android.content.Context; import android.content.Context;
import android.nearby.IScanListener; import android.nearby.IScanListener;
import android.nearby.ScanRequest; import android.nearby.ScanRequest;
import android.os.IBinder;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry;
@ -61,10 +62,14 @@ public final class NearbyServiceTest {
private IScanListener mScanListener; private IScanListener mScanListener;
@Mock @Mock
private AppOpsManager mMockAppOpsManager; private AppOpsManager mMockAppOpsManager;
@Mock
private IBinder mIBinder;
@Before @Before
public void setUp() { public void setUp() {
initMocks(this); initMocks(this);
when(mScanListener.asBinder()).thenReturn(mIBinder);
mUiAutomation.adoptShellPermissionIdentity( mUiAutomation.adoptShellPermissionIdentity(
READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG, BLUETOOTH_PRIVILEGED); READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG, BLUETOOTH_PRIVILEGED);
mContext = InstrumentationRegistry.getInstrumentation().getContext(); mContext = InstrumentationRegistry.getInstrumentation().getContext();

View file

@ -0,0 +1,378 @@
/*
* Copyright (C) 2023 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 static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
import android.nearby.DataElement;
import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
import android.nearby.PresenceScanFilter;
import android.nearby.PublicCredential;
import android.nearby.ScanRequest;
import android.os.IBinder;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.provider.BleDiscoveryProvider;
import com.android.server.nearby.provider.ChreCommunication;
import com.android.server.nearby.provider.ChreDiscoveryProvider;
import com.android.server.nearby.provider.DiscoveryProviderController;
import com.android.server.nearby.util.identity.CallerIdentity;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Unit test for {@link DiscoveryProviderManagerLegacy} class.
*/
public class DiscoveryProviderManagerLegacyTest {
private static final int SCAN_MODE_CHRE_ONLY = 3;
private static final int DATA_TYPE_SCAN_MODE = 102;
private static final int UID = 1234;
private static final int PID = 5678;
private static final String PACKAGE_NAME = "android.nearby.test";
private static final int RSSI = -60;
@Mock
Injector mInjector;
@Mock
Context mContext;
@Mock
AppOpsManager mAppOpsManager;
@Mock
BleDiscoveryProvider mBleDiscoveryProvider;
@Mock
ChreDiscoveryProvider mChreDiscoveryProvider;
@Mock
DiscoveryProviderController mBluetoothController;
@Mock
DiscoveryProviderController mChreController;
@Mock
IScanListener mScanListener;
@Mock
CallerIdentity mCallerIdentity;
@Mock
DiscoveryProviderManagerLegacy.ScanListenerDeathRecipient mScanListenerDeathRecipient;
@Mock
IBinder mIBinder;
private DiscoveryProviderManagerLegacy mDiscoveryProviderManager;
private Map<IBinder, DiscoveryProviderManagerLegacy.ScanListenerRecord>
mScanTypeScanListenerRecordMap;
private static PresenceScanFilter getPresenceScanFilter() {
final byte[] secretId = new byte[]{1, 2, 3, 4};
final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
final byte[] publicKey = new byte[]{1, 1, 2, 2};
final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5};
final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5};
PublicCredential credential = new PublicCredential.Builder(
secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag)
.setIdentityType(IDENTITY_TYPE_PRIVATE)
.build();
final int action = 123;
return new PresenceScanFilter.Builder()
.addCredential(credential)
.setMaxPathLoss(RSSI)
.addPresenceAction(action)
.build();
}
private static PresenceScanFilter getChreOnlyPresenceScanFilter() {
final byte[] secretId = new byte[]{1, 2, 3, 4};
final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
final byte[] publicKey = new byte[]{1, 1, 2, 2};
final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5};
final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5};
PublicCredential credential = new PublicCredential.Builder(
secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag)
.setIdentityType(IDENTITY_TYPE_PRIVATE)
.build();
final int action = 123;
DataElement scanModeElement = new DataElement(DATA_TYPE_SCAN_MODE,
new byte[]{SCAN_MODE_CHRE_ONLY});
return new PresenceScanFilter.Builder()
.addCredential(credential)
.setMaxPathLoss(RSSI)
.addPresenceAction(action)
.addExtendedProperty(scanModeElement)
.build();
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController);
when(mChreDiscoveryProvider.getController()).thenReturn(mChreController);
mScanTypeScanListenerRecordMap = new HashMap<>();
mDiscoveryProviderManager =
new DiscoveryProviderManagerLegacy(mContext, mInjector,
mBleDiscoveryProvider,
mChreDiscoveryProvider,
mScanTypeScanListenerRecordMap);
mCallerIdentity = CallerIdentity
.forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null);
}
@Test
public void testOnNearbyDeviceDiscovered() {
NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.build();
mDiscoveryProviderManager.onNearbyDeviceDiscovered(nearbyDeviceParcelable);
}
@Test
public void testInvalidateProviderScanMode() {
mDiscoveryProviderManager.invalidateProviderScanMode();
}
@Test
public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(true);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, never()).start();
assertThat(start).isTrue();
}
@Test
public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(true);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter())
.addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, never()).start();
assertThat(start).isTrue();
}
@Test
public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(false);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, never()).start();
assertThat(start).isFalse();
}
@Test
public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(true);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, never()).start();
assertThat(start).isTrue();
}
@Test
public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(false);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, atLeastOnce()).start();
assertThat(start).isTrue();
}
@Test
public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(null);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, never()).start();
assertThat(start).isNull();
}
@Test
public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(null);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest);
verify(mBluetoothController, atLeastOnce()).start();
assertThat(start).isTrue();
}
@Test
public void test_stopChreProvider_clearFilters() throws Exception {
// Cannot use mocked ChreDiscoveryProvider,
// so we cannot use class variable mDiscoveryProviderManager
ExecutorService executor = Executors.newSingleThreadExecutor();
DiscoveryProviderManagerLegacy manager =
new DiscoveryProviderManagerLegacy(mContext, mInjector,
mBleDiscoveryProvider,
new ChreDiscoveryProvider(
mContext,
new ChreCommunication(mInjector, mContext, executor), executor),
mScanTypeScanListenerRecordMap);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
manager.startChreProvider(List.of(getPresenceScanFilter()));
// This is an asynchronized process. The filters will be set in executor thread. So we need
// to wait for some time to get the correct result.
Thread.sleep(200);
assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
.isTrue();
assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
manager.stopChreProvider();
Thread.sleep(200);
// The filters should be cleared right after.
assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
.isFalse();
assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isEmpty();
}
@Test
public void test_restartChreProvider() throws Exception {
// Cannot use mocked ChreDiscoveryProvider,
// so we cannot use class variable mDiscoveryProviderManager
ExecutorService executor = Executors.newSingleThreadExecutor();
DiscoveryProviderManagerLegacy manager =
new DiscoveryProviderManagerLegacy(mContext, mInjector,
mBleDiscoveryProvider,
new ChreDiscoveryProvider(
mContext,
new ChreCommunication(mInjector, mContext, executor), executor),
mScanTypeScanListenerRecordMap);
ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManagerLegacy.ScanListenerRecord record =
new DiscoveryProviderManagerLegacy.ScanListenerRecord(scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
manager.startChreProvider(List.of(getPresenceScanFilter()));
// This is an asynchronized process. The filters will be set in executor thread. So we need
// to wait for some time to get the correct result.
Thread.sleep(200);
assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
.isTrue();
assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
// We want to make sure quickly restart the provider the filters should
// be reset correctly.
// See b/255922206, there can be a race condition that filters get cleared because onStop()
// get executed after onStart() if they are called from different threads.
manager.stopChreProvider();
manager.mChreDiscoveryProvider.getController().setProviderScanFilters(
List.of(getPresenceScanFilter()));
manager.startChreProvider(List.of(getPresenceScanFilter()));
Thread.sleep(200);
assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
.isTrue();
assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
// Wait for enough time
Thread.sleep(1000);
assertThat(manager.mChreDiscoveryProvider.getController().isStarted())
.isTrue();
assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
}
}

View file

@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -48,10 +49,8 @@ import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public class DiscoveryProviderManagerTest { public class DiscoveryProviderManagerTest {
@ -80,12 +79,9 @@ public class DiscoveryProviderManagerTest {
@Mock @Mock
CallerIdentity mCallerIdentity; CallerIdentity mCallerIdentity;
@Mock @Mock
DiscoveryProviderManager.ScanListenerDeathRecipient mScanListenerDeathRecipient;
@Mock
IBinder mIBinder; IBinder mIBinder;
private Executor mExecutor;
private DiscoveryProviderManager mDiscoveryProviderManager; private DiscoveryProviderManager mDiscoveryProviderManager;
private Map<IBinder, DiscoveryProviderManager.ScanListenerRecord>
mScanTypeScanListenerRecordMap;
private static PresenceScanFilter getPresenceScanFilter() { private static PresenceScanFilter getPresenceScanFilter() {
final byte[] secretId = new byte[]{1, 2, 3, 4}; final byte[] secretId = new byte[]{1, 2, 3, 4};
@ -133,16 +129,16 @@ public class DiscoveryProviderManagerTest {
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mExecutor = Executors.newSingleThreadExecutor();
when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager); when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController); when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController);
when(mChreDiscoveryProvider.getController()).thenReturn(mChreController); when(mChreDiscoveryProvider.getController()).thenReturn(mChreController);
when(mScanListener.asBinder()).thenReturn(mIBinder);
mScanTypeScanListenerRecordMap = new HashMap<>();
mDiscoveryProviderManager = mDiscoveryProviderManager =
new DiscoveryProviderManager(mContext, mInjector, new DiscoveryProviderManager(mContext, mExecutor, mInjector,
mBleDiscoveryProvider, mBleDiscoveryProvider,
mChreDiscoveryProvider, mChreDiscoveryProvider);
mScanTypeScanListenerRecordMap);
mCallerIdentity = CallerIdentity mCallerIdentity = CallerIdentity
.forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null); .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null);
} }
@ -162,55 +158,45 @@ public class DiscoveryProviderManagerTest {
@Test @Test
public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() { public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() {
reset(mBluetoothController);
when(mChreDiscoveryProvider.available()).thenReturn(true); when(mChreDiscoveryProvider.available()).thenReturn(true);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()).build(); .addScanFilter(getChreOnlyPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, never()).start(); verify(mBluetoothController, never()).start();
assertThat(start).isTrue(); assertThat(start).isTrue();
} }
@Test @Test
public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() { public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() {
reset(mBluetoothController);
when(mChreDiscoveryProvider.available()).thenReturn(true); when(mChreDiscoveryProvider.available()).thenReturn(true);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()) .addScanFilter(getChreOnlyPresenceScanFilter()).build();
.addScanFilter(getPresenceScanFilter()).build(); mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
DiscoveryProviderManager.ScanListenerRecord record =
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, never()).start(); verify(mBluetoothController, never()).start();
assertThat(start).isTrue(); assertThat(start).isTrue();
} }
@Test @Test
public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() { public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() {
reset(mBluetoothController);
when(mChreDiscoveryProvider.available()).thenReturn(false); when(mChreDiscoveryProvider.available()).thenReturn(false);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()).build(); .addScanFilter(getChreOnlyPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, never()).start(); verify(mBluetoothController, never()).start();
assertThat(start).isFalse(); assertThat(start).isFalse();
} }
@ -218,17 +204,14 @@ public class DiscoveryProviderManagerTest {
@Test @Test
public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() { public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(true); when(mChreDiscoveryProvider.available()).thenReturn(true);
reset(mBluetoothController);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build(); .addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, never()).start(); verify(mBluetoothController, never()).start();
assertThat(start).isTrue(); assertThat(start).isTrue();
} }
@ -236,17 +219,14 @@ public class DiscoveryProviderManagerTest {
@Test @Test
public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() { public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(false); when(mChreDiscoveryProvider.available()).thenReturn(false);
reset(mBluetoothController);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build(); .addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, atLeastOnce()).start(); verify(mBluetoothController, atLeastOnce()).start();
assertThat(start).isTrue(); assertThat(start).isTrue();
} }
@ -254,17 +234,14 @@ public class DiscoveryProviderManagerTest {
@Test @Test
public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() { public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(null); when(mChreDiscoveryProvider.available()).thenReturn(null);
reset(mBluetoothController);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getChreOnlyPresenceScanFilter()).build(); .addScanFilter(getChreOnlyPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, never()).start(); verify(mBluetoothController, never()).start();
assertThat(start).isNull(); assertThat(start).isNull();
} }
@ -272,17 +249,14 @@ public class DiscoveryProviderManagerTest {
@Test @Test
public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() { public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() {
when(mChreDiscoveryProvider.available()).thenReturn(null); when(mChreDiscoveryProvider.available()).thenReturn(null);
reset(mBluetoothController);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build(); .addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); Boolean start = mDiscoveryProviderManager.startProviders();
verify(mBluetoothController, atLeastOnce()).start(); verify(mBluetoothController, atLeastOnce()).start();
assertThat(start).isTrue(); assertThat(start).isTrue();
} }
@ -291,22 +265,16 @@ public class DiscoveryProviderManagerTest {
public void test_stopChreProvider_clearFilters() throws Exception { public void test_stopChreProvider_clearFilters() throws Exception {
// Cannot use mocked ChreDiscoveryProvider, // Cannot use mocked ChreDiscoveryProvider,
// so we cannot use class variable mDiscoveryProviderManager // so we cannot use class variable mDiscoveryProviderManager
ExecutorService executor = Executors.newSingleThreadExecutor();
DiscoveryProviderManager manager = DiscoveryProviderManager manager =
new DiscoveryProviderManager(mContext, mInjector, new DiscoveryProviderManager(mContext, mExecutor, mInjector,
mBleDiscoveryProvider, mBleDiscoveryProvider,
new ChreDiscoveryProvider( new ChreDiscoveryProvider(
mContext, mContext,
new ChreCommunication(mInjector, mContext, executor), executor), new ChreCommunication(mInjector, mContext, mExecutor), mExecutor));
mScanTypeScanListenerRecordMap);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build(); .addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = manager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(
scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
manager.startChreProvider(List.of(getPresenceScanFilter())); manager.startChreProvider(List.of(getPresenceScanFilter()));
// This is an asynchronized process. The filters will be set in executor thread. So we need // This is an asynchronized process. The filters will be set in executor thread. So we need
// to wait for some time to get the correct result. // to wait for some time to get the correct result.
@ -328,21 +296,17 @@ public class DiscoveryProviderManagerTest {
public void test_restartChreProvider() throws Exception { public void test_restartChreProvider() throws Exception {
// Cannot use mocked ChreDiscoveryProvider, // Cannot use mocked ChreDiscoveryProvider,
// so we cannot use class variable mDiscoveryProviderManager // so we cannot use class variable mDiscoveryProviderManager
ExecutorService executor = Executors.newSingleThreadExecutor();
DiscoveryProviderManager manager = DiscoveryProviderManager manager =
new DiscoveryProviderManager(mContext, mInjector, new DiscoveryProviderManager(mContext, mExecutor, mInjector,
mBleDiscoveryProvider, mBleDiscoveryProvider,
new ChreDiscoveryProvider( new ChreDiscoveryProvider(
mContext, mContext,
new ChreCommunication(mInjector, mContext, executor), executor), new ChreCommunication(mInjector, mContext, mExecutor), mExecutor));
mScanTypeScanListenerRecordMap);
ScanRequest scanRequest = new ScanRequest.Builder() ScanRequest scanRequest = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_PRESENCE) .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
.addScanFilter(getPresenceScanFilter()).build(); .addScanFilter(getPresenceScanFilter()).build();
DiscoveryProviderManager.ScanListenerRecord record = manager.registerScanListener(scanRequest, mScanListener, mCallerIdentity);
new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
mCallerIdentity, mScanListenerDeathRecipient);
mScanTypeScanListenerRecordMap.put(mIBinder, record);
manager.startChreProvider(List.of(getPresenceScanFilter())); manager.startChreProvider(List.of(getPresenceScanFilter()));
// This is an asynchronized process. The filters will be set in executor thread. So we need // This is an asynchronized process. The filters will be set in executor thread. So we need
// to wait for some time to get the correct result. // to wait for some time to get the correct result.