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.android.bluetooth.gatt; 18 19 import android.bluetooth.le.AdvertiseCallback; 20 import android.bluetooth.le.AdvertiseData; 21 import android.bluetooth.le.AdvertisingSetParameters; 22 import android.bluetooth.le.IAdvertisingSetCallback; 23 import android.bluetooth.le.PeriodicAdvertisingParameters; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.IBinder; 27 import android.os.IInterface; 28 import android.os.Looper; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import com.android.bluetooth.btservice.AdapterService; 33 import com.android.bluetooth.gatt.GattService.AdvertiserMap; 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.Map; 39 40 /** 41 * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests. 42 * 43 * @hide 44 */ 45 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 46 public class AdvertiseManager { 47 private static final boolean DBG = GattServiceConfig.DBG; 48 private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager"; 49 50 private final GattService mService; 51 private final AdapterService mAdapterService; 52 private final AdvertiserMap mAdvertiserMap; 53 private Handler mHandler; 54 Map<IBinder, AdvertiserInfo> mAdvertisers = Collections.synchronizedMap(new HashMap<>()); 55 static int sTempRegistrationId = -1; 56 57 /** 58 * Constructor of {@link AdvertiseManager}. 59 */ AdvertiseManager(GattService service, AdapterService adapterService, AdvertiserMap advertiserMap)60 AdvertiseManager(GattService service, AdapterService adapterService, 61 AdvertiserMap advertiserMap) { 62 if (DBG) { 63 Log.d(TAG, "advertise manager created"); 64 } 65 mService = service; 66 mAdapterService = adapterService; 67 mAdvertiserMap = advertiserMap; 68 } 69 70 /** 71 * Start a {@link HandlerThread} that handles advertising operations. 72 */ start()73 void start() { 74 initializeNative(); 75 HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager"); 76 thread.start(); 77 mHandler = new Handler(thread.getLooper()); 78 } 79 cleanup()80 void cleanup() { 81 if (DBG) { 82 Log.d(TAG, "cleanup()"); 83 } 84 cleanupNative(); 85 mAdvertisers.clear(); 86 sTempRegistrationId = -1; 87 88 if (mHandler != null) { 89 // Shut down the thread 90 mHandler.removeCallbacksAndMessages(null); 91 Looper looper = mHandler.getLooper(); 92 if (looper != null) { 93 looper.quit(); 94 } 95 mHandler = null; 96 } 97 } 98 99 class AdvertiserInfo { 100 /* When id is negative, the registration is ongoing. When the registration finishes, id 101 * becomes equal to advertiser_id */ 102 public Integer id; 103 public AdvertisingSetDeathRecipient deathRecipient; 104 public IAdvertisingSetCallback callback; 105 AdvertiserInfo(Integer id, AdvertisingSetDeathRecipient deathRecipient, IAdvertisingSetCallback callback)106 AdvertiserInfo(Integer id, AdvertisingSetDeathRecipient deathRecipient, 107 IAdvertisingSetCallback callback) { 108 this.id = id; 109 this.deathRecipient = deathRecipient; 110 this.callback = callback; 111 } 112 } 113 toBinder(IAdvertisingSetCallback e)114 IBinder toBinder(IAdvertisingSetCallback e) { 115 return ((IInterface) e).asBinder(); 116 } 117 118 class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient { 119 public IAdvertisingSetCallback callback; 120 AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback)121 AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback) { 122 this.callback = callback; 123 } 124 125 @Override binderDied()126 public void binderDied() { 127 if (DBG) { 128 Log.d(TAG, "Binder is dead - unregistering advertising set"); 129 } 130 stopAdvertisingSet(callback); 131 } 132 } 133 findAdvertiser(int advertiserId)134 Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) { 135 Map.Entry<IBinder, AdvertiserInfo> entry = null; 136 for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) { 137 if (e.getValue().id == advertiserId) { 138 entry = e; 139 break; 140 } 141 } 142 return entry; 143 } 144 onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status)145 void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) 146 throws Exception { 147 if (DBG) { 148 Log.d(TAG, 149 "onAdvertisingSetStarted() - regId=" + regId + ", advertiserId=" + advertiserId 150 + ", status=" + status); 151 } 152 153 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(regId); 154 155 if (entry == null) { 156 Log.i(TAG, "onAdvertisingSetStarted() - no callback found for regId " + regId); 157 // Advertising set was stopped before it was properly registered. 158 stopAdvertisingSetNative(advertiserId); 159 return; 160 } 161 162 IAdvertisingSetCallback callback = entry.getValue().callback; 163 if (status == 0) { 164 entry.setValue( 165 new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback)); 166 167 mAdvertiserMap.setAdvertiserIdByRegId(regId, advertiserId); 168 } else { 169 IBinder binder = entry.getKey(); 170 binder.unlinkToDeath(entry.getValue().deathRecipient, 0); 171 mAdvertisers.remove(binder); 172 173 AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(regId); 174 if (stats != null) { 175 stats.recordAdvertiseStop(); 176 } 177 mAdvertiserMap.removeAppAdvertiseStats(regId); 178 } 179 180 callback.onAdvertisingSetStarted(advertiserId, txPower, status); 181 } 182 onAdvertisingEnabled(int advertiserId, boolean enable, int status)183 void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception { 184 if (DBG) { 185 Log.d(TAG, "onAdvertisingSetEnabled() - advertiserId=" + advertiserId + ", enable=" 186 + enable + ", status=" + status); 187 } 188 189 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 190 if (entry == null) { 191 Log.i(TAG, "onAdvertisingSetEnable() - no callback found for advertiserId " 192 + advertiserId); 193 return; 194 } 195 196 IAdvertisingSetCallback callback = entry.getValue().callback; 197 callback.onAdvertisingEnabled(advertiserId, enable, status); 198 199 if (!enable && status != 0) { 200 AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); 201 if (stats != null) { 202 stats.recordAdvertiseStop(); 203 } 204 } 205 } 206 startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents, IAdvertisingSetCallback callback)207 void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, 208 AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, 209 AdvertiseData periodicData, int duration, int maxExtAdvEvents, 210 IAdvertisingSetCallback callback) { 211 AdvertisingSetDeathRecipient deathRecipient = new AdvertisingSetDeathRecipient(callback); 212 IBinder binder = toBinder(callback); 213 try { 214 binder.linkToDeath(deathRecipient, 0); 215 } catch (RemoteException e) { 216 throw new IllegalArgumentException("Can't link to advertiser's death"); 217 } 218 219 String deviceName = AdapterService.getAdapterService().getName(); 220 try { 221 byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName); 222 byte[] scanResponseBytes = 223 AdvertiseHelper.advertiseDataToBytes(scanResponse, deviceName); 224 byte[] periodicDataBytes = 225 AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName); 226 227 int cbId = --sTempRegistrationId; 228 mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); 229 230 if (DBG) { 231 Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder); 232 } 233 234 mAdvertiserMap.add(cbId, callback, mService); 235 mAdvertiserMap.recordAdvertiseStart(cbId, parameters, advertiseData, 236 scanResponse, periodicParameters, periodicData, duration, maxExtAdvEvents); 237 238 startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, 239 periodicParameters, periodicDataBytes, duration, maxExtAdvEvents, cbId); 240 241 } catch (IllegalArgumentException e) { 242 try { 243 binder.unlinkToDeath(deathRecipient, 0); 244 callback.onAdvertisingSetStarted(0x00, 0x00, 245 AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 246 } catch (RemoteException exception) { 247 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception)); 248 } 249 } 250 } 251 onOwnAddressRead(int advertiserId, int addressType, String address)252 void onOwnAddressRead(int advertiserId, int addressType, String address) 253 throws RemoteException { 254 if (DBG) { 255 Log.d(TAG, "onOwnAddressRead() advertiserId=" + advertiserId); 256 } 257 258 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 259 if (entry == null) { 260 Log.w(TAG, "onOwnAddressRead() - bad advertiserId " + advertiserId); 261 return; 262 } 263 264 IAdvertisingSetCallback callback = entry.getValue().callback; 265 callback.onOwnAddressRead(advertiserId, addressType, address); 266 } 267 getOwnAddress(int advertiserId)268 void getOwnAddress(int advertiserId) { 269 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 270 if (entry == null) { 271 Log.w(TAG, "getOwnAddress() - bad advertiserId " + advertiserId); 272 return; 273 } 274 getOwnAddressNative(advertiserId); 275 } 276 stopAdvertisingSet(IAdvertisingSetCallback callback)277 void stopAdvertisingSet(IAdvertisingSetCallback callback) { 278 IBinder binder = toBinder(callback); 279 if (DBG) { 280 Log.d(TAG, "stopAdvertisingSet() " + binder); 281 } 282 283 AdvertiserInfo adv = mAdvertisers.remove(binder); 284 if (adv == null) { 285 Log.e(TAG, "stopAdvertisingSet() - no client found for callback"); 286 return; 287 } 288 289 Integer advertiserId = adv.id; 290 binder.unlinkToDeath(adv.deathRecipient, 0); 291 292 if (advertiserId < 0) { 293 Log.i(TAG, "stopAdvertisingSet() - advertiser not finished registration yet"); 294 // Advertiser will be freed once initiated in onAdvertisingSetStarted() 295 return; 296 } 297 298 stopAdvertisingSetNative(advertiserId); 299 300 try { 301 callback.onAdvertisingSetStopped(advertiserId); 302 } catch (RemoteException e) { 303 Log.i(TAG, "error sending onAdvertisingSetStopped callback", e); 304 } 305 306 mAdvertiserMap.recordAdvertiseStop(advertiserId); 307 } 308 enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents)309 void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) { 310 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 311 if (entry == null) { 312 Log.w(TAG, "enableAdvertisingSet() - bad advertiserId " + advertiserId); 313 return; 314 } 315 enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents); 316 317 mAdvertiserMap.enableAdvertisingSet(advertiserId, 318 enable, duration, maxExtAdvEvents); 319 } 320 setAdvertisingData(int advertiserId, AdvertiseData data)321 void setAdvertisingData(int advertiserId, AdvertiseData data) { 322 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 323 if (entry == null) { 324 Log.w(TAG, "setAdvertisingData() - bad advertiserId " + advertiserId); 325 return; 326 } 327 String deviceName = AdapterService.getAdapterService().getName(); 328 try { 329 setAdvertisingDataNative(advertiserId, 330 AdvertiseHelper.advertiseDataToBytes(data, deviceName)); 331 332 mAdvertiserMap.setAdvertisingData(advertiserId, data); 333 } catch (IllegalArgumentException e) { 334 try { 335 onAdvertisingDataSet(advertiserId, 336 AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 337 } catch (Exception exception) { 338 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception)); 339 } 340 } 341 } 342 setScanResponseData(int advertiserId, AdvertiseData data)343 void setScanResponseData(int advertiserId, AdvertiseData data) { 344 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 345 if (entry == null) { 346 Log.w(TAG, "setScanResponseData() - bad advertiserId " + advertiserId); 347 return; 348 } 349 String deviceName = AdapterService.getAdapterService().getName(); 350 try { 351 setScanResponseDataNative(advertiserId, 352 AdvertiseHelper.advertiseDataToBytes(data, deviceName)); 353 354 mAdvertiserMap.setScanResponseData(advertiserId, data); 355 } catch (IllegalArgumentException e) { 356 try { 357 onScanResponseDataSet(advertiserId, 358 AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 359 } catch (Exception exception) { 360 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception)); 361 } 362 } 363 } 364 setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters)365 void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) { 366 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 367 if (entry == null) { 368 Log.w(TAG, "setAdvertisingParameters() - bad advertiserId " + advertiserId); 369 return; 370 } 371 setAdvertisingParametersNative(advertiserId, parameters); 372 373 mAdvertiserMap.setAdvertisingParameters(advertiserId, parameters); 374 } 375 setPeriodicAdvertisingParameters(int advertiserId, PeriodicAdvertisingParameters parameters)376 void setPeriodicAdvertisingParameters(int advertiserId, 377 PeriodicAdvertisingParameters parameters) { 378 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 379 if (entry == null) { 380 Log.w(TAG, "setPeriodicAdvertisingParameters() - bad advertiserId " + advertiserId); 381 return; 382 } 383 setPeriodicAdvertisingParametersNative(advertiserId, parameters); 384 385 mAdvertiserMap.setPeriodicAdvertisingParameters(advertiserId, parameters); 386 } 387 setPeriodicAdvertisingData(int advertiserId, AdvertiseData data)388 void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) { 389 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 390 if (entry == null) { 391 Log.w(TAG, "setPeriodicAdvertisingData() - bad advertiserId " + advertiserId); 392 return; 393 } 394 String deviceName = AdapterService.getAdapterService().getName(); 395 try { 396 setPeriodicAdvertisingDataNative(advertiserId, 397 AdvertiseHelper.advertiseDataToBytes(data, deviceName)); 398 399 mAdvertiserMap.setPeriodicAdvertisingData(advertiserId, data); 400 } catch (IllegalArgumentException e) { 401 try { 402 onPeriodicAdvertisingDataSet(advertiserId, 403 AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 404 } catch (Exception exception) { 405 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception)); 406 } 407 } 408 } 409 setPeriodicAdvertisingEnable(int advertiserId, boolean enable)410 void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) { 411 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 412 if (entry == null) { 413 Log.w(TAG, "setPeriodicAdvertisingEnable() - bad advertiserId " + advertiserId); 414 return; 415 } 416 setPeriodicAdvertisingEnableNative(advertiserId, enable); 417 } 418 onAdvertisingDataSet(int advertiserId, int status)419 void onAdvertisingDataSet(int advertiserId, int status) throws Exception { 420 if (DBG) { 421 Log.d(TAG, 422 "onAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status); 423 } 424 425 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 426 if (entry == null) { 427 Log.i(TAG, "onAdvertisingDataSet() - bad advertiserId " + advertiserId); 428 return; 429 } 430 431 IAdvertisingSetCallback callback = entry.getValue().callback; 432 callback.onAdvertisingDataSet(advertiserId, status); 433 } 434 onScanResponseDataSet(int advertiserId, int status)435 void onScanResponseDataSet(int advertiserId, int status) throws Exception { 436 if (DBG) { 437 Log.d(TAG, 438 "onScanResponseDataSet() advertiserId=" + advertiserId + ", status=" + status); 439 } 440 441 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 442 if (entry == null) { 443 Log.i(TAG, "onScanResponseDataSet() - bad advertiserId " + advertiserId); 444 return; 445 } 446 447 IAdvertisingSetCallback callback = entry.getValue().callback; 448 callback.onScanResponseDataSet(advertiserId, status); 449 } 450 onAdvertisingParametersUpdated(int advertiserId, int txPower, int status)451 void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) 452 throws Exception { 453 if (DBG) { 454 Log.d(TAG, 455 "onAdvertisingParametersUpdated() advertiserId=" + advertiserId + ", txPower=" 456 + txPower + ", status=" + status); 457 } 458 459 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 460 if (entry == null) { 461 Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiserId " + advertiserId); 462 return; 463 } 464 465 IAdvertisingSetCallback callback = entry.getValue().callback; 466 callback.onAdvertisingParametersUpdated(advertiserId, txPower, status); 467 } 468 onPeriodicAdvertisingParametersUpdated(int advertiserId, int status)469 void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception { 470 if (DBG) { 471 Log.d(TAG, "onPeriodicAdvertisingParametersUpdated() advertiserId=" + advertiserId 472 + ", status=" + status); 473 } 474 475 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 476 if (entry == null) { 477 Log.i(TAG, 478 "onPeriodicAdvertisingParametersUpdated() - bad advertiserId " + advertiserId); 479 return; 480 } 481 482 IAdvertisingSetCallback callback = entry.getValue().callback; 483 callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status); 484 } 485 onPeriodicAdvertisingDataSet(int advertiserId, int status)486 void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception { 487 if (DBG) { 488 Log.d(TAG, "onPeriodicAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" 489 + status); 490 } 491 492 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 493 if (entry == null) { 494 Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiserId " + advertiserId); 495 return; 496 } 497 498 IAdvertisingSetCallback callback = entry.getValue().callback; 499 callback.onPeriodicAdvertisingDataSet(advertiserId, status); 500 } 501 onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status)502 void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) 503 throws Exception { 504 if (DBG) { 505 Log.d(TAG, "onPeriodicAdvertisingEnabled() advertiserId=" + advertiserId + ", status=" 506 + status); 507 } 508 509 Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); 510 if (entry == null) { 511 Log.i(TAG, "onAdvertisingSetEnable() - bad advertiserId " + advertiserId); 512 return; 513 } 514 515 IAdvertisingSetCallback callback = entry.getValue().callback; 516 callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status); 517 518 AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); 519 if (stats != null) { 520 stats.onPeriodicAdvertiseEnabled(enable); 521 } 522 } 523 524 static { classInitNative()525 classInitNative(); 526 } 527 classInitNative()528 private static native void classInitNative(); 529 initializeNative()530 private native void initializeNative(); 531 cleanupNative()532 private native void cleanupNative(); 533 startAdvertisingSetNative(AdvertisingSetParameters parameters, byte[] advertiseData, byte[] scanResponse, PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration, int maxExtAdvEvents, int regId)534 private native void startAdvertisingSetNative(AdvertisingSetParameters parameters, 535 byte[] advertiseData, byte[] scanResponse, 536 PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration, 537 int maxExtAdvEvents, int regId); 538 getOwnAddressNative(int advertiserId)539 private native void getOwnAddressNative(int advertiserId); 540 stopAdvertisingSetNative(int advertiserId)541 private native void stopAdvertisingSetNative(int advertiserId); 542 enableAdvertisingSetNative(int advertiserId, boolean enable, int duration, int maxExtAdvEvents)543 private native void enableAdvertisingSetNative(int advertiserId, boolean enable, int duration, 544 int maxExtAdvEvents); 545 setAdvertisingDataNative(int advertiserId, byte[] data)546 private native void setAdvertisingDataNative(int advertiserId, byte[] data); 547 setScanResponseDataNative(int advertiserId, byte[] data)548 private native void setScanResponseDataNative(int advertiserId, byte[] data); 549 setAdvertisingParametersNative(int advertiserId, AdvertisingSetParameters parameters)550 private native void setAdvertisingParametersNative(int advertiserId, 551 AdvertisingSetParameters parameters); 552 setPeriodicAdvertisingParametersNative(int advertiserId, PeriodicAdvertisingParameters parameters)553 private native void setPeriodicAdvertisingParametersNative(int advertiserId, 554 PeriodicAdvertisingParameters parameters); 555 setPeriodicAdvertisingDataNative(int advertiserId, byte[] data)556 private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data); 557 setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable)558 private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable); 559 } 560