• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.car.bluetooth;
18 
19 import static com.android.car.bluetooth.FastPairAccountKeyStorage.AccountKey;
20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothManager;
25 import android.bluetooth.le.AdvertiseData;
26 import android.bluetooth.le.AdvertisingSet;
27 import android.bluetooth.le.AdvertisingSetCallback;
28 import android.bluetooth.le.AdvertisingSetParameters;
29 import android.bluetooth.le.BluetoothLeAdvertiser;
30 import android.car.Car;
31 import android.car.PlatformVersion;
32 import android.car.builtin.bluetooth.le.AdvertisingSetCallbackHelper;
33 import android.car.builtin.bluetooth.le.AdvertisingSetHelper;
34 import android.car.builtin.util.Slogf;
35 import android.content.Context;
36 import android.os.Handler;
37 import android.os.Message;
38 import android.os.ParcelUuid;
39 import android.util.Log;
40 
41 import com.android.car.CarLog;
42 import com.android.car.CarServiceUtils;
43 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
44 import com.android.car.internal.util.IndentingPrintWriter;
45 
46 import java.nio.ByteBuffer;
47 import java.nio.ByteOrder;
48 import java.security.MessageDigest;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.List;
52 import java.util.Objects;
53 import java.util.Random;
54 
55 /**
56  * The FastPairAdvertiser is responsible for the BLE advertisement of either the model ID while
57  * in pairing mode or the stored account keys while not in pairing mode.
58  *
59  * This advertiser should always be advertising either the model ID or the account key filter if the
60  * Bluetooth adapter is on.
61  *
62  * Additionally, the Fast Pair Advertiser is the only entity allowed to receive notifications about
63  * our private address, which is used by the protocol to verify the remote device we're talking to.
64  *
65  * Advertisement packet formats and timing/intervals are described by the Fast Pair specification
66  */
67 public class FastPairAdvertiser {
68     private static final String TAG = CarLog.tagFor(FastPairAdvertiser.class);
69     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
70 
71     public static final int STATE_STOPPED = 0;
72     public static final int STATE_STARTING = 1;
73     public static final int STATE_STARTED = 2;
74     public static final int STATE_STOPPING = 3;
75 
76     // Service ID assigned for FastPair.
77     public static final ParcelUuid SERVICE_UUID = ParcelUuid
78             .fromString("0000FE2C-0000-1000-8000-00805f9b34fb");
79 
80     private static final byte ACCOUNT_KEY_FILTER_FLAGS = 0x00;
81     private static final byte SALT_FIELD_DESCRIPTOR = 0x11;
82 
83     private final Context mContext;
84     private final BluetoothAdapter mBluetoothAdapter;
85     private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
86     private AdvertisingSetParameters mAdvertisingSetParameters;
87     private AdvertisingSetCallback mAdvertisingSetCallback;
88     private AdvertiseData mData;
89     private int mTxPower = 0;
90     private Callbacks mCallbacks;
91 
92     private final AdvertisingHandler mAdvertisingHandler;
93 
94     /**
95      * Receive events from this FastPairAdvertiser
96      */
97     public interface Callbacks {
98         /**
99          * Notify the Resolvable Private Address of the BLE advertiser.
100          *
101          * @param device The current LE address
102          */
onRpaUpdated(BluetoothDevice device)103         void onRpaUpdated(BluetoothDevice device);
104     }
105 
FastPairAdvertiser(Context context)106     FastPairAdvertiser(Context context) {
107         mContext = context;
108         mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
109         Objects.requireNonNull(mBluetoothAdapter, "Bluetooth adapter cannot be null");
110         mAdvertisingHandler = new AdvertisingHandler();
111         initializeAdvertisingSetCallback();
112     }
113 
114     /**
115      * Advertise the Fast Pair model ID.
116      *
117      * Model ID advertisements have the following format:
118      *
119      * Octet | Type   | Description                             | Value
120      * --------------------------------------------------------------------------------------------
121      * 0-2   | uint24 | 24-bit Model ID                         | varies, example: 0x123456
122      * --------------------------------------------------------------------------------------------
123      *
124      * Ensure advertising is stopped before switching the underlying advertising data. This can be
125      * done by calling stopAdvertising().
126      */
advertiseModelId(int modelId, Callbacks callback)127     public void advertiseModelId(int modelId, Callbacks callback) {
128         if (DBG) {
129             Slogf.d(TAG, "advertiseModelId(id=0x%s)", Integer.toHexString(modelId));
130         }
131 
132         ByteBuffer modelIdBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(
133                 modelId);
134         mAdvertisingHandler.startAdvertising(Arrays.copyOfRange(modelIdBytes.array(), 1, 4),
135                 AdvertisingSetParameters.INTERVAL_LOW, callback);
136     }
137 
138     /**
139      * Advertise the stored account keys.
140      *
141      * Account Keys advertisements have the following format:
142      *
143      * Octet | Type   | Description                             | Value
144      * --------------------------------------------------------------------------------------------
145      * 0     | uint8  | Flags, all bits reserved for future use | 0x00
146      * --------------------------------------------------------------------------------------------
147      * 1-N   |        | Account Key Data                        | 0x00, if empty
148      *       |        |                                         | bloom(account keys), otherwise
149      * --------------------------------------------------------------------------------------------
150      *
151      * The Account Key Data has the following format:
152      *
153      * Octet | Type   | Description                             | Value
154      * --------------------------------------------------------------------------------------------
155      * 0     | uint8  | 0bLLLLTTTT (T=type, L=Length)           | length=0bLLLL, 4 bit field length
156      *       |        |                                         | type=0bTTTT, 0b0000 (show UI)
157      *       |        |                                         | type=0bTTTT, 0b0010 (hide UI)
158      * --------------------------------------------------------------------------------------------
159      * 1-N   |        | Account Key Filter                      | 0x00, if empty
160      * --------------------------------------------------------------------------------------------
161      * N+1   | uint8  | Salt Field Length and Type              | 0b00010001
162      * --------------------------------------------------------------------------------------------
163      * N+2   | uint8  | Salt                                    | varies
164      * --------------------------------------------------------------------------------------------
165      *
166      * The Account Key Filter is a bloom filter representation of the stored keys. The filter alone
167      * requires 1.2 * <number of keys> + 3 bytes. This means an Account Key Filter packet is a total
168      * size of 4 (flags, filter field id + length, salt field id + length, salt) + 1.2 * <keys> + 3
169      * bytes.
170      *
171      * Keep this in mind when defining your max keys size, as it will directly impact the size of
172      * advertisement data and packet. Make sure your controller supports your maximum advertisement
173      * size.
174      *
175      * Ensure advertising is stopped before switching the underlying advertising data. This can be
176      * done by calling stopAdvertising().
177      */
advertiseAccountKeys(List<AccountKey> accountKeys, Callbacks callback)178     public void advertiseAccountKeys(List<AccountKey> accountKeys, Callbacks callback) {
179         if (DBG) {
180             Slogf.d(TAG, "advertiseAccountKeys(keys=%s)", accountKeys);
181         }
182 
183         // If we have account keys, then create a salt value and generate the account key filter
184         byte[] accountKeyFilter = null;
185         byte[] salt = null;
186         if (accountKeys != null && accountKeys.size() > 0) {
187             salt = new byte[1];
188             new Random().nextBytes(salt);
189             accountKeyFilter = getAccountKeyFilter(accountKeys, salt[0]);
190         }
191 
192         // If we have an account key filter, then create an advertisement payload using it and the
193         // salt. Otherwise, create an empty advertisement.
194         ByteBuffer accountKeyAdvertisement = null;
195         if (accountKeyFilter != null) {
196             int size = accountKeyFilter.length;
197             accountKeyAdvertisement = ByteBuffer.allocate(size + 4); // filter + 3b flags + 1b salt
198             accountKeyAdvertisement.put(ACCOUNT_KEY_FILTER_FLAGS); // Reserved Flags byte
199             accountKeyAdvertisement.put((byte) (size << 4)); // Length Type and Size, 0bLLLLTTTT
200             accountKeyAdvertisement.put(accountKeyFilter); // Account Key Bloom Results
201             accountKeyAdvertisement.put(SALT_FIELD_DESCRIPTOR); // Salt Field/Size, 0bLLLLTTTT
202             accountKeyAdvertisement.put(salt); // The actual 1 byte of salt
203         } else {
204             accountKeyAdvertisement = ByteBuffer.allocate(2);
205             accountKeyAdvertisement.put((byte) 0x00); // Reserved Flags Byte
206             accountKeyAdvertisement.put((byte) 0x00); // Empty Keys Byte
207         }
208 
209         mAdvertisingHandler.startAdvertising(accountKeyAdvertisement.array(),
210                 AdvertisingSetParameters.INTERVAL_MEDIUM, callback);
211     }
212 
213     /**
214      * Calculate the account key filter, defined as the bloom of the set of account keys.
215      *
216      * @param keys The list of Fast Pair Account keys
217      * @param salt The salt to be used here, as well as appended to the Account Data Advertisment
218      * @return A byte array representing the account key filter
219      */
getAccountKeyFilter(List<AccountKey> keys, byte salt)220     byte[] getAccountKeyFilter(List<AccountKey> keys, byte salt) {
221         if (keys == null || keys.size() <= 0) {
222             Slogf.e(TAG, "Cannot generate account key filter, keys=%s, salt=%s", keys, salt);
223             return null;
224         }
225 
226         int size = (int) (1.2 * keys.size()) + 3;
227         byte[] filter = new byte[size];
228 
229         for (AccountKey key : keys) {
230             byte[] v = Arrays.copyOf(key.toBytes(), 17);
231             v[16] = salt;
232             try {
233                 byte[] hashed = MessageDigest.getInstance("SHA-256").digest(v);
234                 ByteBuffer byteBuffer = ByteBuffer.wrap(hashed);
235                 for (int j = 0; j < 8; j++) {
236                     long k = Integer.toUnsignedLong(byteBuffer.getInt()) % (size * 8);
237                     filter[(int) (k / 8)] |= (byte) (1 << (k % 8));
238                 }
239             } catch (Exception e) {
240                 Slogf.e(TAG, "Error calculating account key filter: %s", e);
241                 return null;
242             }
243         }
244         return filter;
245     }
246 
247     /**
248      * Stop advertising any data.
249      */
stopAdvertising()250     public void stopAdvertising() {
251         if (DBG) {
252             Slogf.d(TAG, "stoppingAdvertising");
253         }
254         mAdvertisingHandler.stopAdvertising();
255     }
256 
257     /**
258      * Start a BLE advertisement using the given data, interval, and callbacks.
259      *
260      * Must be called on the Advertising Handler.
261      *
262      * @param data The data to advertise
263      * @param interval The interval at which to advertise
264      * @param callbacks The callback object to notify of FastPairAdvertiser events
265      */
startAdvertisingInternal(byte[] data, int interval, Callbacks callbacks)266     private boolean startAdvertisingInternal(byte[] data, int interval, Callbacks callbacks) {
267         if (DBG) {
268             Slogf.d(TAG, "startAdvertisingInternal(data=%s, internval=%d, cb=%s)",
269                     Arrays.toString(data), interval, callbacks);
270         }
271 
272         mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
273         if (mBluetoothLeAdvertiser == null) {
274             Slogf.e(TAG, "startAdvertisingInternal: Failed to get an advertiser.");
275             mBluetoothLeAdvertiser = null;
276             return false;
277         }
278 
279         mAdvertisingSetParameters = new AdvertisingSetParameters.Builder()
280                 .setLegacyMode(true)
281                 .setInterval(interval)
282                 .setScannable(true)
283                 .setConnectable(true)
284                 .build();
285         mData = new AdvertiseData.Builder()
286                 .addServiceUuid(SERVICE_UUID)
287                 .addServiceData(SERVICE_UUID, data)
288                 .setIncludeTxPowerLevel(true)
289                 .build();
290         mCallbacks = callbacks;
291 
292         mBluetoothLeAdvertiser.startAdvertisingSet(mAdvertisingSetParameters, mData, null, null,
293                 null, mAdvertisingSetCallback);
294         return true;
295     }
296 
297     /**
298      * Stop advertising any data.
299      *
300      * This must be called on the Advertising Handler.
301      */
stopAdvertisingInternal()302     private void stopAdvertisingInternal() {
303         if (DBG) {
304             Slogf.d(TAG, "stoppingAdvertisingInternal");
305         }
306 
307         if (mBluetoothLeAdvertiser == null) return;
308 
309         mBluetoothLeAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback);
310         mTxPower = 0;
311         mBluetoothLeAdvertiser = null;
312     }
313 
isAdvertising()314     public boolean isAdvertising() {
315         return getAdvertisingState() == STATE_STARTED;
316     }
317 
getAdvertisingState()318     public int getAdvertisingState() {
319         return mAdvertisingHandler.getState();
320     }
321 
initializeAdvertisingSetCallback()322     private void initializeAdvertisingSetCallback() {
323         // Certain functionality of {@link AdvertisingSetCallback} were disabled in
324         // {@code TIRAMISU} (major == 33, minor == 0) due to hidden API usage. These functionality
325         // were later restored, but require platform version to be at least TM-QPR-1
326         // (major == 33, minor == 1).
327         PlatformVersion version = Car.getPlatformVersion();
328         if (DBG) {
329             Slogf.d(TAG, "AdvertisingSetCallback running on platform version (major=%d, minor=%d)",
330                     version.getMajorVersion(), version.getMinorVersion());
331         }
332         if (version.isAtLeast(PlatformVersion.VERSION_CODES.TIRAMISU_1)) {
333             AdvertisingSetCallbackHelper.Callback proxy =
334                     new AdvertisingSetCallbackHelper.Callback() {
335                 @Override
336                 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
337                         int status) {
338                     onAdvertisingSetStartedHandler(advertisingSet, txPower, status);
339                     if (advertisingSet != null) {
340                         AdvertisingSetHelper.getOwnAddress(advertisingSet);
341                     }
342                 }
343 
344                 @Override
345                 public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
346                     onAdvertisingSetStoppedHandler(advertisingSet);
347                 }
348 
349                 @Override
350                 public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType,
351                         String address) {
352                     onOwnAddressReadHandler(addressType, address);
353                 }
354             };
355 
356             mAdvertisingSetCallback =
357                     AdvertisingSetCallbackHelper.createRealCallbackFromProxy(proxy);
358         } else {
359             mAdvertisingSetCallback = new AdvertisingSetCallback() {
360                 @Override
361                 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
362                         int status) {
363                     onAdvertisingSetStartedHandler(advertisingSet, txPower, status);
364                     // TODO(b/241933163): once there are formal APIs to get own address, this
365                     // warning can be removed.
366                     Slogf.w(TAG, "AdvertisingSet#getOwnAddress not called."
367                             + " This feature is not supported in this platform version.");
368                 }
369 
370                 @Override
371                 public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
372                     onAdvertisingSetStoppedHandler(advertisingSet);
373                 }
374             };
375         }
376     }
377 
378     // For {@link AdvertisingSetCallback#onAdvertisingSetStarted} and its proxy
onAdvertisingSetStartedHandler(AdvertisingSet advertisingSet, int txPower, int status)379     private void onAdvertisingSetStartedHandler(AdvertisingSet advertisingSet, int txPower,
380             int status) {
381         if (DBG) {
382             Slogf.d(TAG, "onAdvertisingSetStarted(): txPower: %d, status: %d", txPower, status);
383         }
384         if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS || advertisingSet == null) {
385             Slogf.w(TAG, "Failed to start advertising, status=%s, advertiser=%s",
386                     BluetoothUtils.getAdvertisingCallbackStatusName(status), advertisingSet);
387             mAdvertisingHandler.advertisingStopped();
388             return;
389         }
390         mTxPower = txPower;
391         mAdvertisingHandler.advertisingStarted();
392     }
393 
394     // For {@link AdvertisingSetCallback#onAdvertisingSetStopped} and its proxy
onAdvertisingSetStoppedHandler(AdvertisingSet advertisingSet)395     private void onAdvertisingSetStoppedHandler(AdvertisingSet advertisingSet) {
396         if (DBG) Slogf.d(TAG, "onAdvertisingSetStopped()");
397         mAdvertisingHandler.advertisingStopped();
398     }
399 
400     // For {@link AdvertisingSetCallback#onOwnAddressRead} and its proxy
onOwnAddressReadHandler(int addressType, String address)401     private void onOwnAddressReadHandler(int addressType, String address) {
402         if (DBG) Slogf.d(TAG, "onOwnAddressRead Type= %d, Address= %s", addressType, address);
403         mCallbacks.onRpaUpdated(mBluetoothAdapter.getRemoteDevice(address));
404     }
405 
406     /**
407      * A handler that synchronizes advertising events
408      */
409     // TODO (243161113): Clean this handler up to make it more clear and enable direct advertising
410     // data changes without stopping
411     private class AdvertisingHandler extends Handler {
412         private static final int MSG_ADVERTISING_STOPPED = 0;
413         private static final int MSG_START_ADVERTISING = 1;
414         private static final int MSG_ADVERTISING_STARTED = 2;
415         private static final int MSG_STOP_ADVERTISING = 3;
416         private static final int MSG_TIMEOUT = 4;
417 
418         private static final int OPERATION_TIMEOUT_MS = 4000;
419 
420         private int mState = STATE_STOPPED;
421         private final ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
422 
423         private class AdvertisingRequest {
424             public final byte[] mData;
425             public final int mInterval;
426             public final Callbacks mCallback;
427 
AdvertisingRequest(byte[] data, int interval, Callbacks callback)428             AdvertisingRequest(byte[] data, int interval, Callbacks callback) {
429                 mInterval = interval;
430                 mData = data;
431                 mCallback = callback;
432             }
433         }
434 
AdvertisingHandler()435         AdvertisingHandler() {
436             super(CarServiceUtils.getHandlerThread(FastPairProvider.THREAD_NAME).getLooper());
437         }
438 
startAdvertising(byte[] data, int interval, Callbacks callback)439         public void startAdvertising(byte[] data, int interval, Callbacks callback) {
440             if (DBG) Slogf.d(TAG, "HANDLER: startAdvertising(data=%s)", Arrays.toString(data));
441             AdvertisingRequest request = new AdvertisingRequest(data, interval, callback);
442             sendMessage(obtainMessage(MSG_START_ADVERTISING, request));
443         }
444 
advertisingStarted()445         public void advertisingStarted() {
446             if (DBG) Slogf.d(TAG, "HANDLER: advertisingStart()");
447             sendMessage(obtainMessage(MSG_ADVERTISING_STARTED));
448         }
449 
stopAdvertising()450         public void stopAdvertising() {
451             if (DBG) Slogf.d(TAG, "HANDLER: stopAdvertising()");
452             sendMessage(obtainMessage(MSG_STOP_ADVERTISING));
453         }
454 
advertisingStopped()455         public void advertisingStopped() {
456             if (DBG) Slogf.d(TAG, "HANDLER: advertisingStop()");
457             sendMessage(obtainMessage(MSG_ADVERTISING_STOPPED));
458         }
459 
queueOperationTimeout()460         private void queueOperationTimeout() {
461             removeMessages(MSG_TIMEOUT);
462             sendMessageDelayed(obtainMessage(MSG_TIMEOUT), OPERATION_TIMEOUT_MS);
463         }
464 
465         @Override
handleMessage(Message msg)466         public void handleMessage(Message msg) {
467             if (DBG) {
468                 Slogf.i(TAG, "HANDLER: Received message %s, state=%s", messageToString(msg.what),
469                         stateToString(mState));
470             }
471             switch (msg.what) {
472                 case MSG_ADVERTISING_STOPPED:
473                     removeMessages(MSG_TIMEOUT);
474                     transitionTo(STATE_STOPPED);
475                     processDeferredMessages();
476                     break;
477 
478                 case MSG_START_ADVERTISING:
479                     if (mState == STATE_STARTED) {
480                         break;
481                     } else if (mState != STATE_STOPPED) {
482                         deferMessage(msg);
483                         return;
484                     }
485                     AdvertisingRequest request = (AdvertisingRequest) msg.obj;
486                     if (startAdvertisingInternal(request.mData, request.mInterval,
487                             request.mCallback)) {
488                         transitionTo(STATE_STARTING);
489                     }
490                     queueOperationTimeout();
491                     break;
492 
493                 case MSG_ADVERTISING_STARTED:
494                     removeMessages(MSG_TIMEOUT);
495                     transitionTo(STATE_STARTED);
496                     processDeferredMessages();
497                     break;
498 
499                 case MSG_STOP_ADVERTISING:
500                     if (mState == STATE_STOPPED) {
501                         break;
502                     } else if (mState != STATE_STARTED) {
503                         deferMessage(msg);
504                         return;
505                     }
506                     stopAdvertisingInternal();
507                     transitionTo(STATE_STOPPING);
508                     queueOperationTimeout();
509                     break;
510                 case MSG_TIMEOUT:
511                     if (mState == STATE_STARTING) {
512                         Slogf.w(TAG, "HANDLER: Timed out waiting for startAdvertising");
513                         stopAdvertisingInternal();
514                     } else if (mState == STATE_STOPPING) {
515                         Slogf.w(TAG, "HANDLER: Timed out waiting for stopAdvertising");
516                     } else {
517                         Slogf.e(TAG, "HANDLER: Unexpected timeout in state %s",
518                                 stateToString(mState));
519                     }
520                     transitionTo(STATE_STOPPED);
521                     processDeferredMessages();
522                     break;
523 
524                 default:
525                     Slogf.e(TAG, "HANDLER: Unexpected message: %d", msg.what);
526             }
527         }
528 
transitionTo(int state)529         private void transitionTo(int state) {
530             if (DBG) Slogf.d(TAG, "HANDLER: %s -> %s", stateToString(mState), stateToString(state));
531             mState = state;
532         }
533 
deferMessage(Message message)534         private void deferMessage(Message message) {
535             if (DBG) {
536                 Slogf.i(TAG, "HANDLER: Deferred message, message=%s",
537                         messageToString(message.what));
538             }
539 
540             Message copy = obtainMessage();
541             copy.copyFrom(message);
542             mDeferredMessages.add(copy);
543 
544             if (DBG) {
545                 StringBuilder sb = new StringBuilder();
546                 sb.append("[");
547                 for (Message m : mDeferredMessages) {
548                     sb.append(" ").append(messageToString(m.what));
549                 }
550                 sb.append(" ]");
551                 Slogf.d(TAG, "HANDLER: Deferred List: %s", sb.toString());
552             }
553         }
554 
processDeferredMessages()555         private void processDeferredMessages() {
556             if (DBG) {
557                 Slogf.d(TAG, "HANDLER: Process deferred Messages, size=%d",
558                         mDeferredMessages.size());
559             }
560             for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
561                 Message message = mDeferredMessages.get(i);
562                 if (DBG) {
563                     Slogf.i(TAG, "HANDLER: Adding deferred message to front, message=%s",
564                             messageToString(message.what));
565                 }
566                 sendMessageAtFrontOfQueue(message);
567             }
568             mDeferredMessages.clear();
569         }
570 
getState()571         public int getState() {
572             return mState;
573         }
574 
messageToString(int message)575         private String messageToString(int message) {
576             switch (message) {
577                 case MSG_ADVERTISING_STOPPED:
578                     return "MSG_ADVERTISING_STOPPED";
579                 case MSG_START_ADVERTISING:
580                     return "MSG_START_ADVERTISING";
581                 case MSG_ADVERTISING_STARTED:
582                     return "MSG_ADVERTISING_STARTED";
583                 case MSG_STOP_ADVERTISING:
584                     return "MSG_STOP_ADVERTISING";
585                 case MSG_TIMEOUT:
586                     return "MSG_TIMEOUT";
587                 default:
588                     return "Unknown";
589             }
590         }
591     }
592 
stateToString(int state)593     private String stateToString(int state) {
594         switch (state) {
595             case STATE_STOPPED:
596                 return "STATE_STOPPED";
597             case STATE_STARTING:
598                 return "STATE_STARTING";
599             case STATE_STARTED:
600                 return "STATE_STARTED";
601             case STATE_STOPPING:
602                 return "STATE_STOPPING";
603             default:
604                 return "Unknown";
605         }
606     }
607 
608     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)609     public void dump(IndentingPrintWriter writer) {
610         writer.println("FastPairAdvertiser:");
611         writer.increaseIndent();
612         writer.println("AdvertisingState     : " + stateToString(getAdvertisingState()));
613         if (isAdvertising()) {
614             writer.println("Advertising Interval : " + mAdvertisingSetParameters.getInterval());
615             writer.println("TX Power             : " + mTxPower + "/"
616                     + mAdvertisingSetParameters.getTxPowerLevel());
617             writer.println("Advertising Data     : " + mData);
618         }
619         writer.decreaseIndent();
620     }
621 }
622