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 com.android.cts.verifier.bluetooth;
18
19import java.util.Date;
20import java.util.List;
21import java.util.Timer;
22import java.util.TimerTask;
23import java.util.UUID;
24
25import android.app.Service;
26import android.bluetooth.BluetoothAdapter;
27import android.bluetooth.BluetoothDevice;
28import android.bluetooth.BluetoothGatt;
29import android.bluetooth.BluetoothGattCharacteristic;
30import android.bluetooth.BluetoothGattDescriptor;
31import android.bluetooth.BluetoothGattServer;
32import android.bluetooth.BluetoothGattServerCallback;
33import android.bluetooth.BluetoothGattService;
34import android.bluetooth.BluetoothManager;
35import android.bluetooth.BluetoothProfile;
36import android.bluetooth.le.AdvertiseCallback;
37import android.bluetooth.le.AdvertiseData;
38import android.bluetooth.le.AdvertiseSettings;
39import android.bluetooth.le.BluetoothLeAdvertiser;
40import android.content.Context;
41import android.content.Intent;
42import android.os.Handler;
43import android.os.IBinder;
44import android.os.ParcelUuid;
45import android.util.Log;
46import android.widget.Toast;
47
48public class BleServerService extends Service {
49
50    public static final boolean DEBUG = true;
51    public static final String TAG = "BleServerService";
52
53    public static final int COMMAND_ADD_SERVICE = 0;
54    public static final int COMMAND_WRITE_CHARACTERISTIC = 1;
55    public static final int COMMAND_WRITE_DESCRIPTOR = 2;
56
57    public static final String BLE_SERVER_CONNECTED =
58            "com.android.cts.verifier.bluetooth.BLE_SERVER_CONNECTED";
59    public static final String BLE_SERVER_DISCONNECTED =
60            "com.android.cts.verifier.bluetooth.BLE_SERVER_DISCONNECTED";
61    public static final String BLE_SERVICE_ADDED =
62            "com.android.cts.verifier.bluetooth.BLE_SERVICE_ADDED";
63    public static final String BLE_CHARACTERISTIC_READ_REQUEST =
64            "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_REQUEST";
65    public static final String BLE_CHARACTERISTIC_WRITE_REQUEST =
66            "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_WRITE_REQUEST";
67    public static final String BLE_DESCRIPTOR_READ_REQUEST =
68            "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_READ_REQUEST";
69    public static final String BLE_DESCRIPTOR_WRITE_REQUEST =
70            "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_WRITE_REQUEST";
71    public static final String BLE_EXECUTE_WRITE =
72            "com.android.cts.verifier.bluetooth.BLE_EXECUTE_WRITE";
73    public static final String BLE_OPEN_FAIL =
74            "com.android.cts.verifier.bluetooth.BLE_OPEN_FAIL";
75
76    private static final UUID SERVICE_UUID =
77            UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
78    private static final UUID CHARACTERISTIC_UUID =
79            UUID.fromString("00009998-0000-1000-8000-00805f9b34fb");
80    private static final UUID UPDATE_CHARACTERISTIC_UUID =
81            UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
82    private static final UUID DESCRIPTOR_UUID =
83            UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
84    public static final UUID ADV_SERVICE_UUID=
85            UUID.fromString("00003333-0000-1000-8000-00805f9b34fb");
86
87    private BluetoothManager mBluetoothManager;
88    private BluetoothGattServer mGattServer;
89    private BluetoothGattService mService;
90    private BluetoothDevice mDevice;
91    private Timer mNotificationTimer;
92    private Handler mHandler;
93    private String mReliableWriteValue;
94    private BluetoothLeAdvertiser mAdvertiser;
95
96    @Override
97    public void onCreate() {
98        super.onCreate();
99
100        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
101        mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser();
102        mGattServer = mBluetoothManager.openGattServer(this, mCallbacks);
103        mService = createService();
104        if (mGattServer != null) {
105            mGattServer.addService(mService);
106        }
107        mDevice = null;
108        mReliableWriteValue = null;
109
110        mHandler = new Handler();
111        if (mGattServer == null) {
112            notifyOpenFail();
113        }
114    }
115
116    @Override
117    public int onStartCommand(Intent intent, int flags, int startId) {
118        startAdvertise();
119        return START_NOT_STICKY;
120    }
121
122    @Override
123    public IBinder onBind(Intent intent) {
124        return null;
125    }
126
127    @Override
128    public void onDestroy() {
129        super.onDestroy();
130        stopAdvertise();
131        if (mGattServer == null) {
132           return;
133        }
134        if (mDevice != null) mGattServer.cancelConnection(mDevice);
135        mGattServer.close();
136    }
137
138    private void writeCharacteristic(String writeValue) {
139        BluetoothGattCharacteristic characteristic = getCharacteristic(CHARACTERISTIC_UUID);
140        if (characteristic != null) return;
141        characteristic.setValue(writeValue);
142    }
143
144    private void writeDescriptor(String writeValue) {
145        BluetoothGattDescriptor descriptor = getDescriptor();
146        if (descriptor == null) return;
147        descriptor.setValue(writeValue.getBytes());
148    }
149
150    private void notifyOpenFail() {
151        if (DEBUG) Log.d(TAG, "notifyOpenFail");
152        Intent intent = new Intent(BLE_OPEN_FAIL);
153        sendBroadcast(intent);
154    }
155
156    private void notifyConnected() {
157        if (DEBUG) Log.d(TAG, "notifyConnected");
158        Intent intent = new Intent(BLE_SERVER_CONNECTED);
159        sendBroadcast(intent);
160    }
161
162    private void notifyDisconnected() {
163        if (DEBUG) Log.d(TAG, "notifyDisconnected");
164        Intent intent = new Intent(BLE_SERVER_DISCONNECTED);
165        sendBroadcast(intent);
166    }
167
168    private void notifyServiceAdded() {
169        if (DEBUG) Log.d(TAG, "notifyServiceAdded");
170        Intent intent = new Intent(BLE_SERVICE_ADDED);
171        sendBroadcast(intent);
172    }
173
174    private void notifyCharacteristicReadRequest() {
175        if (DEBUG) Log.d(TAG, "notifyCharacteristicReadRequest");
176        Intent intent = new Intent(BLE_CHARACTERISTIC_READ_REQUEST);
177        sendBroadcast(intent);
178    }
179
180    private void notifyCharacteristicWriteRequest() {
181        if (DEBUG) Log.d(TAG, "notifyCharacteristicWriteRequest");
182        Intent intent = new Intent(BLE_CHARACTERISTIC_WRITE_REQUEST);
183        sendBroadcast(intent);
184    }
185
186    private void notifyDescriptorReadRequest() {
187        if (DEBUG) Log.d(TAG, "notifyDescriptorReadRequest");
188        Intent intent = new Intent(BLE_DESCRIPTOR_READ_REQUEST);
189        sendBroadcast(intent);
190    }
191
192    private void notifyDescriptorWriteRequest() {
193        if (DEBUG) Log.d(TAG, "notifyDescriptorWriteRequest");
194        Intent intent = new Intent(BLE_DESCRIPTOR_WRITE_REQUEST);
195        sendBroadcast(intent);
196    }
197
198    private void notifyExecuteWrite() {
199        if (DEBUG) Log.d(TAG, "notifyExecuteWrite");
200        Intent intent = new Intent(BLE_EXECUTE_WRITE);
201        sendBroadcast(intent);
202    }
203
204    private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
205        BluetoothGattCharacteristic characteristic =
206                mService.getCharacteristic(uuid);
207        if (characteristic == null) {
208            showMessage("Characteristic not found");
209            return null;
210        }
211        return characteristic;
212    }
213
214    private BluetoothGattDescriptor getDescriptor() {
215        BluetoothGattCharacteristic characteristic = getCharacteristic(CHARACTERISTIC_UUID);
216        if (characteristic == null) return null;
217
218        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(DESCRIPTOR_UUID);
219        if (descriptor == null) {
220            showMessage("Descriptor not found");
221            return null;
222        }
223        return descriptor;
224    }
225
226    private BluetoothGattService createService() {
227        BluetoothGattService service =
228                new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
229        BluetoothGattCharacteristic characteristic =
230                new BluetoothGattCharacteristic(CHARACTERISTIC_UUID, 0x0A, 0x11);
231        BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0x11);
232        characteristic.addDescriptor(descriptor);
233        service.addCharacteristic(characteristic);
234
235        BluetoothGattCharacteristic notiCharacteristic =
236                new BluetoothGattCharacteristic(UPDATE_CHARACTERISTIC_UUID, 0x10, 0x00);
237        service.addCharacteristic(notiCharacteristic);
238
239        return service;
240    }
241
242    private void beginNotification() {
243        TimerTask task = new TimerTask() {
244            @Override
245            public void run() {
246                if (mGattServer == null) {
247                    if (DEBUG) Log.d(TAG, "GattServer is null, return");
248                    return;
249                }
250                BluetoothGattCharacteristic characteristic =
251                        mService.getCharacteristic(UPDATE_CHARACTERISTIC_UUID);
252                if (characteristic == null) return;
253
254                String date = (new Date()).toString();
255                characteristic.setValue(date);
256                mGattServer.notifyCharacteristicChanged(mDevice, characteristic, false);
257            }
258        };
259        mNotificationTimer = new Timer();
260        mNotificationTimer.schedule(task, 0, 1000);
261    }
262
263    private void stopNotification() {
264        if (mNotificationTimer == null) return;
265        mNotificationTimer.cancel();
266        mNotificationTimer = null;
267    }
268
269    private void showMessage(final String msg) {
270        mHandler.post(new Runnable() {
271            public void run() {
272                Toast.makeText(BleServerService.this, msg, Toast.LENGTH_SHORT).show();
273            }
274        });
275    }
276
277    private final BluetoothGattServerCallback mCallbacks = new BluetoothGattServerCallback() {
278        @Override
279        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
280            if (DEBUG) Log.d(TAG, "onConnectionStateChange: newState=" + newState);
281            if (status == BluetoothGatt.GATT_SUCCESS) {
282                if (newState == BluetoothProfile.STATE_CONNECTED) {
283                    mDevice = device;
284                    notifyConnected();
285                    beginNotification();
286                } else if (status == BluetoothProfile.STATE_DISCONNECTED) {
287                    stopNotification();
288                    notifyDisconnected();
289                    mDevice = null;
290                }
291            }
292        }
293
294        @Override
295        public void onServiceAdded(int status, BluetoothGattService service) {
296            if (DEBUG) Log.d(TAG, "onServiceAdded()");
297            if (status == BluetoothGatt.GATT_SUCCESS) notifyServiceAdded();
298        }
299
300        @Override
301        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
302                int offset, BluetoothGattCharacteristic characteristic) {
303            if (mGattServer == null) {
304                if (DEBUG) Log.d(TAG, "GattServer is null, return");
305                return;
306            }
307            if (DEBUG) Log.d(TAG, "onCharacteristicReadRequest()");
308
309            notifyCharacteristicReadRequest();
310            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0,
311                                     characteristic.getValue());
312        }
313
314        @Override
315        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
316                BluetoothGattCharacteristic characteristic,
317                boolean preparedWrite, boolean responseNeeded,
318                int offset, byte[] value) {
319            if (mGattServer == null) {
320                if (DEBUG) Log.d(TAG, "GattServer is null, return");
321                return;
322            }
323            if (DEBUG) Log.d(TAG, "onCharacteristicWriteRequest: preparedWrite=" + preparedWrite);
324
325            notifyCharacteristicWriteRequest();
326            if (preparedWrite) mReliableWriteValue = new String(value);
327            else characteristic.setValue(value);
328
329            if (responseNeeded)
330                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
331        }
332
333        @Override
334        public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
335                int offset, BluetoothGattDescriptor descriptor) {
336            if (mGattServer == null) {
337                if (DEBUG) Log.d(TAG, "GattServer is null, return");
338                return;
339            }
340            if (DEBUG) Log.d(TAG, "onDescriptorReadRequest(): (descriptor == getDescriptor())="
341                                  + (descriptor == getDescriptor()));
342
343            notifyDescriptorReadRequest();
344            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0,
345                                     descriptor.getValue());
346        }
347
348        @Override
349        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
350                BluetoothGattDescriptor descriptor,
351                boolean preparedWrite, boolean responseNeeded,
352                int offset,  byte[] value) {
353            if (mGattServer == null) {
354                if (DEBUG) Log.d(TAG, "GattServer is null, return");
355                return;
356            }
357            if (DEBUG) Log.d(TAG, "onDescriptorWriteRequest(): (descriptor == getDescriptor())="
358                                  + (descriptor == getDescriptor()));
359
360            notifyDescriptorWriteRequest();
361            descriptor.setValue(value);
362            if (responseNeeded)
363                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
364        }
365
366        @Override
367        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
368            if (mGattServer == null) {
369                if (DEBUG) Log.d(TAG, "GattServer is null, return");
370                return;
371            }
372            if (DEBUG) Log.d(TAG, "onExecuteWrite");
373            if (execute) {
374                notifyExecuteWrite();
375                getCharacteristic(CHARACTERISTIC_UUID).setValue(mReliableWriteValue);
376                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
377            }
378        }
379    };
380
381    private void startAdvertise() {
382        if (DEBUG) Log.d(TAG, "startAdvertise");
383        AdvertiseData data = new AdvertiseData.Builder()
384            .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1,2,3})
385            .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
386            .build();
387        AdvertiseSettings setting = new AdvertiseSettings.Builder()
388            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
389            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
390            .setConnectable(true)
391            .build();
392        mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback);
393    }
394
395    private void stopAdvertise() {
396        if (DEBUG) Log.d(TAG, "stopAdvertise");
397        mAdvertiser.stopAdvertising(mAdvertiseCallback);
398    }
399
400    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){};
401}
402
403