BluetoothDevice.java revision 32d8571f509c392dca732c243e9b2138c15daecf
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
17package android.bluetooth;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.content.Context;
22import android.os.IBinder;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.util.Log;
28
29import java.io.IOException;
30import java.io.UnsupportedEncodingException;
31
32/**
33 * Represents a remote Bluetooth device.
34 *
35 * <p>Use {@link BluetoothAdapter#getRemoteDevice} to create a {@link
36 * BluetoothDevice}.
37 *
38 * <p>This class is really just a thin wrapper for a Bluetooth hardware
39 * address. Objects of this class are immutable. Operations on this class
40 * are performed on the remote Bluetooth hardware address, using the
41 * {@link BluetoothAdapter} that was used to create this {@link
42 * BluetoothDevice}.
43 */
44public final class BluetoothDevice implements Parcelable {
45    private static final String TAG = "BluetoothDevice";
46
47    /**
48     * Sentinel error value for this class. Guaranteed to not equal any other
49     * integer constant in this class. Provided as a convenience for functions
50     * that require a sentinel error value, for example:
51     * <p><code>Intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
52     * BluetoothDevice.ERROR)</code>
53     */
54    public static final int ERROR = Integer.MIN_VALUE;
55
56    /**
57     * Broadcast Action: Remote device discovered.
58     * <p>Sent when a remote device is found during discovery.
59     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
60     * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or
61     * {@link #EXTRA_RSSI} if they are available.
62     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
63     */
64     // TODO: Change API to not broadcast RSSI if not available (incoming connection)
65    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
66    public static final String ACTION_FOUND =
67            "android.bluetooth.device.action.FOUND";
68
69    /**
70     * Broadcast Action: Remote device disappeared.
71     * <p>Sent when a remote device that was found in the last discovery is not
72     * found in the current discovery.
73     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
74     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
75     * @hide
76     */
77    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
78    public static final String ACTION_DISAPPEARED =
79            "android.bluetooth.device.action.DISAPPEARED";
80
81    /**
82     * Broadcast Action: Bluetooth class of a remote device has changed.
83     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
84     * #EXTRA_CLASS}.
85     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
86     * @see {@link BluetoothClass}
87     */
88    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
89    public static final String ACTION_CLASS_CHANGED =
90            "android.bluetooth.device.action.CLASS_CHANGED";
91
92    /**
93     * Broadcast Action: Indicates a low level (ACL) connection has been
94     * established with a remote device.
95     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
96     * <p>ACL connections are managed automatically by the Android Bluetooth
97     * stack.
98     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
99     */
100    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
101    public static final String ACTION_ACL_CONNECTED =
102            "android.bluetooth.device.action.ACL_CONNECTED";
103
104    /**
105     * Broadcast Action: Indicates that a low level (ACL) disconnection has
106     * been requested for a remote device, and it will soon be disconnected.
107     * <p>This is useful for graceful disconnection. Applications should use
108     * this intent as a hint to immediately terminate higher level connections
109     * (RFCOMM, L2CAP, or profile connections) to the remote device.
110     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
111     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
112     */
113    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
114    public static final String ACTION_ACL_DISCONNECT_REQUESTED =
115            "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
116
117    /**
118     * Broadcast Action: Indicates a low level (ACL) disconnection from a
119     * remote device.
120     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
121     * <p>ACL connections are managed automatically by the Android Bluetooth
122     * stack.
123     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
124     */
125    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
126    public static final String ACTION_ACL_DISCONNECTED =
127            "android.bluetooth.device.action.ACL_DISCONNECTED";
128
129    /**
130     * Broadcast Action: Indicates the friendly name of a remote device has
131     * been retrieved for the first time, or changed since the last retrieval.
132     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
133     * #EXTRA_NAME}.
134     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
135     */
136    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
137    public static final String ACTION_NAME_CHANGED =
138            "android.bluetooth.device.action.NAME_CHANGED";
139
140    /**
141     * Broadcast Action: Indicates a change in the bond state of a remote
142     * device. For example, if a device is bonded (paired).
143     * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link
144     * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}.
145     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
146     */
147    // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also
148    // contain a hidden extra field EXTRA_REASON with the result code.
149    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
150    public static final String ACTION_BOND_STATE_CHANGED =
151            "android.bluetooth.device.action.BOND_STATE_CHANGED";
152
153    /**
154     * Used as a Parcelable {@link BluetoothDevice} extra field in every intent
155     * broadcast by this class. It contains the {@link BluetoothDevice} that
156     * the intent applies to.
157     */
158    public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
159
160    /**
161     * Used as a String extra field in {@link #ACTION_NAME_CHANGED} and {@link
162     * #ACTION_FOUND} intents. It contains the friendly Bluetooth name.
163     */
164    public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
165
166    /**
167     * Used as an optional short extra field in {@link #ACTION_FOUND} intents.
168     * Contains the RSSI value of the remote device as reported by the
169     * Bluetooth hardware.
170     */
171    public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI";
172
173    /**
174     * Used as an Parcelable {@link BluetoothClass} extra field in {@link
175     * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents.
176     */
177    public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
178
179    /**
180     * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents.
181     * Contains the bond state of the remote device.
182     * <p>Possible values are:
183     * {@link #BOND_NONE},
184     * {@link #BOND_BONDING},
185     * {@link #BOND_BONDED}.
186      */
187    public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
188    /**
189     * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents.
190     * Contains the previous bond state of the remote device.
191     * <p>Possible values are:
192     * {@link #BOND_NONE},
193     * {@link #BOND_BONDING},
194     * {@link #BOND_BONDED}.
195      */
196    public static final String EXTRA_PREVIOUS_BOND_STATE =
197            "android.bluetooth.device.extra.PREVIOUS_BOND_STATE";
198    /**
199     * Indicates the remote device is not bonded (paired).
200     * <p>There is no shared link key with the remote device, so communication
201     * (if it is allowed at all) will be unauthenticated and unencrypted.
202     */
203    public static final int BOND_NONE = 10;
204    /**
205     * Indicates bonding (pairing) is in progress with the remote device.
206     */
207    public static final int BOND_BONDING = 11;
208    /**
209     * Indicates the remote device is bonded (paired).
210     * <p>A shared link keys exists locally for the remote device, so
211     * communication can be authenticated and encrypted.
212     * <p><i>Being bonded (paired) with a remote device does not necessarily
213     * mean the device is currently connected. It just means that the ponding
214     * procedure was compeleted at some earlier time, and the link key is still
215     * stored locally, ready to use on the next connection.
216     * </i>
217     */
218    public static final int BOND_BONDED = 12;
219
220    /** @hide */
221    public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
222    /** @hide */
223    public static final String EXTRA_PAIRING_VARIANT =
224            "android.bluetooth.device.extra.PAIRING_VARIANT";
225    /** @hide */
226    public static final String EXTRA_PASSKEY = "android.bluetooth.device.extra.PASSKEY";
227
228    /**
229     * Broadcast Action: Indicates a failure to retrieve the name of a remote
230     * device.
231     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
232     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
233     * @hide
234     */
235    //TODO: is this actually useful?
236    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
237    public static final String ACTION_NAME_FAILED =
238            "android.bluetooth.device.action.NAME_FAILED";
239
240    /** @hide */
241    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
242    public static final String ACTION_PAIRING_REQUEST =
243            "android.bluetooth.device.action.PAIRING_REQUEST";
244    /** @hide */
245    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
246    public static final String ACTION_PAIRING_CANCEL =
247            "android.bluetooth.device.action.PAIRING_CANCEL";
248
249    /** A bond attempt succeeded
250     * @hide */
251    public static final int BOND_SUCCESS = 0;
252    /** A bond attempt failed because pins did not match, or remote device did
253     * not respond to pin request in time
254     * @hide */
255    public static final int UNBOND_REASON_AUTH_FAILED = 1;
256    /** A bond attempt failed because the other side explicilty rejected
257     * bonding
258     * @hide */
259    public static final int UNBOND_REASON_AUTH_REJECTED = 2;
260    /** A bond attempt failed because we canceled the bonding process
261     * @hide */
262    public static final int UNBOND_REASON_AUTH_CANCELED = 3;
263    /** A bond attempt failed because we could not contact the remote device
264     * @hide */
265    public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
266    /** A bond attempt failed because a discovery is in progress
267     * @hide */
268    public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
269    /** A bond attempt failed because of authentication timeout
270     * @hide */
271    public static final int UNBOND_REASON_AUTH_TIMEOUT = 6;
272    /** A bond attempt failed because of repeated attempts
273     * @hide */
274    public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7;
275    /** An existing bond was explicitly revoked
276     * @hide */
277    public static final int UNBOND_REASON_REMOVED = 8;
278
279    /** The user will be prompted to enter a pin
280     * @hide */
281    public static final int PAIRING_VARIANT_PIN = 0;
282    /** The user will be prompted to enter a passkey
283     * @hide */
284    public static final int PAIRING_VARIANT_PASSKEY = 1;
285    /** The user will be prompted to confirm the passkey displayed on the screen
286     * @hide */
287    public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
288    /** The user will be prompted to accept or deny the incoming pairing request
289     * @hide */
290    public static final int PAIRING_VARIANT_CONSENT = 3;
291    /** The user will be prompted to enter the passkey displayed on remote device
292     * @hide */
293    public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
294
295    private static IBluetooth sService;  /* Guarenteed constant after first object constructed */
296
297    private final String mAddress;
298
299    /**
300     * Create a new BluetoothDevice
301     * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
302     * and is validated in this constructor.
303     * @param address valid Bluetooth MAC address
304     * @throws RuntimeException Bluetooth is not available on this platform
305     * @throws IllegalArgumentException address is invalid
306     * @hide
307     */
308    /*package*/ BluetoothDevice(String address) {
309        synchronized (BluetoothDevice.class) {
310            if (sService == null) {
311                IBinder b = ServiceManager.getService(Context.BLUETOOTH_SERVICE);
312                if (b == null) {
313                    throw new RuntimeException("Bluetooth service not available");
314                }
315                sService = IBluetooth.Stub.asInterface(b);
316            }
317        }
318
319        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
320            throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
321        }
322
323        mAddress = address;
324    }
325
326    @Override
327    public boolean equals(Object o) {
328        if (o instanceof BluetoothDevice) {
329            return mAddress.equals(((BluetoothDevice)o).getAddress());
330        }
331        return false;
332    }
333
334    @Override
335    public int hashCode() {
336        return mAddress.hashCode();
337    }
338
339    /**
340     * Returns a string representation of this BluetoothDevice.
341     * <p>Currently this is the Bluetooth hardware address, for example
342     * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress}
343     * if you explicitly require the Bluetooth hardware address in case the
344     * {@link #toString} representation changes in the future.
345     * @return string representation of this BluetoothDevice
346     */
347    @Override
348    public String toString() {
349        return mAddress;
350    }
351
352    /** @hide */
353    public int describeContents() {
354        return 0;
355    }
356
357    /** @hide */
358    public static final Parcelable.Creator<BluetoothDevice> CREATOR =
359            new Parcelable.Creator<BluetoothDevice>() {
360        public BluetoothDevice createFromParcel(Parcel in) {
361            return new BluetoothDevice(in.readString());
362        }
363        public BluetoothDevice[] newArray(int size) {
364            return new BluetoothDevice[size];
365        }
366    };
367
368    /** @hide */
369    public void writeToParcel(Parcel out, int flags) {
370        out.writeString(mAddress);
371    }
372
373    /**
374     * Returns the hardware address of this BluetoothDevice.
375     * <p> For example, "00:11:22:AA:BB:CC".
376     * @return Bluetooth hardware address as string
377     */
378    public String getAddress() {
379        return mAddress;
380    }
381
382    /**
383     * Get the friendly Bluetooth name of the remote device.
384     *
385     * <p>The local adapter will automatically retrieve remote names when
386     * performing a device scan, and will cache them. This method just returns
387     * the name for this device from the cache.
388     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
389     *
390     * @return the Bluetooth name, or null if there was a problem.
391     */
392    public String getName() {
393        try {
394            return sService.getRemoteName(mAddress);
395        } catch (RemoteException e) {Log.e(TAG, "", e);}
396        return null;
397    }
398
399    /**
400     * Start the bonding (pairing) process with the remote device.
401     * <p>This is an asynchronous call, it will return immediately. Register
402     * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
403     * the bonding process completes, and its result.
404     * <p>Android system services will handle the necessary user interactions
405     * to confirm and complete the bonding process.
406     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
407     *
408     * @return false on immediate error, true if bonding will begin
409     */
410    public boolean createBond() {
411        try {
412            return sService.createBond(mAddress);
413        } catch (RemoteException e) {Log.e(TAG, "", e);}
414        return false;
415    }
416
417    /**
418     * Cancel an in-progress bonding request started with {@link #createBond}.
419     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
420     *
421     * @return true on sucess, false on error
422     */
423    public boolean cancelBondProcess() {
424        try {
425            return sService.cancelBondProcess(mAddress);
426        } catch (RemoteException e) {Log.e(TAG, "", e);}
427        return false;
428    }
429
430    /**
431     * Remove bond (pairing) with the remote device.
432     * <p>Delete the link key associated with the remote device, and
433     * immediately terminate connections to that device that require
434     * authentication and encryption.
435     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
436     *
437     * @return true on sucess, false on error
438     */
439    public boolean removeBond() {
440        try {
441            return sService.removeBond(mAddress);
442        } catch (RemoteException e) {Log.e(TAG, "", e);}
443        return false;
444    }
445
446    /**
447     * Get the bond state of the remote device.
448     * <p>Possible values for the bond state are:
449     * {@link #BOND_NONE},
450     * {@link #BOND_BONDING},
451     * {@link #BOND_BONDED}.
452     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
453     *
454     * @return the bond state
455     */
456    public int getBondState() {
457        try {
458            return sService.getBondState(mAddress);
459        } catch (RemoteException e) {Log.e(TAG, "", e);}
460        return BOND_NONE;
461    }
462
463    /**
464     * Get the Bluetooth class of the remote device.
465     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
466     *
467     * @return Bluetooth class object, or null on error
468     */
469    public BluetoothClass getBluetoothClass() {
470        try {
471            int classInt = sService.getRemoteClass(mAddress);
472            if (classInt == BluetoothClass.ERROR) return null;
473            return new BluetoothClass(classInt);
474        } catch (RemoteException e) {Log.e(TAG, "", e);}
475        return null;
476    }
477
478    /**
479     * Get trust state of a remote device.
480     * @hide
481     */
482    public boolean getTrustState() {
483        try {
484            return sService.getTrustState(mAddress);
485        } catch (RemoteException e) {
486            Log.e(TAG, "", e);
487        }
488        return false;
489    }
490
491    /**
492     * Set trust state for a remote device.
493     * @param value the trust state value (true or false)
494     * @hide
495     */
496    public boolean setTrust(boolean value) {
497        try {
498            return sService.setTrust(mAddress, value);
499        } catch (RemoteException e) {
500            Log.e(TAG, "", e);
501        }
502        return false;
503    }
504
505    /** @hide */
506     public String[] getUuids() {
507        try {
508            return sService.getRemoteUuids(mAddress);
509        } catch (RemoteException e) {Log.e(TAG, "", e);}
510        return null;
511    }
512
513    /** @hide */
514    public int getServiceChannel(String uuid) {
515         try {
516             return sService.getRemoteServiceChannel(mAddress, uuid);
517         } catch (RemoteException e) {Log.e(TAG, "", e);}
518         return BluetoothDevice.ERROR;
519    }
520
521    /** @hide */
522    public boolean setPin(byte[] pin) {
523        try {
524            return sService.setPin(mAddress, pin);
525        } catch (RemoteException e) {Log.e(TAG, "", e);}
526        return false;
527    }
528
529    /** @hide */
530    public boolean setPasskey(int passkey) {
531        try {
532            return sService.setPasskey(mAddress, passkey);
533        } catch (RemoteException e) {Log.e(TAG, "", e);}
534        return false;
535    }
536
537    /** @hide */
538    public boolean setPairingConfirmation(boolean confirm) {
539        try {
540            return sService.setPairingConfirmation(mAddress, confirm);
541        } catch (RemoteException e) {Log.e(TAG, "", e);}
542        return false;
543    }
544
545    /** @hide */
546    public boolean cancelPairingUserInput() {
547        try {
548            return sService.cancelPairingUserInput(mAddress);
549        } catch (RemoteException e) {Log.e(TAG, "", e);}
550        return false;
551    }
552
553    /**
554     * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
555     * outgoing connection to this remote device.
556     * <p>The remote device will be authenticated and communication on this
557     * socket will be encrypted.
558     * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
559     * connection.
560     * <p>Valid RFCOMM channels are in range 1 to 30.
561     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
562     *
563     * @param channel RFCOMM channel to connect to
564     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
565     * @throws IOException on error, for example Bluetooth not available, or
566     *                     insufficient permissions
567     */
568    public BluetoothSocket createRfcommSocket(int channel) throws IOException {
569        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel);
570    }
571
572    /**
573     * Construct an insecure RFCOMM socket ready to start an outgoing
574     * connection.
575     * Call #connect on the returned #BluetoothSocket to begin the connection.
576     * The remote device will not be authenticated and communication on this
577     * socket will not be encrypted.
578     * @param port    remote port
579     * @return An RFCOMM BluetoothSocket
580     * @throws IOException On error, for example Bluetooth not available, or
581     *                     insufficient permissions.
582     * @hide
583     */
584    public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
585        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port);
586    }
587
588    /**
589     * Construct a SCO socket ready to start an outgoing connection.
590     * Call #connect on the returned #BluetoothSocket to begin the connection.
591     * @return a SCO BluetoothSocket
592     * @throws IOException on error, for example Bluetooth not available, or
593     *                     insufficient permissions.
594     * @hide
595     */
596    public BluetoothSocket createScoSocket() throws IOException {
597        return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1);
598    }
599
600    /**
601     * Check that a pin is valid and convert to byte array.
602     *
603     * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
604     * @param pin pin as java String
605     * @return the pin code as a UTF8 byte array, or null if it is an invalid
606     *         Bluetooth pin.
607     * @hide
608     */
609    public static byte[] convertPinToBytes(String pin) {
610        if (pin == null) {
611            return null;
612        }
613        byte[] pinBytes;
614        try {
615            pinBytes = pin.getBytes("UTF8");
616        } catch (UnsupportedEncodingException uee) {
617            Log.e(TAG, "UTF8 not supported?!?");  // this should not happen
618            return null;
619        }
620        if (pinBytes.length <= 0 || pinBytes.length > 16) {
621            return null;
622        }
623        return pinBytes;
624    }
625
626}
627