From e7a349bec4453e7d482b8cef0aa31107317be931 Mon Sep 17 00:00:00 2001 From: Chen Wang Date: Mon, 28 Jul 2025 19:25:05 +0000 Subject: [PATCH] Introduce BLE advertiser wrapper for unit tests. This removes Nearby unittest dependences on BLE hardware and get rid of flaky tests periodically reported from release testing. Bug: 430923101 Test: atest NearbyUnitTests Flag: EXEMPT mainline Change-Id: If5527a47efa7513c463b50ec5529c8c9eae74300 --- .../android/server/nearby/NearbyService.java | 17 +++ .../BluetoothLeAdvertiserWrapper.java | 136 ++++++++++++++++++ .../server/nearby/injector/Injector.java | 3 + .../nearby/provider/BleBroadcastProvider.java | 92 ++++++------ .../provider/BleBroadcastProviderTest.java | 43 ++---- .../provider/BleDiscoveryProviderTest.java | 6 + 6 files changed, 218 insertions(+), 79 deletions(-) create mode 100644 nearby/service/java/com/android/server/nearby/injector/BluetoothLeAdvertiserWrapper.java diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java index 1575f0776a..af51956e1a 100644 --- a/nearby/service/java/com/android/server/nearby/NearbyService.java +++ b/nearby/service/java/com/android/server/nearby/NearbyService.java @@ -41,6 +41,7 @@ import android.nearby.aidl.IOffloadCallback; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.nearby.injector.BluetoothLeAdvertiserWrapper; import com.android.server.nearby.injector.Injector; import com.android.server.nearby.managers.BluetoothFinderManager; import com.android.server.nearby.managers.BroadcastProviderManager; @@ -195,6 +196,7 @@ public class NearbyService extends INearbyManager.Stub { if (mInjector instanceof SystemInjector) { // The nearby service must be functioning after this boot phase. ((SystemInjector) mInjector).initializeBluetoothAdapter(); + ((SystemInjector) mInjector).initializeBluetoothLeAdvertiserWrapper(); // Initialize ContextManager for CHRE scan. ((SystemInjector) mInjector).initializeContextHubManager(); } @@ -227,6 +229,8 @@ public class NearbyService extends INearbyManager.Stub { private static final class SystemInjector implements Injector { private final Context mContext; @Nullable private BluetoothAdapter mBluetoothAdapter; + + @Nullable private BluetoothLeAdvertiserWrapper mBluetoothLeAdvertiserWrapper; @Nullable private ContextHubManager mContextHubManager; @Nullable private AppOpsManager mAppOpsManager; @@ -240,6 +244,12 @@ public class NearbyService extends INearbyManager.Stub { return mBluetoothAdapter; } + @Override + @Nullable + public BluetoothLeAdvertiserWrapper getBluetoothLeAdvertiserWrapper() { + return mBluetoothLeAdvertiserWrapper; + }; + @Override @Nullable public ContextHubManager getContextHubManager() { @@ -263,6 +273,13 @@ public class NearbyService extends INearbyManager.Stub { mBluetoothAdapter = manager.getAdapter(); } + synchronized void initializeBluetoothLeAdvertiserWrapper() { + if (mBluetoothLeAdvertiserWrapper != null) { + return; + } + mBluetoothLeAdvertiserWrapper = new BluetoothLeAdvertiserWrapper(mContext); + } + synchronized void initializeContextHubManager() { if (mContextHubManager != null) { return; diff --git a/nearby/service/java/com/android/server/nearby/injector/BluetoothLeAdvertiserWrapper.java b/nearby/service/java/com/android/server/nearby/injector/BluetoothLeAdvertiserWrapper.java new file mode 100644 index 0000000000..4ef5510c0b --- /dev/null +++ b/nearby/service/java/com/android/server/nearby/injector/BluetoothLeAdvertiserWrapper.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2025 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.injector; + +import static com.android.server.nearby.NearbyService.TAG; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.AdvertiseCallback; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertisingSetCallback; +import android.bluetooth.le.AdvertisingSetParameters; +import android.bluetooth.le.BluetoothLeAdvertiser; +import android.bluetooth.le.PeriodicAdvertisingParameters; +import android.content.Context; +import android.util.Log; + +/** + * Wrapper for BluetoothLeAdvertiser. + * + * This class proxies all functions to BluetoothLeAdvertiser. + * It will be mocked for unit tests. + * + * @see BluetoothLeAdvertiser for the details of each public function APIs. + */ +public class BluetoothLeAdvertiserWrapper { + private final BluetoothAdapter mAdapter; + private final BluetoothLeAdvertiser mLeAdvertiser; + + /** + * Returns a wrapper of BluetoothLeAdvertiser. + * + * @return an object of BluetoothLeAdvertiserWrapper + * @see BluetoothLeAdvertiser + */ + public BluetoothLeAdvertiserWrapper(Context context) { + BluetoothManager manager = context.getSystemService(BluetoothManager.class); + if (manager == null) { + mAdapter = null; + mLeAdvertiser = null; + return; + } + mAdapter = manager.getAdapter(); + mLeAdvertiser = mAdapter == null ? null : mAdapter.getBluetoothLeAdvertiser(); + } + + /** + * Starts BLE advertising. + * + * @see BluetoothLeAdvertiser + */ + public void startAdvertising( + AdvertiseSettings settings, + AdvertiseData advertiseData, + final AdvertiseCallback callback) { + if (mLeAdvertiser == null) { + Log.e(TAG, "Call startAdvertising from null advertiser."); + } + mLeAdvertiser.startAdvertising(settings, advertiseData, callback); + } + + /** + * Starts BLE advertisingSet. + * + * @see BluetoothLeAdvertiser + */ + public void startAdvertisingSet( + AdvertisingSetParameters parameters, + AdvertiseData advertiseData, + AdvertiseData scanResponse, + PeriodicAdvertisingParameters periodicParameters, + AdvertiseData periodicData, + AdvertisingSetCallback callback) { + if (mLeAdvertiser == null) { + Log.e(TAG, "Call startAdvertisingSet from null advertiser."); + } + mLeAdvertiser.startAdvertisingSet( + parameters, + advertiseData, + scanResponse, + periodicParameters, + periodicData, + callback); + } + + /** + * Stops BLE advertising. + * + * @see BluetoothLeAdvertiser + */ + public void stopAdvertising(final AdvertiseCallback callback) { + if (mLeAdvertiser == null) { + Log.e(TAG, "Call startAdvertising from null advertiser."); + } + mLeAdvertiser.stopAdvertising(callback); + } + + /** + * Stops BLE advertisingSet. + * + * @see BluetoothLeAdvertiser + */ + public void stopAdvertisingSet(AdvertisingSetCallback callback) { + if (mLeAdvertiser == null) { + Log.e(TAG, "Call startAdvertisingSet from null advertiser."); + } + mLeAdvertiser.stopAdvertisingSet(callback); + } + + /** + * Checks if BLE supports extended advertising. + * + * @return true if extended advertisng is supported. + */ + public Boolean isLeExtendedAdvertisingSupported() { + if (mAdapter == null) { + Log.e(TAG, "Checks extended BLE support from null BLE adapter."); + } + return mAdapter.isLeExtendedAdvertisingSupported(); + } +} diff --git a/nearby/service/java/com/android/server/nearby/injector/Injector.java b/nearby/service/java/com/android/server/nearby/injector/Injector.java index 3152ee655b..c2b75ef955 100644 --- a/nearby/service/java/com/android/server/nearby/injector/Injector.java +++ b/nearby/service/java/com/android/server/nearby/injector/Injector.java @@ -29,6 +29,9 @@ public interface Injector { /** Get the BluetoothAdapter for BleDiscoveryProvider to scan. */ BluetoothAdapter getBluetoothAdapter(); + /** Get the BluetoothLeAdvertiserWrapper to start BLE broadcast. */ + BluetoothLeAdvertiserWrapper getBluetoothLeAdvertiserWrapper(); + /** Get the ContextHubManagerAdapter for ChreDiscoveryProvider to scan. */ ContextHubManager getContextHubManager(); diff --git a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java index ac381b8b56..e0b08f5971 100644 --- a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java +++ b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java @@ -19,19 +19,18 @@ package com.android.server.nearby.provider; import static com.android.server.nearby.NearbyService.TAG; import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.AdvertisingSet; import android.bluetooth.le.AdvertisingSetCallback; import android.bluetooth.le.AdvertisingSetParameters; -import android.bluetooth.le.BluetoothLeAdvertiser; import android.nearby.BroadcastCallback; import android.nearby.BroadcastRequest; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.nearby.injector.BluetoothLeAdvertiserWrapper; import com.android.server.nearby.injector.Injector; import java.util.concurrent.Executor; @@ -55,6 +54,7 @@ public class BleBroadcastProvider extends AdvertiseCallback { private boolean mIsAdvertising; @VisibleForTesting AdvertisingSetCallback mAdvertisingSetCallback; + public BleBroadcastProvider(Injector injector, Executor executor) { mInjector = injector; mExecutor = executor; @@ -70,49 +70,45 @@ public class BleBroadcastProvider extends AdvertiseCallback { stop(); } boolean advertiseStarted = false; - BluetoothAdapter adapter = mInjector.getBluetoothAdapter(); - if (adapter != null) { - BluetoothLeAdvertiser bluetoothLeAdvertiser = - mInjector.getBluetoothAdapter().getBluetoothLeAdvertiser(); - if (bluetoothLeAdvertiser != null) { - advertiseStarted = true; - AdvertiseData advertiseData = - new AdvertiseData.Builder() - .addServiceData(PRESENCE_UUID, advertisementPackets).build(); - try { - mBroadcastListener = listener; - switch (version) { - case BroadcastRequest.PRESENCE_VERSION_V0: - bluetoothLeAdvertiser.startAdvertising(getAdvertiseSettings(), - advertiseData, this); - Log.v(TAG, "Start to broadcast V0 advertisement."); - break; - case BroadcastRequest.PRESENCE_VERSION_V1: - if (adapter.isLeExtendedAdvertisingSupported()) { - if (mAdvertisingSetCallback == null) { - mAdvertisingSetCallback = getAdvertisingSetCallback(); - } - bluetoothLeAdvertiser.startAdvertisingSet( - getAdvertisingSetParameters(), - advertiseData, - null, null, null, mAdvertisingSetCallback); - Log.v(TAG, "Start to broadcast V1 advertisement."); - } else { - Log.w(TAG, "Failed to start advertising set because the chipset" - + " does not supports LE Extended Advertising feature."); - advertiseStarted = false; + BluetoothLeAdvertiserWrapper advertiser = mInjector.getBluetoothLeAdvertiserWrapper(); + if (advertiser != null) { + advertiseStarted = true; + AdvertiseData advertiseData = + new AdvertiseData.Builder() + .addServiceData(PRESENCE_UUID, advertisementPackets).build(); + try { + mBroadcastListener = listener; + switch (version) { + case BroadcastRequest.PRESENCE_VERSION_V0: + advertiser.startAdvertising(getAdvertiseSettings(), + advertiseData, this); + Log.v(TAG, "Start to broadcast V0 advertisement."); + break; + case BroadcastRequest.PRESENCE_VERSION_V1: + if (advertiser.isLeExtendedAdvertisingSupported()) { + if (mAdvertisingSetCallback == null) { + mAdvertisingSetCallback = getAdvertisingSetCallback(); } - break; - default: - Log.w(TAG, "Failed to start advertising set because the advertisement" - + " is wrong."); + advertiser.startAdvertisingSet( + getAdvertisingSetParameters(), + advertiseData, + null, null, null, mAdvertisingSetCallback); + Log.v(TAG, "Start to broadcast V1 advertisement."); + } else { + Log.w(TAG, "Failed to start advertising set because the chipset" + + " does not supports LE Extended Advertising feature."); advertiseStarted = false; - } - } catch (NullPointerException | IllegalStateException | SecurityException - | IllegalArgumentException e) { - Log.w(TAG, "Failed to start advertising.", e); - advertiseStarted = false; + } + break; + default: + Log.w(TAG, "Failed to start advertising set because the advertisement" + + " is wrong."); + advertiseStarted = false; } + } catch (NullPointerException | IllegalStateException | SecurityException + | IllegalArgumentException e) { + Log.w(TAG, "Failed to start advertising.", e); + advertiseStarted = false; } } if (!advertiseStarted) { @@ -125,14 +121,10 @@ public class BleBroadcastProvider extends AdvertiseCallback { */ public void stop() { if (mIsAdvertising) { - BluetoothAdapter adapter = mInjector.getBluetoothAdapter(); - if (adapter != null) { - BluetoothLeAdvertiser bluetoothLeAdvertiser = - mInjector.getBluetoothAdapter().getBluetoothLeAdvertiser(); - if (bluetoothLeAdvertiser != null) { - bluetoothLeAdvertiser.stopAdvertising(this); - bluetoothLeAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback); - } + BluetoothLeAdvertiserWrapper advertiser = mInjector.getBluetoothLeAdvertiserWrapper(); + if (advertiser != null) { + advertiser.stopAdvertising(this); + advertiser.stopAdvertisingSet(mAdvertisingSetCallback); } mBroadcastListener = null; mIsAdvertising = false; diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java index 0b1a742856..71118db3a9 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java @@ -21,19 +21,14 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import android.app.AppOpsManager; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothManager; import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.AdvertisingSetCallback; -import android.content.Context; -import android.hardware.location.ContextHubManager; import android.nearby.BroadcastCallback; import android.nearby.BroadcastRequest; -import androidx.test.core.app.ApplicationProvider; - +import com.android.server.nearby.injector.BluetoothLeAdvertiserWrapper; import com.android.server.nearby.injector.Injector; import com.google.common.util.concurrent.MoreExecutors; @@ -42,6 +37,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -52,14 +48,22 @@ public class BleBroadcastProviderTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + @Mock + private Injector mInjector; + + @Mock + private BluetoothLeAdvertiserWrapper mBluetoothLeAdvertiserWrapper; + @Mock private BleBroadcastProvider.BroadcastListener mBroadcastListener; private BleBroadcastProvider mBleBroadcastProvider; @Before public void setUp() { - mBleBroadcastProvider = new BleBroadcastProvider(new TestInjector(), - MoreExecutors.directExecutor()); + MockitoAnnotations.initMocks(this); + when(mInjector.getBluetoothLeAdvertiserWrapper()).thenReturn(mBluetoothLeAdvertiserWrapper); + when(mBluetoothLeAdvertiserWrapper.isLeExtendedAdvertisingSupported()).thenReturn(true); + mBleBroadcastProvider = new BleBroadcastProvider(mInjector, MoreExecutors.directExecutor()); } @Test @@ -114,24 +118,5 @@ public class BleBroadcastProviderTest { public void testStop() { mBleBroadcastProvider.stop(); } - - private static class TestInjector implements Injector { - - @Override - public BluetoothAdapter getBluetoothAdapter() { - Context context = ApplicationProvider.getApplicationContext(); - BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); - return bluetoothManager.getAdapter(); - } - - @Override - public ContextHubManager getContextHubManager() { - return null; - } - - @Override - public AppOpsManager getAppOpsManager() { - return null; - } - } } + diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java index 2d8bd632d9..f7afbf75db 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java @@ -40,6 +40,7 @@ import android.nearby.ScanFilter; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.nearby.injector.BluetoothLeAdvertiserWrapper; import com.android.server.nearby.injector.Injector; import org.junit.Before; @@ -122,6 +123,11 @@ public final class BleDiscoveryProviderTest { return mBluetoothAdapter; } + @Override + public BluetoothLeAdvertiserWrapper getBluetoothLeAdvertiserWrapper() { + return null; + }; + @Override public ContextHubManager getContextHubManager() { return null;