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     * Application should call this method as early as possible after it is done with
294     * this GATT server.
295     */
296    public void close() {
297        if (DBG) Log.d(TAG, "close()");
298        unregisterCallback();
299    }
300
301    /**
302     * Register an application callback to start using GattServer.
303     *
304     * <p>This is an asynchronous call. The callback is used to notify
305     * success or failure if the function returns true.
306     *
307     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
308     *
309     * @param callback GATT callback handler that will receive asynchronous
310     *                 callbacks.
311     * @return true, the callback will be called to notify success or failure,
312     *         false on immediate error
313     */
314    /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
315        if (DBG) Log.d(TAG, "registerCallback()");
316        if (mService == null) {
317            Log.e(TAG, "GATT service not available");
318            return false;
319        }
320        UUID uuid = UUID.randomUUID();
321        if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
322
323        synchronized(mServerIfLock) {
324            if (mCallback != null) {
325                Log.e(TAG, "App can register callback only once");
326                return false;
327            }
328
329            mCallback = callback;
330            try {
331                mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
332            } catch (RemoteException e) {
333                Log.e(TAG,"",e);
334                mCallback = null;
335                return false;
336            }
337
338            try {
339                mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
340            } catch (InterruptedException e) {
341                Log.e(TAG, "" + e);
342                mCallback = null;
343            }
344
345            if (mServerIf == 0) {
346                mCallback = null;
347                return false;
348            } else {
349                return true;
350            }
351        }
352    }
353
354    /**
355     * Unregister the current application and callbacks.
356     */
357    private void unregisterCallback() {
358        if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
359        if (mService == null || mServerIf == 0) return;
360
361        try {
362            mCallback = null;
363            mService.unregisterServer(mServerIf);
364            mServerIf = 0;
365        } catch (RemoteException e) {
366            Log.e(TAG,"",e);
367        }
368    }
369
370    /**
371     * Returns a service by UUID, instance and type.
372     * @hide
373     */
374    /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
375        for(BluetoothGattService svc : mServices) {
376            if (svc.getType() == type &&
377                svc.getInstanceId() == instanceId &&
378                svc.getUuid().equals(uuid)) {
379                return svc;
380            }
381        }
382        return null;
383    }
384
385    /**
386     * Initiate a connection to a Bluetooth GATT capable device.
387     *
388     * <p>The connection may not be established right away, but will be
389     * completed when the remote device is available. A
390     * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
391     * invoked when the connection state changes as a result of this function.
392     *
393     * <p>The autoConnect paramter determines whether to actively connect to
394     * the remote device, or rather passively scan and finalize the connection
395     * when the remote device is in range/available. Generally, the first ever
396     * connection to a device should be direct (autoConnect set to false) and
397     * subsequent connections to known devices should be invoked with the
398     * autoConnect parameter set to true.
399     *
400     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
401     *
402     * @param autoConnect Whether to directly connect to the remote device (false)
403     *                    or to automatically connect as soon as the remote
404     *                    device becomes available (true).
405     * @return true, if the connection attempt was initiated successfully
406     */
407    public boolean connect(BluetoothDevice device, boolean autoConnect) {
408        if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
409        if (mService == null || mServerIf == 0) return false;
410
411        try {
412            mService.serverConnect(mServerIf, device.getAddress(),
413                               autoConnect ? false : true); // autoConnect is inverse of "isDirect"
414        } catch (RemoteException e) {
415            Log.e(TAG,"",e);
416            return false;
417        }
418
419        return true;
420    }
421
422    /**
423     * Disconnects an established connection, or cancels a connection attempt
424     * currently in progress.
425     *
426     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
427     *
428     * @param device Remote device
429     */
430    public void cancelConnection(BluetoothDevice device) {
431        if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
432        if (mService == null || mServerIf == 0) return;
433
434        try {
435            mService.serverDisconnect(mServerIf, device.getAddress());
436        } catch (RemoteException e) {
437            Log.e(TAG,"",e);
438        }
439    }
440
441    /**
442     * Send a response to a read or write request to a remote device.
443     *
444     * <p>This function must be invoked in when a remote read/write request
445     * is received by one of these callback methods:
446     *
447     * <ul>
448     *      <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
449     *      <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
450     *      <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
451     *      <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
452     * </ul>
453     *
454     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
455     *
456     * @param device The remote device to send this response to
457     * @param requestId The ID of the request that was received with the callback
458     * @param status The status of the request to be sent to the remote devices
459     * @param offset Value offset for partial read/write response
460     * @param value The value of the attribute that was read/written (optional)
461     */
462    public boolean sendResponse(BluetoothDevice device, int requestId,
463                                int status, int offset, byte[] value) {
464        if (DBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
465        if (mService == null || mServerIf == 0) return false;
466
467        try {
468            mService.sendResponse(mServerIf, device.getAddress(), requestId,
469                                  status, offset, value);
470        } catch (RemoteException e) {
471            Log.e(TAG,"",e);
472            return false;
473        }
474        return true;
475    }
476
477    /**
478     * Send a notification or indication that a local characteristic has been
479     * updated.
480     *
481     * <p>A notification or indication is sent to the remote device to signal
482     * that the characteristic has been updated. This function should be invoked
483     * for every client that requests notifications/indications by writing
484     * to the "Client Configuration" descriptor for the given characteristic.
485     *
486     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
487     *
488     * @param device The remote device to receive the notification/indication
489     * @param characteristic The local characteristic that has been updated
490     * @param confirm true to request confirmation from the client (indication),
491     *                false to send a notification
492     * @return true, if the notification has been triggered successfully
493     */
494    public boolean notifyCharacteristicChanged(BluetoothDevice device,
495                    BluetoothGattCharacteristic characteristic, boolean confirm) {
496        if (DBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
497        if (mService == null || mServerIf == 0) return false;
498
499        BluetoothGattService service = characteristic.getService();
500        if (service == null) return false;
501
502        try {
503            mService.sendNotification(mServerIf, device.getAddress(),
504                    service.getType(), service.getInstanceId(),
505                    new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
506                    new ParcelUuid(characteristic.getUuid()), confirm,
507                    characteristic.getValue());
508        } catch (RemoteException e) {
509            Log.e(TAG,"",e);
510            return false;
511        }
512
513        return true;
514    }
515
516    /**
517     * Add a service to the list of services to be hosted.
518     *
519     * <p>Once a service has been addded to the the list, the service and it's
520     * included characteristics will be provided by the local device.
521     *
522     * <p>If the local device has already exposed services when this function
523     * is called, a service update notification will be sent to all clients.
524     *
525     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
526     *
527     * @param service Service to be added to the list of services provided
528     *                by this device.
529     * @return true, if the service has been added successfully
530     */
531    public boolean addService(BluetoothGattService service) {
532        if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
533        if (mService == null || mServerIf == 0) return false;
534
535        mServices.add(service);
536
537        try {
538            mService.beginServiceDeclaration(mServerIf, service.getType(),
539                service.getInstanceId(), service.getHandles(),
540                new ParcelUuid(service.getUuid()), service.isAdvertisePreferred());
541
542            List<BluetoothGattService> includedServices = service.getIncludedServices();
543            for (BluetoothGattService includedService : includedServices) {
544                mService.addIncludedService(mServerIf,
545                    includedService.getType(),
546                    includedService.getInstanceId(),
547                    new ParcelUuid(includedService.getUuid()));
548            }
549
550            List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
551            for (BluetoothGattCharacteristic characteristic : characteristics) {
552                int permission = ((characteristic.getKeySize() - 7) << 12)
553                                    + characteristic.getPermissions();
554                mService.addCharacteristic(mServerIf,
555                    new ParcelUuid(characteristic.getUuid()),
556                    characteristic.getProperties(), permission);
557
558                List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
559                for (BluetoothGattDescriptor descriptor: descriptors) {
560                    permission = ((characteristic.getKeySize() - 7) << 12)
561                                        + descriptor.getPermissions();
562                    mService.addDescriptor(mServerIf,
563                        new ParcelUuid(descriptor.getUuid()), permission);
564                }
565            }
566
567            mService.endServiceDeclaration(mServerIf);
568        } catch (RemoteException e) {
569            Log.e(TAG,"",e);
570            return false;
571        }
572
573        return true;
574    }
575
576    /**
577     * Removes a service from the list of services to be provided.
578     *
579     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
580     *
581     * @param service Service to be removed.
582     * @return true, if the service has been removed
583     */
584    public boolean removeService(BluetoothGattService service) {
585        if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
586        if (mService == null || mServerIf == 0) return false;
587
588        BluetoothGattService intService = getService(service.getUuid(),
589                                service.getInstanceId(), service.getType());
590        if (intService == null) return false;
591
592        try {
593            mService.removeService(mServerIf, service.getType(),
594                service.getInstanceId(), new ParcelUuid(service.getUuid()));
595            mServices.remove(intService);
596        } catch (RemoteException e) {
597            Log.e(TAG,"",e);
598            return false;
599        }
600
601        return true;
602    }
603
604    /**
605     * Remove all services from the list of provided services.
606     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
607     */
608    public void clearServices() {
609        if (DBG) Log.d(TAG, "clearServices()");
610        if (mService == null || mServerIf == 0) return;
611
612        try {
613            mService.clearServices(mServerIf);
614            mServices.clear();
615        } catch (RemoteException e) {
616            Log.e(TAG,"",e);
617        }
618    }
619
620    /**
621     * Returns a list of GATT services offered by this device.
622     *
623     * <p>An application must call {@link #addService} to add a serice to the
624     * list of services offered by this device.
625     *
626     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
627     *
628     * @return List of services. Returns an empty list
629     *         if no services have been added yet.
630     */
631    public List<BluetoothGattService> getServices() {
632        return mServices;
633    }
634
635    /**
636     * Returns a {@link BluetoothGattService} from the list of services offered
637     * by this device.
638     *
639     * <p>If multiple instances of the same service (as identified by UUID)
640     * exist, the first instance of the service is returned.
641     *
642     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
643     *
644     * @param uuid UUID of the requested service
645     * @return BluetoothGattService if supported, or null if the requested
646     *         service is not offered by this device.
647     */
648    public BluetoothGattService getService(UUID uuid) {
649        for (BluetoothGattService service : mServices) {
650            if (service.getUuid().equals(uuid)) {
651                return service;
652            }
653        }
654
655        return null;
656    }
657
658
659    /**
660     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
661     * with {@link BluetoothProfile#GATT} as argument
662     *
663     * @throws UnsupportedOperationException
664     */
665    @Override
666    public int getConnectionState(BluetoothDevice device) {
667        throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
668    }
669
670    /**
671     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
672     * with {@link BluetoothProfile#GATT} as argument
673     *
674     * @throws UnsupportedOperationException
675     */
676    @Override
677    public List<BluetoothDevice> getConnectedDevices() {
678        throw new UnsupportedOperationException
679            ("Use BluetoothManager#getConnectedDevices instead.");
680    }
681
682    /**
683     * Not supported - please use
684     * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
685     * with {@link BluetoothProfile#GATT} as first argument
686     *
687     * @throws UnsupportedOperationException
688     */
689    @Override
690    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
691        throw new UnsupportedOperationException
692            ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
693    }
694}
695