blob: 6829fba42d6d835546f4348b9051411604c35558 [file] [log] [blame]
/*
* 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 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.Injector;
import java.util.concurrent.Executor;
/**
* A provider for Bluetooth Low Energy advertisement.
*/
public class BleBroadcastProvider extends AdvertiseCallback {
/**
* Listener for Broadcast status changes.
*/
public interface BroadcastListener {
void onStatusChanged(int status);
}
private final Injector mInjector;
private final Executor mExecutor;
private BroadcastListener mBroadcastListener;
private boolean mIsAdvertising;
@VisibleForTesting
AdvertisingSetCallback mAdvertisingSetCallback;
public BleBroadcastProvider(Injector injector, Executor executor) {
mInjector = injector;
mExecutor = executor;
mAdvertisingSetCallback = getAdvertisingSetCallback();
}
/**
* Starts to broadcast with given bytes.
*/
public void start(@BroadcastRequest.BroadcastVersion int version, 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;
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);
break;
case BroadcastRequest.PRESENCE_VERSION_V1:
if (adapter.isLeExtendedAdvertisingSupported()) {
bluetoothLeAdvertiser.startAdvertisingSet(
getAdvertisingSetParameters(),
advertiseData,
null, null, null, mAdvertisingSetCallback);
} else {
Log.w(TAG, "Failed to start advertising set because the chipset"
+ " does not supports LE Extended Advertising feature.");
advertiseStarted = false;
}
break;
default:
Log.w(TAG, "Failed to start advertising set because the advertisement"
+ " is wrong.");
advertiseStarted = false;
}
} catch (NullPointerException | IllegalStateException | SecurityException e) {
Log.w(TAG, "Failed to start advertising.", e);
advertiseStarted = false;
}
}
}
if (!advertiseStarted) {
listener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
}
}
/**
* Stops current advertisement.
*/
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);
}
}
mBroadcastListener = null;
mIsAdvertising = false;
}
}
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
mExecutor.execute(() -> {
if (mBroadcastListener != null) {
mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_OK);
}
mIsAdvertising = true;
});
}
@Override
public void onStartFailure(int errorCode) {
if (mBroadcastListener != null) {
mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
}
}
private static AdvertiseSettings getAdvertiseSettings() {
return new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.setConnectable(true)
.build();
}
private static AdvertisingSetParameters getAdvertisingSetParameters() {
return new AdvertisingSetParameters.Builder()
.setInterval(AdvertisingSetParameters.INTERVAL_MEDIUM)
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM)
.setIncludeTxPower(true)
.setConnectable(true)
.build();
}
private AdvertisingSetCallback getAdvertisingSetCallback() {
return new AdvertisingSetCallback() {
@Override
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet,
int txPower, int status) {
if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
if (mBroadcastListener != null) {
mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_OK);
}
mIsAdvertising = true;
} else {
Log.e(TAG, "Starts advertising failed in status " + status);
if (mBroadcastListener != null) {
mBroadcastListener.onStatusChanged(BroadcastCallback.STATUS_FAILURE);
}
}
}
};
}
}