• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.le.AdvertiseData;
22 import android.bluetooth.le.AdvertisingSet;
23 import android.bluetooth.le.AdvertisingSetCallback;
24 import android.bluetooth.le.AdvertisingSetParameters;
25 import android.bluetooth.le.PeriodicAdvertisingParameters;
26 import android.os.Bundle;
27 import android.os.ParcelUuid;
28 
29 import com.googlecode.android_scripting.Log;
30 import com.googlecode.android_scripting.MainThread;
31 import com.googlecode.android_scripting.facade.EventFacade;
32 import com.googlecode.android_scripting.facade.FacadeManager;
33 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
34 import com.googlecode.android_scripting.rpc.Rpc;
35 import com.googlecode.android_scripting.rpc.RpcParameter;
36 
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.Map;
40 import java.util.concurrent.Callable;
41 
42 import org.json.JSONArray;
43 import org.json.JSONObject;
44 
45 /**
46  * BluetoothLe AdvertisingSet functions.
47  */
48 public class BluetoothLeAdvertisingSetFacade extends RpcReceiver {
49 
50     private static int sAdvertisingSetCount;
51     private static int sAdvertisingSetCallbackCount;
52     private final EventFacade mEventFacade;
53     private final HashMap<Integer, MyAdvertisingSetCallback> mAdvertisingSetCallbacks;
54     private final HashMap<Integer, AdvertisingSet> mAdvertisingSets;
55     private BluetoothAdapter mBluetoothAdapter;
56 
57     private static final Map<String, Integer> ADV_PHYS = new HashMap<>();
58     static {
59         ADV_PHYS.put("PHY_LE_1M", BluetoothDevice.PHY_LE_1M);
60         ADV_PHYS.put("PHY_LE_2M", BluetoothDevice.PHY_LE_2M);
61         ADV_PHYS.put("PHY_LE_CODED", BluetoothDevice.PHY_LE_CODED);
62     }
63 
BluetoothLeAdvertisingSetFacade(FacadeManager manager)64     public BluetoothLeAdvertisingSetFacade(FacadeManager manager) {
65         super(manager);
66         mBluetoothAdapter = MainThread.run(manager.getService(),
67                 new Callable<BluetoothAdapter>() {
68                     @Override
69                     public BluetoothAdapter call() throws Exception {
70                         return BluetoothAdapter.getDefaultAdapter();
71                     }
72                 });
73         mEventFacade = manager.getReceiver(EventFacade.class);
74         mAdvertisingSetCallbacks = new HashMap<>();
75         mAdvertisingSets = new HashMap<>();
76     }
77 
78     /**
79      * Constructs a MyAdvertisingSetCallback obj and returns its index
80      *
81      * @return MyAdvertisingSetCallback.index
82      */
83     @Rpc(description = "Generate a new MyAdvertisingSetCallback Object")
bleAdvSetGenCallback()84     public Integer bleAdvSetGenCallback() {
85         int index = ++sAdvertisingSetCallbackCount;
86         MyAdvertisingSetCallback callback = new MyAdvertisingSetCallback(index);
87         mAdvertisingSetCallbacks.put(callback.index, callback);
88         return callback.index;
89     }
90 
91     /**
92      * Converts String or JSONArray representation of byte array into raw byte array
93      */
somethingToByteArray(Object something)94     public byte[] somethingToByteArray(Object something) throws Exception {
95         if (something instanceof String) {
96             String s = (String) something;
97             int len = s.length();
98             byte[] data = new byte[len / 2];
99             for (int i = 0; i < len; i += 2) {
100                 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
101                                      + Character.digit(s.charAt(i + 1), 16));
102             }
103             return data;
104         } else if (something instanceof JSONArray) {
105             JSONArray arr = (JSONArray) something;
106             int len = arr.length();
107             byte[] data = new byte[len];
108             for (int i = 0; i < len; i++) {
109                 data[i] = (byte) arr.getInt(i);
110             }
111             return data;
112         } else {
113             throw new IllegalArgumentException("Don't know how to convert "
114                 + something.getClass().getName() + " to byte array!");
115         }
116     }
117 
118     /**
119      * Converts JSONObject representation of AdvertiseData into actual object.
120      */
buildAdvData(JSONObject params)121     public AdvertiseData buildAdvData(JSONObject params) throws Exception {
122         AdvertiseData.Builder builder = new AdvertiseData.Builder();
123 
124         Iterator<String> keys = params.keys();
125         while (keys.hasNext()) {
126             String key = keys.next();
127 
128             /** Python doesn't have multi map, if advertise data should repeat use
129               * serviceUuid, serviceUuid2, serviceUuid3... . For that use "startsWith"
130               */
131             if (key.startsWith("manufacturerData")) {
132                 JSONArray manuf = params.getJSONArray(key);
133                 if (manuf.length() != 2) {
134                     throw new IllegalArgumentException(
135                         "manufacturerData should contain exactly two elements");
136                 }
137                 int manufId = manuf.getInt(0);
138                 byte[] data = somethingToByteArray(manuf.get(1));
139                 builder.addManufacturerData(manufId, data);
140             } else if (key.startsWith("serviceData")) {
141                 JSONArray serDat = params.getJSONArray(key);
142                 ParcelUuid uuid = ParcelUuid.fromString(serDat.getString(0));
143                 byte[] data = somethingToByteArray(serDat.get(1));
144                 builder.addServiceData(uuid, data);
145             } else if (key.startsWith("serviceUuid")) {
146                 builder.addServiceUuid(ParcelUuid.fromString(params.getString(key)));
147             } else if (key.startsWith("includeDeviceName")) {
148                 builder.setIncludeDeviceName(params.getBoolean(key));
149             } else if (key.startsWith("includeTxPowerLevel")) {
150                 builder.setIncludeTxPowerLevel(params.getBoolean(key));
151             } else {
152                 throw new IllegalArgumentException("Unknown AdvertiseData field " + key);
153             }
154         }
155 
156         return builder.build();
157     }
158 
159     /**
160      * Converts JSONObject representation of AdvertisingSetParameters into actual object.
161      */
buildParameters(JSONObject params)162     public AdvertisingSetParameters buildParameters(JSONObject params) throws Exception {
163         AdvertisingSetParameters.Builder builder = new AdvertisingSetParameters.Builder();
164 
165         Iterator<String> keys = params.keys();
166         while (keys.hasNext()) {
167             String key = keys.next();
168 
169             if (key.equals("connectable")) {
170                 builder.setConnectable(params.getBoolean(key));
171             } else if (key.equals("scannable")) {
172                 builder.setScannable(params.getBoolean(key));
173             } else if (key.equals("legacyMode")) {
174                 builder.setLegacyMode(params.getBoolean(key));
175             } else if (key.equals("anonymous")) {
176                 builder.setAnonymous(params.getBoolean(key));
177             } else if (key.equals("includeTxPower")) {
178                 builder.setIncludeTxPower(params.getBoolean(key));
179             } else if (key.equals("primaryPhy")) {
180                 builder.setPrimaryPhy(ADV_PHYS.get(params.getString(key)));
181             } else if (key.equals("secondaryPhy")) {
182                 builder.setSecondaryPhy(ADV_PHYS.get(params.getString(key)));
183             } else if (key.equals("interval")) {
184                 builder.setInterval(params.getInt(key));
185             } else if (key.equals("txPowerLevel")) {
186                 builder.setTxPowerLevel(params.getInt(key));
187             } else {
188                 throw new IllegalArgumentException("Unknown AdvertisingSetParameters field " + key);
189             }
190         }
191 
192         return builder.build();
193     }
194 
195     /**
196      * Converts JSONObject representation of PeriodicAdvertisingParameters into actual object.
197      */
buildPeriodicParameters(JSONObject params)198     public PeriodicAdvertisingParameters buildPeriodicParameters(JSONObject params)
199             throws Exception {
200         PeriodicAdvertisingParameters.Builder builder = new PeriodicAdvertisingParameters.Builder();
201 
202         Iterator<String> keys = params.keys();
203         while (keys.hasNext()) {
204             String key = keys.next();
205 
206             if (key.equals("includeTxPower")) {
207                 builder.setIncludeTxPower(params.getBoolean(key));
208             } else if (key.equals("interval")) {
209                 builder.setInterval(params.getInt(key));
210             } else {
211                 throw new IllegalArgumentException(
212                         "Unknown PeriodicAdvertisingParameters field " + key);
213             }
214         }
215 
216         return builder.build();
217     }
218 
219     /**
220      * Starts ble advertising
221      *
222      * @throws Exception
223      */
224     @Rpc(description = "Starts ble advertisement")
bleAdvSetStartAdvertisingSet( @pcParametername = "params") JSONObject parametersJson, @RpcParameter(name = "data") JSONObject dataJson, @RpcParameter(name = "scanResponse") JSONObject scanResponseJson, @RpcParameter(name = "periodicParameters") JSONObject periodicParametersJson, @RpcParameter(name = "periodicDataIndex") JSONObject periodicDataJson, @RpcParameter(name = "duration") Integer duration, @RpcParameter(name = "maxExtAdvEvents") Integer maxExtAdvEvents, @RpcParameter(name = "callbackIndex") Integer callbackIndex)225     public void bleAdvSetStartAdvertisingSet(
226             @RpcParameter(name = "params") JSONObject parametersJson,
227             @RpcParameter(name = "data") JSONObject dataJson,
228             @RpcParameter(name = "scanResponse") JSONObject scanResponseJson,
229             @RpcParameter(name = "periodicParameters") JSONObject periodicParametersJson,
230             @RpcParameter(name = "periodicDataIndex") JSONObject periodicDataJson,
231             @RpcParameter(name = "duration") Integer duration,
232             @RpcParameter(name = "maxExtAdvEvents") Integer maxExtAdvEvents,
233             @RpcParameter(name = "callbackIndex") Integer callbackIndex) throws Exception {
234 
235         AdvertisingSetParameters parameters = null;
236         if (parametersJson != null) {
237             parameters = buildParameters(parametersJson);
238         }
239 
240         AdvertiseData data = null;
241         if (dataJson != null) {
242             data = buildAdvData(dataJson);
243         }
244 
245         AdvertiseData scanResponse = null;
246         if (scanResponseJson != null) {
247             scanResponse = buildAdvData(scanResponseJson);
248         }
249 
250         PeriodicAdvertisingParameters periodicParameters = null;
251         if (periodicParametersJson != null) {
252             periodicParameters = buildPeriodicParameters(periodicParametersJson);
253         }
254 
255         AdvertiseData periodicData = null;
256         if (periodicDataJson != null) {
257             periodicData = buildAdvData(periodicDataJson);
258         }
259 
260         MyAdvertisingSetCallback callback = mAdvertisingSetCallbacks.get(callbackIndex);
261         if (callback != null) {
262             Log.d("starting le advertising set on callback index: " + callbackIndex);
263             mBluetoothAdapter.getBluetoothLeAdvertiser().startAdvertisingSet(
264                     parameters, data, scanResponse, periodicParameters, periodicData, callback);
265         } else {
266             throw new Exception("Invalid callbackIndex input" + callbackIndex);
267         }
268     }
269 
270     /**
271      * Get the address associated with this Advertising set. This method returns immediately,
272      * the operation result is delivered through callback.onOwnAddressRead().
273      * This is for PTS only.
274      */
275     @Rpc(description = "Get own address")
bleAdvSetGetOwnAddress( @pcParametername = "setIndex") Integer setIndex)276     public void bleAdvSetGetOwnAddress(
277             @RpcParameter(name = "setIndex") Integer setIndex) throws Exception {
278         mAdvertisingSets.get(setIndex).getOwnAddress();
279     }
280 
281     /**
282      * Enables Advertising. This method returns immediately, the operation status is
283      * delivered through callback.onAdvertisingEnabled().
284      *
285      * @param enable whether the advertising should be enabled (true), or disabled (false)
286      * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to
287      *                     65535 (655,350 ms)
288      * @param maxExtAdvEvents maximum number of extended advertising events the
289      *                     controller shall attempt to send prior to terminating the extended
290      *                     advertising, even if the duration has not expired. Valid range is
291      *                     from 1 to 255.
292      */
293     @Rpc(description = "Enable/disable advertising")
bleAdvSetEnableAdvertising( @pcParametername = "setIndex") Integer setIndex, @RpcParameter(name = "enable") Boolean enable, @RpcParameter(name = "duration") Integer duration, @RpcParameter(name = "maxExtAdvEvents") Integer maxExtAdvEvents)294     public void bleAdvSetEnableAdvertising(
295             @RpcParameter(name = "setIndex") Integer setIndex,
296             @RpcParameter(name = "enable") Boolean enable,
297             @RpcParameter(name = "duration") Integer duration,
298             @RpcParameter(name = "maxExtAdvEvents") Integer maxExtAdvEvents) throws Exception {
299         mAdvertisingSets.get(setIndex).enableAdvertising(enable, duration, maxExtAdvEvents);
300     }
301 
302     /**
303      * Set/update data being Advertised. Make sure that data doesn't exceed the size limit for
304      * specified AdvertisingSetParameters. This method returns immediately, the operation status is
305      * delivered through callback.onAdvertisingDataSet().
306      *
307      * Advertising data must be empty if non-legacy scannable advertising is used.
308      *
309      * @param dataJson Advertisement data to be broadcasted. Size must not exceed
310      *                     {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the
311      *                     advertisement is connectable, three bytes will be added for flags. If the
312      *                     update takes place when the advertising set is enabled, the data can be
313      *                     maximum 251 bytes long.
314      */
315     @Rpc(description = "Set advertise data")
bleAdvSetSetAdvertisingData( @pcParametername = "setIndex") Integer setIndex, @RpcParameter(name = "data") JSONObject dataJson)316     public void bleAdvSetSetAdvertisingData(
317             @RpcParameter(name = "setIndex") Integer setIndex,
318             @RpcParameter(name = "data") JSONObject dataJson) throws Exception {
319         AdvertiseData data = null;
320         if (dataJson != null) {
321             data = buildAdvData(dataJson);
322         }
323 
324         Log.i("setAdvertisingData()");
325         mAdvertisingSets.get(setIndex).setAdvertisingData(data);
326     }
327 
328     /**
329      * Stops a ble advertising set
330      *
331      * @param index the id of the advertising set to stop
332      * @throws Exception
333      */
334     @Rpc(description = "Stops an ongoing ble advertising set")
bleAdvSetStopAdvertisingSet( @pcParametername = "index") Integer index)335     public void bleAdvSetStopAdvertisingSet(
336             @RpcParameter(name = "index")
337             Integer index) throws Exception {
338         MyAdvertisingSetCallback callback = mAdvertisingSetCallbacks.remove(index);
339         if (callback == null) {
340             throw new Exception("Invalid index input:" + index);
341         }
342 
343         Log.d("stopping le advertising set " + index);
344         mBluetoothAdapter.getBluetoothLeAdvertiser().stopAdvertisingSet(callback);
345     }
346 
347     private class MyAdvertisingSetCallback extends AdvertisingSetCallback {
348         public Integer index;
349         public Integer setIndex = -1;
350         String mEventType;
351 
MyAdvertisingSetCallback(int idx)352         MyAdvertisingSetCallback(int idx) {
353             index = idx;
354             mEventType = "AdvertisingSet";
355         }
356 
357         @Override
onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status)358         public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
359                     int status) {
360             Log.d("onAdvertisingSetStarted" + mEventType + " " + index);
361             Bundle results = new Bundle();
362             results.putString("Type", "onAdvertisingSetStarted");
363             results.putInt("status", status);
364             if (advertisingSet != null) {
365                 setIndex = ++sAdvertisingSetCount;
366                 mAdvertisingSets.put(setIndex, advertisingSet);
367                 results.putInt("setId", setIndex);
368             } else {
369                 mAdvertisingSetCallbacks.remove(index);
370             }
371             mEventFacade.postEvent(mEventType + index + "onAdvertisingSetStarted", results);
372         }
373 
374         @Override
onAdvertisingSetStopped(AdvertisingSet advertisingSet)375         public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
376             Log.d("onAdvertisingSetStopped" + mEventType + " " + index);
377             Bundle results = new Bundle();
378             results.putString("Type", "onAdvertisingSetStopped");
379             mEventFacade.postEvent(mEventType + index + "onAdvertisingSetStopped", results);
380         }
381 
382         @Override
onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status)383         public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
384                 int status) {
385             sendGeneric("onAdvertisingEnabled", setIndex, status, enable);
386         }
387 
388         @Override
onAdvertisingDataSet(AdvertisingSet advertisingSet, int status)389         public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
390             sendGeneric("onAdvertisingDataSet", setIndex, status);
391         }
392 
393         @Override
onScanResponseDataSet(AdvertisingSet advertisingSet, int status)394         public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
395             sendGeneric("onScanResponseDataSet", setIndex, status);
396         }
397 
398         @Override
onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int txPower, int status)399         public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int txPower,
400                 int status) {
401             sendGeneric("onAdvertisingParametersUpdated", setIndex, status);
402         }
403 
404         @Override
onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int status)405         public void onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
406                 int status) {
407             sendGeneric("onPeriodicAdvertisingParametersUpdated", setIndex, status);
408         }
409 
410         @Override
onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet, int status)411         public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
412             sendGeneric("onPeriodicAdvertisingDataSet", setIndex, status);
413         }
414 
415         @Override
onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status)416         public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
417                 int status) {
418             sendGeneric("onPeriodicAdvertisingEnabled", setIndex, status, enable);
419         }
420 
421         @Override
onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, String address)422         public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType,
423                 String address) {
424             Log.d("onOwnAddressRead" + mEventType + " " + setIndex);
425             Bundle results = new Bundle();
426             results.putInt("setId", setIndex);
427             results.putInt("addressType", addressType);
428             results.putString("address", address);
429             mEventFacade.postEvent(mEventType + setIndex + "onOwnAddressRead", results);
430         }
431 
sendGeneric(String cb, int setIndex, int status)432         public void sendGeneric(String cb, int setIndex, int status) {
433             sendGeneric(cb, setIndex, status, null);
434         }
435 
sendGeneric(String cb, int setIndex, int status, Boolean enable)436         public void sendGeneric(String cb, int setIndex, int status, Boolean enable) {
437             Log.d(cb + mEventType + " " + index);
438             Bundle results = new Bundle();
439             results.putInt("setId", setIndex);
440             results.putInt("status", status);
441             if (enable != null) results.putBoolean("enable", enable);
442             mEventFacade.postEvent(mEventType + index + cb, results);
443         }
444     }
445 
446     @Override
shutdown()447     public void shutdown() {
448         if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
449             Iterator<Map.Entry<Integer, MyAdvertisingSetCallback>> it =
450                     mAdvertisingSetCallbacks.entrySet().iterator();
451             while (it.hasNext()) {
452                 Map.Entry<Integer, MyAdvertisingSetCallback> entry = it.next();
453                 MyAdvertisingSetCallback advertisingSetCb = entry.getValue();
454                 it.remove();
455 
456                 if (advertisingSetCb == null) continue;
457 
458                 Log.d("shutdown() stopping le advertising set " + advertisingSetCb.index);
459                 try {
460                     mBluetoothAdapter.getBluetoothLeAdvertiser()
461                         .stopAdvertisingSet(advertisingSetCb);
462                 } catch (NullPointerException e) {
463                     Log.e("Failed to stop ble advertising.", e);
464                 }
465             }
466         }
467         mAdvertisingSetCallbacks.clear();
468         mAdvertisingSets.clear();
469     }
470 }
471