BluetoothDevice.java revision cf44059813539bf7f36dabd278cef93ba3122c56
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.content.Context;
20import android.os.IBinder;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25import android.util.Log;
26
27import java.io.IOException;
28import java.io.UnsupportedEncodingException;
29
30/**
31 * Represents a remote Bluetooth device.
32 *
33 * <p>Use {@link BluetoothAdapter#getRemoteDevice} to create a {@link
34 * BluetoothDevice}.
35 *
36 * <p>This class is really just a thin wrapper for a Bluetooth hardware
37 * address. Objects of this class are immutable. Operations on this class
38 * are performed on the remote Bluetooth hardware address, using the
39 * {@link BluetoothAdapter} that was used to create this {@link
40 * BluetoothDevice}.
41 *
42 * TODO: unhide more of this class
43 */
44public final class BluetoothDevice implements Parcelable {
45    private static final String TAG = "BluetoothDevice";
46
47    /** We do not have a link key for the remote device, and are therefore not
48     * bonded
49     * @hide*/
50    public static final int BOND_NOT_BONDED = 0;
51    /** We have a link key for the remote device, and are probably bonded.
52     *  @hide */
53    public static final int BOND_BONDED = 1;
54    /** We are currently attempting bonding
55     *  @hide */
56    public static final int BOND_BONDING = 2;
57
58    /** Ask device picker to show all kinds of BT devices.
59     *  @hide */
60    public static final int DEVICE_PICKER_FILTER_TYPE_ALL = 0;
61    /** Ask device picker to show BT devices that support AUDIO profiles.
62     *  @hide */
63    public static final int DEVICE_PICKER_FILTER_TYPE_AUDIO = 1;
64    /** Ask device picker to show BT devices that support Object Transfer.
65     *  @hide */
66    public static final int DEVICE_PICKER_FILTER_TYPE_TRANSFER = 2;
67
68    //TODO: Unify these result codes in BluetoothResult or BluetoothError
69    /** A bond attempt failed because pins did not match, or remote device did
70     * not respond to pin request in time
71     * @hide */
72    public static final int UNBOND_REASON_AUTH_FAILED = 1;
73    /** A bond attempt failed because the other side explicilty rejected
74     * bonding
75     * @hide */
76    public static final int UNBOND_REASON_AUTH_REJECTED = 2;
77    /** A bond attempt failed because we canceled the bonding process
78     * @hide */
79    public static final int UNBOND_REASON_AUTH_CANCELED = 3;
80    /** A bond attempt failed because we could not contact the remote device
81     * @hide */
82    public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
83    /** A bond attempt failed because a discovery is in progress
84     * @hide */
85    public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
86    /** An existing bond was explicitly revoked
87     * @hide */
88    public static final int UNBOND_REASON_REMOVED = 6;
89
90    //TODO: Remove duplicates between here and BluetoothAdapter
91    /** The user will be prompted to enter a pin
92     * @hide */
93    public static final int PAIRING_VARIANT_PIN = 0;
94    /** The user will be prompted to enter a passkey
95     * @hide */
96    public static final int PAIRING_VARIANT_PASSKEY = 1;
97    /** The user will be prompted to confirm the passkey displayed on the screen
98     * @hide */
99    public static final int PAIRING_VARIANT_CONFIRMATION = 2;
100
101    private static final int ADDRESS_LENGTH = 17;
102
103    private static IBluetooth sService;  /* Guarenteed constant after first object constructed */
104
105    private final String mAddress;
106
107    /**
108     * Create a new BluetoothDevice
109     * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
110     * and is validated in this constructor.
111     * @param address valid Bluetooth MAC address
112     * @throws RuntimeException Bluetooth is not available on this platform
113     * @throws IllegalArgumentException address is invalid
114     * @hide
115     */
116    /*package*/ BluetoothDevice(String address) {
117        synchronized (BluetoothDevice.class) {
118            if (sService == null) {
119                IBinder b = ServiceManager.getService(Context.BLUETOOTH_SERVICE);
120                if (b == null) {
121                    throw new RuntimeException("Bluetooth service not available");
122                }
123                sService = IBluetooth.Stub.asInterface(b);
124            }
125        }
126
127        if (!checkBluetoothAddress(address)) {
128            throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
129        }
130
131        mAddress = address;
132    }
133
134    @Override
135    public boolean equals(Object o) {
136        if (o instanceof BluetoothDevice) {
137            return mAddress.equals(((BluetoothDevice)o).getAddress());
138        }
139        return false;
140    }
141
142    @Override
143    public int hashCode() {
144        return mAddress.hashCode();
145    }
146
147    /**
148     * Returns a string representation of this BluetoothDevice.
149     * <p>Currently this is the Bluetooth hardware address, for example
150     * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress}
151     * if you explicitly require the Bluetooth hardware address in case the
152     * {@link #toString} representation changes in the future.
153     * @return string representation of this BluetoothDevice
154     */
155    @Override
156    public String toString() {
157        return mAddress;
158    }
159
160    /** @hide */
161    public int describeContents() {
162        return 0;
163    }
164
165    /** @hide */
166    public static final Parcelable.Creator<BluetoothDevice> CREATOR =
167            new Parcelable.Creator<BluetoothDevice>() {
168        public BluetoothDevice createFromParcel(Parcel in) {
169            return new BluetoothDevice(in.readString());
170        }
171        public BluetoothDevice[] newArray(int size) {
172            return new BluetoothDevice[size];
173        }
174    };
175
176    /** @hide */
177    public void writeToParcel(Parcel out, int flags) {
178        out.writeString(mAddress);
179    }
180
181    /**
182     * Returns the hardware address of this BluetoothDevice.
183     * <p> For example, "00:11:22:AA:BB:CC".
184     * @return Bluetooth hardware address as string
185     */
186    public String getAddress() {
187        return mAddress;
188    }
189
190    /**
191     * Get the friendly Bluetooth name of the remote device.
192     *
193     * <p>The local adapter will automatically retrieve remote names when
194     * performing a device scan, and will cache them. This method just returns
195     * the name for this device from the cache.
196     *
197     * @return the Bluetooth name, or null if there was a problem.
198     * @hide
199     */
200    public String getName() {
201        try {
202            return sService.getRemoteName(mAddress);
203        } catch (RemoteException e) {Log.e(TAG, "", e);}
204        return null;
205    }
206
207    /**
208     * Create a bonding with a remote bluetooth device.
209     *
210     * This is an asynchronous call. The result of this bonding attempt can be
211     * observed through BluetoothIntent.BOND_STATE_CHANGED_ACTION intents.
212     *
213     * @param address the remote device Bluetooth address.
214     * @return false If there was an immediate problem creating the bonding,
215     *         true otherwise.
216     * @hide
217     */
218    public boolean createBond() {
219        try {
220            return sService.createBond(mAddress);
221        } catch (RemoteException e) {Log.e(TAG, "", e);}
222        return false;
223    }
224
225    /**
226     * Cancel an in-progress bonding request started with createBond.
227     * @hide
228     */
229    public boolean cancelBondProcess() {
230        try {
231            return sService.cancelBondProcess(mAddress);
232        } catch (RemoteException e) {Log.e(TAG, "", e);}
233        return false;
234    }
235
236    /**
237     * Removes the remote device and the pairing information associated
238     * with it.
239     *
240     * @return true if the device was disconnected, false otherwise and on
241     *         error.
242     * @hide
243     */
244    public boolean removeBond() {
245        try {
246            return sService.removeBond(mAddress);
247        } catch (RemoteException e) {Log.e(TAG, "", e);}
248        return false;
249    }
250
251    /**
252     * Get the bonding state of a remote device.
253     *
254     * Result is one of:
255     * BluetoothError.*
256     * BOND_*
257     *
258     * @param address Bluetooth hardware address of the remote device to check.
259     * @return Result code
260     * @hide
261     */
262    public int getBondState() {
263        try {
264            return sService.getBondState(mAddress);
265        } catch (RemoteException e) {Log.e(TAG, "", e);}
266        return BluetoothError.ERROR_IPC;
267    }
268
269    /**
270     * Get trust state of a remote device.
271     * @hide
272     */
273    public boolean getTrustState() {
274        try {
275            return sService.getTrustState(mAddress);
276        } catch (RemoteException e) {
277            Log.e(TAG, "", e);
278        }
279        return false;
280    }
281
282    /**
283     * Set trust state for a remote device.
284     * @param value the trust state value (true or false)
285     * @hide
286     */
287    public boolean setTrust(boolean value) {
288        try {
289            return sService.setTrust(mAddress, value);
290        } catch (RemoteException e) {
291            Log.e(TAG, "", e);
292        }
293        return false;
294    }
295
296    /** @hide */
297    public int getBluetoothClass() {
298        try {
299            return sService.getRemoteClass(mAddress);
300        } catch (RemoteException e) {Log.e(TAG, "", e);}
301        return BluetoothError.ERROR_IPC;
302    }
303
304    /** @hide */
305     public String[] getUuids() {
306        try {
307            return sService.getRemoteUuids(mAddress);
308        } catch (RemoteException e) {Log.e(TAG, "", e);}
309        return null;
310    }
311
312    /** @hide */
313    public int getServiceChannel(String uuid) {
314         try {
315             return sService.getRemoteServiceChannel(mAddress, uuid);
316         } catch (RemoteException e) {Log.e(TAG, "", e);}
317         return BluetoothError.ERROR_IPC;
318    }
319
320    /** @hide */
321    public boolean setPin(byte[] pin) {
322        try {
323            return sService.setPin(mAddress, pin);
324        } catch (RemoteException e) {Log.e(TAG, "", e);}
325        return false;
326    }
327
328    /** @hide */
329    public boolean setPasskey(int passkey) {
330        try {
331            return sService.setPasskey(mAddress, passkey);
332        } catch (RemoteException e) {Log.e(TAG, "", e);}
333        return false;
334    }
335
336    /** @hide */
337    public boolean setPairingConfirmation(boolean confirm) {
338        try {
339            return sService.setPairingConfirmation(mAddress, confirm);
340        } catch (RemoteException e) {Log.e(TAG, "", e);}
341        return false;
342    }
343
344    /** @hide */
345    public boolean cancelPairingUserInput() {
346        try {
347            return sService.cancelPairingUserInput(mAddress);
348        } catch (RemoteException e) {Log.e(TAG, "", e);}
349        return false;
350    }
351
352    /**
353     * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
354     * outgoing connection to this remote device.
355     * <p>The remote device will be authenticated and communication on this
356     * socket will be encrypted.
357     * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
358     * connection.
359     * <p>Valid RFCOMM channels are in range 1 to 30.
360     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
361     * @param channel RFCOMM channel to connect to
362     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
363     * @throws IOException on error, for example Bluetooth not available, or
364     *                     insufficient permissions
365     */
366    public BluetoothSocket createRfcommSocket(int channel) throws IOException {
367        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel);
368    }
369
370    /**
371     * Construct an insecure RFCOMM socket ready to start an outgoing
372     * connection.
373     * Call #connect on the returned #BluetoothSocket to begin the connection.
374     * The remote device will not be authenticated and communication on this
375     * socket will not be encrypted.
376     * @param port    remote port
377     * @return An RFCOMM BluetoothSocket
378     * @throws IOException On error, for example Bluetooth not available, or
379     *                     insufficient permissions.
380     * @hide
381     */
382    public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
383        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port);
384    }
385
386    /**
387     * Construct a SCO socket ready to start an outgoing connection.
388     * Call #connect on the returned #BluetoothSocket to begin the connection.
389     * @return a SCO BluetoothSocket
390     * @throws IOException on error, for example Bluetooth not available, or
391     *                     insufficient permissions.
392     * @hide
393     */
394    public BluetoothSocket createScoSocket() throws IOException {
395        return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1);
396    }
397
398    /**
399     * Check that a pin is valid and convert to byte array.
400     *
401     * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
402     * @param pin pin as java String
403     * @return the pin code as a UTF8 byte array, or null if it is an invalid
404     *         Bluetooth pin.
405     * @hide
406     */
407    public static byte[] convertPinToBytes(String pin) {
408        if (pin == null) {
409            return null;
410        }
411        byte[] pinBytes;
412        try {
413            pinBytes = pin.getBytes("UTF8");
414        } catch (UnsupportedEncodingException uee) {
415            Log.e(TAG, "UTF8 not supported?!?");  // this should not happen
416            return null;
417        }
418        if (pinBytes.length <= 0 || pinBytes.length > 16) {
419            return null;
420        }
421        return pinBytes;
422    }
423
424    /** Sanity check a bluetooth address, such as "00:43:A8:23:10:F0"
425     * @hide */
426    public static boolean checkBluetoothAddress(String address) {
427        if (address == null || address.length() != ADDRESS_LENGTH) {
428            return false;
429        }
430        for (int i = 0; i < ADDRESS_LENGTH; i++) {
431            char c = address.charAt(i);
432            switch (i % 3) {
433            case 0:
434            case 1:
435                if (Character.digit(c, 16) != -1) {
436                    break;  // hex character, OK
437                }
438                return false;
439            case 2:
440                if (c == ':') {
441                    break;  // OK
442                }
443                return false;
444            }
445        }
446        return true;
447    }
448}
449