• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.presence.ble;
18 
19 import android.annotation.SuppressLint;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.le.AdvertiseCallback;
22 import android.bluetooth.le.AdvertiseData;
23 import android.bluetooth.le.AdvertiseSettings;
24 import android.bluetooth.le.AdvertisingSet;
25 import android.bluetooth.le.AdvertisingSetCallback;
26 import android.bluetooth.le.AdvertisingSetParameters;
27 import android.bluetooth.le.BluetoothLeAdvertiser;
28 import android.util.Log;
29 
30 import com.google.common.base.Preconditions;
31 
32 import java.util.Arrays;
33 
34 /** Advertises BLE beacons. */
35 @SuppressLint("MissingPermission")
36 public class BleAdvertiser {
37     private static final String TAG = BleAdvertiser.class.getName();
38 
39     private static final int AVAILABLE_BYTES = 31;
40     private static final int PARCEL_UUID_BYTES = 16;
41     private static final int OVERHEAD_BYTES = 2;
42 
43     private final BluetoothLeAdvertiser advertiser =
44             BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
45     private boolean isAdvertising;
46     private boolean mIsExtendedAdvertisement;
47 
48     private final AdvertiseSettings settings =
49             new AdvertiseSettings.Builder()
50                     .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
51                     // 1db per
52                     // http://cs/android/packages/modules/Bluetooth/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java?l=156&rcl=53aad85c594243e9d8e2778ce582f5c7382ad19c
53                     .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
54                     .setConnectable(false)
55                     .build();
56 
57     private final AdvertiseCallback advertisingCallback =
58             new AdvertiseCallback() {
59                 @Override
60                 public void onStartSuccess(AdvertiseSettings settingsInEffect) {
61                     super.onStartSuccess(settingsInEffect);
62                     Log.i(TAG, "Advertising onStartSuccess: " + settingsInEffect);
63                 }
64 
65                 @Override
66                 public void onStartFailure(int errorCode) {
67                     super.onStartFailure(errorCode);
68                     Log.i(TAG, "Advertising onStartFailure: " + errorCode);
69                 }
70             };
71 
72     private final AdvertisingSetCallback mAdvertisingSetCallback =
73             new AdvertisingSetCallback() {
74                 @Override
75                 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
76                         int status) {
77                     super.onAdvertisingSetStarted(advertisingSet, txPower, status);
78                     Log.i(TAG, "onAdvertisingSetStarted");
79                     mIsExtendedAdvertisement = true;
80                 }
81 
82                 @Override
83                 public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
84                     super.onAdvertisingSetStopped(advertisingSet);
85                     Log.i(TAG, "onAdvertisingSetStopped");
86                     mIsExtendedAdvertisement = false;
87                 }
88             };
89 
BleAdvertiser()90     public BleAdvertiser() {}
91 
startAdvertising(byte[] payload, boolean isExtendedAdvertisement, int txPower)92     public void startAdvertising(byte[] payload, boolean isExtendedAdvertisement, int txPower) {
93         if (isAdvertising) {
94             stopAdvertising();
95         }
96 
97         int freeBytesForPayload = getFreeBytesForPayload();
98         Preconditions.checkArgument(
99                 payload.length <= freeBytesForPayload,
100                 "provided payload is too large! Limit is %s bytes, provided=%s",
101                 freeBytesForPayload,
102                 payload.length);
103 
104         AdvertiseData data = new AdvertiseData.Builder()
105                 // UUID (16 bytes) + service data overhead (2 bytes) + service data +
106                 // device name
107                 // overhead (2 bytes) = 20 bytes + service data bytes
108                 // This leaves 10 bytes or fewer, depending on the service data, for the
109                 // device name
110                 // since we only have 31 bytes available in AdvertiseData. To prevent BLE
111                 // advertising
112                 // from failing, don't set the device name.
113                 .setIncludeDeviceName(false)
114                 .addServiceData(Const.PARCEL_UUID, payload)
115                 .build();
116         if (isExtendedAdvertisement) {
117             advertiser.startAdvertisingSet(buildAdvertisingSetParameters(txPower), data, /*scanResponse= */ null, /*periodicParameters= */ null, /*periodicData= */ null, mAdvertisingSetCallback);
118         } else {
119             advertiser.startAdvertising(settings, data, advertisingCallback);
120         }
121 
122         Log.i(TAG, "startAdvertising: %s" + Arrays.toString(payload));
123 
124         isAdvertising = true;
125     }
126 
stopAdvertising()127     public void stopAdvertising() {
128         Log.i(TAG, "stopAdvertising");
129         if (mIsExtendedAdvertisement) {
130             advertiser.stopAdvertisingSet(mAdvertisingSetCallback);
131         } else {
132             advertiser.stopAdvertising(advertisingCallback);
133         }
134         isAdvertising = false;
135     }
136 
isAdvertising()137     public boolean isAdvertising() {
138         return isAdvertising;
139     }
140 
141     /** Returns the number of bytes available for a payload */
getFreeBytesForPayload()142     private static int getFreeBytesForPayload() {
143         int bytesForServiceData = OVERHEAD_BYTES + 1; // + 1 for txOffset
144         return AVAILABLE_BYTES - PARCEL_UUID_BYTES - bytesForServiceData;
145     }
146 
buildAdvertisingSetParameters(int txPower)147     private static AdvertisingSetParameters buildAdvertisingSetParameters(int txPower) {
148         return new AdvertisingSetParameters.Builder()
149                 .setConnectable(true)
150                 .setInterval(AdvertisingSetParameters.INTERVAL_LOW)
151                 .setTxPowerLevel(txPower)
152                 .setIncludeTxPower(true)
153                 .build();
154     }
155 }
156