1/*
2 * Copyright (C) 2013 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.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
22import android.content.Context;
23import android.os.ParcelUuid;
24import android.os.RemoteException;
25import android.util.Log;
26
27import java.util.ArrayList;
28import java.util.List;
29import java.util.UUID;
30
31/**
32 * Public API for the Bluetooth GATT Profile server role.
33 *
34 * <p>This class provides Bluetooth GATT server role functionality,
35 * allowing applications to create Bluetooth Smart services and
36 * characteristics.
37 *
38 * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
39 * via IPC.  Use {@link BluetoothManager#openGattServer} to get an instance
40 * of this class.
41 */
42public final class BluetoothGattServer implements BluetoothProfile {
43    private static final String TAG = "BluetoothGattServer";
44    private static final boolean DBG = true;
45    private static final boolean VDBG = false;
46
47    private BluetoothAdapter mAdapter;
48    private IBluetoothGatt mService;
49    private BluetoothGattServerCallback mCallback;
50
51    private Object mServerIfLock = new Object();
52    private int mServerIf;
53    private int mTransport;
54    private List<BluetoothGattService> mServices;
55
56    private static final int CALLBACK_REG_TIMEOUT = 10000;
57
58    /**
59     * Bluetooth GATT interface callbacks
60     */
61    private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
62        new IBluetoothGattServerCallback.Stub() {
63            /**
64             * Application interface registered - app is ready to go
65             * @hide
66             */
67            public void onServerRegistered(int status, int serverIf) {
68                if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
69                    + " serverIf=" + serverIf);
70                synchronized(mServerIfLock) {
71                    if (mCallback != null) {
72                        mServerIf = serverIf;
73                        mServerIfLock.notify();
74                    } else {
75                        // registration timeout
76                        Log.e(TAG, "onServerRegistered: mCallback is null");
77                    }
78                }
79            }
80
81            /**
82             * Callback reporting an LE scan result.
83             * @hide
84             */
85            public void onScanResult(String address, int rssi, byte[] advData) {
86                if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
87                // no op
88            }
89
90            /**
91             * Server connection state changed
92             * @hide
93             */
94            public void onServerConnectionState(int status, int serverIf,
95                                                boolean connected, String address) {
96                if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
97                    + " serverIf=" + serverIf + " device=" + address);
98                try {
99                    mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
100                                                      connected ? BluetoothProfile.STATE_CONNECTED :
101                                                      BluetoothProfile.STATE_DISCONNECTED);
102                } catch (Exception ex) {
103                    Log.w(TAG, "Unhandled exception in callback", ex);
104                }
105            }
106
107            /**
108             * Service has been added
109             * @hide
110             */
111            public void onServiceAdded(int status, int srvcType,
112                                       int srvcInstId, ParcelUuid srvcId) {
113                UUID srvcUuid = srvcId.getUuid();
114                if (DBG) Log.d(TAG, "onServiceAdded() - service=" + srvcUuid
115                    + "status=" + status);
116
117                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
118                if (service == null) return;
119
120                try {
121                    mCallback.onServiceAdded((int)status, service);
122                } catch (Exception ex) {
123                    Log.w(TAG, "Unhandled exception in callback", ex);
124                }
125            }
126
127            /**
128             * Remote client characteristic read request.
129             * @hide
130             */
131            public void onCharacteristicReadRequest(String address, int transId,
132                            int offset, boolean isLong, int srvcType, int srvcInstId,
133                            ParcelUuid srvcId, int charInstId, ParcelUuid charId) {
134                UUID srvcUuid = srvcId.getUuid();
135                UUID charUuid = charId.getUuid();
136                if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - "
137                    + "service=" + srvcUuid + ", characteristic=" + charUuid);
138
139                BluetoothDevice device = mAdapter.getRemoteDevice(address);
140                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
141                if (service == null) return;
142
143                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
144                if (characteristic == null) return;
145
146                try {
147                    mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic);
148                } catch (Exception ex) {
149                    Log.w(TAG, "Unhandled exception in callback", ex);
150                }
151            }
152
153            /**
154             * Remote client descriptor read request.
155             * @hide
156             */
157            public void onDescriptorReadRequest(String address, int transId,
158                            int offset, boolean isLong, int srvcType, int srvcInstId,
159                            ParcelUuid srvcId, int charInstId, ParcelUuid charId,
160                            ParcelUuid descrId) {
161                UUID srvcUuid = srvcId.getUuid();
162                UUID charUuid = charId.getUuid();
163                UUID descrUuid = descrId.getUuid();
164                if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - "
165                    + "service=" + srvcUuid + ", characteristic=" + charUuid
166                    + "descriptor=" + descrUuid);
167
168                BluetoothDevice device = mAdapter.getRemoteDevice(address);
169                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
170                if (service == null) return;
171
172                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
173                if (characteristic == null) return;
174
175                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
176                if (descriptor == null) return;
177
178                try {
179                    mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
180                } catch (Exception ex) {
181                    Log.w(TAG, "Unhandled exception in callback", ex);
182                }
183            }
184
185            /**
186             * Remote client characteristic write request.
187             * @hide
188             */
189            public void onCharacteristicWriteRequest(String address, int transId,
190                            int offset, int length, boolean isPrep, boolean needRsp,
191                            int srvcType, int srvcInstId, ParcelUuid srvcId,
192                            int charInstId, ParcelUuid charId, byte[] value) {
193                UUID srvcUuid = srvcId.getUuid();
194                UUID charUuid = charId.getUuid();
195                if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - "
196                    + "service=" + srvcUuid + ", characteristic=" + charUuid);
197
198                BluetoothDevice device = mAdapter.getRemoteDevice(address);
199                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
200                if (service == null) return;
201
202                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
203                if (characteristic == null) return;
204
205                try {
206                    mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
207                                                           isPrep, needRsp, offset, value);
208                } catch (Exception ex) {
209                    Log.w(TAG, "Unhandled exception in callback", ex);
210                }
211
212            }
213
214            /**
215             * Remote client descriptor write request.
216             * @hide
217             */
218            public void onDescriptorWriteRequest(String address, int transId,
219                            int offset, int length, boolean isPrep, boolean needRsp,
220                            int srvcType, int srvcInstId, ParcelUuid srvcId,
221                            int charInstId, ParcelUuid charId, ParcelUuid descrId,
222                            byte[] value) {
223                UUID srvcUuid = srvcId.getUuid();
224                UUID charUuid = charId.getUuid();
225                UUID descrUuid = descrId.getUuid();
226                if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - "
227                    + "service=" + srvcUuid + ", characteristic=" + charUuid
228                    + "descriptor=" + descrUuid);
229
230                BluetoothDevice device = mAdapter.getRemoteDevice(address);
231
232                BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
233                if (service == null) return;
234
235                BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
236                if (characteristic == null) return;
237
238                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
239                if (descriptor == null) return;
240
241                try {
242                    mCallback.onDescriptorWriteRequest(device, transId, descriptor,
243                                                       isPrep, needRsp, offset, value);
244                } catch (Exception ex) {
245                    Log.w(TAG, "Unhandled exception in callback", ex);
246                }
247            }
248
249            /**
250             * Execute pending writes.
251             * @hide
252             */
253            public void onExecuteWrite(String address, int transId,
254                                       boolean execWrite) {
255                if (DBG) Log.d(TAG, "onExecuteWrite() - "
256                    + "device=" + address + ", transId=" + transId
257                    + "execWrite=" + execWrite);
258
259                BluetoothDevice device = mAdapter.getRemoteDevice(address);
260                if (device == null) return;
261
262                try {
263                    mCallback.onExecuteWrite(device, transId, execWrite);
264                } catch (Exception ex) {
265                    Log.w(TAG, "Unhandled exception in callback", ex);
266                }
267            }
268
269            /**
270             * A notification/indication has been sent.
271             * @hide
272             */
273            public void onNotificationSent(String address, int status) {
274                if (VDBG) Log.d(TAG, "onNotificationSent() - "
275                    + "device=" + address + ", status=" + status);
276
277                BluetoothDevice device = mAdapter.getRemoteDevice(address);
278                if (device == null) return;
279
280                try {
281                    mCallback.onNotificationSent(device, status);
282                } catch (Exception ex) {
283                    Log.w(TAG, "Unhandled exception: " + ex);
284                }
285            }
286
287            /**
288             * The MTU for a connection has changed
289             * @hide
290             */
291            public void onMtuChanged(String address, int mtu) {
292                if (DBG) Log.d(TAG, "onMtuChanged() - "
293                    + "device=" + address + ", mtu=" + mtu);
294
295                BluetoothDevice device = mAdapter.getRemoteDevice(address);
296                if (device == null) return;
297
298                try {
299                    mCallback.onMtuChanged(device, mtu);
300                } catch (Exception ex) {
301                    Log.w(TAG, "Unhandled exception: " + ex);
302                }
303            }
304        };
305
306    /**
307     * Create a BluetoothGattServer proxy object.
308     */
309    /*package*/ BluetoothGattServer(IBluetoothGatt iGatt, int transport) {
310        mService = iGatt;
311        mAdapter = BluetoothAdapter.getDefaultAdapter();
312        mCallback = null;
313        mServerIf = 0;
314        mTransport = transport;
315        mServices = new ArrayList<BluetoothGattService>();
316    }
317
318    /**
319     * Close this GATT server instance.
320     *
321     * Application should call this method as early as possible after it is done with
322     * this GATT server.
323     */
324    public void close() {
325        if (DBG) Log.d(TAG, "close()");
326        unregisterCallback();
327    }
328
329    /**
330     * Register an application callback to start using GattServer.
331     *
332     * <p>This is an asynchronous call. The callback is used to notify
333     * success or failure if the function returns true.
334     *
335     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
336     *
337     * @param callback GATT callback handler that will receive asynchronous
338     *                 callbacks.
339     * @return true, the callback will be called to notify success or failure,
340     *         false on immediate error
341     */
342    /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
343        if (DBG) Log.d(TAG, "registerCallback()");
344        if (mService == null) {
345            Log.e(TAG, "GATT service not available");
346            return false;
347        }
348        UUID uuid = UUID.randomUUID();
349        if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
350
351        synchronized(mServerIfLock) {
352            if (mCallback != null) {
353                Log.e(TAG, "App can register callback only once");
354                return false;
355            }
356
357            mCallback = callback;
358            try {
359                mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
360            } catch (RemoteException e) {
361                Log.e(TAG,"",e);
362                mCallback = null;
363                return false;
364            }
365
366            try {
367                mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
368            } catch (InterruptedException e) {
369                Log.e(TAG, "" + e);
370                mCallback = null;
371            }
372
373            if (mServerIf == 0) {
374                mCallback = null;
375                return false;
376            } else {
377                return true;
378            }
379        }
380    }
381
382    /**
383     * Unregister the current application and callbacks.
384     */
385    private void unregisterCallback() {
386        if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
387        if (mService == null || mServerIf == 0) return;
388
389        try {
390            mCallback = null;
391            mService.unregisterServer(mServerIf);
392            mServerIf = 0;
393        } catch (RemoteException e) {
394            Log.e(TAG,"",e);
395        }
396    }
397
398    /**
399     * Returns a service by UUID, instance and type.
400     * @hide
401     */
402    /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
403        for(BluetoothGattService svc : mServices) {
404            if (svc.getType() == type &&
405                svc.getInstanceId() == instanceId &&
406                svc.getUuid().equals(uuid)) {
407                return svc;
408            }
409        }
410        return null;
411    }
412
413    /**
414     * Initiate a connection to a Bluetooth GATT capable device.
415     *
416     * <p>The connection may not be established right away, but will be
417     * completed when the remote device is available. A
418     * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
419     * invoked when the connection state changes as a result of this function.
420     *
421     * <p>The autoConnect paramter determines whether to actively connect to
422     * the remote device, or rather passively scan and finalize the connection
423     * when the remote device is in range/available. Generally, the first ever
424     * connection to a device should be direct (autoConnect set to false) and
425     * subsequent connections to known devices should be invoked with the
426     * autoConnect parameter set to true.
427     *
428     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
429     *
430     * @param autoConnect Whether to directly connect to the remote device (false)
431     *                    or to automatically connect as soon as the remote
432     *                    device becomes available (true).
433     * @return true, if the connection attempt was initiated successfully
434     */
435    public boolean connect(BluetoothDevice device, boolean autoConnect) {
436        if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
437        if (mService == null || mServerIf == 0) return false;
438
439        try {
440            mService.serverConnect(mServerIf, device.getAddress(),
441                               autoConnect ? false : true,mTransport); // autoConnect is inverse of "isDirect"
442        } catch (RemoteException e) {
443            Log.e(TAG,"",e);
444            return false;
445        }
446
447        return true;
448    }
449
450    /**
451     * Disconnects an established connection, or cancels a connection attempt
452     * currently in progress.
453     *
454     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
455     *
456     * @param device Remote device
457     */
458    public void cancelConnection(BluetoothDevice device) {
459        if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
460        if (mService == null || mServerIf == 0) return;
461
462        try {
463            mService.serverDisconnect(mServerIf, device.getAddress());
464        } catch (RemoteException e) {
465            Log.e(TAG,"",e);
466        }
467    }
468
469    /**
470     * Send a response to a read or write request to a remote device.
471     *
472     * <p>This function must be invoked in when a remote read/write request
473     * is received by one of these callback methods:
474     *
475     * <ul>
476     *      <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
477     *      <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
478     *      <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
479     *      <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
480     * </ul>
481     *
482     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
483     *
484     * @param device The remote device to send this response to
485     * @param requestId The ID of the request that was received with the callback
486     * @param status The status of the request to be sent to the remote devices
487     * @param offset Value offset for partial read/write response
488     * @param value The value of the attribute that was read/written (optional)
489     */
490    public boolean sendResponse(BluetoothDevice device, int requestId,
491                                int status, int offset, byte[] value) {
492        if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
493        if (mService == null || mServerIf == 0) return false;
494
495        try {
496            mService.sendResponse(mServerIf, device.getAddress(), requestId,
497                                  status, offset, value);
498        } catch (RemoteException e) {
499            Log.e(TAG,"",e);
500            return false;
501        }
502        return true;
503    }
504
505    /**
506     * Send a notification or indication that a local characteristic has been
507     * updated.
508     *
509     * <p>A notification or indication is sent to the remote device to signal
510     * that the characteristic has been updated. This function should be invoked
511     * for every client that requests notifications/indications by writing
512     * to the "Client Configuration" descriptor for the given characteristic.
513     *
514     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
515     *
516     * @param device The remote device to receive the notification/indication
517     * @param characteristic The local characteristic that has been updated
518     * @param confirm true to request confirmation from the client (indication),
519     *                false to send a notification
520     * @throws IllegalArgumentException
521     * @return true, if the notification has been triggered successfully
522     */
523    public boolean notifyCharacteristicChanged(BluetoothDevice device,
524                    BluetoothGattCharacteristic characteristic, boolean confirm) {
525        if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
526        if (mService == null || mServerIf == 0) return false;
527
528        BluetoothGattService service = characteristic.getService();
529        if (service == null) return false;
530
531        if (characteristic.getValue() == null) {
532            throw new IllegalArgumentException("Chracteristic value is empty. Use "
533                    + "BluetoothGattCharacteristic#setvalue to update");
534        }
535
536        try {
537            mService.sendNotification(mServerIf, device.getAddress(),
538                    service.getType(), service.getInstanceId(),
539                    new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
540                    new ParcelUuid(characteristic.getUuid()), confirm,
541                    characteristic.getValue());
542        } catch (RemoteException e) {
543            Log.e(TAG,"",e);
544            return false;
545        }
546
547        return true;
548    }
549
550    /**
551     * Add a service to the list of services to be hosted.
552     *
553     * <p>Once a service has been addded to the the list, the service and its
554     * included characteristics will be provided by the local device.
555     *
556     * <p>If the local device has already exposed services when this function
557     * is called, a service update notification will be sent to all clients.
558     *
559     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
560     *
561     * @param service Service to be added to the list of services provided
562     *                by this device.
563     * @return true, if the service has been added successfully
564     */
565    public boolean addService(BluetoothGattService service) {
566        if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
567        if (mService == null || mServerIf == 0) return false;
568
569        mServices.add(service);
570
571        try {
572            mService.beginServiceDeclaration(mServerIf, service.getType(),
573                service.getInstanceId(), service.getHandles(),
574                new ParcelUuid(service.getUuid()), service.isAdvertisePreferred());
575
576            List<BluetoothGattService> includedServices = service.getIncludedServices();
577            for (BluetoothGattService includedService : includedServices) {
578                mService.addIncludedService(mServerIf,
579                    includedService.getType(),
580                    includedService.getInstanceId(),
581                    new ParcelUuid(includedService.getUuid()));
582            }
583
584            List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
585            for (BluetoothGattCharacteristic characteristic : characteristics) {
586                int permission = ((characteristic.getKeySize() - 7) << 12)
587                                    + characteristic.getPermissions();
588                mService.addCharacteristic(mServerIf,
589                    new ParcelUuid(characteristic.getUuid()),
590                    characteristic.getProperties(), permission);
591
592                List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
593                for (BluetoothGattDescriptor descriptor: descriptors) {
594                    permission = ((characteristic.getKeySize() - 7) << 12)
595                                        + descriptor.getPermissions();
596                    mService.addDescriptor(mServerIf,
597                        new ParcelUuid(descriptor.getUuid()), permission);
598                }
599            }
600
601            mService.endServiceDeclaration(mServerIf);
602        } catch (RemoteException e) {
603            Log.e(TAG,"",e);
604            return false;
605        }
606
607        return true;
608    }
609
610    /**
611     * Removes a service from the list of services to be provided.
612     *
613     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
614     *
615     * @param service Service to be removed.
616     * @return true, if the service has been removed
617     */
618    public boolean removeService(BluetoothGattService service) {
619        if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
620        if (mService == null || mServerIf == 0) return false;
621
622        BluetoothGattService intService = getService(service.getUuid(),
623                                service.getInstanceId(), service.getType());
624        if (intService == null) return false;
625
626        try {
627            mService.removeService(mServerIf, service.getType(),
628                service.getInstanceId(), new ParcelUuid(service.getUuid()));
629            mServices.remove(intService);
630        } catch (RemoteException e) {
631            Log.e(TAG,"",e);
632            return false;
633        }
634
635        return true;
636    }
637
638    /**
639     * Remove all services from the list of provided services.
640     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
641     */
642    public void clearServices() {
643        if (DBG) Log.d(TAG, "clearServices()");
644        if (mService == null || mServerIf == 0) return;
645
646        try {
647            mService.clearServices(mServerIf);
648            mServices.clear();
649        } catch (RemoteException e) {
650            Log.e(TAG,"",e);
651        }
652    }
653
654    /**
655     * Returns a list of GATT services offered by this device.
656     *
657     * <p>An application must call {@link #addService} to add a serice to the
658     * list of services offered by this device.
659     *
660     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
661     *
662     * @return List of services. Returns an empty list
663     *         if no services have been added yet.
664     */
665    public List<BluetoothGattService> getServices() {
666        return mServices;
667    }
668
669    /**
670     * Returns a {@link BluetoothGattService} from the list of services offered
671     * by this device.
672     *
673     * <p>If multiple instances of the same service (as identified by UUID)
674     * exist, the first instance of the service is returned.
675     *
676     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
677     *
678     * @param uuid UUID of the requested service
679     * @return BluetoothGattService if supported, or null if the requested
680     *         service is not offered by this device.
681     */
682    public BluetoothGattService getService(UUID uuid) {
683        for (BluetoothGattService service : mServices) {
684            if (service.getUuid().equals(uuid)) {
685                return service;
686            }
687        }
688
689        return null;
690    }
691
692
693    /**
694     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
695     * with {@link BluetoothProfile#GATT} as argument
696     *
697     * @throws UnsupportedOperationException
698     */
699    @Override
700    public int getConnectionState(BluetoothDevice device) {
701        throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
702    }
703
704    /**
705     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
706     * with {@link BluetoothProfile#GATT} as argument
707     *
708     * @throws UnsupportedOperationException
709     */
710    @Override
711    public List<BluetoothDevice> getConnectedDevices() {
712        throw new UnsupportedOperationException
713            ("Use BluetoothManager#getConnectedDevices instead.");
714    }
715
716    /**
717     * Not supported - please use
718     * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
719     * with {@link BluetoothProfile#GATT} as first argument
720     *
721     * @throws UnsupportedOperationException
722     */
723    @Override
724    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
725        throw new UnsupportedOperationException
726            ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
727    }
728}
729