• 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.android.bluetooth.gatt;
18 
19 import static com.android.bluetooth.gatt.AdvertiseHelper.advertiseDataToBytes;
20 
21 import android.app.ActivityManager;
22 import android.bluetooth.le.AdvertiseCallback;
23 import android.bluetooth.le.AdvertiseData;
24 import android.bluetooth.le.AdvertisingSetParameters;
25 import android.bluetooth.le.IAdvertisingSetCallback;
26 import android.bluetooth.le.PeriodicAdvertisingParameters;
27 import android.content.AttributionSource;
28 import android.content.pm.PackageManager;
29 import android.os.Binder;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.RemoteException;
34 import android.util.Log;
35 
36 import com.android.bluetooth.Utils;
37 import com.android.bluetooth.btservice.AdapterService;
38 import com.android.bluetooth.flags.Flags;
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.util.HashMap;
42 import java.util.Map;
43 import java.util.concurrent.CompletableFuture;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.TimeoutException;
47 
48 /** Manages Bluetooth LE advertising operations. */
49 public class AdvertiseManager {
50     private static final String TAG =
51             GattServiceConfig.TAG_PREFIX + AdvertiseManager.class.getSimpleName();
52 
53     private static final long RUN_SYNC_WAIT_TIME_MS = 2000L;
54 
55     private final Map<IBinder, AdvertiserInfo> mAdvertisers = new HashMap<>();
56 
57     private final AdapterService mService;
58     private final AdvertiseManagerNativeInterface mNativeInterface;
59     private final AdvertiseBinder mAdvertiseBinder;
60     private final AdvertiserMap mAdvertiserMap;
61     private final ActivityManager mActivityManager;
62     private final Handler mHandler;
63 
64     private volatile boolean mIsAvailable = true;
65     @VisibleForTesting int mTempRegistrationId = -1;
66 
AdvertiseManager(AdapterService service, Looper advertiseLooper)67     AdvertiseManager(AdapterService service, Looper advertiseLooper) {
68         this(
69                 service,
70                 advertiseLooper,
71                 AdvertiseManagerNativeInterface.getInstance(),
72                 new AdvertiserMap());
73     }
74 
75     @VisibleForTesting
AdvertiseManager( AdapterService service, Looper advertiseLooper, AdvertiseManagerNativeInterface nativeInterface, AdvertiserMap advertiserMap)76     AdvertiseManager(
77             AdapterService service,
78             Looper advertiseLooper,
79             AdvertiseManagerNativeInterface nativeInterface,
80             AdvertiserMap advertiserMap) {
81         Log.d(TAG, "advertise manager created");
82         mService = service;
83         mNativeInterface = nativeInterface;
84         mAdvertiserMap = advertiserMap;
85         mActivityManager = mService.getSystemService(ActivityManager.class);
86         mNativeInterface.init(this);
87         mHandler = new Handler(advertiseLooper);
88         mAdvertiseBinder = new AdvertiseBinder(service, this);
89     }
90 
cleanup()91     void cleanup() {
92         Log.d(TAG, "cleanup()");
93         mIsAvailable = false;
94         mHandler.removeCallbacksAndMessages(null);
95         forceRunSyncOnAdvertiseThread(
96                 () -> {
97                     mAdvertiserMap.clear();
98                     mAdvertiseBinder.cleanup();
99                     mNativeInterface.cleanup();
100                     mAdvertisers.clear();
101                 });
102     }
103 
dump(StringBuilder sb)104     void dump(StringBuilder sb) {
105         forceRunSyncOnAdvertiseThread(() -> mAdvertiserMap.dump(sb));
106     }
107 
getBinder()108     AdvertiseBinder getBinder() {
109         return mAdvertiseBinder;
110     }
111 
AdvertiserInfo( Integer id, AdvertisingSetDeathRecipient deathRecipient, IAdvertisingSetCallback callback)112     private record AdvertiserInfo(
113             /* When id is negative, the registration is ongoing. When the registration finishes, id
114              * becomes equal to advertiser_id */
115             Integer id,
116             AdvertisingSetDeathRecipient deathRecipient,
117             IAdvertisingSetCallback callback) {}
118 
119     private interface CallbackWrapper {
call()120         void call() throws RemoteException;
121     }
122 
toBinder(IAdvertisingSetCallback e)123     IBinder toBinder(IAdvertisingSetCallback e) {
124         return e.asBinder();
125     }
126 
127     class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient {
128         public IAdvertisingSetCallback callback;
129         private final String mPackageName;
130 
AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback, String packageName)131         AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback, String packageName) {
132             this.callback = callback;
133             this.mPackageName = packageName;
134         }
135 
136         @Override
binderDied()137         public void binderDied() {
138             Log.d(TAG, "Binder is dead - unregistering advertising set (" + mPackageName + ")!");
139             doOnAdvertiseThread(() -> stopAdvertisingSet(callback));
140         }
141     }
142 
findAdvertiser(int advertiserId)143     private Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) {
144         return mAdvertisers.entrySet().stream()
145                 .filter(e -> e.getValue().id == advertiserId)
146                 .findFirst()
147                 .orElse(null);
148     }
149 
onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status)150     void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) {
151         Log.d(
152                 TAG,
153                 "onAdvertisingSetStarted() - regId="
154                         + regId
155                         + ", advertiserId="
156                         + advertiserId
157                         + ", status="
158                         + status);
159         checkThread();
160 
161         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(regId);
162         if (entry == null) {
163             Log.i(TAG, "onAdvertisingSetStarted() - no callback found for regId " + regId);
164             // Advertising set was stopped before it was properly registered.
165             mNativeInterface.stopAdvertisingSet(advertiserId);
166             return;
167         }
168 
169         AdvertisingSetDeathRecipient deathRecipient = entry.getValue().deathRecipient;
170         IAdvertisingSetCallback callback = entry.getValue().callback;
171         if (status == 0) {
172             entry.setValue(new AdvertiserInfo(advertiserId, deathRecipient, callback));
173             mAdvertiserMap.setAdvertiserIdByRegId(regId, advertiserId);
174         } else {
175             IBinder binder = entry.getKey();
176             binder.unlinkToDeath(deathRecipient, 0);
177             mAdvertisers.remove(binder);
178 
179             AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(regId);
180             if (stats != null) {
181                 stats.recordAdvertiseStop(mAdvertisers.size());
182                 stats.recordAdvertiseErrorCount(status);
183             }
184             mAdvertiserMap.removeAppAdvertiseStats(regId);
185         }
186 
187         sendToCallback(
188                 advertiserId,
189                 () ->
190                         callback.onAdvertisingSetStarted(
191                                 mAdvertiseBinder, advertiserId, txPower, status));
192     }
193 
onAdvertisingEnabled(int advertiserId, boolean enable, int status)194     void onAdvertisingEnabled(int advertiserId, boolean enable, int status) {
195         Log.d(
196                 TAG,
197                 "onAdvertisingSetEnabled() - advertiserId="
198                         + advertiserId
199                         + ", enable="
200                         + enable
201                         + ", status="
202                         + status);
203         checkThread();
204 
205         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
206         if (entry == null) {
207             Log.i(
208                     TAG,
209                     "onAdvertisingSetEnable() - no callback found for advertiserId "
210                             + advertiserId);
211             return;
212         }
213 
214         IAdvertisingSetCallback callback = entry.getValue().callback;
215         sendToCallback(
216                 advertiserId, () -> callback.onAdvertisingEnabled(advertiserId, enable, status));
217 
218         if (!enable && status != 0) {
219             AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId);
220             if (stats != null) {
221                 stats.recordAdvertiseStop(mAdvertisers.size());
222             }
223         }
224     }
225 
fetchAppForegroundState(int id)226     private void fetchAppForegroundState(int id) {
227         PackageManager packageManager = mService.getPackageManager();
228         if (mActivityManager == null || packageManager == null) {
229             return;
230         }
231         int appUid = Binder.getCallingUid();
232         String[] packages = packageManager.getPackagesForUid(appUid);
233         if (packages == null || packages.length == 0) {
234             return;
235         }
236         int importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
237         for (String packageName : packages) {
238             importance = Math.min(importance, mActivityManager.getPackageImportance(packageName));
239         }
240         AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(id);
241         if (stats != null) {
242             stats.setAppImportance(importance);
243         }
244     }
245 
startAdvertisingSet( AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents, int serverIf, IAdvertisingSetCallback callback, AttributionSource attrSource)246     void startAdvertisingSet(
247             AdvertisingSetParameters parameters,
248             AdvertiseData advertiseData,
249             AdvertiseData scanResponse,
250             PeriodicAdvertisingParameters periodicParameters,
251             AdvertiseData periodicData,
252             int duration,
253             int maxExtAdvEvents,
254             int serverIf,
255             IAdvertisingSetCallback callback,
256             AttributionSource attrSource) {
257         checkThread();
258         // If we are using an isolated server, force usage of an NRPA
259         if (serverIf != 0
260                 && parameters.getOwnAddressType()
261                         != AdvertisingSetParameters.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE) {
262             Log.w(TAG, "Cannot advertise an isolated GATT server using a resolvable address");
263             try {
264                 callback.onAdvertisingSetStarted(
265                         mAdvertiseBinder,
266                         0x00,
267                         0x00,
268                         AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
269             } catch (RemoteException exception) {
270                 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception));
271             }
272             return;
273         }
274 
275         int appUid = Binder.getCallingUid();
276         String packageName = null;
277         if (mService.getPackageManager() != null) {
278             packageName = mService.getPackageManager().getNameForUid(appUid);
279         }
280         if (packageName == null) {
281             packageName = "Unknown package name (UID: " + appUid + ")";
282         }
283         AdvertisingSetDeathRecipient deathRecipient =
284                 new AdvertisingSetDeathRecipient(callback, packageName);
285         IBinder binder = toBinder(callback);
286         try {
287             binder.linkToDeath(deathRecipient, 0);
288         } catch (RemoteException e) {
289             throw new IllegalArgumentException("Can't link to advertiser's death");
290         }
291 
292         final String deviceName = mService.getName();
293         try {
294             byte[] advDataBytes = advertiseDataToBytes(advertiseData, deviceName);
295             byte[] scanResponseBytes = advertiseDataToBytes(scanResponse, deviceName);
296             byte[] periodicDataBytes = advertiseDataToBytes(periodicData, deviceName);
297 
298             int cbId = --mTempRegistrationId;
299             mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback));
300 
301             Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
302 
303             mAdvertiserMap.addAppAdvertiseStats(cbId, mService, attrSource);
304             fetchAppForegroundState(cbId);
305             mAdvertiserMap.recordAdvertiseStart(
306                     cbId,
307                     parameters,
308                     advertiseData,
309                     scanResponse,
310                     periodicParameters,
311                     periodicData,
312                     duration,
313                     maxExtAdvEvents);
314 
315             mNativeInterface.startAdvertisingSet(
316                     parameters,
317                     advDataBytes,
318                     scanResponseBytes,
319                     periodicParameters,
320                     periodicDataBytes,
321                     duration,
322                     maxExtAdvEvents,
323                     cbId,
324                     serverIf);
325 
326         } catch (IllegalArgumentException e) {
327             try {
328                 binder.unlinkToDeath(deathRecipient, 0);
329                 callback.onAdvertisingSetStarted(
330                         mAdvertiseBinder,
331                         0x00,
332                         0x00,
333                         AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
334             } catch (RemoteException exception) {
335                 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception));
336             }
337         }
338     }
339 
onOwnAddressRead(int advertiserId, int addressType, String address)340     void onOwnAddressRead(int advertiserId, int addressType, String address) {
341         Log.d(TAG, "onOwnAddressRead() advertiserId=" + advertiserId);
342         checkThread();
343 
344         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
345         if (entry == null) {
346             Log.w(TAG, "onOwnAddressRead() - bad advertiserId " + advertiserId);
347             return;
348         }
349 
350         IAdvertisingSetCallback callback = entry.getValue().callback;
351         sendToCallback(
352                 advertiserId, () -> callback.onOwnAddressRead(advertiserId, addressType, address));
353     }
354 
getOwnAddress(int advertiserId)355     void getOwnAddress(int advertiserId) {
356         checkThread();
357         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
358         if (entry == null) {
359             Log.w(TAG, "getOwnAddress() - bad advertiserId " + advertiserId);
360             return;
361         }
362         mNativeInterface.getOwnAddress(advertiserId);
363     }
364 
stopAdvertisingSet(IAdvertisingSetCallback callback)365     void stopAdvertisingSet(IAdvertisingSetCallback callback) {
366         checkThread();
367         IBinder binder = toBinder(callback);
368         Log.d(TAG, "stopAdvertisingSet() " + binder);
369 
370         AdvertiserInfo adv = mAdvertisers.remove(binder);
371         if (adv == null) {
372             Log.e(TAG, "stopAdvertisingSet() - no client found for callback");
373             return;
374         }
375 
376         Integer advertiserId = adv.id;
377         binder.unlinkToDeath(adv.deathRecipient, 0);
378 
379         if (advertiserId < 0) {
380             Log.i(TAG, "stopAdvertisingSet() - advertiser not finished registration yet");
381             // Advertiser will be freed once initiated in onAdvertisingSetStarted()
382             return;
383         }
384 
385         mNativeInterface.stopAdvertisingSet(advertiserId);
386 
387         try {
388             callback.onAdvertisingSetStopped(advertiserId);
389         } catch (RemoteException e) {
390             Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
391         }
392 
393         mAdvertiserMap.recordAdvertiseStop(advertiserId);
394     }
395 
enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents)396     void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
397         checkThread();
398         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
399         if (entry == null) {
400             Log.w(TAG, "enableAdvertisingSet() - bad advertiserId " + advertiserId);
401             return;
402         }
403         fetchAppForegroundState(advertiserId);
404         mNativeInterface.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents);
405 
406         mAdvertiserMap.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents);
407     }
408 
setAdvertisingData(int advertiserId, AdvertiseData data)409     void setAdvertisingData(int advertiserId, AdvertiseData data) {
410         checkThread();
411         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
412         if (entry == null) {
413             Log.w(TAG, "setAdvertisingData() - bad advertiserId " + advertiserId);
414             return;
415         }
416         final String deviceName = mService.getName();
417         try {
418             mNativeInterface.setAdvertisingData(
419                     advertiserId, advertiseDataToBytes(data, deviceName));
420 
421             mAdvertiserMap.setAdvertisingData(advertiserId, data);
422         } catch (IllegalArgumentException e) {
423             try {
424                 onAdvertisingDataSet(
425                         advertiserId, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
426             } catch (Exception exception) {
427                 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception));
428             }
429         }
430     }
431 
setScanResponseData(int advertiserId, AdvertiseData data)432     void setScanResponseData(int advertiserId, AdvertiseData data) {
433         checkThread();
434         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
435         if (entry == null) {
436             Log.w(TAG, "setScanResponseData() - bad advertiserId " + advertiserId);
437             return;
438         }
439         final String deviceName = mService.getName();
440         try {
441             mNativeInterface.setScanResponseData(
442                     advertiserId, advertiseDataToBytes(data, deviceName));
443 
444             mAdvertiserMap.setScanResponseData(advertiserId, data);
445         } catch (IllegalArgumentException e) {
446             try {
447                 onScanResponseDataSet(
448                         advertiserId, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
449             } catch (Exception exception) {
450                 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception));
451             }
452         }
453     }
454 
setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters)455     void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) {
456         checkThread();
457         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
458         if (entry == null) {
459             Log.w(TAG, "setAdvertisingParameters() - bad advertiserId " + advertiserId);
460             return;
461         }
462         mNativeInterface.setAdvertisingParameters(advertiserId, parameters);
463 
464         mAdvertiserMap.setAdvertisingParameters(advertiserId, parameters);
465     }
466 
setPeriodicAdvertisingParameters( int advertiserId, PeriodicAdvertisingParameters parameters)467     void setPeriodicAdvertisingParameters(
468             int advertiserId, PeriodicAdvertisingParameters parameters) {
469         checkThread();
470         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
471         if (entry == null) {
472             Log.w(TAG, "setPeriodicAdvertisingParameters() - bad advertiserId " + advertiserId);
473             return;
474         }
475         mNativeInterface.setPeriodicAdvertisingParameters(advertiserId, parameters);
476 
477         mAdvertiserMap.setPeriodicAdvertisingParameters(advertiserId, parameters);
478     }
479 
setPeriodicAdvertisingData(int advertiserId, AdvertiseData data)480     void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
481         checkThread();
482         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
483         if (entry == null) {
484             Log.w(TAG, "setPeriodicAdvertisingData() - bad advertiserId " + advertiserId);
485             return;
486         }
487         final String deviceName = mService.getName();
488         try {
489             mNativeInterface.setPeriodicAdvertisingData(
490                     advertiserId, advertiseDataToBytes(data, deviceName));
491 
492             mAdvertiserMap.setPeriodicAdvertisingData(advertiserId, data);
493         } catch (IllegalArgumentException e) {
494             try {
495                 onPeriodicAdvertisingDataSet(
496                         advertiserId, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
497             } catch (Exception exception) {
498                 Log.e(TAG, "Failed to callback:" + Log.getStackTraceString(exception));
499             }
500         }
501     }
502 
setPeriodicAdvertisingEnable(int advertiserId, boolean enable)503     void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
504         checkThread();
505         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
506         if (entry == null) {
507             Log.w(TAG, "setPeriodicAdvertisingEnable() - bad advertiserId " + advertiserId);
508             return;
509         }
510         mNativeInterface.setPeriodicAdvertisingEnable(advertiserId, enable);
511     }
512 
onAdvertisingDataSet(int advertiserId, int status)513     void onAdvertisingDataSet(int advertiserId, int status) {
514         checkThread();
515         Log.d(TAG, "onAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status);
516 
517         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
518         if (entry == null) {
519             Log.i(TAG, "onAdvertisingDataSet() - bad advertiserId " + advertiserId);
520             return;
521         }
522 
523         IAdvertisingSetCallback callback = entry.getValue().callback;
524         sendToCallback(advertiserId, () -> callback.onAdvertisingDataSet(advertiserId, status));
525     }
526 
onScanResponseDataSet(int advertiserId, int status)527     void onScanResponseDataSet(int advertiserId, int status) {
528         checkThread();
529         Log.d(TAG, "onScanResponseDataSet() advertiserId=" + advertiserId + ", status=" + status);
530 
531         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
532         if (entry == null) {
533             Log.i(TAG, "onScanResponseDataSet() - bad advertiserId " + advertiserId);
534             return;
535         }
536 
537         IAdvertisingSetCallback callback = entry.getValue().callback;
538         sendToCallback(advertiserId, () -> callback.onScanResponseDataSet(advertiserId, status));
539     }
540 
onAdvertisingParametersUpdated(int advertiserId, int txPower, int status)541     void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
542         Log.d(
543                 TAG,
544                 "onAdvertisingParametersUpdated() advertiserId="
545                         + advertiserId
546                         + ", txPower="
547                         + txPower
548                         + ", status="
549                         + status);
550         checkThread();
551 
552         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
553         if (entry == null) {
554             Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiserId " + advertiserId);
555             return;
556         }
557 
558         IAdvertisingSetCallback callback = entry.getValue().callback;
559         sendToCallback(
560                 advertiserId,
561                 () -> callback.onAdvertisingParametersUpdated(advertiserId, txPower, status));
562     }
563 
onPeriodicAdvertisingParametersUpdated(int advertiserId, int status)564     void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
565         Log.d(
566                 TAG,
567                 "onPeriodicAdvertisingParametersUpdated() advertiserId="
568                         + advertiserId
569                         + ", status="
570                         + status);
571         checkThread();
572 
573         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
574         if (entry == null) {
575             Log.i(
576                     TAG,
577                     "onPeriodicAdvertisingParametersUpdated() - bad advertiserId " + advertiserId);
578             return;
579         }
580 
581         IAdvertisingSetCallback callback = entry.getValue().callback;
582         sendToCallback(
583                 advertiserId,
584                 () -> callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status));
585     }
586 
onPeriodicAdvertisingDataSet(int advertiserId, int status)587     void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
588         Log.d(
589                 TAG,
590                 "onPeriodicAdvertisingDataSet() advertiserId="
591                         + advertiserId
592                         + ", status="
593                         + status);
594         checkThread();
595 
596         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
597         if (entry == null) {
598             Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiserId " + advertiserId);
599             return;
600         }
601 
602         IAdvertisingSetCallback callback = entry.getValue().callback;
603         sendToCallback(
604                 advertiserId, () -> callback.onPeriodicAdvertisingDataSet(advertiserId, status));
605     }
606 
onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status)607     void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) {
608         Log.d(
609                 TAG,
610                 "onPeriodicAdvertisingEnabled() advertiserId="
611                         + advertiserId
612                         + ", status="
613                         + status);
614 
615         Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
616         if (entry == null) {
617             Log.i(TAG, "onAdvertisingSetEnable() - bad advertiserId " + advertiserId);
618             return;
619         }
620         checkThread();
621 
622         IAdvertisingSetCallback callback = entry.getValue().callback;
623         sendToCallback(
624                 advertiserId,
625                 () -> callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status));
626 
627         AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId);
628         if (stats != null) {
629             stats.onPeriodicAdvertiseEnabled(enable);
630         }
631     }
632 
doOnAdvertiseThread(Runnable r)633     void doOnAdvertiseThread(Runnable r) {
634         if (mIsAvailable) {
635             if (Flags.advertiseThread()) {
636                 boolean posted =
637                         mHandler.post(
638                                 () -> {
639                                     if (mIsAvailable) {
640                                         r.run();
641                                     }
642                                 });
643                 if (!posted) {
644                     Log.w(TAG, "Unable to post async task");
645                 }
646             } else {
647                 r.run();
648             }
649         }
650     }
651 
forceRunSyncOnAdvertiseThread(Runnable r)652     private void forceRunSyncOnAdvertiseThread(Runnable r) {
653         if (!Flags.advertiseThread()) {
654             r.run();
655             return;
656         }
657         final CompletableFuture<Void> future = new CompletableFuture<>();
658         boolean posted =
659                 mHandler.postAtFrontOfQueue(
660                         () -> {
661                             r.run();
662                             future.complete(null);
663                         });
664         if (!posted) {
665             Log.w(TAG, "Unable to post sync task");
666             return;
667         }
668         try {
669             future.get(RUN_SYNC_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
670         } catch (InterruptedException | TimeoutException | ExecutionException e) {
671             Log.w(TAG, "Unable to complete sync task: " + e);
672         }
673     }
674 
checkThread()675     private void checkThread() {
676         if (Flags.advertiseThread()
677                 && !mHandler.getLooper().isCurrentThread()
678                 && !Utils.isInstrumentationTestMode()) {
679             throw new IllegalStateException("Not on advertise thread");
680         }
681     }
682 
sendToCallback(int advertiserId, CallbackWrapper wrapper)683     private static void sendToCallback(int advertiserId, CallbackWrapper wrapper) {
684         try {
685             wrapper.call();
686         } catch (RemoteException e) {
687             Log.i(TAG, "RemoteException in callback for advertiserId: " + advertiserId);
688         }
689     }
690 }
691