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