Merge "Implement Presence Broadcast." into tm-dev
diff --git a/nearby/framework/java/android/nearby/BroadcastRequest.aidl b/nearby/framework/java/android/nearby/BroadcastRequest.aidl
new file mode 100644
index 0000000..53f7d42
--- /dev/null
+++ b/nearby/framework/java/android/nearby/BroadcastRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+parcelable BroadcastRequest;
diff --git a/nearby/framework/java/android/nearby/IBroadcastListener.aidl b/nearby/framework/java/android/nearby/IBroadcastListener.aidl
new file mode 100644
index 0000000..98c7e17
--- /dev/null
+++ b/nearby/framework/java/android/nearby/IBroadcastListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+/**
+ * Callback when brodacast status changes.
+ *
+ * {@hide}
+ */
+oneway interface IBroadcastListener {
+ /** Called when the broadcast status changes. */
+ void onStatusChanged(int status);
+}
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 4fff563..91dd485 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -16,7 +16,9 @@
package android.nearby;
+import android.nearby.IBroadcastListener;
import android.nearby.IScanListener;
+import android.nearby.BroadcastRequest;
import android.nearby.ScanRequest;
/**
@@ -29,4 +31,8 @@
void registerScanListener(in ScanRequest scanRequest, in IScanListener listener);
void unregisterScanListener(in IScanListener listener);
+
+ void startBroadcast(in BroadcastRequest broadcastRequest, in IBroadcastListener callback);
+
+ void stopBroadcast(in IBroadcastListener callback);
}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index a217677..211ec34 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -59,6 +59,10 @@
@GuardedBy("sScanListeners")
private static final WeakHashMap<ScanCallback, WeakReference<ScanListenerTransport>>
sScanListeners = new WeakHashMap<>();
+ @GuardedBy("sBroadcastListeners")
+ private static final WeakHashMap<BroadcastCallback, WeakReference<BroadcastListenerTransport>>
+ sBroadcastListeners = new WeakHashMap<>();
+
private final INearbyManager mService;
/**
@@ -157,7 +161,23 @@
*/
public void startBroadcast(@NonNull BroadcastRequest broadcastRequest,
@CallbackExecutor @NonNull Executor executor, @NonNull BroadcastCallback callback) {
- // TODO(b/218187205): implement broadcast.
+ try {
+ synchronized (sBroadcastListeners) {
+ WeakReference<BroadcastListenerTransport> reference = sBroadcastListeners.get(
+ callback);
+ BroadcastListenerTransport transport = reference != null ? reference.get() : null;
+ if (transport == null) {
+ transport = new BroadcastListenerTransport(callback, executor);
+ } else {
+ Preconditions.checkState(transport.isRegistered());
+ transport.setExecutor(executor);
+ }
+ mService.startBroadcast(broadcastRequest, transport);
+ sBroadcastListeners.put(callback, new WeakReference<>(transport));
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -167,7 +187,19 @@
*/
@SuppressLint("ExecutorRegistration")
public void stopBroadcast(@NonNull BroadcastCallback callback) {
- // TODO(b/218187205): implement broadcast.
+ try {
+ synchronized (sBroadcastListeners) {
+ WeakReference<BroadcastListenerTransport> reference = sBroadcastListeners.remove(
+ callback);
+ BroadcastListenerTransport transport = reference != null ? reference.get() : null;
+ if (transport != null) {
+ transport.unregister();
+ mService.stopBroadcast(transport);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -248,4 +280,34 @@
toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
}
}
+
+ private static class BroadcastListenerTransport extends IBroadcastListener.Stub {
+ private volatile @Nullable BroadcastCallback mBroadcastCallback;
+ private Executor mExecutor;
+
+ BroadcastListenerTransport(BroadcastCallback broadcastCallback,
+ @CallbackExecutor Executor executor) {
+ mBroadcastCallback = broadcastCallback;
+ mExecutor = executor;
+ }
+
+ void setExecutor(Executor executor) {
+ Preconditions.checkArgument(
+ executor != null, "invalid null executor");
+ mExecutor = executor;
+ }
+
+ boolean isRegistered() {
+ return mBroadcastCallback != null;
+ }
+
+ void unregister() {
+ mBroadcastCallback = null;
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ mExecutor.execute(()-> mBroadcastCallback.onStatus(status));
+ }
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
new file mode 100644
index 0000000..8fdac87
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.provider.DeviceConfig;
+
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * A utility class for encapsulating Nearby feature flag configurations.
+ */
+public class NearbyConfiguration {
+
+ /**
+ * Flag use to enable presence legacy broadcast.
+ */
+ public static final String NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY =
+ "nearby_enable_presence_broadcast_legacy";
+
+ private boolean mEnablePresenceBroadcastLegacy;
+
+ public NearbyConfiguration() {
+ mEnablePresenceBroadcastLegacy = getDeviceConfigBoolean(
+ NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, false /* defaultValue */);
+
+ }
+
+ /**
+ * Returns whether broadcasting legacy presence spec is enabled.
+ */
+ public boolean isPresenceBroadcastLegacyEnabled() {
+ return mEnablePresenceBroadcastLegacy;
+ }
+
+ private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
+ final String value = getDeviceConfigProperty(name);
+ return value != null ? Boolean.parseBoolean(value) : defaultValue;
+ }
+
+ @VisibleForTesting
+ protected String getDeviceConfigProperty(String name) {
+ return DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TETHERING, name);
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 6d149fc..74b327a 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -27,6 +27,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.location.ContextHubManager;
+import android.nearby.BroadcastRequest;
+import android.nearby.IBroadcastListener;
import android.nearby.INearbyManager;
import android.nearby.IScanListener;
import android.nearby.ScanRequest;
@@ -38,6 +40,7 @@
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.presence.ChreCommunication;
import com.android.server.nearby.presence.PresenceManager;
+import com.android.server.nearby.provider.BroadcastProviderManager;
import com.android.server.nearby.provider.DiscoveryProviderManager;
import com.android.server.nearby.provider.FastPairDataProvider;
@@ -71,11 +74,13 @@
}
};
private DiscoveryProviderManager mProviderManager;
+ private BroadcastProviderManager mBroadcastProviderManager;
public NearbyService(Context context) {
mContext = context;
mSystemInjector = new SystemInjector(context);
mProviderManager = new DiscoveryProviderManager(context, mSystemInjector);
+ mBroadcastProviderManager = new BroadcastProviderManager(context, mSystemInjector);
final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
mFastPairManager = new FastPairManager(lcw);
mPresenceManager =
@@ -103,6 +108,16 @@
mProviderManager.unregisterScanListener(listener);
}
+ @Override
+ public void startBroadcast(BroadcastRequest broadcastRequest, IBroadcastListener listener) {
+ mBroadcastProviderManager.startBroadcast(broadcastRequest, listener);
+ }
+
+ @Override
+ public void stopBroadcast(IBroadcastListener listener) {
+ mBroadcastProviderManager.stopBroadcast(listener);
+ }
+
/**
* Called by the service initializer.
*
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
new file mode 100644
index 0000000..40fc46f
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 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.provider;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.nearby.BroadcastCallback;
+import android.os.ParcelUuid;
+
+import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.PresenceConstants;
+
+/**
+ * A provider for Bluetooth Low Energy advertisement.
+ */
+public class BleBroadcastProvider extends AdvertiseCallback {
+
+ /**
+ * Listener for Broadcast status changes.
+ */
+ interface BroadcastListener {
+ void onStatusChanged(int status);
+ }
+
+ private final Injector mInjector;
+ private BroadcastListener mBroadcastListener;
+ private boolean mIsAdvertising;
+
+ BleBroadcastProvider(Injector injector) {
+ mInjector = injector;
+ }
+
+ void start(byte[] advertisementPackets, BroadcastListener listener) {
+
+ if (mIsAdvertising) {
+ stop();
+ }
+ boolean advertiseStarted = false;
+ BluetoothAdapter adapter = mInjector.getBluetoothAdapter();
+ if (adapter != null) {
+ BluetoothLeAdvertiser bluetoothLeAdvertiser =
+ mInjector.getBluetoothAdapter().getBluetoothLeAdvertiser();
+ if (bluetoothLeAdvertiser != null) {
+ advertiseStarted = true;
+ AdvertiseSettings settings =
+ new AdvertiseSettings.Builder()
+ .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
+ .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
+ .setConnectable(true)
+ .build();
+ AdvertiseData advertiseData =
+ new AdvertiseData.Builder()
+ .addServiceData(new ParcelUuid(PresenceConstants.PRESENCE_UUID),
+ advertisementPackets).build();
+
+ try {
+ mBroadcastListener = listener;
+ bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, this);
+ } catch (NullPointerException | IllegalStateException | SecurityException e) {
+ advertiseStarted = false;
+ }
+ }
+ }
+ if (!advertiseStarted) {
+ listener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
+ }
+ }
+
+ void stop() {
+ if (mIsAdvertising) {
+ BluetoothAdapter adapter = mInjector.getBluetoothAdapter();
+ if (adapter != null) {
+ BluetoothLeAdvertiser bluetoothLeAdvertiser =
+ mInjector.getBluetoothAdapter().getBluetoothLeAdvertiser();
+ if (bluetoothLeAdvertiser != null) {
+ bluetoothLeAdvertiser.stopAdvertising(this);
+ }
+ }
+ mBroadcastListener = null;
+ mIsAdvertising = false;
+ }
+ }
+
+ @Override
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ if (mBroadcastListener != null) {
+ mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_OK);
+ }
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ if (mBroadcastListener != null) {
+ mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
new file mode 100644
index 0000000..6a0f637
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 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.provider;
+
+import android.content.Context;
+import android.nearby.BroadcastCallback;
+import android.nearby.BroadcastRequest;
+import android.nearby.IBroadcastListener;
+import android.nearby.PresenceBroadcastRequest;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.NearbyConfiguration;
+import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.FastAdvertisement;
+import com.android.server.nearby.util.ForegroundThread;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A manager for nearby broadcasts.
+ */
+public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastListener {
+
+ private static final String TAG = "BroadcastProvider";
+
+ private final Object mLock;
+ private final BleBroadcastProvider mBleBroadcastProvider;
+ private final Executor mExecutor;
+ private final NearbyConfiguration mNearbyConfiguration;
+
+ private IBroadcastListener mBroadcastListener;
+
+ public BroadcastProviderManager(Context context, Injector injector) {
+ this(ForegroundThread.getExecutor(), new BleBroadcastProvider(injector));
+ }
+
+ @VisibleForTesting
+ BroadcastProviderManager(Executor executor, BleBroadcastProvider bleBroadcastProvider) {
+ mExecutor = executor;
+ mBleBroadcastProvider = bleBroadcastProvider;
+ mLock = new Object();
+ mNearbyConfiguration = new NearbyConfiguration();
+ mBroadcastListener = null;
+ }
+
+ /**
+ * Starts a nearby broadcast, the callback is sent through the given listener.
+ */
+ public void startBroadcast(BroadcastRequest broadcastRequest, IBroadcastListener listener) {
+ synchronized (mLock) {
+ if (!mNearbyConfiguration.isPresenceBroadcastLegacyEnabled()) {
+ reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+ return;
+ }
+ if (broadcastRequest.getType() != BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE) {
+ reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+ return;
+ }
+ PresenceBroadcastRequest presenceBroadcastRequest =
+ (PresenceBroadcastRequest) broadcastRequest;
+ if (presenceBroadcastRequest.getVersion() != BroadcastRequest.PRESENCE_VERSION_V0) {
+ reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+ return;
+ }
+ FastAdvertisement fastAdvertisement = FastAdvertisement.createFromRequest(
+ presenceBroadcastRequest);
+ byte[] advertisementPackets = fastAdvertisement.toBytes();
+ mBroadcastListener = listener;
+ mExecutor.execute(() -> {
+ mBleBroadcastProvider.start(advertisementPackets, this);
+ });
+ }
+ }
+
+ /**
+ * Stops the nearby broadcast.
+ */
+ public void stopBroadcast(IBroadcastListener listener) {
+ synchronized (mLock) {
+ if (!mNearbyConfiguration.isPresenceBroadcastLegacyEnabled()) {
+ reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+ return;
+ }
+ mBroadcastListener = null;
+ mExecutor.execute(() -> mBleBroadcastProvider.stop());
+ }
+ }
+
+ @Override
+ public void onStatusChanged(int status) {
+ IBroadcastListener listener = null;
+ synchronized (mLock) {
+ listener = mBroadcastListener;
+ }
+ // Don't invoke callback while holding the local lock, as this could cause deadlock.
+ if (listener != null) {
+ reportBroadcastStatus(listener, status);
+ }
+ }
+
+ private void reportBroadcastStatus(IBroadcastListener listener, int status) {
+ try {
+ listener.onStatusChanged(status);
+ } catch (RemoteException exception) {
+ Log.e(TAG, "remote exception when reporting status");
+ }
+ }
+}
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 c20cf22..faa3016 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
@@ -16,8 +16,11 @@
package com.android.server.nearby;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+
import static org.mockito.MockitoAnnotations.initMocks;
+import android.app.UiAutomation;
import android.content.Context;
import android.nearby.IScanListener;
import android.nearby.ScanRequest;
@@ -33,13 +36,16 @@
private Context mContext;
private NearbyService mService;
private ScanRequest mScanRequest;
+ private UiAutomation mUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
@Mock
private IScanListener mScanListener;
@Before
public void setup() {
initMocks(this);
-
+ mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG);
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mService = new NearbyService(mContext);
mScanRequest = createScanRequest();
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
new file mode 100644
index 0000000..3467407
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.provider;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.AdvertiseSettings;
+import android.content.Context;
+import android.nearby.BroadcastCallback;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.nearby.injector.ContextHubManagerAdapter;
+import com.android.server.nearby.injector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Unit test for {@link BleBroadcastProvider}.
+ */
+public class BleBroadcastProviderTest {
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+
+ @Mock
+ private BleBroadcastProvider.BroadcastListener mBroadcastListener;
+ private BleBroadcastProvider mBleBroadcastProvider;
+
+ @Before
+ public void setUp() {
+ mBleBroadcastProvider = new BleBroadcastProvider(new TestInjector());
+ }
+
+ @Test
+ public void testOnStatus_success() {
+ byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
+ mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener);
+ verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
+
+ AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
+ mBleBroadcastProvider.onStartSuccess(settings);
+ verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
+ }
+
+ @Test
+ public void testOnStatus_failure() {
+ byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
+ mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener);
+
+ mBleBroadcastProvider.onStartFailure(BroadcastCallback.STATUS_FAILURE);
+ verify(mBroadcastListener, times(2)).onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
+ }
+
+ 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 ContextHubManagerAdapter getContextHubManagerAdapter() {
+ return null;
+ }
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
new file mode 100644
index 0000000..7f2168e
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 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.provider;
+
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.server.nearby.NearbyConfiguration.NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.nearby.BroadcastCallback;
+import android.nearby.BroadcastRequest;
+import android.nearby.IBroadcastListener;
+import android.nearby.PresenceBroadcastRequest;
+import android.nearby.PresenceCredential;
+import android.nearby.PrivateCredential;
+import android.provider.DeviceConfig;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Collections;
+
+/**
+ * Unit test for {@link BroadcastProviderManager}.
+ */
+public class BroadcastProviderManagerTest {
+ private static final byte[] IDENTITY = new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ private static final int MEDIUM_TYPE_BLE = 0;
+ private static final byte[] SALT = {2, 3};
+ private static final byte TX_POWER = 4;
+ private static final int PRESENCE_ACTION = 123;
+ private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
+ private static final byte[] AUTHENTICITY_KEY = new byte[]{12, 13, 14};
+
+ @Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+
+ @Mock
+ IBroadcastListener mBroadcastListener;
+ @Mock
+ BleBroadcastProvider mBleBroadcastProvider;
+ private Context mContext;
+ private BroadcastProviderManager mBroadcastProviderManager;
+ private BroadcastRequest mBroadcastRequest;
+ private UiAutomation mUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+ @Before
+ public void setUp() {
+ mUiAutomation.adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, READ_DEVICE_CONFIG);
+ DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY,
+ "true", false);
+
+ mContext = ApplicationProvider.getApplicationContext();
+ mBroadcastProviderManager = new BroadcastProviderManager(MoreExecutors.directExecutor(),
+ mBleBroadcastProvider);
+
+ PrivateCredential privateCredential =
+ new PrivateCredential.Builder(SECRET_ID, AUTHENTICITY_KEY)
+ .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+ .setMetadataEncryptionKey(IDENTITY)
+ .build();
+ mBroadcastRequest =
+ new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
+ SALT)
+ .setTxPower(TX_POWER)
+ .setCredential(privateCredential)
+ .setVersion(BroadcastRequest.PRESENCE_VERSION_V0)
+ .addAction(PRESENCE_ACTION).build();
+ }
+
+ @Test
+ public void testStartAdvertising() {
+ mBroadcastProviderManager.startBroadcast(mBroadcastRequest, mBroadcastListener);
+ verify(mBleBroadcastProvider).start(any(byte[].class), any(
+ BleBroadcastProvider.BroadcastListener.class));
+ }
+
+ @Test
+ public void testStartAdvertising_featureDisabled() throws Exception {
+ DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY,
+ "false", false);
+ mBroadcastProviderManager = new BroadcastProviderManager(MoreExecutors.directExecutor(),
+ mBleBroadcastProvider);
+ mBroadcastProviderManager.startBroadcast(mBroadcastRequest, mBroadcastListener);
+ verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
+ }
+
+ @Test
+ public void testOnStatusChanged() throws Exception {
+ mBroadcastProviderManager.startBroadcast(mBroadcastRequest, mBroadcastListener);
+ mBroadcastProviderManager.onStatusChanged(BroadcastCallback.STATUS_OK);
+ verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
+ }
+}