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