diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java index 8d35e089e6..2ac2b00214 100644 --- a/nearby/service/java/com/android/server/nearby/NearbyService.java +++ b/nearby/service/java/com/android/server/nearby/NearbyService.java @@ -45,7 +45,9 @@ import com.android.server.nearby.common.locator.LocatorContextWrapper; import com.android.server.nearby.fastpair.FastPairManager; import com.android.server.nearby.injector.Injector; 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.DiscoveryProviderManagerLegacy; import com.android.server.nearby.presence.PresenceManager; import com.android.server.nearby.provider.FastPairDataProvider; 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; public NearbyService(Context context) { mContext = context; mInjector = new SystemInjector(context); - mProviderManager = new DiscoveryProviderManager(context, mInjector); mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector); final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null); mFastPairManager = new FastPairManager(lcw); mPresenceManager = new PresenceManager(lcw); mNearbyConfiguration = new NearbyConfiguration(); + mDiscoveryProviderManager = + mNearbyConfiguration.refactorDiscoveryManager() + ? new DiscoveryProviderManager(context, mInjector) + : new DiscoveryProviderManagerLegacy(context, mInjector); } @VisibleForTesting @@ -108,7 +113,7 @@ public class NearbyService extends INearbyManager.Stub { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity); - return mProviderManager.registerScanListener(scanRequest, listener, identity); + return mDiscoveryProviderManager.registerScanListener(scanRequest, listener, identity); } @Override @@ -119,7 +124,7 @@ public class NearbyService extends INearbyManager.Stub { CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity); - mProviderManager.unregisterScanListener(listener); + mDiscoveryProviderManager.unregisterScanListener(listener); } @Override @@ -147,7 +152,7 @@ public class NearbyService extends INearbyManager.Stub { @Override 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. ((SystemInjector) mInjector).initializeContextHubManager(); } - mProviderManager.init(); + mDiscoveryProviderManager.init(); mContext.registerReceiver( mBluetoothReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryManager.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryManager.java new file mode 100644 index 0000000000..c9b9a43d48 --- /dev/null +++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryManager.java @@ -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(); +} diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java index 29b18f3bf1..4cda93a098 100644 --- a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java +++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java @@ -21,320 +21,74 @@ 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 androidx.annotation.NonNull; + 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.managers.registration.DiscoveryRegistration; 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.Collection; import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.stream.Collectors; + +import javax.annotation.concurrent.GuardedBy; /** Manages all aspects of discovery providers. */ -public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Listener { +public class DiscoveryProviderManager extends + ListenerMultiplexer implements + AbstractDiscoveryProvider.Listener, + DiscoveryManager { protected final Object mLock = new Object(); - private final Context mContext; - private final BleDiscoveryProvider mBleDiscoveryProvider; @VisibleForTesting @Nullable final ChreDiscoveryProvider mChreDiscoveryProvider; - private @ScanRequest.ScanMode int mScanMode; + private final Context mContext; + private final BleDiscoveryProvider mBleDiscoveryProvider; private final Injector mInjector; - - @GuardedBy("mLock") - private Map 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 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); - } - } - } - } + private final Executor mExecutor; public DiscoveryProviderManager(Context context, Injector injector) { + Log.v(TAG, "DiscoveryProviderManager: "); mContext = context; mBleDiscoveryProvider = new BleDiscoveryProvider(mContext, injector); - Executor executor = Executors.newSingleThreadExecutor(); - mChreDiscoveryProvider = - new ChreDiscoveryProvider( - mContext, new ChreCommunication(injector, mContext, executor), executor); - mScanTypeScanListenerRecordMap = new HashMap<>(); + mExecutor = Executors.newSingleThreadExecutor(); + mChreDiscoveryProvider = new ChreDiscoveryProvider(mContext, + new ChreCommunication(injector, mContext, mExecutor), mExecutor); mInjector = injector; } @VisibleForTesting - DiscoveryProviderManager(Context context, Injector injector, + DiscoveryProviderManager(Context context, Executor executor, Injector injector, BleDiscoveryProvider bleDiscoveryProvider, - ChreDiscoveryProvider chreDiscoveryProvider, - Map scanTypeScanListenerRecordMap) { + ChreDiscoveryProvider chreDiscoveryProvider) { mContext = context; + mExecutor = executor; mInjector = injector; mBleDiscoveryProvider = bleDiscoveryProvider; mChreDiscoveryProvider = chreDiscoveryProvider; - mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap; } - /** 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 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 scanFilters) { + private static boolean isChreOnly(Set scanFilters) { for (ScanFilter scanFilter : scanFilters) { List dataElements = ((PresenceScanFilter) scanFilter).getExtendedProperties(); @@ -355,41 +109,160 @@ public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Liste return false; } - private void startBleProvider(List 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 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 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().setProviderScanMode(mMerged.getScanMode()); + mBleDiscoveryProvider.getController().setProviderScanFilters( + new ArrayList<>(scanFilters)); mBleDiscoveryProvider.getController().start(); } } @VisibleForTesting - void startChreProvider(List scanFilters) { - Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning."); - mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters); - mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode); + @GuardedBy("mMultiplexerLock") + void startChreProvider(Collection scanFilters) { + Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning. " + mMerged); + mChreDiscoveryProvider.getController().setProviderScanFilters(new ArrayList<>(scanFilters)); + mChreDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode()); mChreDiscoveryProvider.getController().start(); } - private List getPresenceScanFilters() { - synchronized (mLock) { - List scanFilters = new ArrayList(); - for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) { - ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder); - List 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(); @@ -407,96 +280,40 @@ public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Liste @VisibleForTesting void invalidateProviderScanMode() { if (mBleDiscoveryProvider.getController().isStarted()) { - mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode); + synchronized (mMultiplexerLock) { + mBleDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode()); + } } else { - Log.d( - TAG, - "Skip invalidating BleDiscoveryProvider scan mode because the provider not " - + "started."); + Log.d(TAG, "Skip invalidating BleDiscoveryProvider scan mode because the provider not " + + "started."); } } - @VisibleForTesting - static boolean presenceFilterMatches( - NearbyDeviceParcelable device, List 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; + @Override + public MergedDiscoveryRequest mergeRegistrations( + @NonNull Collection registrations) { + MergedDiscoveryRequest.Builder builder = new MergedDiscoveryRequest.Builder(); + int scanMode = ScanRequest.SCAN_MODE_NO_POWER; + for (DiscoveryRegistration registration : registrations) { + builder.addActions(registration.getActions()); + builder.addScanFilters(registration.getPresenceScanFilters()); + Log.d(TAG, + "mergeRegistrations: type is " + registration.getScanRequest().getScanType()); + 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(); } - /** - * 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); - } - } - - @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); - } + @Override + public void onMergedRegistrationsUpdated() { + invalidateProviderScanMode(); } } diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java new file mode 100644 index 0000000000..e68d22a97e --- /dev/null +++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java @@ -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 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 scanTypeScanListenerRecordMap) { + mContext = context; + mInjector = injector; + mBleDiscoveryProvider = bleDiscoveryProvider; + mChreDiscoveryProvider = chreDiscoveryProvider; + mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap; + } + + private static boolean isChreOnly(List scanFilters) { + for (ScanFilter scanFilter : scanFilters) { + List 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 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 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 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 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 scanFilters) { + Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning."); + mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters); + mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode); + mChreDiscoveryProvider.getController().start(); + } + + private List getPresenceScanFilters() { + synchronized (mLock) { + List scanFilters = new ArrayList(); + for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) { + ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder); + List 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); + } + } +} diff --git a/nearby/service/java/com/android/server/nearby/managers/registration/DiscoveryRegistration.java b/nearby/service/java/com/android/server/nearby/managers/registration/DiscoveryRegistration.java index 1cb70ac7c0..bef319eca4 100644 --- a/nearby/service/java/com/android/server/nearby/managers/registration/DiscoveryRegistration.java +++ b/nearby/service/java/com/android/server/nearby/managers/registration/DiscoveryRegistration.java @@ -243,6 +243,11 @@ public class DiscoveryRegistration extends BinderListenerRegistration reportError(@ScanCallback.ErrorCode int errorCode) { + return listener -> listener.onError(errorCode); + } + @Nullable ListenerOperation reportResult(@DiscoveryResult int result, NearbyDeviceParcelable device, @Nullable Runnable successReportCallback) { @@ -250,7 +255,7 @@ public class DiscoveryRegistration extends BinderListenerRegistration listener.onError(ScanCallback.ERROR_PERMISSION_DENIED); + return reportError(ScanCallback.ERROR_PERMISSION_DENIED); } return new ListenerOperation<>() { diff --git a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java index 0d46d3eeea..90c85ab29a 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java @@ -36,6 +36,7 @@ import android.app.UiAutomation; import android.content.Context; import android.nearby.IScanListener; import android.nearby.ScanRequest; +import android.os.IBinder; import android.provider.DeviceConfig; import androidx.test.platform.app.InstrumentationRegistry; @@ -61,10 +62,14 @@ public final class NearbyServiceTest { private IScanListener mScanListener; @Mock private AppOpsManager mMockAppOpsManager; + @Mock + private IBinder mIBinder; @Before public void setUp() { initMocks(this); + when(mScanListener.asBinder()).thenReturn(mIBinder); + mUiAutomation.adoptShellPermissionIdentity( READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG, BLUETOOTH_PRIVILEGED); mContext = InstrumentationRegistry.getInstrumentation().getContext(); diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java new file mode 100644 index 0000000000..aa0dad328c --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java @@ -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 + 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(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java index 542ceed2af..7ecf631df3 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java @@ -23,6 +23,7 @@ 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.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -48,10 +49,8 @@ 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.Executor; import java.util.concurrent.Executors; public class DiscoveryProviderManagerTest { @@ -80,12 +79,9 @@ public class DiscoveryProviderManagerTest { @Mock CallerIdentity mCallerIdentity; @Mock - DiscoveryProviderManager.ScanListenerDeathRecipient mScanListenerDeathRecipient; - @Mock IBinder mIBinder; + private Executor mExecutor; private DiscoveryProviderManager mDiscoveryProviderManager; - private Map - mScanTypeScanListenerRecordMap; private static PresenceScanFilter getPresenceScanFilter() { final byte[] secretId = new byte[]{1, 2, 3, 4}; @@ -133,16 +129,16 @@ public class DiscoveryProviderManagerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); + mExecutor = Executors.newSingleThreadExecutor(); when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager); when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController); when(mChreDiscoveryProvider.getController()).thenReturn(mChreController); + when(mScanListener.asBinder()).thenReturn(mIBinder); - mScanTypeScanListenerRecordMap = new HashMap<>(); mDiscoveryProviderManager = - new DiscoveryProviderManager(mContext, mInjector, + new DiscoveryProviderManager(mContext, mExecutor, mInjector, mBleDiscoveryProvider, - mChreDiscoveryProvider, - mScanTypeScanListenerRecordMap); + mChreDiscoveryProvider); mCallerIdentity = CallerIdentity .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null); } @@ -162,55 +158,45 @@ public class DiscoveryProviderManagerTest { @Test public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() { + reset(mBluetoothController); when(mChreDiscoveryProvider.available()).thenReturn(true); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getChreOnlyPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, never()).start(); assertThat(start).isTrue(); } @Test public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() { + reset(mBluetoothController); when(mChreDiscoveryProvider.available()).thenReturn(true); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) - .addScanFilter(getChreOnlyPresenceScanFilter()) - .addScanFilter(getPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, never()).start(); assertThat(start).isTrue(); } @Test public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() { + reset(mBluetoothController); when(mChreDiscoveryProvider.available()).thenReturn(false); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getChreOnlyPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, never()).start(); assertThat(start).isFalse(); } @@ -218,17 +204,14 @@ public class DiscoveryProviderManagerTest { @Test public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() { when(mChreDiscoveryProvider.available()).thenReturn(true); + reset(mBluetoothController); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, never()).start(); assertThat(start).isTrue(); } @@ -236,17 +219,14 @@ public class DiscoveryProviderManagerTest { @Test public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() { when(mChreDiscoveryProvider.available()).thenReturn(false); + reset(mBluetoothController); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, atLeastOnce()).start(); assertThat(start).isTrue(); } @@ -254,17 +234,14 @@ public class DiscoveryProviderManagerTest { @Test public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() { when(mChreDiscoveryProvider.available()).thenReturn(null); + reset(mBluetoothController); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getChreOnlyPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, never()).start(); assertThat(start).isNull(); } @@ -272,17 +249,14 @@ public class DiscoveryProviderManagerTest { @Test public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() { when(mChreDiscoveryProvider.available()).thenReturn(null); + reset(mBluetoothController); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); - Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + Boolean start = mDiscoveryProviderManager.startProviders(); verify(mBluetoothController, atLeastOnce()).start(); assertThat(start).isTrue(); } @@ -291,22 +265,16 @@ public class DiscoveryProviderManagerTest { public void test_stopChreProvider_clearFilters() throws Exception { // Cannot use mocked ChreDiscoveryProvider, // so we cannot use class variable mDiscoveryProviderManager - ExecutorService executor = Executors.newSingleThreadExecutor(); DiscoveryProviderManager manager = - new DiscoveryProviderManager(mContext, mInjector, + new DiscoveryProviderManager(mContext, mExecutor, mInjector, mBleDiscoveryProvider, new ChreDiscoveryProvider( mContext, - new ChreCommunication(mInjector, mContext, executor), executor), - mScanTypeScanListenerRecordMap); + new ChreCommunication(mInjector, mContext, mExecutor), mExecutor)); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord( - scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + manager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); 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. @@ -328,21 +296,17 @@ public class DiscoveryProviderManagerTest { public void test_restartChreProvider() throws Exception { // Cannot use mocked ChreDiscoveryProvider, // so we cannot use class variable mDiscoveryProviderManager - ExecutorService executor = Executors.newSingleThreadExecutor(); DiscoveryProviderManager manager = - new DiscoveryProviderManager(mContext, mInjector, + new DiscoveryProviderManager(mContext, mExecutor, mInjector, mBleDiscoveryProvider, new ChreDiscoveryProvider( mContext, - new ChreCommunication(mInjector, mContext, executor), executor), - mScanTypeScanListenerRecordMap); + new ChreCommunication(mInjector, mContext, mExecutor), mExecutor)); ScanRequest scanRequest = new ScanRequest.Builder() .setScanType(SCAN_TYPE_NEARBY_PRESENCE) .addScanFilter(getPresenceScanFilter()).build(); - DiscoveryProviderManager.ScanListenerRecord record = - new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener, - mCallerIdentity, mScanListenerDeathRecipient); - mScanTypeScanListenerRecordMap.put(mIBinder, record); + manager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + 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.