BluetoothDevice.java revision a4433af5ac677be7c1f63447c0cd78829bdee159
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    /** @hide */
270    public int getBluetoothClass() {
271        try {
272            return sService.getRemoteClass(mAddress);
273        } catch (RemoteException e) {Log.e(TAG, "", e);}
274        return BluetoothError.ERROR_IPC;
275    }
276
277    /** @hide */
278     public String[] getUuids() {
279        try {
280            return sService.getRemoteUuids(mAddress);
281        } catch (RemoteException e) {Log.e(TAG, "", e);}
282        return null;
283    }
284
285    /** @hide */
286    public int getServiceChannel(String uuid) {
287         try {
288             return sService.getRemoteServiceChannel(mAddress, uuid);
289         } catch (RemoteException e) {Log.e(TAG, "", e);}
290         return BluetoothError.ERROR_IPC;
291    }
292
293    /** @hide */
294    public boolean setPin(byte[] pin) {
295        try {
296            return sService.setPin(mAddress, pin);
297        } catch (RemoteException e) {Log.e(TAG, "", e);}
298        return false;
299    }
300
301    /** @hide */
302    public boolean setPasskey(int passkey) {
303        try {
304            return sService.setPasskey(mAddress, passkey);
305        } catch (RemoteException e) {Log.e(TAG, "", e);}
306        return false;
307    }
308
309    /** @hide */
310    public boolean setPairingConfirmation(boolean confirm) {
311        try {
312            return sService.setPairingConfirmation(mAddress, confirm);
313        } catch (RemoteException e) {Log.e(TAG, "", e);}
314        return false;
315    }
316
317    /** @hide */
318    public boolean cancelPairingUserInput() {
319        try {
320            return sService.cancelPairingUserInput(mAddress);
321        } catch (RemoteException e) {Log.e(TAG, "", e);}
322        return false;
323    }
324
325    /**
326     * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
327     * outgoing connection to this remote device.
328     * <p>The remote device will be authenticated and communication on this
329     * socket will be encrypted.
330     * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing
331     * connection.
332     * <p>Valid RFCOMM channels are in range 1 to 30.
333     * @param channel RFCOMM channel to connect to
334     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
335     * @throws IOException on error, for example Bluetooth not available, or
336     *                     insufficient permissions
337     */
338    public BluetoothSocket createRfcommSocket(int channel) throws IOException {
339        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel);
340    }
341
342    /**
343     * Construct an insecure RFCOMM socket ready to start an outgoing
344     * connection.
345     * Call #connect on the returned #BluetoothSocket to begin the connection.
346     * The remote device will not be authenticated and communication on this
347     * socket will not be encrypted.
348     * @param port    remote port
349     * @return An RFCOMM BluetoothSocket
350     * @throws IOException On error, for example Bluetooth not available, or
351     *                     insufficient permissions.
352     * @hide
353     */
354    public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
355        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port);
356    }
357
358    /**
359     * Construct a SCO socket ready to start an outgoing connection.
360     * Call #connect on the returned #BluetoothSocket to begin the connection.
361     * @return a SCO BluetoothSocket
362     * @throws IOException on error, for example Bluetooth not available, or
363     *                     insufficient permissions.
364     * @hide
365     */
366    public BluetoothSocket createScoSocket() throws IOException {
367        return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1);
368    }
369
370    /**
371     * Check that a pin is valid and convert to byte array.
372     *
373     * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
374     * @param pin pin as java String
375     * @return the pin code as a UTF8 byte array, or null if it is an invalid
376     *         Bluetooth pin.
377     * @hide
378     */
379    public static byte[] convertPinToBytes(String pin) {
380        if (pin == null) {
381            return null;
382        }
383        byte[] pinBytes;
384        try {
385            pinBytes = pin.getBytes("UTF8");
386        } catch (UnsupportedEncodingException uee) {
387            Log.e(TAG, "UTF8 not supported?!?");  // this should not happen
388            return null;
389        }
390        if (pinBytes.length <= 0 || pinBytes.length > 16) {
391            return null;
392        }
393        return pinBytes;
394    }
395
396    /** Sanity check a bluetooth address, such as "00:43:A8:23:10:F0"
397     * @hide */
398    public static boolean checkBluetoothAddress(String address) {
399        if (address == null || address.length() != ADDRESS_LENGTH) {
400            return false;
401        }
402        for (int i = 0; i < ADDRESS_LENGTH; i++) {
403            char c = address.charAt(i);
404            switch (i % 3) {
405            case 0:
406            case 1:
407                if (Character.digit(c, 16) != -1) {
408                    break;  // hex character, OK
409                }
410                return false;
411            case 2:
412                if (c == ':') {
413                    break;  // OK
414                }
415                return false;
416            }
417        }
418        return true;
419    }
420}
421