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.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothGatt;
23 import android.bluetooth.BluetoothGattCallback;
24 import android.bluetooth.BluetoothGattCharacteristic;
25 import android.bluetooth.BluetoothGattService;
26 import android.bluetooth.BluetoothManager;
27 import android.bluetooth.BluetoothProfile;
28 import android.bluetooth.le.BluetoothLeScanner;
29 import android.bluetooth.le.ScanCallback;
30 import android.bluetooth.le.ScanFilter;
31 import android.bluetooth.le.ScanResult;
32 import android.bluetooth.le.ScanSettings;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.os.DeadObjectException;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.ParcelUuid;
39 import android.util.Log;
40 import android.widget.Toast;
41 
42 import java.util.Arrays;
43 import java.util.Date;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.Timer;
47 import java.util.TimerTask;
48 import java.util.UUID;
49 
50 public class BleConnectionPriorityClientService extends Service {
51     public static final boolean DEBUG = true;
52     public static final String TAG = "BlePriorityClient";
53 
54     public static final String ACTION_BLUETOOTH_DISABLED =
55             "com.android.cts.verifier.bluetooth.action.BLUETOOTH_DISABLED";
56 
57     public static final String ACTION_CONNECTION_SERVICES_DISCOVERED =
58             "com.android.cts.verifier.bluetooth.action.CONNECTION_SERVICES_DISCOVERED";
59 
60     public static final String ACTION_BLUETOOTH_MISMATCH_SECURE =
61             "com.android.cts.verifier.bluetooth.action.ACTION_BLUETOOTH_MISMATCH_SECURE";
62     public static final String ACTION_BLUETOOTH_MISMATCH_INSECURE =
63             "com.android.cts.verifier.bluetooth.action.ACTION_BLUETOOTH_MISMATCH_INSECURE";
64 
65     public static final String ACTION_CONNECTION_PRIORITY_START =
66             "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_LOW_POWER";
67 
68     public static final String ACTION_CONNECTION_PRIORITY_FINISH =
69             "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_FINISH";
70 
71     public static final String ACTION_CLIENT_CONNECT_SECURE =
72             "com.android.cts.verifier.bluetooth.action.CLIENT_CONNECT_SECURE";
73 
74     public static final String ACTION_DISCONNECT =
75             "com.android.cts.verifier.bluetooth.action.DISCONNECT";
76     public static final String ACTION_FINISH_DISCONNECT =
77             "com.android.cts.verifier.bluetooth.action.FINISH_DISCONNECT";
78 
79     public static final int NOT_UNDER_TEST = -1;
80 
81     private BluetoothManager mBluetoothManager;
82     private BluetoothAdapter mBluetoothAdapter;
83     private BluetoothGatt mBluetoothGatt;
84     private BluetoothLeScanner mScanner;
85     private Handler mHandler;
86     private Timer mConnectionTimer;
87     private Context mContext;
88 
89     private String mAction;
90     private boolean mSecure;
91 
92     private int mPriority = NOT_UNDER_TEST;
93     private int interval_low = 0;
94     private int interval_balanced = 0;
95     private int interval_high = 0;
96 
97     private TestTaskQueue mTaskQueue;
98 
99     @Override
onCreate()100     public void onCreate() {
101         super.onCreate();
102 
103         mTaskQueue = new TestTaskQueue(getClass().getName() + "__taskHandlerThread");
104 
105         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
106         mBluetoothAdapter = mBluetoothManager.getAdapter();
107         mScanner = mBluetoothAdapter.getBluetoothLeScanner();
108         mHandler = new Handler();
109         mContext = this;
110 
111         startScan();
112     }
113 
114     @Override
onDestroy()115     public void onDestroy() {
116         super.onDestroy();
117         if (mBluetoothGatt != null) {
118             try {
119                 mBluetoothGatt.disconnect();
120                 mBluetoothGatt.close();
121             } catch (Exception e) {}
122             finally {
123                 mBluetoothGatt = null;
124             }
125         }
126         stopScan();
127 
128         mTaskQueue.quit();
129     }
130 
131     @Override
onBind(Intent intent)132     public IBinder onBind(Intent intent) {
133         return null;
134     }
135 
notifyBluetoothDisabled()136     private void notifyBluetoothDisabled() {
137         Intent intent = new Intent(ACTION_BLUETOOTH_DISABLED);
138         sendBroadcast(intent);
139     }
140 
141     @Override
onStartCommand(Intent intent, int flags, int startId)142     public int onStartCommand(Intent intent, int flags, int startId) {
143         if (intent != null) {
144             mAction = intent.getAction();
145             if (mAction != null) {
146                 switch (mAction) {
147                 case ACTION_CLIENT_CONNECT_SECURE:
148                     mSecure = true;
149                     break;
150                 case ACTION_CONNECTION_PRIORITY_START:
151                     myRequestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
152                     break;
153                 case ACTION_DISCONNECT:
154                     if (mBluetoothGatt != null) {
155                         mBluetoothGatt.disconnect();
156                     } else {
157                         notifyDisconnect();
158                     }
159                     break;
160                 }
161             }
162         }
163         return START_NOT_STICKY;
164     }
165 
myRequestConnectionPriority(final int priority)166     private void myRequestConnectionPriority(final int priority) {
167         mTaskQueue.addTask(new Runnable() {
168                                 @Override
169                                 public void run() {
170                                     mPriority = priority;
171                                     mBluetoothGatt.requestConnectionPriority(mPriority);
172                                     //continue in onConnectionUpdated() callback
173                                 }
174                             });
175     }
176 
177 
sleep(int millis)178     private void sleep(int millis) {
179         try {
180             Thread.sleep(millis);
181         } catch (InterruptedException e) {
182             Log.e(TAG, "Error in thread sleep", e);
183         }
184     }
185 
showMessage(final String msg)186     private void showMessage(final String msg) {
187         mHandler.post(new Runnable() {
188             public void run() {
189                 Toast.makeText(BleConnectionPriorityClientService.this, msg, Toast.LENGTH_SHORT).show();
190             }
191         });
192     }
193 
194     private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
195         @Override
196         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
197             if (DEBUG) Log.d(TAG, "onConnectionStateChange");
198             if (status == BluetoothGatt.GATT_SUCCESS) {
199                 if (newState == BluetoothProfile.STATE_CONNECTED) {
200                     int bond = gatt.getDevice().getBondState();
201                     boolean bonded = false;
202                     BluetoothDevice target = gatt.getDevice();
203                     Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
204                     if (pairedDevices.size() > 0) {
205                         for (BluetoothDevice device : pairedDevices) {
206                             if (device.getAddress().equals(target.getAddress())) {
207                                 bonded = true;
208                                 break;
209                             }
210                         }
211                     }
212                     if (mSecure && ((bond == BluetoothDevice.BOND_NONE) || !bonded)) {
213                         // not pairing and execute Secure Test
214                         mBluetoothGatt.disconnect();
215                         notifyMismatchSecure();
216                     } else if (!mSecure && ((bond != BluetoothDevice.BOND_NONE) || bonded)) {
217                         // already pairing nad execute Insecure Test
218                         mBluetoothGatt.disconnect();
219                         notifyMismatchInsecure();
220                     } else {
221                         showMessage("Bluetooth LE connected");
222                         mBluetoothGatt.discoverServices();
223                     }
224                 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
225                     showMessage("Bluetooth LE disconnected");
226 
227                     notifyDisconnect();
228                 }
229             } else {
230                 showMessage("Failed to connect");
231                 mBluetoothGatt.close();
232                 mBluetoothGatt = null;
233             }
234         }
235 
236         @Override
237         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
238             if (DEBUG){
239                 Log.d(TAG, "onServiceDiscovered");
240             }
241             if (status == BluetoothGatt.GATT_SUCCESS) {
242                 showMessage("Service discovered");
243                 Intent intent = new Intent(ACTION_CONNECTION_SERVICES_DISCOVERED);
244                 sendBroadcast(intent);
245             }
246 
247             //onConnectionUpdated is hidden callback, can't be marked as @Override.
248             // We must have a call to it, otherwise compiler will delete it during optimization.
249             if (status == 0xFFEFFEE) {
250                 // This should never execute, but will make compiler not remove onConnectionUpdated
251                 onConnectionUpdated(null, 0, 0, 0, 0);
252                 throw new IllegalStateException("This should never happen!");
253             }
254         }
255 
256         // @Override uncomment once this becomes public API
257         public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
258             int status) {
259             if (mPriority == NOT_UNDER_TEST) return;
260 
261             if (status != 0) {
262                 showMessage("onConnectionUpdated() error, status=" + status );
263                 Log.e(TAG, "onConnectionUpdated() status=" + status);
264                 return;
265             }
266 
267             Log.i(TAG, "onConnectionUpdated() status=" + status + ", interval=" + interval);
268             if (mPriority == BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER) {
269                 interval_low = interval;
270                 myRequestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
271             } else if (mPriority == BluetoothGatt.CONNECTION_PRIORITY_BALANCED) {
272                 interval_balanced = interval;
273                 myRequestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
274             } else if (mPriority == BluetoothGatt.CONNECTION_PRIORITY_HIGH) {
275                 interval_high = interval;
276 
277                 if (interval_low < interval_balanced || interval_balanced < interval_high) {
278                    showMessage("interval value should be descending - failure!");
279                    Log.e(TAG, "interval values should be descending: interval_low=" + interval_low +
280                             ", interval_balanced=" + interval_balanced + ", interval_high=" + interval_high);
281                    return;
282                 }
283 
284                 showMessage("intervals: " + interval_low +" > " + interval_balanced + " > " + interval_high);
285 
286                 Intent intent = new Intent();
287                 intent.setAction(ACTION_CONNECTION_PRIORITY_FINISH);
288                 sendBroadcast(intent);
289 
290                 mPriority = NOT_UNDER_TEST;
291             }
292         }
293     };
294 
295     private final ScanCallback mScanCallback = new ScanCallback() {
296         @Override
297         public void onScanResult(int callbackType, ScanResult result) {
298             if (mBluetoothGatt == null) {
299                 stopScan();
300                 mBluetoothGatt = BleClientService.connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks);
301             }
302         }
303     };
304 
startScan()305     private void startScan() {
306         if (DEBUG) {
307             Log.d(TAG, "startScan");
308         }
309         if (!mBluetoothAdapter.isEnabled()) {
310             notifyBluetoothDisabled();
311         } else {
312             List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
313                     new ParcelUuid(BleConnectionPriorityServerService.ADV_SERVICE_UUID)).build());
314             ScanSettings setting = new ScanSettings.Builder()
315                     .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
316             mScanner.startScan(filter, setting, mScanCallback);
317         }
318     }
319 
stopScan()320     private void stopScan() {
321         if (DEBUG) {
322             Log.d(TAG, "stopScan");
323         }
324         if (mScanner != null) {
325             mScanner.stopScan(mScanCallback);
326         }
327     }
328 
notifyMismatchSecure()329     private void notifyMismatchSecure() {
330         Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_SECURE);
331         sendBroadcast(intent);
332     }
333 
notifyMismatchInsecure()334     private void notifyMismatchInsecure() {
335         Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_INSECURE);
336         sendBroadcast(intent);
337     }
338 
notifyDisconnect()339     private void notifyDisconnect() {
340         Intent intent = new Intent(ACTION_FINISH_DISCONNECT);
341         sendBroadcast(intent);
342     }
343 }
344