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