1 /*
2  * Copyright 2018 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 static android.content.Context.RECEIVER_EXPORTED;
20 
21 import android.app.Service;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothGatt;
25 import android.bluetooth.BluetoothGattCallback;
26 import android.bluetooth.BluetoothGattCharacteristic;
27 import android.bluetooth.BluetoothGattService;
28 import android.bluetooth.BluetoothManager;
29 import android.bluetooth.BluetoothProfile;
30 import android.bluetooth.BluetoothSocket;
31 import android.bluetooth.le.BluetoothLeScanner;
32 import android.bluetooth.le.ScanCallback;
33 import android.bluetooth.le.ScanFilter;
34 import android.bluetooth.le.ScanResult;
35 import android.bluetooth.le.ScanSettings;
36 import android.content.BroadcastReceiver;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.os.Build;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Message;
44 import android.os.ParcelUuid;
45 import android.util.Log;
46 import android.widget.Toast;
47 
48 import java.util.Arrays;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.UUID;
52 
53 public class BleCocClientService extends Service {
54 
55     public static final boolean DEBUG = true;
56     public static final String TAG = "BleCocClientService";
57 
58     private static final int TRANSPORT_MODE_FOR_SECURE_CONNECTION = BluetoothDevice.TRANSPORT_LE;
59 
60     public static final String BLE_LE_CONNECTED =
61             "com.android.cts.verifier.bluetooth.BLE_LE_CONNECTED";
62     public static final String BLE_GOT_PSM =
63             "com.android.cts.verifier.bluetooth.BLE_GOT_PSM";
64     public static final String BLE_COC_CONNECTED =
65             "com.android.cts.verifier.bluetooth.BLE_COC_CONNECTED";
66     public static final String BLE_CONNECTION_TYPE_CHECKED =
67             "com.android.cts.verifier.bluetooth.BLE_CONNECTION_TYPE_CHECKED";
68     public static final String BLE_DATA_8BYTES_SENT =
69             "com.android.cts.verifier.bluetooth.BLE_DATA_8BYTES_SENT";
70     public static final String BLE_DATA_8BYTES_READ =
71             "com.android.cts.verifier.bluetooth.BLE_DATA_8BYTES_READ";
72     public static final String BLE_DATA_LARGEBUF_READ =
73             "com.android.cts.verifier.bluetooth.BLE_DATA_LARGEBUF_READ";
74     public static final String BLE_LE_DISCONNECTED =
75             "com.android.cts.verifier.bluetooth.BLE_LE_DISCONNECTED";
76 
77     public static final String BLE_BLUETOOTH_MISMATCH_SECURE =
78             "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_MISMATCH_SECURE";
79     public static final String BLE_BLUETOOTH_MISMATCH_INSECURE =
80             "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_MISMATCH_INSECURE";
81     public static final String BLE_BLUETOOTH_DISABLED =
82             "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_DISABLED";
83     public static final String BLE_GATT_CONNECTED =
84             "com.android.cts.verifier.bluetooth.BLE_GATT_CONNECTED";
85     public static final String BLE_BLUETOOTH_DISCONNECTED =
86             "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_DISCONNECTED";
87     public static final String BLE_CLIENT_ERROR =
88             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ERROR";
89     public static final String EXTRA_COMMAND =
90             "com.android.cts.verifier.bluetooth.EXTRA_COMMAND";
91     public static final String EXTRA_WRITE_VALUE =
92             "com.android.cts.verifier.bluetooth.EXTRA_WRITE_VALUE";
93     public static final String EXTRA_BOOL =
94             "com.android.cts.verifier.bluetooth.EXTRA_BOOL";
95 
96     // Literal for Client Action
97     public static final String BLE_COC_CLIENT_ACTION_LE_INSECURE_CONNECT =
98             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_LE_INSECURE_CONNECT";
99     public static final String BLE_COC_CLIENT_ACTION_LE_SECURE_CONNECT =
100             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_LE_SECURE_CONNECT";
101     public static final String BLE_COC_CLIENT_ACTION_GET_PSM =
102             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_GET_PSM";
103     public static final String BLE_COC_CLIENT_ACTION_COC_CLIENT_CONNECT =
104             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_COC_CLIENT_CONNECT";
105     public static final String BLE_COC_CLIENT_ACTION_CHECK_CONNECTION_TYPE =
106             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_CHECK_CONNECTION_TYPE";
107     public static final String BLE_COC_CLIENT_ACTION_SEND_DATA_8BYTES =
108             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_SEND_DATA_8BYTES";
109     public static final String BLE_COC_CLIENT_ACTION_READ_DATA_8BYTES =
110             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_READ_DATA_8BYTES";
111     public static final String BLE_COC_CLIENT_ACTION_EXCHANGE_DATA =
112             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_EXCHANGE_DATA";
113     public static final String BLE_COC_CLIENT_ACTION_CLIENT_CONNECT =
114             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_CLIENT_CONNECT";
115     public static final String BLE_COC_CLIENT_ACTION_CLIENT_CONNECT_SECURE =
116             "com.android.cts.verifier.bluetooth.BLE_COC_CLIENT_ACTION_CLIENT_CONNECT_SECURE";
117     public static final String BLE_CLIENT_ACTION_CLIENT_DISCONNECT =
118             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_DISCONNECT";
119 
120     private static final UUID SERVICE_UUID =
121             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
122 
123     /**
124      * UUID of the GATT Read Characteristics for LE_PSM value.
125      */
126     public static final UUID LE_PSM_CHARACTERISTIC_UUID =
127             UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a");
128 
129     public static final String WRITE_VALUE = "CLIENT_TEST";
130     private static final String NOTIFY_VALUE = "NOTIFY_TEST";
131     private int mBleState = BluetoothProfile.STATE_DISCONNECTED;
132     private static final int EXECUTION_DELAY = 1500;
133 
134     // current test category
135     private String mCurrentAction;
136 
137     private BluetoothManager mBluetoothManager;
138     private BluetoothAdapter mBluetoothAdapter;
139     private BluetoothDevice mDevice;
140     private BluetoothGatt mBluetoothGatt;
141     private BluetoothLeScanner mScanner;
142     private Handler mHandler;
143     private boolean mSecure;
144     private boolean mValidityService;
145     private int mPsm;
146     private BluetoothChatService mChatService;
147     private int mNextReadExpectedLen = -1;
148     private String mNextReadCompletionIntent;
149     private int mTotalReadLen = 0;
150     private byte mNextReadByte;
151     private int mNextWriteExpectedLen = -1;
152     private String mNextWriteCompletionIntent = null;
153 
154     // Handler for communicating task with peer.
155     private TestTaskQueue mTaskQueue;
156 
157     @Override
onCreate()158     public void onCreate() {
159         super.onCreate();
160 
161         registerReceiver(
162                 mBondStatusReceiver,
163                 new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
164                 RECEIVER_EXPORTED);
165 
166         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
167         mBluetoothAdapter = mBluetoothManager.getAdapter();
168         mScanner = mBluetoothAdapter.getBluetoothLeScanner();
169         mHandler = new Handler();
170 
171         mTaskQueue = new TestTaskQueue(getClass().getName() + "_taskHandlerThread");
172     }
173 
174     @Override
onStartCommand(final Intent intent, int flags, int startId)175     public int onStartCommand(final Intent intent, int flags, int startId) {
176         if (!mBluetoothAdapter.isEnabled()) {
177             notifyBluetoothDisabled();
178         } else {
179             mTaskQueue.addTask(new Runnable() {
180                 @Override
181                 public void run() {
182                     onTestFinish(intent.getAction());
183                 }
184             }, EXECUTION_DELAY);
185         }
186         return START_NOT_STICKY;
187     }
188 
onTestFinish(String action)189     private void onTestFinish(String action) {
190         mCurrentAction = action;
191         if (mCurrentAction != null) {
192             switch (mCurrentAction) {
193                 case BLE_COC_CLIENT_ACTION_LE_INSECURE_CONNECT:
194                     mSecure = false;
195                     startScan();
196                     break;
197                 case BLE_COC_CLIENT_ACTION_LE_SECURE_CONNECT:
198                     mSecure = true;
199                     startScan();
200                     break;
201                 case BLE_COC_CLIENT_ACTION_GET_PSM:
202                     startLeDiscovery();
203                     break;
204                 case BLE_COC_CLIENT_ACTION_COC_CLIENT_CONNECT:
205                     leCocClientConnect();
206                     break;
207                 case BLE_COC_CLIENT_ACTION_CHECK_CONNECTION_TYPE:
208                     leCheckConnectionType();
209                     break;
210                 case BLE_COC_CLIENT_ACTION_SEND_DATA_8BYTES:
211                     sendData8bytes();
212                     break;
213                 case BLE_COC_CLIENT_ACTION_READ_DATA_8BYTES:
214                     readData8bytes();
215                     break;
216                 case BLE_COC_CLIENT_ACTION_EXCHANGE_DATA:
217                     sendDataLargeBuf();
218                     break;
219                 case BLE_CLIENT_ACTION_CLIENT_DISCONNECT:
220                     if (mBluetoothGatt != null) {
221                         mBluetoothGatt.disconnect();
222                     }
223                     if (mChatService != null) {
224                         mChatService.stop();
225                     }
226                     break;
227                 default:
228                     Log.e(TAG, "Error: Unhandled or invalid action=" + mCurrentAction);
229             }
230         }
231     }
232 
233     @Override
onBind(Intent intent)234     public IBinder onBind(Intent intent) {
235         return null;
236     }
237 
238     @Override
onDestroy()239     public void onDestroy() {
240         super.onDestroy();
241         if (mBluetoothGatt != null) {
242             mBluetoothGatt.disconnect();
243             mBluetoothGatt.close();
244             mBluetoothGatt = null;
245         }
246         stopScan();
247         unregisterReceiver(mBondStatusReceiver);
248 
249         if (mChatService != null) {
250             mChatService.stop();
251         }
252 
253         mTaskQueue.quit();
254     }
255 
connectGatt(BluetoothDevice device, Context context, boolean autoConnect, boolean isSecure, BluetoothGattCallback callback)256     public static BluetoothGatt connectGatt(BluetoothDevice device, Context context,
257                                             boolean autoConnect, boolean isSecure,
258                                             BluetoothGattCallback callback) {
259         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
260             if (isSecure) {
261                 if (TRANSPORT_MODE_FOR_SECURE_CONNECTION == BluetoothDevice.TRANSPORT_AUTO) {
262                     Toast.makeText(context, "connectGatt(transport=AUTO)", Toast.LENGTH_SHORT)
263                     .show();
264                 } else {
265                     Toast.makeText(context, "connectGatt(transport=LE)", Toast.LENGTH_SHORT).show();
266                 }
267                 return device.connectGatt(context, autoConnect, callback,
268                                           TRANSPORT_MODE_FOR_SECURE_CONNECTION);
269             } else {
270                 Toast.makeText(context, "connectGatt(transport=LE)", Toast.LENGTH_SHORT).show();
271                 return device.connectGatt(context, autoConnect, callback,
272                                           BluetoothDevice.TRANSPORT_LE);
273             }
274         } else {
275             Toast.makeText(context, "connectGatt", Toast.LENGTH_SHORT).show();
276             return device.connectGatt(context, autoConnect, callback);
277         }
278     }
279 
readCharacteristic(UUID uuid)280     private void readCharacteristic(UUID uuid) {
281         BluetoothGattCharacteristic characteristic = getCharacteristic(uuid);
282         if (characteristic != null) {
283             mBluetoothGatt.readCharacteristic(characteristic);
284         }
285     }
286 
notifyError(String message)287     private void notifyError(String message) {
288         showMessage(message);
289         Log.e(TAG, message);
290 
291         Intent intent = new Intent(BLE_CLIENT_ERROR);
292         sendBroadcast(intent);
293     }
294 
notifyMismatchSecure()295     private void notifyMismatchSecure() {
296         Intent intent = new Intent(BLE_BLUETOOTH_MISMATCH_SECURE);
297         sendBroadcast(intent);
298     }
299 
notifyMismatchInsecure()300     private void notifyMismatchInsecure() {
301         Intent intent = new Intent(BLE_BLUETOOTH_MISMATCH_INSECURE);
302         sendBroadcast(intent);
303     }
304 
notifyBluetoothDisabled()305     private void notifyBluetoothDisabled() {
306         Intent intent = new Intent(BLE_BLUETOOTH_DISABLED);
307         sendBroadcast(intent);
308     }
309 
notifyConnected()310     private void notifyConnected() {
311         showMessage("Bluetooth LE GATT connected");
312         Intent intent = new Intent(BLE_LE_CONNECTED);
313         sendBroadcast(intent);
314     }
315 
startLeDiscovery()316     private void startLeDiscovery() {
317         // Start Service Discovery
318         if (mBluetoothGatt != null && mBleState == BluetoothProfile.STATE_CONNECTED) {
319             mBluetoothGatt.discoverServices();
320         } else {
321             showMessage("Bluetooth LE GATT not connected.");
322         }
323     }
324 
notifyDisconnected()325     private void notifyDisconnected() {
326         showMessage("Bluetooth LE disconnected");
327         Intent intent = new Intent(BLE_BLUETOOTH_DISCONNECTED);
328         sendBroadcast(intent);
329     }
330 
notifyServicesDiscovered()331     private void notifyServicesDiscovered() {
332         showMessage("Service discovered");
333         // Find the LE_COC_PSM characteristics
334         if (DEBUG) {
335             Log.d(TAG, "notifyServicesDiscovered: Next step is to read the PSM char.");
336         }
337         readCharacteristic(LE_PSM_CHARACTERISTIC_UUID);
338     }
339 
getService()340     private BluetoothGattService getService() {
341         BluetoothGattService service = null;
342 
343         if (mBluetoothGatt != null) {
344             service = mBluetoothGatt.getService(SERVICE_UUID);
345             if (service == null) {
346                 showMessage("GATT Service not found");
347             }
348         }
349         return service;
350     }
351 
getCharacteristic(UUID uuid)352     private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
353         BluetoothGattCharacteristic characteristic = null;
354 
355         BluetoothGattService service = getService();
356         if (service != null) {
357             characteristic = service.getCharacteristic(uuid);
358             if (characteristic == null) {
359                 showMessage("Characteristic not found");
360             }
361         }
362         return characteristic;
363     }
364 
showMessage(final String msg)365     private void showMessage(final String msg) {
366         mHandler.post(new Runnable() {
367             public void run() {
368                 Toast.makeText(BleCocClientService.this, msg, Toast.LENGTH_SHORT).show();
369             }
370         });
371     }
372 
373     private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
374         @Override
375         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
376             if (DEBUG) {
377                 Log.d(TAG, "onConnectionStateChange: status=" + status + ", newState=" + newState);
378             }
379             if (status == BluetoothGatt.GATT_SUCCESS) {
380                 if (newState == BluetoothProfile.STATE_CONNECTED) {
381                     mBleState = newState;
382                     int bondState = gatt.getDevice().getBondState();
383                     boolean bonded = false;
384                     BluetoothDevice target = gatt.getDevice();
385                     Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
386                     if (!pairedDevices.isEmpty()) {
387                         for (BluetoothDevice device : pairedDevices) {
388                             if (device.getAddress().equals(target.getAddress())) {
389                                 bonded = true;
390                                 break;
391                             }
392                         }
393                     }
394                     if (mSecure && ((bondState == BluetoothDevice.BOND_NONE) || !bonded)) {
395                         // not pairing and execute Secure Test
396                         Log.e(TAG, "BluetoothGattCallback.onConnectionStateChange: "
397                               + "Not paired but execute secure test");
398                         mBluetoothGatt.disconnect();
399                         notifyMismatchSecure();
400                     } else if (!mSecure && ((bondState != BluetoothDevice.BOND_NONE) || bonded)) {
401                         // already pairing and execute Insecure Test
402                         Log.e(TAG, "BluetoothGattCallback.onConnectionStateChange: "
403                               + "Paired but execute insecure test");
404                         mBluetoothGatt.disconnect();
405                         notifyMismatchInsecure();
406                     } else {
407                         notifyConnected();
408                     }
409                 } else if (status == BluetoothProfile.STATE_DISCONNECTED) {
410                     mBleState = newState;
411                     mSecure = false;
412                     mBluetoothGatt.close();
413                     notifyDisconnected();
414                 }
415             } else {
416                 showMessage("Failed to connect: " + status + " , newState = " + newState);
417                 mBluetoothGatt.close();
418                 mBluetoothGatt = null;
419             }
420         }
421 
422         @Override
423         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
424             if (DEBUG) {
425                 Log.d(TAG, "onServicesDiscovered: status=" + status);
426             }
427             if ((status == BluetoothGatt.GATT_SUCCESS) &&
428                 (mBluetoothGatt.getService(SERVICE_UUID) != null)) {
429                 notifyServicesDiscovered();
430             }
431         }
432 
433         @Override
434         public void onCharacteristicRead(BluetoothGatt gatt,
435                 BluetoothGattCharacteristic characteristic, int status) {
436             UUID uid = characteristic.getUuid();
437             if (DEBUG) {
438                 Log.d(TAG, "onCharacteristicRead: status=" + status + ", uuid=" + uid);
439             }
440             if (status == BluetoothGatt.GATT_SUCCESS) {
441                 String value = characteristic.getStringValue(0);
442                 if (characteristic.getUuid().equals(LE_PSM_CHARACTERISTIC_UUID)) {
443                     mPsm = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
444                     if (DEBUG) {
445                         Log.d(TAG, "onCharacteristicRead: reading PSM=" + mPsm);
446                     }
447                     Intent intent = new Intent(BLE_GOT_PSM);
448                     sendBroadcast(intent);
449                 } else {
450                     if (DEBUG) {
451                         Log.d(TAG, "onCharacteristicRead: Note: unknown uuid=" + uid);
452                     }
453                 }
454             } else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) {
455                 notifyError("Not Permission Read: " + status + " : " + uid);
456             } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
457                 notifyError("Not Authentication Read: " + status + " : " + uid);
458             } else {
459                 notifyError("Failed to read characteristic: " + status + " : " + uid);
460             }
461         }
462     };
463 
464     private final ScanCallback mScanCallback = new ScanCallback() {
465         @Override
466         public void onScanResult(int callbackType, ScanResult result) {
467             if (mBluetoothGatt == null) {
468                 // verify the validity of the advertisement packet.
469                 mValidityService = false;
470                 List<ParcelUuid> uuids = result.getScanRecord().getServiceUuids();
471                 for (ParcelUuid uuid : uuids) {
472                     if (uuid.getUuid().equals(BleCocServerService.ADV_COC_SERVICE_UUID)) {
473                         if (DEBUG) {
474                             Log.d(TAG, "onScanResult: Found ADV with LE CoC Service UUID.");
475                         }
476                         mValidityService = true;
477                         break;
478                     }
479                 }
480                 if (mValidityService) {
481                     stopScan();
482 
483                     BluetoothDevice device = result.getDevice();
484                     mDevice = device;
485                     if (DEBUG) {
486                         Log.d(TAG, "onScanResult: Found ADV with CoC UUID on device="
487                               + device);
488                     }
489                     if (mSecure) {
490                         if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
491                             if (!device.createBond()) {
492                                 notifyError("Failed to call create bond");
493                             }
494                         } else {
495                             mBluetoothGatt = connectGatt(result.getDevice(), BleCocClientService.this, false,
496                                                          mSecure, mGattCallbacks);
497                         }
498                     } else {
499                         mBluetoothGatt = connectGatt(result.getDevice(), BleCocClientService.this, false, mSecure,
500                                                      mGattCallbacks);
501                     }
502                 } else {
503                     notifyError("No valid service in Advertisement");
504                 }
505             }
506         }
507     };
508 
checkReadBufContent(byte[] buf, int len)509     private boolean checkReadBufContent(byte[] buf, int len) {
510         // Check that the content is correct
511         for (int i = 0; i < len; i++) {
512             if (buf[i] != mNextReadByte) {
513                 Log.e(TAG, "handleMessageRead: Error: wrong byte content. buf["
514                       + i + "]=" + buf[i] + " not equal to " + mNextReadByte);
515                 return false;
516             }
517             mNextReadByte++;
518         }
519         return true;
520     }
521 
handleMessageRead(Message msg)522     private void handleMessageRead(Message msg) {
523         byte[] buf = (byte[])msg.obj;
524         int len = msg.arg1;
525         if (len <= 0) {
526             return;
527         }
528         mTotalReadLen += len;
529         if (DEBUG) {
530             Log.d(TAG, "handleMessageRead: receive buffer of length=" + len + ", mTotalReadLen="
531                   + mTotalReadLen + ", mNextReadExpectedLen=" + mNextReadExpectedLen);
532         }
533 
534         if (mNextReadExpectedLen == mTotalReadLen) {
535             if (!checkReadBufContent(buf, len)) {
536                 mNextReadExpectedLen = -1;
537                 return;
538             }
539             showMessage("Read " + len + " bytes");
540             if (DEBUG) {
541                 Log.d(TAG, "handleMessageRead: broadcast intent " + mNextReadCompletionIntent);
542             }
543             Intent intent = new Intent(mNextReadCompletionIntent);
544             sendBroadcast(intent);
545             mTotalReadLen = 0;
546             if (mNextReadCompletionIntent.equals(BLE_DATA_8BYTES_READ)) {
547                 // The server will not wait for any signal to send out the next bunch of data, after
548                 // it finishes sending the first 8 bytes. That means if we set the expectation
549                 // asynchronously, the data could come before that, so we have to do that here.
550                 readDataLargeBuf();
551             } else {
552                 mNextReadExpectedLen = -1;
553                 mNextReadCompletionIntent = null;
554             }
555         } else if (mNextReadExpectedLen > mTotalReadLen) {
556             if (!checkReadBufContent(buf, len)) {
557                 mNextReadExpectedLen = -1;
558                 return;
559             }
560         } else if (mNextReadExpectedLen < mTotalReadLen) {
561             Log.e(TAG, "handleMessageRead: Unexpected receive buffer of length=" + len
562                   + ", expected len=" + mNextReadExpectedLen);
563         }
564     }
565 
sendMessage(byte[] buf)566     private void sendMessage(byte[] buf) {
567         mChatService.write(buf);
568     }
569 
handleMessageWrite(Message msg)570     private void handleMessageWrite(Message msg) {
571         byte[] buffer = (byte[]) msg.obj;
572         int len = buffer.length;
573 
574         showMessage("LE Coc Client wrote " + len + " bytes");
575         if (len == mNextWriteExpectedLen) {
576             if (mNextWriteCompletionIntent != null) {
577                 Intent intent = new Intent(mNextWriteCompletionIntent);
578                 sendBroadcast(intent);
579             }
580         } else {
581             Log.d(TAG, "handleMessageWrite: unrecognized length=" + len);
582         }
583     }
584 
585     private class ChatHandler extends Handler {
586         @Override
handleMessage(Message msg)587         public void handleMessage(Message msg) {
588             super.handleMessage(msg);
589             if (DEBUG) {
590                 Log.d(TAG, "ChatHandler.handleMessage: msg=" + msg);
591             }
592             int state = msg.arg1;
593             switch (msg.what) {
594             case BluetoothChatService.MESSAGE_STATE_CHANGE:
595                 if (state == BluetoothChatService.STATE_CONNECTED) {
596                     // LE CoC is established
597                     notifyLeCocClientConnected();
598                 }
599                 break;
600             case BluetoothChatService.MESSAGE_READ:
601                 handleMessageRead(msg);
602                 break;
603             case BluetoothChatService.MESSAGE_WRITE:
604                 handleMessageWrite(msg);
605                 break;
606             }
607         }
608     }
609 
notifyLeCocClientConnected()610     private void notifyLeCocClientConnected() {
611         if (DEBUG) {
612             Log.d(TAG, "notifyLeCocClientConnected: device=" + mDevice + ", mSecure=" + mSecure);
613         }
614         showMessage("Bluetooth LE Coc connected");
615         Intent intent = new Intent(BLE_COC_CONNECTED);
616         sendBroadcast(intent);
617     }
618 
leCocClientConnect()619     private void leCocClientConnect() {
620         if (DEBUG) {
621             Log.d(TAG, "leCocClientConnect: device=" + mDevice + ", mSecure=" + mSecure);
622         }
623         if (mDevice == null) {
624             Log.e(TAG, "leCocClientConnect: mDevice is null");
625             return;
626         }
627         // Construct BluetoothChatService with useBle=true parameter
628         mChatService = new BluetoothChatService(this, new ChatHandler(), true);
629         mChatService.connect(mDevice, mSecure, mPsm);
630     }
631 
leCheckConnectionType()632     private void leCheckConnectionType() {
633         if (mChatService == null) {
634             Log.e(TAG, "leCheckConnectionType: no LE Coc connection");
635             return;
636         }
637         int type = mChatService.getSocketConnectionType();
638         if (type != BluetoothSocket.TYPE_L2CAP) {
639             Log.e(TAG, "leCheckConnectionType: invalid connection type=" + type);
640             return;
641         }
642         showMessage("LE Coc Connection Type Checked");
643         Intent intent = new Intent(BLE_CONNECTION_TYPE_CHECKED);
644         sendBroadcast(intent);
645     }
646 
sendData8bytes()647     private void sendData8bytes() {
648         if (DEBUG) Log.d(TAG, "sendData8bytes");
649 
650         final byte[] buf = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
651         mNextWriteExpectedLen = 8;
652         mNextWriteCompletionIntent = BLE_DATA_8BYTES_SENT;
653         sendMessage(buf);
654     }
655 
sendDataLargeBuf()656     private void sendDataLargeBuf() {
657         final int len = BleCocServerService.TEST_DATA_EXCHANGE_BUFSIZE;
658         if (DEBUG) Log.d(TAG, "sendDataLargeBuf of size=" + len);
659 
660         byte[] buf = new byte[len];
661         for (int i = 0; i < len; i++) {
662             buf[i] = (byte)(i + 1);
663         }
664         mNextWriteExpectedLen = len;
665         mNextWriteCompletionIntent = null;
666         sendMessage(buf);
667     }
668 
readData8bytes()669     private void readData8bytes() {
670         mNextReadExpectedLen = 8;
671         mNextReadCompletionIntent = BLE_DATA_8BYTES_READ;
672         mNextReadByte = 1;
673     }
674 
readDataLargeBuf()675     private void readDataLargeBuf() {
676         mNextReadExpectedLen = BleCocServerService.TEST_DATA_EXCHANGE_BUFSIZE;
677         mNextReadCompletionIntent = BLE_DATA_LARGEBUF_READ;
678         mNextReadByte = 1;
679     }
680 
startScan()681     private void startScan() {
682         if (DEBUG) Log.d(TAG, "startScan");
683         List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
684                 new ParcelUuid(BleCocServerService.ADV_COC_SERVICE_UUID)).build());
685         ScanSettings setting = new ScanSettings.Builder()
686                 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
687         mScanner.startScan(filter, setting, mScanCallback);
688     }
689 
stopScan()690     private void stopScan() {
691         if (DEBUG) Log.d(TAG, "stopScan");
692         if (mScanner != null) {
693             mScanner.stopScan(mScanCallback);
694         }
695     }
696 
697     private final BroadcastReceiver mBondStatusReceiver = new BroadcastReceiver() {
698         @Override
699         public void onReceive(Context context, Intent intent) {
700             if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
701                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
702                 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
703                                                BluetoothDevice.BOND_NONE);
704                 switch (state) {
705                     case BluetoothDevice.BOND_BONDED:
706                         if (mBluetoothGatt == null) {
707                             if (DEBUG) {
708                                 Log.d(TAG, "onReceive:BOND_BONDED: calling connectGatt. device="
709                                              + device + ", mSecure=" + mSecure
710                                              + ", mDevice=" + mDevice);
711                             }
712                             mDevice = device;
713                             mBluetoothGatt = connectGatt(mDevice, BleCocClientService.this, false,
714                                                          mSecure, mGattCallbacks);
715                         }
716                         break;
717                     case BluetoothDevice.BOND_NONE:
718                         notifyError("Failed to create bond");
719                         break;
720                     case BluetoothDevice.BOND_BONDING:
721                         // fall through
722                     default:
723                         // wait for next state
724                         break;
725                 }
726             }
727         }
728     };
729 }
730