1 /*
2  * Copyright (C) 2016 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 
17 package com.android.cts.verifier.bluetooth;
18 
19 import android.app.Service;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothGatt;
22 import android.bluetooth.BluetoothGattCharacteristic;
23 import android.bluetooth.BluetoothGattDescriptor;
24 import android.bluetooth.BluetoothGattServer;
25 import android.bluetooth.BluetoothGattServerCallback;
26 import android.bluetooth.BluetoothGattService;
27 import android.bluetooth.BluetoothManager;
28 import android.bluetooth.BluetoothProfile;
29 import android.bluetooth.le.AdvertiseCallback;
30 import android.bluetooth.le.AdvertiseData;
31 import android.bluetooth.le.AdvertiseSettings;
32 import android.bluetooth.le.BluetoothLeAdvertiser;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.ParcelUuid;
38 import android.util.Log;
39 import android.widget.Toast;
40 
41 import java.util.Timer;
42 import java.util.TimerTask;
43 import java.util.UUID;
44 
45 public class BleConnectionPriorityServerService extends Service {
46     public static final boolean DEBUG = true;
47     public static final String TAG = "BlePriorityServer";
48     private static final String RESET_COUNT_VALUE = "RESET";
49     private static final String START_VALUE = "START";
50     private static final String STOP_VALUE = "STOP";
51     public static final String CONNECTION_PRIORITY_HIGH = "PR_H";
52     public static final String CONNECTION_PRIORITY_BALANCED = "PR_B";
53     public static final String CONNECTION_PRIORITY_LOW_POWER = "PR_L";
54 
55     public static final String ACTION_BLUETOOTH_DISABLED =
56             "com.android.cts.verifier.bluetooth.action.BLUETOOTH_DISABLED";
57 
58     public static final String ACTION_CONNECTION_WRITE_REQUEST =
59             "com.android.cts.verifier.bluetooth.action.CONNECTION_WRITE_REQUEST";
60     public static final String EXTRA_REQUEST_COUNT =
61             "com.android.cts.verifier.bluetooth.intent.EXTRA_REQUEST_COUNT";
62     public static final String ACTION_FINICH_CONNECTION_PRIORITY_HIGHT =
63             "com.android.cts.verifier.bluetooth.action.ACTION_FINICH_CONNECTION_PRIORITY_HIGHT";
64     public static final String ACTION_FINICH_CONNECTION_PRIORITY_BALANCED =
65             "com.android.cts.verifier.bluetooth.action.ACTION_FINICH_CONNECTION_PRIORITY_BALANCED";
66     public static final String ACTION_FINICH_CONNECTION_PRIORITY_LOW =
67             "com.android.cts.verifier.bluetooth.action.ACTION_FINICH_CONNECTION_PRIORITY_LOW";
68 
69     public static final String ACTION_START_CONNECTION_PRIORITY_TEST =
70             "com.android.cts.verifier.bluetooth.action.ACTION_START_CONNECTION_PRIORITY_TEST";
71 
72     public static final String EXTRA_AVERAGE =
73             "com.android.cts.verifier.bluetooth.intent.EXTRA_AVERAGE";
74 
75     private static final UUID SERVICE_UUID =
76             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
77     private static final UUID CHARACTERISTIC_UUID =
78             UUID.fromString("00009998-0000-1000-8000-00805f9b34fb");
79     private static final UUID START_CHARACTERISTIC_UUID =
80             UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
81     private static final UUID STOP_CHARACTERISTIC_UUID =
82             UUID.fromString("00009995-0000-1000-8000-00805f9b34fb");
83     private static final UUID DESCRIPTOR_UUID =
84             UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
85     public static final UUID ADV_SERVICE_UUID=
86             UUID.fromString("00002222-0000-1000-8000-00805f9b34fb");
87 
88     private BluetoothManager mBluetoothManager;
89     private BluetoothGattServer mGattServer;
90     private BluetoothGattService mService;
91     private BluetoothDevice mDevice;
92     private Handler mHandler;
93     private BluetoothLeAdvertiser mAdvertiser;
94     private long mReceiveWriteCount;
95     private Timer mTimeoutTimer;
96     private TimerTask mTimeoutTimerTask;
97 
98     @Override
onCreate()99     public void onCreate() {
100         super.onCreate();
101 
102         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
103         mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser();
104         mGattServer = mBluetoothManager.openGattServer(this, mCallbacks);
105         mService = createService();
106         if ((mGattServer != null) && (mAdvertiser != null)) {
107             mGattServer.addService(mService);
108         }
109         mDevice = null;
110         mHandler = new Handler();
111 
112         if (!mBluetoothManager.getAdapter().isEnabled()) {
113             notifyBluetoothDisabled();
114         } else if (mGattServer == null) {
115             notifyOpenFail();
116         } else if (mAdvertiser == null) {
117             notifyAdvertiseUnsupported();
118         } else {
119             startAdvertise();
120         }
121     }
122 
123     @Override
onDestroy()124     public void onDestroy() {
125         super.onDestroy();
126 
127         cancelTimeoutTimer(false);
128 
129         if (mTimeoutTimer != null) {
130             mTimeoutTimer.cancel();
131             mTimeoutTimer = null;
132         }
133         mTimeoutTimerTask = null;
134 
135         stopAdvertise();
136         if (mGattServer == null) {
137             return;
138         }
139         if (mDevice != null) {
140             mGattServer.cancelConnection(mDevice);
141         }
142         mGattServer.clearServices();
143         mGattServer.close();
144     }
145 
146     @Override
onBind(Intent intent)147     public IBinder onBind(Intent intent) {
148         return null;
149     }
150 
151     @Override
onStartCommand(Intent intent, int flags, int startId)152     public int onStartCommand(Intent intent, int flags, int startId) {
153         return START_NOT_STICKY;
154     }
155 
notifyBluetoothDisabled()156     private void notifyBluetoothDisabled() {
157         if (DEBUG) {
158             Log.d(TAG, "notifyBluetoothDisabled");
159         }
160         Intent intent = new Intent(ACTION_BLUETOOTH_DISABLED);
161         sendBroadcast(intent);
162     }
163 
notifyTestStart()164     private void notifyTestStart() {
165         Intent intent = new Intent(BleConnectionPriorityServerService.ACTION_START_CONNECTION_PRIORITY_TEST);
166         sendBroadcast(intent);
167     }
168 
notifyOpenFail()169     private void notifyOpenFail() {
170         if (DEBUG) {
171             Log.d(TAG, "notifyOpenFail");
172         }
173         Intent intent = new Intent(BleServerService.BLE_OPEN_FAIL);
174         sendBroadcast(intent);
175     }
176 
notifyAdvertiseUnsupported()177     private void notifyAdvertiseUnsupported() {
178         if (DEBUG) {
179             Log.d(TAG, "notifyAdvertiseUnsupported");
180         }
181         Intent intent = new Intent(BleServerService.BLE_ADVERTISE_UNSUPPORTED);
182         sendBroadcast(intent);
183     }
184 
notifyConnected()185     private void notifyConnected() {
186         if (DEBUG) {
187             Log.d(TAG, "notifyConnected");
188         }
189     }
190 
notifyDisconnected()191     private void notifyDisconnected() {
192         if (DEBUG) {
193             Log.d(TAG, "notifyDisconnected");
194         }
195     }
196 
notifyServiceAdded()197     private void notifyServiceAdded() {
198         if (DEBUG) {
199             Log.d(TAG, "notifyServiceAdded");
200         }
201     }
202 
notifyCharacteristicWriteRequest()203     private void notifyCharacteristicWriteRequest() {
204         if (DEBUG) {
205             Log.d(TAG, "notifyCharacteristicWriteRequest");
206         }
207         Intent intent = new Intent(ACTION_CONNECTION_WRITE_REQUEST);
208         intent.putExtra(EXTRA_REQUEST_COUNT, String.valueOf(mReceiveWriteCount));
209         sendBroadcast(intent);
210     }
211 
showMessage(final String msg)212     private void showMessage(final String msg) {
213         mHandler.post(new Runnable() {
214             @Override
215             public void run() {
216                 Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
217             }
218         });
219     }
220 
cancelTimeoutTimer(boolean runTimeout)221     private synchronized void cancelTimeoutTimer(boolean runTimeout) {
222         if (mTimeoutTimerTask != null) {
223             mTimeoutTimer.cancel();
224             if (runTimeout) {
225                 mTimeoutTimerTask.run();
226             }
227             mTimeoutTimerTask = null;
228             mTimeoutTimer = null;
229         }
230     }
231 
createService()232     private BluetoothGattService createService() {
233         BluetoothGattService service =
234                 new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
235         // add characteristic to service
236         //   property: 0x0A (read, write)
237         //   permission: 0x11 (read, write)
238         BluetoothGattCharacteristic characteristic =
239                 new BluetoothGattCharacteristic(CHARACTERISTIC_UUID, 0x0A, 0x11);
240         BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0x11);
241         characteristic.addDescriptor(descriptor);
242         service.addCharacteristic(characteristic);
243         characteristic = new BluetoothGattCharacteristic(START_CHARACTERISTIC_UUID, 0x0A, 0x11);
244         characteristic.addDescriptor(descriptor);
245         service.addCharacteristic(characteristic);
246         characteristic = new BluetoothGattCharacteristic(STOP_CHARACTERISTIC_UUID, 0x0A, 0x11);
247         characteristic.addDescriptor(descriptor);
248         service.addCharacteristic(characteristic);
249 
250         return service;
251     }
252 
253     private final BluetoothGattServerCallback mCallbacks = new BluetoothGattServerCallback() {
254         @Override
255         public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
256             if (DEBUG) {
257                 Log.d(TAG, "onConnectionStateChange: newState=" + newState);
258             }
259             if (status == BluetoothGatt.GATT_SUCCESS) {
260                 if (newState == BluetoothProfile.STATE_CONNECTED) {
261                     mDevice = device;
262                     notifyConnected();
263                 } else if (status == BluetoothProfile.STATE_DISCONNECTED) {
264                     cancelTimeoutTimer(true);
265                     notifyDisconnected();
266                     mDevice = null;
267                 }
268             }
269         }
270 
271         @Override
272         public void onServiceAdded(int status, BluetoothGattService service) {
273             if (DEBUG) {
274                 Log.d(TAG, "onServiceAdded()");
275             }
276             if (status == BluetoothGatt.GATT_SUCCESS) {
277                 notifyServiceAdded();
278             }
279         }
280 
281         String mPriority = null;
282 
283         @Override
284         public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
285                                                  BluetoothGattCharacteristic characteristic,
286                                                  boolean preparedWrite, boolean responseNeeded,
287                                                  int offset, byte[] value) {
288             if (mGattServer == null) {
289                 if (DEBUG) {
290                     Log.d(TAG, "GattServer is null, return");
291                 }
292                 return;
293             }
294             if (DEBUG) {
295                 Log.d(TAG, "onCharacteristicWriteRequest: preparedWrite=" + preparedWrite);
296             }
297 
298             if (characteristic.getUuid().equals(START_CHARACTERISTIC_UUID)) {
299                 // time out if previous measurement is running
300                 cancelTimeoutTimer(true);
301 
302                 mPriority = new String(value);
303                 Log.d(TAG, "Start Count Up. Priority is " + mPriority);
304                 if (BleConnectionPriorityServerService.CONNECTION_PRIORITY_HIGH.equals(mPriority)) {
305                     notifyTestStart();
306                 }
307 
308                 // start timeout timer
309                 mTimeoutTimer = new Timer(getClass().getName() + "_TimeoutTimer");
310                 mTimeoutTimerTask = new TimerTask() {
311                     @Override
312                     public void run() {
313                         // measurement timed out
314                         mTimeoutTimerTask = null;
315                         mTimeoutTimer = null;
316                         mReceiveWriteCount = 0;
317                         notifyMeasurementFinished(mPriority, Long.MAX_VALUE);
318                     }
319                 };
320                 mTimeoutTimer.schedule(mTimeoutTimerTask, (BleConnectionPriorityClientService.DEFAULT_PERIOD * 2));
321 
322                 mReceiveWriteCount = 0;
323             } else if (characteristic.getUuid().equals(STOP_CHARACTERISTIC_UUID)) {
324                 boolean isRunning = (mTimeoutTimerTask != null);
325                 cancelTimeoutTimer(false);
326 
327                 String valeStr = new String(value);
328                 String priority = null;
329                 int writeCount = -1;
330                 int sep = valeStr.indexOf(",");
331                 if (sep > 0) {
332                     priority = valeStr.substring(0, sep);
333                     writeCount = Integer.valueOf(valeStr.substring(sep + 1));
334                 }
335 
336                 if ((mPriority != null) && isRunning) {
337                     if (mPriority.equals(priority)) {
338                         long averageTime = BleConnectionPriorityClientService.DEFAULT_PERIOD / mReceiveWriteCount;
339                         notifyMeasurementFinished(mPriority, averageTime);
340                         Log.d(TAG, "Received " + mReceiveWriteCount + " of " + writeCount + " messages");
341                     } else {
342                         Log.d(TAG, "Connection priority does not match");
343                         showMessage("Connection priority does not match");
344                     }
345                 } else {
346                     Log.d(TAG, "Not Start Count UP.");
347                 }
348                 mReceiveWriteCount = 0;
349             } else {
350                 if (mTimeoutTimerTask != null) {
351                     ++mReceiveWriteCount;
352                 }
353                 if (!preparedWrite) {
354                     characteristic.setValue(value);
355                 }
356             }
357 
358             if (responseNeeded) {
359                 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
360             }
361         }
362     };
363 
notifyMeasurementFinished(String priority, long averageTime)364     private void notifyMeasurementFinished(String priority, long averageTime) {
365         Intent intent = new Intent();
366         intent.putExtra(EXTRA_AVERAGE, averageTime);
367         switch (priority) {
368             case CONNECTION_PRIORITY_HIGH:
369                 intent.setAction(ACTION_FINICH_CONNECTION_PRIORITY_HIGHT);
370                 break;
371             case CONNECTION_PRIORITY_BALANCED:
372                 intent.setAction(ACTION_FINICH_CONNECTION_PRIORITY_BALANCED);
373                 break;
374             case CONNECTION_PRIORITY_LOW_POWER:
375                 intent.setAction(ACTION_FINICH_CONNECTION_PRIORITY_LOW);
376                 break;
377         }
378         sendBroadcast(intent);
379     }
380 
381     private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
382         @Override
383         public void onStartFailure(int errorCode) {
384             super.onStartFailure(errorCode);
385             if (errorCode == ADVERTISE_FAILED_FEATURE_UNSUPPORTED) {
386                 notifyAdvertiseUnsupported();
387             } else {
388                 notifyOpenFail();
389             }
390         }
391     };
392 
startAdvertise()393     private void startAdvertise() {
394         if (DEBUG) {
395             Log.d(TAG, "startAdvertise");
396         }
397         AdvertiseData data = new AdvertiseData.Builder()
398                 .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1, 2, 3})
399                 .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
400                 .build();
401         AdvertiseSettings setting = new AdvertiseSettings.Builder()
402                 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
403                 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
404                 .setConnectable(true)
405                 .build();
406         mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback);
407     }
408 
stopAdvertise()409     private void stopAdvertise() {
410         if (DEBUG) {
411             Log.d(TAG, "stopAdvertise");
412         }
413         if (mAdvertiser != null) {
414             mAdvertiser.stopAdvertising(mAdvertiseCallback);
415         }
416     }
417 
418 }
419