BluetoothAdapter.java revision 3219ab4de27cd40b2149b0dd13edd412efe40244
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.os.RemoteException;
22import android.util.Log;
23
24import java.io.IOException;
25import java.util.Collections;
26import java.util.Set;
27import java.util.HashSet;
28
29/**
30 * Represents the local Bluetooth adapter.
31 *
32 * <p>Use {@link android.content.Context#getSystemService} with {@link
33 * android.content.Context#BLUETOOTH_SERVICE} to get the default local
34 * Bluetooth adapter. On most Android devices there is only one local
35 * Bluetotoh adapter.
36 *
37 * <p>Use the {@link BluetoothDevice} class for operations on remote Bluetooth
38 * devices.
39 */
40public final class BluetoothAdapter {
41    private static final String TAG = "BluetoothAdapter";
42
43    /**
44     * Sentinel error value for this class. Guaranteed to not equal any other
45     * integer constant in this class. Provided as a convenience for functions
46     * that require a sentinel error value, for example:
47     * <p><code>Intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
48     * BluetoothAdapter.ERROR)</code>
49     */
50    public static final int ERROR = Integer.MIN_VALUE;
51
52    /**
53     * Broadcast Action: The state of the local Bluetooth adapter has been
54     * changed.
55     * <p>For example, Bluetooth has been turned on or off.
56     * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link
57     * #EXTRA_PREVIOUS_STATE} containing the new and old states
58     * respectively.
59     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
60     */
61    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
62    public static final String ACTION_STATE_CHANGED =
63            "android.bluetooth.adapter.action.STATE_CHANGED";
64
65    /**
66     * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
67     * intents to request the current power state. Possible values are:
68     * {@link #STATE_OFF},
69     * {@link #STATE_TURNING_ON},
70     * {@link #STATE_ON},
71     * {@link #STATE_TURNING_OFF},
72     */
73    public static final String EXTRA_STATE =
74            "android.bluetooth.adapter.extra.STATE";
75    /**
76     * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
77     * intents to request the previous power state. Possible values are:
78     * {@link #STATE_OFF},
79     * {@link #STATE_TURNING_ON},
80     * {@link #STATE_ON},
81     * {@link #STATE_TURNING_OFF},
82     */
83    public static final String EXTRA_PREVIOUS_STATE =
84            "android.bluetooth.adapter.extra.PREVIOUS_STATE";
85
86    /**
87     * Indicates the local Bluetooth adapter is off.
88     */
89    public static final int STATE_OFF = 10;
90    /**
91     * Indicates the local Bluetooth adapter is turning on. However local
92     * clients should wait for {@link #STATE_ON} before attempting to
93     * use the adapter.
94     */
95    public static final int STATE_TURNING_ON = 11;
96    /**
97     * Indicates the local Bluetooth adapter is on, and ready for use.
98     */
99    public static final int STATE_ON = 12;
100    /**
101     * Indicates the local Bluetooth adapter is turning off. Local clients
102     * should immediately attempt graceful disconnection of any remote links.
103     */
104    public static final int STATE_TURNING_OFF = 13;
105
106    /**
107     * Activity Action: Show a system activity that requests discoverable mode.
108     * <p>Discoverable mode is equivalent to {@link
109     * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. It allows remote devices to see
110     * this Bluetooth adapter when they perform a discovery.
111     * <p>For privacy, Android is not by default discoverable.
112     * <p>The sender can optionally use extra field {@link
113     * #EXTRA_DISCOVERABLE_DURATION} to request the duration of
114     * discoverability. Currently the default duration is 120 seconds, and
115     * maximum duration is capped at 300 seconds for each request.
116     * <p>Notification of the result of this activity is posted using the
117     * {@link android.app.Activity#onActivityResult} callback. The
118     * <code>resultCode</code>
119     * will be the duration (in seconds) of discoverability, or a negative
120     * value if the user rejected discoverability.
121     * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED}
122     * for global notification whenever the scan mode changes.
123     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
124     */
125    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
126    public static final String ACTION_REQUEST_DISCOVERABLE =
127            "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
128
129    /**
130     * Used as an optional int extra field in {@link
131     * #ACTION_REQUEST_DISCOVERABLE} intents to request a specific duration
132     * for discoverability in seconds. The current default is 120 seconds, and
133     * requests over 300 seconds will be capped. These values could change.
134     */
135    public static final String EXTRA_DISCOVERABLE_DURATION =
136            "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION";
137
138    /**
139     * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter
140     * has changed.
141     * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link
142     * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes
143     * respectively.
144     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
145     */
146    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
147    public static final String ACTION_SCAN_MODE_CHANGED =
148            "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
149
150    /**
151     * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
152     * intents to request the current scan mode. Possible values are:
153     * {@link #SCAN_MODE_NONE},
154     * {@link #SCAN_MODE_CONNECTABLE},
155     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE},
156     */
157    public static final String EXTRA_SCAN_MODE = "android.bluetooth.adapter.extra.SCAN_MODE";
158    /**
159     * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
160     * intents to request the previous scan mode. Possible values are:
161     * {@link #SCAN_MODE_NONE},
162     * {@link #SCAN_MODE_CONNECTABLE},
163     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE},
164     */
165    public static final String EXTRA_PREVIOUS_SCAN_MODE =
166            "android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE";
167
168    /**
169     * Indicates that both inquiry scan and page scan are disabled on the local
170     * Bluetooth adapter. Therefore this device is neither discoverable
171     * nor connectable from remote Bluetooth devices.
172     */
173    public static final int SCAN_MODE_NONE = 20;
174    /**
175     * Indicates that inquiry scan is disabled, but page scan is enabled on the
176     * local Bluetooth adapter. Therefore this device is not discoverable from
177     * remote Bluetooth devices, but is connectable from remote devices that
178     * have previously discovered this device.
179     */
180    public static final int SCAN_MODE_CONNECTABLE = 21;
181    /**
182     * Indicates that both inquiry scan and page scan are enabled on the local
183     * Bluetooth adapter. Therefore this device is both discoverable and
184     * connectable from remote Bluetooth devices.
185     */
186    public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23;
187
188
189    /**
190     * Broadcast Action: The local Bluetooth adapter has started the remote
191     * device discovery process.
192     * <p>This usually involves an inquiry scan of about 12 seconds, followed
193     * by a page scan of each new device to retrieve its Bluetooth name.
194     * <p>Register for {@link BluetoothDevice#ACTION_FOUND} to be notified as
195     * remote Bluetooth devices are found.
196     * <p>Device discovery is a heavyweight procedure. New connections to
197     * remote Bluetooth devices should not be attempted while discovery is in
198     * progress, and existing connections will experience limited bandwidth
199     * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
200     * discovery.
201     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
202     */
203    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
204    public static final String ACTION_DISCOVERY_STARTED =
205            "android.bluetooth.adapter.action.DISCOVERY_STARTED";
206    /**
207     * Broadcast Action: The local Bluetooth adapter has finished the device
208     * discovery process.
209     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
210     */
211    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
212    public static final String ACTION_DISCOVERY_FINISHED =
213            "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
214
215    /**
216     * Broadcast Action: The local Bluetooth adapter has changed its friendly
217     * Bluetooth name.
218     * <p>This name is visible to remote Bluetooth devices.
219     * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing
220     * the name.
221     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
222     */
223    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
224    public static final String ACTION_LOCAL_NAME_CHANGED =
225            "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
226    /**
227     * Used as a String extra field in {@link #ACTION_LOCAL_NAME_CHANGED}
228     * intents to request the local Bluetooth name.
229     */
230    public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME";
231
232    private static final int ADDRESS_LENGTH = 17;
233
234    private final IBluetooth mService;
235
236    /**
237     * Do not use this constructor. Use Context.getSystemService() instead.
238     * @hide
239     */
240    public BluetoothAdapter(IBluetooth service) {
241        if (service == null) {
242            throw new IllegalArgumentException("service is null");
243        }
244        mService = service;
245    }
246
247    /**
248     * Get a {@link BluetoothDevice} object for the given Bluetooth hardware
249     * address.
250     * <p>Valid Bluetooth hardware addresses must be upper case, in a format
251     * such as "00:11:22:33:AA:BB". The helper {@link #checkBluetoothAddress} is
252     * available to validate a Bluetooth address.
253     * <p>A {@link BluetoothDevice} will always be returned for a valid
254     * hardware address, even if this adapter has never seen that device.
255     *
256     * @param address valid Bluetooth MAC address
257     * @throws IllegalArgumentException if address is invalid
258     */
259    public BluetoothDevice getRemoteDevice(String address) {
260        return new BluetoothDevice(address);
261    }
262
263    /**
264     * Return true if Bluetooth is currently enabled and ready for use.
265     * <p>Equivalent to:
266     * <code>getBluetoothState() == STATE_ON</code>
267     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
268     *
269     * @return true if the local adapter is turned on
270     */
271    public boolean isEnabled() {
272        try {
273            return mService.isEnabled();
274        } catch (RemoteException e) {Log.e(TAG, "", e);}
275        return false;
276    }
277
278    /**
279     * Get the current state of the local Bluetooth adapter.
280     * <p>Possible return values are
281     * {@link #STATE_OFF},
282     * {@link #STATE_TURNING_ON},
283     * {@link #STATE_ON},
284     * {@link #STATE_TURNING_OFF}.
285     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
286     *
287     * @return current state of Bluetooth adapter
288     */
289    public int getState() {
290        try {
291            return mService.getBluetoothState();
292        } catch (RemoteException e) {Log.e(TAG, "", e);}
293        return STATE_OFF;
294    }
295
296    /**
297     * Turn on the local Bluetooth adapter.
298     * <p>This powers on the underlying Bluetooth hardware, and starts all
299     * Bluetooth system services.
300     * <p>This is an asynchronous call: it will return immediately, and
301     * clients should listen for {@link #ACTION_STATE_CHANGED}
302     * to be notified of subsequent adapter state changes. If this call returns
303     * true, then the adapter state will immediately transition from {@link
304     * #STATE_OFF} to {@link #STATE_TURNING_ON}, and some time
305     * later transition to either {@link #STATE_OFF} or {@link
306     * #STATE_ON}. If this call returns false then there was an
307     * immediate problem that will prevent the adapter from being turned on -
308     * such as Airplane mode, or the adapter is already turned on.
309     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
310     *
311     * @return true to indicate adapter startup has begun, or false on
312     *         immediate error
313     */
314    public boolean enable() {
315        try {
316            return mService.enable();
317        } catch (RemoteException e) {Log.e(TAG, "", e);}
318        return false;
319    }
320
321    /**
322     * Turn off the local Bluetooth adapter.
323     * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth
324     * system services, and powers down the underlying Bluetooth hardware.
325     * <p>This is an asynchronous call: it will return immediately, and
326     * clients should listen for {@link #ACTION_STATE_CHANGED}
327     * to be notified of subsequent adapter state changes. If this call returns
328     * true, then the adapter state will immediately transition from {@link
329     * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time
330     * later transition to either {@link #STATE_OFF} or {@link
331     * #STATE_ON}. If this call returns false then there was an
332     * immediate problem that will prevent the adapter from being turned off -
333     * such as the adapter already being turned off.
334     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
335     *
336     * @return true to indicate adapter shutdown has begun, or false on
337     *         immediate error
338     */
339    public boolean disable() {
340        try {
341            return mService.disable(true);
342        } catch (RemoteException e) {Log.e(TAG, "", e);}
343        return false;
344    }
345
346    /**
347     * Returns the hardware address of the local Bluetooth adapter.
348     * <p>For example, "00:11:22:AA:BB:CC".
349     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
350     *
351     * @return Bluetooth hardware address as string
352     */
353    public String getAddress() {
354        try {
355            return mService.getAddress();
356        } catch (RemoteException e) {Log.e(TAG, "", e);}
357        return null;
358    }
359
360    /**
361     * Get the friendly Bluetooth name of the local Bluetooth adapter.
362     * <p>This name is visible to remote Bluetooth devices.
363     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
364     *
365     * @return the Bluetooth name, or null on error
366     */
367    public String getName() {
368        try {
369            return mService.getName();
370        } catch (RemoteException e) {Log.e(TAG, "", e);}
371        return null;
372    }
373
374    /**
375     * Set the friendly Bluetooth name of the local Bluetoth adapter.
376     * <p>This name is visible to remote Bluetooth devices.
377     * <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however
378     * many remote devices can only display the first 40 characters, and some
379     * may be limited to just 20.
380     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
381     *
382     * @param name a valid Bluetooth name
383     * @return     true if the name was set, false otherwise
384     */
385    public boolean setName(String name) {
386        try {
387            return mService.setName(name);
388        } catch (RemoteException e) {Log.e(TAG, "", e);}
389        return false;
390    }
391
392    /**
393     * Get the current Bluetooth scan mode of the local Bluetooth adaper.
394     * <p>The Bluetooth scan mode determines if the local adapter is
395     * connectable and/or discoverable from remote Bluetooth devices.
396     * <p>Possible values are:
397     * {@link #SCAN_MODE_NONE},
398     * {@link #SCAN_MODE_CONNECTABLE},
399     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
400     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
401     *
402     * @return scan mode
403     */
404    public int getScanMode() {
405        try {
406            return mService.getScanMode();
407        } catch (RemoteException e) {Log.e(TAG, "", e);}
408        return SCAN_MODE_NONE;
409    }
410
411    /**
412     * Set the Bluetooth scan mode of the local Bluetooth adapter.
413     * <p>The Bluetooth scan mode determines if the local adapter is
414     * connectable and/or discoverable from remote Bluetooth devices.
415     * <p>For privacy reasons, it is recommended to limit the duration of time
416     * that the local adapter remains in a discoverable scan mode. For example,
417     * 2 minutes is a generous time to allow a remote Bluetooth device to
418     * initiate and complete its discovery process.
419     * <p>Valid scan mode values are:
420     * {@link #SCAN_MODE_NONE},
421     * {@link #SCAN_MODE_CONNECTABLE},
422     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
423     * <p>Requires {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}
424     * <p>Applications cannot set the scan mode. They should use
425     * <code>startActivityForResult(
426     * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE})
427     * </code>instead.
428     *
429     * @param mode valid scan mode
430     * @return     true if the scan mode was set, false otherwise
431     * @hide
432     */
433    public boolean setScanMode(int mode) {
434        try {
435            return mService.setScanMode(mode);
436        } catch (RemoteException e) {Log.e(TAG, "", e);}
437        return false;
438    }
439
440    /** @hide */
441    public int getDiscoverableTimeout() {
442        try {
443            return mService.getDiscoverableTimeout();
444        } catch (RemoteException e) {Log.e(TAG, "", e);}
445        return -1;
446    }
447
448    /** @hide */
449    public void setDiscoverableTimeout(int timeout) {
450        try {
451            mService.setDiscoverableTimeout(timeout);
452        } catch (RemoteException e) {Log.e(TAG, "", e);}
453    }
454
455    /**
456     * Start the remote device discovery process.
457     * <p>The discovery process usually involves an inquiry scan of about 12
458     * seconds, followed by a page scan of each new device to retrieve its
459     * Bluetooth name.
460     * <p>This is an asynchronous call, it will return immediately. Register
461     * for {@link #ACTION_DISCOVERY_STARTED} and {@link
462     * #ACTION_DISCOVERY_FINISHED} intents to determine exactly when the
463     * discovery starts and completes. Register for {@link
464     * BluetoothDevice#ACTION_FOUND} to be notified as remote Bluetooth devices
465     * are found.
466     * <p>Device discovery is a heavyweight procedure. New connections to
467     * remote Bluetooth devices should not be attempted while discovery is in
468     * progress, and existing connections will experience limited bandwidth
469     * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
470     * discovery.
471     * <p>Device discovery will only find remote devices that are currently
472     * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are
473     * not discoverable by default, and need to be entered into a special mode.
474     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
475     *
476     * @return true on success, false on error
477     */
478    public boolean startDiscovery() {
479        try {
480            return mService.startDiscovery();
481        } catch (RemoteException e) {Log.e(TAG, "", e);}
482        return false;
483    }
484
485    /**
486     * Cancel the current device discovery process.
487     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
488     *
489     * @return true on success, false on error
490     */
491    public boolean cancelDiscovery() {
492        try {
493            mService.cancelDiscovery();
494        } catch (RemoteException e) {Log.e(TAG, "", e);}
495        return false;
496    }
497
498    /**
499     * Return true if the local Bluetooth adapter is currently in the device
500     * discovery process.
501     * <p>Device discovery is a heavyweight procedure. New connections to
502     * remote Bluetooth devices should not be attempted while discovery is in
503     * progress, and existing connections will experience limited bandwidth
504     * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
505     * discovery.
506     * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED}
507     * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery
508     * starts or completes.
509     *
510     * @return true if discovering
511     */
512    public boolean isDiscovering() {
513        try {
514            return mService.isDiscovering();
515        } catch (RemoteException e) {Log.e(TAG, "", e);}
516        return false;
517    }
518
519    /**
520     * Return the set of {@link BluetoothDevice} objects that are bonded
521     * (paired) to the local adapter.
522     *
523     * @return unmodifiable set of {@link BluetoothDevice}, or null on error
524     */
525    public Set<BluetoothDevice> getBondedDevices() {
526        try {
527            return toDeviceSet(mService.listBonds());
528        } catch (RemoteException e) {Log.e(TAG, "", e);}
529        return null;
530    }
531
532    /**
533     * Create a listening, secure RFCOMM Bluetooth socket.
534     * <p>A remote device connecting to this socket will be authenticated and
535     * communication on this socket will be encrypted.
536     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
537     * connections to listening {@link BluetoothServerSocket}.
538     * <p>Valid RFCOMM channels are in range 1 to 30.
539     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
540     * @param channel RFCOMM channel to listen on
541     * @return a listening RFCOMM BluetoothServerSocket
542     * @throws IOException on error, for example Bluetooth not available, or
543     *                     insufficient permissions, or channel in use.
544     */
545    public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException {
546        BluetoothServerSocket socket = new BluetoothServerSocket(
547                BluetoothSocket.TYPE_RFCOMM, true, true, channel);
548        try {
549            socket.mSocket.bindListen();
550        } catch (IOException e) {
551            try {
552                socket.close();
553            } catch (IOException e2) { }
554            throw e;
555        }
556        return socket;
557    }
558
559    /**
560     * Construct an unencrypted, unauthenticated, RFCOMM server socket.
561     * Call #accept to retrieve connections to this socket.
562     * @return An RFCOMM BluetoothServerSocket
563     * @throws IOException On error, for example Bluetooth not available, or
564     *                     insufficient permissions.
565     * @hide
566     */
567    public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
568        BluetoothServerSocket socket = new BluetoothServerSocket(
569                BluetoothSocket.TYPE_RFCOMM, false, false, port);
570        try {
571            socket.mSocket.bindListen();
572        } catch (IOException e) {
573            try {
574                socket.close();
575            } catch (IOException e2) { }
576            throw e;
577        }
578        return socket;
579    }
580
581    /**
582     * Construct a SCO server socket.
583     * Call #accept to retrieve connections to this socket.
584     * @return A SCO BluetoothServerSocket
585     * @throws IOException On error, for example Bluetooth not available, or
586     *                     insufficient permissions.
587     * @hide
588     */
589    public static BluetoothServerSocket listenUsingScoOn() throws IOException {
590        BluetoothServerSocket socket = new BluetoothServerSocket(
591                BluetoothSocket.TYPE_SCO, false, false, -1);
592        try {
593            socket.mSocket.bindListen();
594        } catch (IOException e) {
595            try {
596                socket.close();
597            } catch (IOException e2) { }
598            throw e;
599        }
600        return socket;
601    }
602
603    private Set<BluetoothDevice> toDeviceSet(String[] addresses) {
604        Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length);
605        for (int i = 0; i < addresses.length; i++) {
606            devices.add(getRemoteDevice(addresses[i]));
607        }
608        return Collections.unmodifiableSet(devices);
609    }
610
611    /**
612     * Validate a Bluetooth address, such as "00:43:A8:23:10:F0"
613     * <p>Alphabetic characters must be uppercase to be valid.
614     *
615     * @param address Bluetooth address as string
616     * @return true if the address is valid, false otherwise
617     */
618    public static boolean checkBluetoothAddress(String address) {
619        if (address == null || address.length() != ADDRESS_LENGTH) {
620            return false;
621        }
622        for (int i = 0; i < ADDRESS_LENGTH; i++) {
623            char c = address.charAt(i);
624            switch (i % 3) {
625            case 0:
626            case 1:
627                if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) {
628                    // hex character, OK
629                    break;
630                }
631                return false;
632            case 2:
633                if (c == ':') {
634                    break;  // OK
635                }
636                return false;
637            }
638        }
639        return true;
640    }
641}
642