• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 android.bluetooth;
18 
19 import android.annotation.SdkConstant;
20 import android.annotation.SdkConstant.SdkConstantType;
21 import android.content.Context;
22 import android.os.IBinder;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.os.ParcelUuid;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.util.Log;
29 
30 import java.io.IOException;
31 import java.io.UnsupportedEncodingException;
32 import java.util.UUID;
33 
34 /**
35  * Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you
36  * create a connection with the respective device or query information about
37  * it, such as the name, address, class, and bonding state.
38  *
39  * <p>This class is really just a thin wrapper for a Bluetooth hardware
40  * address. Objects of this class are immutable. Operations on this class
41  * are performed on the remote Bluetooth hardware address, using the
42  * {@link BluetoothAdapter} that was used to create this {@link
43  * BluetoothDevice}.
44  *
45  * <p>To get a {@link BluetoothDevice}, use
46  * {@link BluetoothAdapter#getRemoteDevice(String)
47  * BluetoothAdapter.getRemoteDevice(String)} to create one representing a device
48  * of a known MAC address (which you can get through device discovery with
49  * {@link BluetoothAdapter}) or get one from the set of bonded devices
50  * returned by {@link BluetoothAdapter#getBondedDevices()
51  * BluetoothAdapter.getBondedDevices()}. You can then open a
52  * {@link BluetoothSocket} for communication with the remote device, using
53  * {@link #createRfcommSocketToServiceRecord(UUID)}.
54  *
55  * <p class="note"><strong>Note:</strong>
56  * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
57  *
58  * <div class="special reference">
59  * <h3>Developer Guides</h3>
60  * <p>For more information about using Bluetooth, read the
61  * <a href="{@docRoot}guide/topics/wireless/bluetooth.html">Bluetooth</a> developer guide.</p>
62  * </div>
63  *
64  * {@see BluetoothAdapter}
65  * {@see BluetoothSocket}
66  */
67 public final class BluetoothDevice implements Parcelable {
68     private static final String TAG = "BluetoothDevice";
69     private static final boolean DBG = false;
70 
71     /**
72      * Sentinel error value for this class. Guaranteed to not equal any other
73      * integer constant in this class. Provided as a convenience for functions
74      * that require a sentinel error value, for example:
75      * <p><code>Intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
76      * BluetoothDevice.ERROR)</code>
77      */
78     public static final int ERROR = Integer.MIN_VALUE;
79 
80     /**
81      * Broadcast Action: Remote device discovered.
82      * <p>Sent when a remote device is found during discovery.
83      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
84      * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or
85      * {@link #EXTRA_RSSI} if they are available.
86      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
87      */
88      // TODO: Change API to not broadcast RSSI if not available (incoming connection)
89     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
90     public static final String ACTION_FOUND =
91             "android.bluetooth.device.action.FOUND";
92 
93     /**
94      * Broadcast Action: Remote device disappeared.
95      * <p>Sent when a remote device that was found in the last discovery is not
96      * found in the current discovery.
97      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
98      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
99      * @hide
100      */
101     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
102     public static final String ACTION_DISAPPEARED =
103             "android.bluetooth.device.action.DISAPPEARED";
104 
105     /**
106      * Broadcast Action: Bluetooth class of a remote device has changed.
107      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
108      * #EXTRA_CLASS}.
109      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
110      * {@see BluetoothClass}
111      */
112     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
113     public static final String ACTION_CLASS_CHANGED =
114             "android.bluetooth.device.action.CLASS_CHANGED";
115 
116     /**
117      * Broadcast Action: Indicates a low level (ACL) connection has been
118      * established with a remote device.
119      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
120      * <p>ACL connections are managed automatically by the Android Bluetooth
121      * stack.
122      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
123      */
124     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
125     public static final String ACTION_ACL_CONNECTED =
126             "android.bluetooth.device.action.ACL_CONNECTED";
127 
128     /**
129      * Broadcast Action: Indicates that a low level (ACL) disconnection has
130      * been requested for a remote device, and it will soon be disconnected.
131      * <p>This is useful for graceful disconnection. Applications should use
132      * this intent as a hint to immediately terminate higher level connections
133      * (RFCOMM, L2CAP, or profile connections) to the remote device.
134      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
135      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
136      */
137     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
138     public static final String ACTION_ACL_DISCONNECT_REQUESTED =
139             "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
140 
141     /**
142      * Broadcast Action: Indicates a low level (ACL) disconnection from a
143      * remote device.
144      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
145      * <p>ACL connections are managed automatically by the Android Bluetooth
146      * stack.
147      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
148      */
149     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
150     public static final String ACTION_ACL_DISCONNECTED =
151             "android.bluetooth.device.action.ACL_DISCONNECTED";
152 
153     /**
154      * Broadcast Action: Indicates the friendly name of a remote device has
155      * been retrieved for the first time, or changed since the last retrieval.
156      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
157      * #EXTRA_NAME}.
158      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
159      */
160     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
161     public static final String ACTION_NAME_CHANGED =
162             "android.bluetooth.device.action.NAME_CHANGED";
163 
164     /**
165      * Broadcast Action: Indicates the alias of a remote device has been
166      * changed.
167      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
168      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
169      *
170      * @hide
171      */
172     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
173     public static final String ACTION_ALIAS_CHANGED =
174             "android.bluetooth.device.action.ALIAS_CHANGED";
175 
176     /**
177      * Broadcast Action: Indicates a change in the bond state of a remote
178      * device. For example, if a device is bonded (paired).
179      * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link
180      * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}.
181      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
182      */
183     // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also
184     // contain a hidden extra field EXTRA_REASON with the result code.
185     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
186     public static final String ACTION_BOND_STATE_CHANGED =
187             "android.bluetooth.device.action.BOND_STATE_CHANGED";
188 
189     /**
190      * Used as a Parcelable {@link BluetoothDevice} extra field in every intent
191      * broadcast by this class. It contains the {@link BluetoothDevice} that
192      * the intent applies to.
193      */
194     public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
195 
196     /**
197      * Used as a String extra field in {@link #ACTION_NAME_CHANGED} and {@link
198      * #ACTION_FOUND} intents. It contains the friendly Bluetooth name.
199      */
200     public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
201 
202     /**
203      * Used as an optional short extra field in {@link #ACTION_FOUND} intents.
204      * Contains the RSSI value of the remote device as reported by the
205      * Bluetooth hardware.
206      */
207     public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI";
208 
209     /**
210      * Used as a Parcelable {@link BluetoothClass} extra field in {@link
211      * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents.
212      */
213     public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
214 
215     /**
216      * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents.
217      * Contains the bond state of the remote device.
218      * <p>Possible values are:
219      * {@link #BOND_NONE},
220      * {@link #BOND_BONDING},
221      * {@link #BOND_BONDED}.
222      */
223     public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
224     /**
225      * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents.
226      * Contains the previous bond state of the remote device.
227      * <p>Possible values are:
228      * {@link #BOND_NONE},
229      * {@link #BOND_BONDING},
230      * {@link #BOND_BONDED}.
231      */
232     public static final String EXTRA_PREVIOUS_BOND_STATE =
233             "android.bluetooth.device.extra.PREVIOUS_BOND_STATE";
234     /**
235      * Indicates the remote device is not bonded (paired).
236      * <p>There is no shared link key with the remote device, so communication
237      * (if it is allowed at all) will be unauthenticated and unencrypted.
238      */
239     public static final int BOND_NONE = 10;
240     /**
241      * Indicates bonding (pairing) is in progress with the remote device.
242      */
243     public static final int BOND_BONDING = 11;
244     /**
245      * Indicates the remote device is bonded (paired).
246      * <p>A shared link keys exists locally for the remote device, so
247      * communication can be authenticated and encrypted.
248      * <p><i>Being bonded (paired) with a remote device does not necessarily
249      * mean the device is currently connected. It just means that the pending
250      * procedure was completed at some earlier time, and the link key is still
251      * stored locally, ready to use on the next connection.
252      * </i>
253      */
254     public static final int BOND_BONDED = 12;
255 
256     /**
257      * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST}
258      * intents for unbond reason.
259      * @hide
260      */
261     public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
262 
263     /**
264      * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST}
265      * intents to indicate pairing method used. Possible values are:
266      * {@link #PAIRING_VARIANT_PIN},
267      * {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION},
268      */
269     public static final String EXTRA_PAIRING_VARIANT =
270             "android.bluetooth.device.extra.PAIRING_VARIANT";
271 
272     /**
273      * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST}
274      * intents as the value of passkey.
275      */
276     public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
277 
278     /**
279      * Bluetooth device type, Unknown
280      */
281     public static final int DEVICE_TYPE_UNKNOWN = 0;
282 
283     /**
284      * Bluetooth device type, Classic - BR/EDR devices
285      */
286     public static final int DEVICE_TYPE_CLASSIC = 1;
287 
288     /**
289      * Bluetooth device type, Low Energy - LE-only
290      */
291     public static final int DEVICE_TYPE_LE = 2;
292 
293     /**
294      * Bluetooth device type, Dual Mode - BR/EDR/LE
295      */
296     public static final int DEVICE_TYPE_DUAL = 3;
297 
298     /**
299      * Broadcast Action: This intent is used to broadcast the {@link UUID}
300      * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
301      * has been fetched. This intent is sent only when the UUIDs of the remote
302      * device are requested to be fetched using Service Discovery Protocol
303      * <p> Always contains the extra field {@link #EXTRA_DEVICE}
304      * <p> Always contains the extra field {@link #EXTRA_UUID}
305      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
306      */
307     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
308     public static final String ACTION_UUID =
309             "android.bluetooth.device.action.UUID";
310 
311     /**
312      * Broadcast Action: Indicates a failure to retrieve the name of a remote
313      * device.
314      * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
315      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
316      * @hide
317      */
318     //TODO: is this actually useful?
319     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
320     public static final String ACTION_NAME_FAILED =
321             "android.bluetooth.device.action.NAME_FAILED";
322 
323     /**
324      * Broadcast Action: This intent is used to broadcast PAIRING REQUEST
325      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to
326      * receive.
327      */
328     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
329     public static final String ACTION_PAIRING_REQUEST =
330             "android.bluetooth.device.action.PAIRING_REQUEST";
331     /** @hide */
332     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
333     public static final String ACTION_PAIRING_CANCEL =
334             "android.bluetooth.device.action.PAIRING_CANCEL";
335 
336     /** @hide */
337     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
338     public static final String ACTION_CONNECTION_ACCESS_REQUEST =
339             "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST";
340 
341     /** @hide */
342     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
343     public static final String ACTION_CONNECTION_ACCESS_REPLY =
344             "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY";
345 
346     /** @hide */
347     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
348     public static final String ACTION_CONNECTION_ACCESS_CANCEL =
349             "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL";
350 
351     /**
352      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent.
353      * @hide
354      */
355     public static final String EXTRA_ACCESS_REQUEST_TYPE =
356         "android.bluetooth.device.extra.ACCESS_REQUEST_TYPE";
357 
358     /**@hide*/
359     public static final int REQUEST_TYPE_PROFILE_CONNECTION = 1;
360 
361     /**@hide*/
362     public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2;
363 
364     /**@hide*/
365     public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3;
366 
367     /**
368      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents,
369      * Contains package name to return reply intent to.
370      * @hide
371      */
372     public static final String EXTRA_PACKAGE_NAME = "android.bluetooth.device.extra.PACKAGE_NAME";
373 
374     /**
375      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents,
376      * Contains class name to return reply intent to.
377      * @hide
378      */
379     public static final String EXTRA_CLASS_NAME = "android.bluetooth.device.extra.CLASS_NAME";
380 
381     /**
382      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intent.
383      * @hide
384      */
385     public static final String EXTRA_CONNECTION_ACCESS_RESULT =
386         "android.bluetooth.device.extra.CONNECTION_ACCESS_RESULT";
387 
388     /**@hide*/
389     public static final int CONNECTION_ACCESS_YES = 1;
390 
391     /**@hide*/
392     public static final int CONNECTION_ACCESS_NO = 2;
393 
394     /**
395      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intents,
396      * Contains boolean to indicate if the allowed response is once-for-all so that
397      * next request will be granted without asking user again.
398      * @hide
399      */
400     public static final String EXTRA_ALWAYS_ALLOWED =
401         "android.bluetooth.device.extra.ALWAYS_ALLOWED";
402 
403     /**
404      * A bond attempt succeeded
405      * @hide
406      */
407     public static final int BOND_SUCCESS = 0;
408 
409     /**
410      * A bond attempt failed because pins did not match, or remote device did
411      * not respond to pin request in time
412      * @hide
413      */
414     public static final int UNBOND_REASON_AUTH_FAILED = 1;
415 
416     /**
417      * A bond attempt failed because the other side explicitly rejected
418      * bonding
419      * @hide
420      */
421     public static final int UNBOND_REASON_AUTH_REJECTED = 2;
422 
423     /**
424      * A bond attempt failed because we canceled the bonding process
425      * @hide
426      */
427     public static final int UNBOND_REASON_AUTH_CANCELED = 3;
428 
429     /**
430      * A bond attempt failed because we could not contact the remote device
431      * @hide
432      */
433     public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
434 
435     /**
436      * A bond attempt failed because a discovery is in progress
437      * @hide
438      */
439     public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
440 
441     /**
442      * A bond attempt failed because of authentication timeout
443      * @hide
444      */
445     public static final int UNBOND_REASON_AUTH_TIMEOUT = 6;
446 
447     /**
448      * A bond attempt failed because of repeated attempts
449      * @hide
450      */
451     public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7;
452 
453     /**
454      * A bond attempt failed because we received an Authentication Cancel
455      * by remote end
456      * @hide
457      */
458     public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8;
459 
460     /**
461      * An existing bond was explicitly revoked
462      * @hide
463      */
464     public static final int UNBOND_REASON_REMOVED = 9;
465 
466     /**
467      * The user will be prompted to enter a pin or
468      * an app will enter a pin for user.
469      */
470     public static final int PAIRING_VARIANT_PIN = 0;
471 
472     /**
473      * The user will be prompted to enter a passkey
474      * @hide
475      */
476     public static final int PAIRING_VARIANT_PASSKEY = 1;
477 
478     /**
479      * The user will be prompted to confirm the passkey displayed on the screen or
480      * an app will confirm the passkey for the user.
481      */
482     public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
483 
484     /**
485      * The user will be prompted to accept or deny the incoming pairing request
486      * @hide
487      */
488     public static final int PAIRING_VARIANT_CONSENT = 3;
489 
490     /**
491      * The user will be prompted to enter the passkey displayed on remote device
492      * This is used for Bluetooth 2.1 pairing.
493      * @hide
494      */
495     public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
496 
497     /**
498      * The user will be prompted to enter the PIN displayed on remote device.
499      * This is used for Bluetooth 2.0 pairing.
500      * @hide
501      */
502     public static final int PAIRING_VARIANT_DISPLAY_PIN = 5;
503 
504     /**
505      * The user will be prompted to accept or deny the OOB pairing request
506      * @hide
507      */
508     public static final int PAIRING_VARIANT_OOB_CONSENT = 6;
509 
510     /**
511      * Used as an extra field in {@link #ACTION_UUID} intents,
512      * Contains the {@link android.os.ParcelUuid}s of the remote device which
513      * is a parcelable version of {@link UUID}.
514      */
515     public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
516 
517     /**
518      * Lazy initialization. Guaranteed final after first object constructed, or
519      * getService() called.
520      * TODO: Unify implementation of sService amongst BluetoothFoo API's
521      */
522     private static IBluetooth sService;
523 
524     private final String mAddress;
525 
getService()526     /*package*/ static IBluetooth getService() {
527         synchronized (BluetoothDevice.class) {
528             if (sService == null) {
529                 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
530                 sService = adapter.getBluetoothService(mStateChangeCallback);
531             }
532         }
533         return sService;
534     }
535 
536     static IBluetoothManagerCallback mStateChangeCallback = new IBluetoothManagerCallback.Stub() {
537 
538         public void onBluetoothServiceUp(IBluetooth bluetoothService)
539                 throws RemoteException {
540             synchronized (BluetoothDevice.class) {
541                 sService = bluetoothService;
542             }
543         }
544 
545         public void onBluetoothServiceDown()
546             throws RemoteException {
547             synchronized (BluetoothDevice.class) {
548                 sService = null;
549             }
550         }
551     };
552     /**
553      * Create a new BluetoothDevice
554      * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
555      * and is validated in this constructor.
556      * @param address valid Bluetooth MAC address
557      * @throws RuntimeException Bluetooth is not available on this platform
558      * @throws IllegalArgumentException address is invalid
559      * @hide
560      */
BluetoothDevice(String address)561     /*package*/ BluetoothDevice(String address) {
562         getService();  // ensures sService is initialized
563         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
564             throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
565         }
566 
567         mAddress = address;
568     }
569 
570     @Override
equals(Object o)571     public boolean equals(Object o) {
572         if (o instanceof BluetoothDevice) {
573             return mAddress.equals(((BluetoothDevice)o).getAddress());
574         }
575         return false;
576     }
577 
578     @Override
hashCode()579     public int hashCode() {
580         return mAddress.hashCode();
581     }
582 
583     /**
584      * Returns a string representation of this BluetoothDevice.
585      * <p>Currently this is the Bluetooth hardware address, for example
586      * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress}
587      * if you explicitly require the Bluetooth hardware address in case the
588      * {@link #toString} representation changes in the future.
589      * @return string representation of this BluetoothDevice
590      */
591     @Override
toString()592     public String toString() {
593         return mAddress;
594     }
595 
describeContents()596     public int describeContents() {
597         return 0;
598     }
599 
600     public static final Parcelable.Creator<BluetoothDevice> CREATOR =
601             new Parcelable.Creator<BluetoothDevice>() {
602         public BluetoothDevice createFromParcel(Parcel in) {
603             return new BluetoothDevice(in.readString());
604         }
605         public BluetoothDevice[] newArray(int size) {
606             return new BluetoothDevice[size];
607         }
608     };
609 
writeToParcel(Parcel out, int flags)610     public void writeToParcel(Parcel out, int flags) {
611         out.writeString(mAddress);
612     }
613 
614     /**
615      * Returns the hardware address of this BluetoothDevice.
616      * <p> For example, "00:11:22:AA:BB:CC".
617      * @return Bluetooth hardware address as string
618      */
getAddress()619     public String getAddress() {
620         if (DBG) Log.d(TAG, "mAddress: " + mAddress);
621         return mAddress;
622     }
623 
624     /**
625      * Get the friendly Bluetooth name of the remote device.
626      *
627      * <p>The local adapter will automatically retrieve remote names when
628      * performing a device scan, and will cache them. This method just returns
629      * the name for this device from the cache.
630      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
631      *
632      * @return the Bluetooth name, or null if there was a problem.
633      */
getName()634     public String getName() {
635         if (sService == null) {
636             Log.e(TAG, "BT not enabled. Cannot get Remote Device name");
637             return null;
638         }
639         try {
640             return sService.getRemoteName(this);
641         } catch (RemoteException e) {Log.e(TAG, "", e);}
642         return null;
643     }
644 
645     /**
646      * Get the Bluetooth device type of the remote device.
647      *
648      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
649      *
650      * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE}
651      *                         {@link #DEVICE_TYPE_DUAL}.
652      *         {@link #DEVICE_TYPE_UNKNOWN} if it's not available
653      */
getType()654     public int getType() {
655         if (sService == null) {
656             Log.e(TAG, "BT not enabled. Cannot get Remote Device type");
657             return DEVICE_TYPE_UNKNOWN;
658         }
659         try {
660             return sService.getRemoteType(this);
661         } catch (RemoteException e) {Log.e(TAG, "", e);}
662         return DEVICE_TYPE_UNKNOWN;
663     }
664 
665     /**
666      * Get the Bluetooth alias of the remote device.
667      * <p>Alias is the locally modified name of a remote device.
668      *
669      * @return the Bluetooth alias, or null if no alias or there was a problem
670      * @hide
671      */
getAlias()672     public String getAlias() {
673         if (sService == null) {
674             Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias");
675             return null;
676         }
677         try {
678             return sService.getRemoteAlias(this);
679         } catch (RemoteException e) {Log.e(TAG, "", e);}
680         return null;
681     }
682 
683     /**
684      * Set the Bluetooth alias of the remote device.
685      * <p>Alias is the locally modified name of a remote device.
686      * <p>This methoid overwrites the alias. The changed
687      * alias is saved in the local storage so that the change
688      * is preserved over power cycle.
689      *
690      * @return true on success, false on error
691      * @hide
692      */
setAlias(String alias)693     public boolean setAlias(String alias) {
694         if (sService == null) {
695             Log.e(TAG, "BT not enabled. Cannot set Remote Device name");
696             return false;
697         }
698         try {
699             return sService.setRemoteAlias(this, alias);
700         } catch (RemoteException e) {Log.e(TAG, "", e);}
701         return false;
702     }
703 
704     /**
705      * Get the Bluetooth alias of the remote device.
706      * If Alias is null, get the Bluetooth name instead.
707      * @see #getAlias()
708      * @see #getName()
709      *
710      * @return the Bluetooth alias, or null if no alias or there was a problem
711      * @hide
712      */
getAliasName()713     public String getAliasName() {
714         String name = getAlias();
715         if (name == null) {
716             name = getName();
717         }
718         return name;
719     }
720 
721     /**
722      * Start the bonding (pairing) process with the remote device.
723      * <p>This is an asynchronous call, it will return immediately. Register
724      * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
725      * the bonding process completes, and its result.
726      * <p>Android system services will handle the necessary user interactions
727      * to confirm and complete the bonding process.
728      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
729      *
730      * @return false on immediate error, true if bonding will begin
731      */
createBond()732     public boolean createBond() {
733         if (sService == null) {
734             Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device");
735             return false;
736         }
737         try {
738             return sService.createBond(this);
739         } catch (RemoteException e) {Log.e(TAG, "", e);}
740         return false;
741     }
742 
743     /**
744      * Start the bonding (pairing) process with the remote device using the
745      * Out Of Band mechanism.
746      *
747      * <p>This is an asynchronous call, it will return immediately. Register
748      * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
749      * the bonding process completes, and its result.
750      *
751      * <p>Android system services will handle the necessary user interactions
752      * to confirm and complete the bonding process.
753      *
754      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
755      *
756      * @param hash - Simple Secure pairing hash
757      * @param randomizer - The random key obtained using OOB
758      * @return false on immediate error, true if bonding will begin
759      *
760      * @hide
761      */
createBondOutOfBand(byte[] hash, byte[] randomizer)762     public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) {
763         //TODO(BT)
764         /*
765         try {
766             return sService.createBondOutOfBand(this, hash, randomizer);
767         } catch (RemoteException e) {Log.e(TAG, "", e);}*/
768         return false;
769     }
770 
771     /**
772      * Set the Out Of Band data for a remote device to be used later
773      * in the pairing mechanism. Users can obtain this data through other
774      * trusted channels
775      *
776      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
777      *
778      * @param hash Simple Secure pairing hash
779      * @param randomizer The random key obtained using OOB
780      * @return false on error; true otherwise
781      *
782      * @hide
783      */
setDeviceOutOfBandData(byte[] hash, byte[] randomizer)784     public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
785       //TODO(BT)
786       /*
787       try {
788         return sService.setDeviceOutOfBandData(this, hash, randomizer);
789       } catch (RemoteException e) {Log.e(TAG, "", e);} */
790       return false;
791     }
792 
793     /**
794      * Cancel an in-progress bonding request started with {@link #createBond}.
795      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
796      *
797      * @return true on success, false on error
798      * @hide
799      */
cancelBondProcess()800     public boolean cancelBondProcess() {
801         if (sService == null) {
802             Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond");
803             return false;
804         }
805         try {
806             return sService.cancelBondProcess(this);
807         } catch (RemoteException e) {Log.e(TAG, "", e);}
808         return false;
809     }
810 
811     /**
812      * Remove bond (pairing) with the remote device.
813      * <p>Delete the link key associated with the remote device, and
814      * immediately terminate connections to that device that require
815      * authentication and encryption.
816      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
817      *
818      * @return true on success, false on error
819      * @hide
820      */
removeBond()821     public boolean removeBond() {
822         if (sService == null) {
823             Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond");
824             return false;
825         }
826         try {
827             return sService.removeBond(this);
828         } catch (RemoteException e) {Log.e(TAG, "", e);}
829         return false;
830     }
831 
832     /**
833      * Get the bond state of the remote device.
834      * <p>Possible values for the bond state are:
835      * {@link #BOND_NONE},
836      * {@link #BOND_BONDING},
837      * {@link #BOND_BONDED}.
838      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
839      *
840      * @return the bond state
841      */
getBondState()842     public int getBondState() {
843         if (sService == null) {
844             Log.e(TAG, "BT not enabled. Cannot get bond state");
845             return BOND_NONE;
846         }
847         try {
848             return sService.getBondState(this);
849         } catch (RemoteException e) {Log.e(TAG, "", e);}
850         catch (NullPointerException npe) {
851             // Handle case where bluetooth service proxy
852             // is already null.
853             Log.e(TAG, "NullPointerException for getBondState() of device ("+
854                 getAddress()+")", npe);
855         }
856         return BOND_NONE;
857     }
858 
859     /**
860      * Get the Bluetooth class of the remote device.
861      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
862      *
863      * @return Bluetooth class object, or null on error
864      */
getBluetoothClass()865     public BluetoothClass getBluetoothClass() {
866         if (sService == null) {
867             Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class");
868             return null;
869         }
870         try {
871             int classInt = sService.getRemoteClass(this);
872             if (classInt == BluetoothClass.ERROR) return null;
873             return new BluetoothClass(classInt);
874         } catch (RemoteException e) {Log.e(TAG, "", e);}
875         return null;
876     }
877 
878     /**
879      * Get trust state of a remote device.
880      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
881      * @hide
882      */
getTrustState()883     public boolean getTrustState() {
884         //TODO(BT)
885         /*
886         try {
887             return sService.getTrustState(this);
888         } catch (RemoteException e) {
889             Log.e(TAG, "", e);
890         }*/
891         return false;
892     }
893 
894     /**
895      * Set trust state for a remote device.
896      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
897      * @param value the trust state value (true or false)
898      * @hide
899      */
setTrust(boolean value)900     public boolean setTrust(boolean value) {
901         //TODO(BT)
902         /*
903         try {
904             return sService.setTrust(this, value);
905         } catch (RemoteException e) {
906             Log.e(TAG, "", e);
907         }*/
908         return false;
909     }
910 
911     /**
912      * Returns the supported features (UUIDs) of the remote device.
913      *
914      * <p>This method does not start a service discovery procedure to retrieve the UUIDs
915      * from the remote device. Instead, the local cached copy of the service
916      * UUIDs are returned.
917      * <p>Use {@link #fetchUuidsWithSdp} if fresh UUIDs are desired.
918      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
919      *
920      * @return the supported features (UUIDs) of the remote device,
921      *         or null on error
922      */
getUuids()923      public ParcelUuid[] getUuids() {
924          if (sService == null) {
925             Log.e(TAG, "BT not enabled. Cannot get remote device Uuids");
926              return null;
927          }
928         try {
929             return sService.getRemoteUuids(this);
930         } catch (RemoteException e) {Log.e(TAG, "", e);}
931         return null;
932     }
933 
934      /**
935       * Perform a service discovery on the remote device to get the UUIDs supported.
936       *
937       * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent,
938       * with the UUIDs supported by the remote end. If there is an error
939       * in getting the SDP records or if the process takes a long time,
940       * {@link #ACTION_UUID} intent is sent with the UUIDs that is currently
941       * present in the cache. Clients should use the {@link #getUuids} to get UUIDs
942       * if service discovery is not to be performed.
943       * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
944       *
945       * @return False if the sanity check fails, True if the process
946       *               of initiating an ACL connection to the remote device
947       *               was started.
948       */
fetchUuidsWithSdp()949      public boolean fetchUuidsWithSdp() {
950         try {
951             return sService.fetchRemoteUuids(this);
952         } catch (RemoteException e) {Log.e(TAG, "", e);}
953             return false;
954     }
955 
956     /** @hide */
getServiceChannel(ParcelUuid uuid)957     public int getServiceChannel(ParcelUuid uuid) {
958         //TODO(BT)
959         /*
960          try {
961              return sService.getRemoteServiceChannel(this, uuid);
962          } catch (RemoteException e) {Log.e(TAG, "", e);}*/
963          return BluetoothDevice.ERROR;
964     }
965 
966     /**
967      * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
968      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
969      *
970      * @return true pin has been set
971      *         false for error
972      */
setPin(byte[] pin)973     public boolean setPin(byte[] pin) {
974         if (sService == null) {
975             Log.e(TAG, "BT not enabled. Cannot set Remote Device pin");
976             return false;
977         }
978         try {
979             return sService.setPin(this, true, pin.length, pin);
980         } catch (RemoteException e) {Log.e(TAG, "", e);}
981         return false;
982     }
983 
984     /** @hide */
setPasskey(int passkey)985     public boolean setPasskey(int passkey) {
986         //TODO(BT)
987         /*
988         try {
989             return sService.setPasskey(this, true, 4, passkey);
990         } catch (RemoteException e) {Log.e(TAG, "", e);}*/
991         return false;
992     }
993 
994     /**
995      * Confirm passkey for {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION} pairing.
996      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
997      *
998      * @return true confirmation has been sent out
999      *         false for error
1000      */
setPairingConfirmation(boolean confirm)1001     public boolean setPairingConfirmation(boolean confirm) {
1002         if (sService == null) {
1003             Log.e(TAG, "BT not enabled. Cannot set pairing confirmation");
1004             return false;
1005         }
1006         try {
1007             return sService.setPairingConfirmation(this, confirm);
1008         } catch (RemoteException e) {Log.e(TAG, "", e);}
1009         return false;
1010     }
1011 
1012     /** @hide */
setRemoteOutOfBandData()1013     public boolean setRemoteOutOfBandData() {
1014         // TODO(BT)
1015         /*
1016         try {
1017           return sService.setRemoteOutOfBandData(this);
1018       } catch (RemoteException e) {Log.e(TAG, "", e);}*/
1019       return false;
1020     }
1021 
1022     /** @hide */
cancelPairingUserInput()1023     public boolean cancelPairingUserInput() {
1024         if (sService == null) {
1025             Log.e(TAG, "BT not enabled. Cannot create pairing user input");
1026             return false;
1027         }
1028         try {
1029             return sService.cancelBondProcess(this);
1030         } catch (RemoteException e) {Log.e(TAG, "", e);}
1031         return false;
1032     }
1033 
1034     /** @hide */
isBluetoothDock()1035     public boolean isBluetoothDock() {
1036         // TODO(BT)
1037         /*
1038         try {
1039             return sService.isBluetoothDock(this);
1040         } catch (RemoteException e) {Log.e(TAG, "", e);}*/
1041         return false;
1042     }
1043 
1044     /**
1045      * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
1046      * outgoing connection to this remote device on given channel.
1047      * <p>The remote device will be authenticated and communication on this
1048      * socket will be encrypted.
1049      * <p> Use this socket only if an authenticated socket link is possible.
1050      * Authentication refers to the authentication of the link key to
1051      * prevent man-in-the-middle type of attacks.
1052      * For example, for Bluetooth 2.1 devices, if any of the devices does not
1053      * have an input and output capability or just has the ability to
1054      * display a numeric key, a secure socket connection is not possible.
1055      * In such a case, use {#link createInsecureRfcommSocket}.
1056      * For more details, refer to the Security Model section 5.2 (vol 3) of
1057      * Bluetooth Core Specification version 2.1 + EDR.
1058      * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
1059      * connection.
1060      * <p>Valid RFCOMM channels are in range 1 to 30.
1061      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
1062      *
1063      * @param channel RFCOMM channel to connect to
1064      * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
1065      * @throws IOException on error, for example Bluetooth not available, or
1066      *                     insufficient permissions
1067      * @hide
1068      */
createRfcommSocket(int channel)1069     public BluetoothSocket createRfcommSocket(int channel) throws IOException {
1070         return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel,
1071                 null);
1072     }
1073 
1074     /**
1075      * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
1076      * outgoing connection to this remote device using SDP lookup of uuid.
1077      * <p>This is designed to be used with {@link
1078      * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer
1079      * Bluetooth applications.
1080      * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
1081      * connection. This will also perform an SDP lookup of the given uuid to
1082      * determine which channel to connect to.
1083      * <p>The remote device will be authenticated and communication on this
1084      * socket will be encrypted.
1085      * <p> Use this socket only if an authenticated socket link is possible.
1086      * Authentication refers to the authentication of the link key to
1087      * prevent man-in-the-middle type of attacks.
1088      * For example, for Bluetooth 2.1 devices, if any of the devices does not
1089      * have an input and output capability or just has the ability to
1090      * display a numeric key, a secure socket connection is not possible.
1091      * In such a case, use {#link createInsecureRfcommSocketToServiceRecord}.
1092      * For more details, refer to the Security Model section 5.2 (vol 3) of
1093      * Bluetooth Core Specification version 2.1 + EDR.
1094      * <p>Hint: If you are connecting to a Bluetooth serial board then try
1095      * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
1096      * However if you are connecting to an Android peer then please generate
1097      * your own unique UUID.
1098      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
1099      *
1100      * @param uuid service record uuid to lookup RFCOMM channel
1101      * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
1102      * @throws IOException on error, for example Bluetooth not available, or
1103      *                     insufficient permissions
1104      */
createRfcommSocketToServiceRecord(UUID uuid)1105     public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
1106         return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1,
1107                 new ParcelUuid(uuid));
1108     }
1109 
1110     /**
1111      * Create an RFCOMM {@link BluetoothSocket} socket ready to start an insecure
1112      * outgoing connection to this remote device using SDP lookup of uuid.
1113      * <p> The communication channel will not have an authenticated link key
1114      * i.e it will be subject to man-in-the-middle attacks. For Bluetooth 2.1
1115      * devices, the link key will be encrypted, as encryption is mandatory.
1116      * For legacy devices (pre Bluetooth 2.1 devices) the link key will
1117      * be not be encrypted. Use {@link #createRfcommSocketToServiceRecord} if an
1118      * encrypted and authenticated communication channel is desired.
1119      * <p>This is designed to be used with {@link
1120      * BluetoothAdapter#listenUsingInsecureRfcommWithServiceRecord} for peer-peer
1121      * Bluetooth applications.
1122      * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
1123      * connection. This will also perform an SDP lookup of the given uuid to
1124      * determine which channel to connect to.
1125      * <p>The remote device will be authenticated and communication on this
1126      * socket will be encrypted.
1127      * <p>Hint: If you are connecting to a Bluetooth serial board then try
1128      * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
1129      * However if you are connecting to an Android peer then please generate
1130      * your own unique UUID.
1131      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
1132      *
1133      * @param uuid service record uuid to lookup RFCOMM channel
1134      * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
1135      * @throws IOException on error, for example Bluetooth not available, or
1136      *                     insufficient permissions
1137      */
createInsecureRfcommSocketToServiceRecord(UUID uuid)1138     public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
1139         return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1,
1140                 new ParcelUuid(uuid));
1141     }
1142 
1143     /**
1144      * Construct an insecure RFCOMM socket ready to start an outgoing
1145      * connection.
1146      * Call #connect on the returned #BluetoothSocket to begin the connection.
1147      * The remote device will not be authenticated and communication on this
1148      * socket will not be encrypted.
1149      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
1150      *
1151      * @param port    remote port
1152      * @return An RFCOMM BluetoothSocket
1153      * @throws IOException On error, for example Bluetooth not available, or
1154      *                     insufficient permissions.
1155      * @hide
1156      */
createInsecureRfcommSocket(int port)1157     public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
1158         return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port,
1159                 null);
1160     }
1161 
1162     /**
1163      * Construct a SCO socket ready to start an outgoing connection.
1164      * Call #connect on the returned #BluetoothSocket to begin the connection.
1165      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
1166      *
1167      * @return a SCO BluetoothSocket
1168      * @throws IOException on error, for example Bluetooth not available, or
1169      *                     insufficient permissions.
1170      * @hide
1171      */
createScoSocket()1172     public BluetoothSocket createScoSocket() throws IOException {
1173         return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null);
1174     }
1175 
1176     /**
1177      * Check that a pin is valid and convert to byte array.
1178      *
1179      * Bluetooth pin's are 1 to 16 bytes of UTF-8 characters.
1180      * @param pin pin as java String
1181      * @return the pin code as a UTF-8 byte array, or null if it is an invalid
1182      *         Bluetooth pin.
1183      * @hide
1184      */
convertPinToBytes(String pin)1185     public static byte[] convertPinToBytes(String pin) {
1186         if (pin == null) {
1187             return null;
1188         }
1189         byte[] pinBytes;
1190         try {
1191             pinBytes = pin.getBytes("UTF-8");
1192         } catch (UnsupportedEncodingException uee) {
1193             Log.e(TAG, "UTF-8 not supported?!?");  // this should not happen
1194             return null;
1195         }
1196         if (pinBytes.length <= 0 || pinBytes.length > 16) {
1197             return null;
1198         }
1199         return pinBytes;
1200     }
1201 
1202     /**
1203      * Connect to GATT Server hosted by this device. Caller acts as GATT client.
1204      * The callback is used to deliver results to Caller, such as connection status as well
1205      * as any further GATT client operations.
1206      * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
1207      * GATT client operations.
1208      * @param callback GATT callback handler that will receive asynchronous callbacks.
1209      * @param autoConnect Whether to directly connect to the remote device (false)
1210      *                    or to automatically connect as soon as the remote
1211      *                    device becomes available (true).
1212      * @throws IllegalArgumentException if callback is null
1213      */
connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)1214     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
1215                                      BluetoothGattCallback callback) {
1216         // TODO(Bluetooth) check whether platform support BLE
1217         //     Do the check here or in GattServer?
1218         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
1219         IBluetoothManager managerService = adapter.getBluetoothManager();
1220         try {
1221             IBluetoothGatt iGatt = managerService.getBluetoothGatt();
1222             if (iGatt == null) {
1223                 // BLE is not supported
1224                 return null;
1225             }
1226             BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this);
1227             gatt.connect(autoConnect, callback);
1228             return gatt;
1229         } catch (RemoteException e) {Log.e(TAG, "", e);}
1230         return null;
1231     }
1232 }
1233