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