Home | History | Annotate | Download | only in bluetooth
      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_BALANCED =
     66             "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_BALANCED";
     67     public static final String ACTION_CONNECTION_PRIORITY_HIGH =
     68             "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_HIGH";
     69     public static final String ACTION_CONNECTION_PRIORITY_LOW_POWER =
     70             "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_LOW_POWER";
     71 
     72     public static final String ACTION_FINISH_CONNECTION_PRIORITY_BALANCED =
     73             "com.android.cts.verifier.bluetooth.action.FINISH_CONNECTION_PRIORITY_BALANCED";
     74     public static final String ACTION_FINISH_CONNECTION_PRIORITY_HIGH =
     75             "com.android.cts.verifier.bluetooth.action.FINISH_CONNECTION_PRIORITY_HIGH";
     76     public static final String ACTION_FINISH_CONNECTION_PRIORITY_LOW_POWER =
     77             "com.android.cts.verifier.bluetooth.action.FINISH_CONNECTION_PRIORITY_LOW_POWER";
     78 
     79     public static final String ACTION_CLIENT_CONNECT_SECURE =
     80             "com.android.cts.verifier.bluetooth.action.CLIENT_CONNECT_SECURE";
     81 
     82     public static final String ACTION_DISCONNECT =
     83             "com.android.cts.verifier.bluetooth.action.DISCONNECT";
     84     public static final String ACTION_FINISH_DISCONNECT =
     85             "com.android.cts.verifier.bluetooth.action.FINISH_DISCONNECT";
     86 
     87     public static final long DEFAULT_INTERVAL = 100L;
     88     public static final long DEFAULT_PERIOD = 10000L;
     89 
     90     // this string will be used at writing test and connection priority test.
     91     private static final String WRITE_VALUE = "TEST";
     92 
     93     private static final UUID SERVICE_UUID =
     94             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
     95     private static final UUID CHARACTERISTIC_UUID =
     96             UUID.fromString("00009998-0000-1000-8000-00805f9b34fb");
     97     private static final UUID START_CHARACTERISTIC_UUID =
     98             UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
     99     private static final UUID STOP_CHARACTERISTIC_UUID =
    100             UUID.fromString("00009995-0000-1000-8000-00805f9b34fb");
    101 
    102     private BluetoothManager mBluetoothManager;
    103     private BluetoothAdapter mBluetoothAdapter;
    104     private BluetoothGatt mBluetoothGatt;
    105     private BluetoothLeScanner mScanner;
    106     private Handler mHandler;
    107     private Timer mConnectionTimer;
    108     private Context mContext;
    109 
    110     private String mAction;
    111     private long mInterval;
    112     private long mPeriod;
    113     private Date mStartDate;
    114     private int mWriteCount;
    115     private boolean mSecure;
    116 
    117     private String mPriority;
    118 
    119     private TestTaskQueue mTaskQueue;
    120 
    121     @Override
    122     public void onCreate() {
    123         super.onCreate();
    124 
    125         mTaskQueue = new TestTaskQueue(getClass().getName() + "__taskHandlerThread");
    126 
    127         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    128         mBluetoothAdapter = mBluetoothManager.getAdapter();
    129         mScanner = mBluetoothAdapter.getBluetoothLeScanner();
    130         mHandler = new Handler();
    131         mContext = this;
    132         mInterval = DEFAULT_INTERVAL;
    133         mPeriod = DEFAULT_PERIOD;
    134 
    135         startScan();
    136     }
    137 
    138     @Override
    139     public void onDestroy() {
    140         super.onDestroy();
    141         if (mBluetoothGatt != null) {
    142             try {
    143                 mBluetoothGatt.disconnect();
    144                 mBluetoothGatt.close();
    145             } catch (Exception e) {}
    146             finally {
    147                 mBluetoothGatt = null;
    148             }
    149         }
    150         stopScan();
    151 
    152         mTaskQueue.quit();
    153     }
    154 
    155     @Override
    156     public IBinder onBind(Intent intent) {
    157         return null;
    158     }
    159 
    160     private void notifyBluetoothDisabled() {
    161         Intent intent = new Intent(ACTION_BLUETOOTH_DISABLED);
    162         sendBroadcast(intent);
    163     }
    164 
    165     @Override
    166     public int onStartCommand(Intent intent, int flags, int startId) {
    167         if (intent != null) {
    168             mAction = intent.getAction();
    169             if (mAction != null) {
    170                 switch (mAction) {
    171                 case ACTION_CLIENT_CONNECT_SECURE:
    172                     mSecure = true;
    173                     break;
    174                 case ACTION_CONNECTION_PRIORITY_BALANCED:
    175                 case ACTION_CONNECTION_PRIORITY_HIGH:
    176                 case ACTION_CONNECTION_PRIORITY_LOW_POWER:
    177                     mTaskQueue.addTask(new Runnable() {
    178                         @Override
    179                         public void run() {
    180                             startPeriodicTransmission();
    181                         }
    182                     });
    183                     break;
    184                 case ACTION_DISCONNECT:
    185                     if (mBluetoothGatt != null) {
    186                         mBluetoothGatt.disconnect();
    187                     } else {
    188                         notifyDisconnect();
    189                     }
    190                     break;
    191                 }
    192             }
    193         }
    194         return START_NOT_STICKY;
    195     }
    196 
    197     private void startPeriodicTransmission() {
    198         mWriteCount = 0;
    199 
    200         // Set connection priority
    201         switch (mAction) {
    202         case ACTION_CONNECTION_PRIORITY_BALANCED:
    203             mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_BALANCED;
    204             mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
    205             break;
    206         case ACTION_CONNECTION_PRIORITY_HIGH:
    207             mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_HIGH;
    208             mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
    209             break;
    210         case ACTION_CONNECTION_PRIORITY_LOW_POWER:
    211             mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_LOW_POWER;
    212             mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
    213             break;
    214         default:
    215             mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_BALANCED;
    216             mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
    217             break;
    218         }
    219 
    220         // Create Timer for Periodic transmission
    221         mStartDate = new Date();
    222         TimerTask task = new TimerTask() {
    223             @Override
    224             public void run() {
    225                 if (mBluetoothGatt == null) {
    226                     if (DEBUG) {
    227                         Log.d(TAG, "BluetoothGatt is null, return");
    228                     }
    229                     return;
    230                 }
    231 
    232                 Date currentData = new Date();
    233                 if ((currentData.getTime() - mStartDate.getTime()) >= mPeriod) {
    234                     if (mConnectionTimer != null) {
    235                         mConnectionTimer.cancel();
    236                         mConnectionTimer = null;
    237                     }
    238                     // The STOP_CHARACTERISTIC_UUID is critical in syncing the client and server
    239                     // states.  Delay the write by 2 seconds to improve the chance of this
    240                     // characteristic going through.  Consider changing the code to use callbacks
    241                     // in the future to make it more robust.
    242                     sleep(2000);
    243                     // write termination data (contains current priority and number of messages wrote)
    244                     String msg = "" + mPriority + "," + mWriteCount;
    245                     writeCharacteristic(STOP_CHARACTERISTIC_UUID, msg);
    246                     sleep(1000);
    247                     Intent intent = new Intent();
    248                     switch (mPriority) {
    249                     case BleConnectionPriorityServerService.CONNECTION_PRIORITY_BALANCED:
    250                         intent.setAction(ACTION_FINISH_CONNECTION_PRIORITY_BALANCED);
    251                         break;
    252                     case BleConnectionPriorityServerService.CONNECTION_PRIORITY_HIGH:
    253                         intent.setAction(ACTION_FINISH_CONNECTION_PRIORITY_HIGH);
    254                         break;
    255                     case BleConnectionPriorityServerService.CONNECTION_PRIORITY_LOW_POWER:
    256                         intent.setAction(ACTION_FINISH_CONNECTION_PRIORITY_LOW_POWER);
    257                         break;
    258                     }
    259                     sendBroadcast(intent);
    260                 }
    261 
    262                 if (mConnectionTimer != null) {
    263                     // write testing data
    264                     ++mWriteCount;
    265                     writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE);
    266                 }
    267             }
    268         };
    269 
    270         // write starting data
    271         writeCharacteristic(START_CHARACTERISTIC_UUID, mPriority);
    272 
    273         // start sending
    274         sleep(1000);
    275         mConnectionTimer = new Timer();
    276         mConnectionTimer.schedule(task, 0, mInterval);
    277     }
    278 
    279     private BluetoothGattService getService() {
    280         BluetoothGattService service = null;
    281 
    282         if (mBluetoothGatt != null) {
    283             service = mBluetoothGatt.getService(SERVICE_UUID);
    284             if (service == null) {
    285                 showMessage("Service not found");
    286             }
    287         }
    288         return service;
    289     }
    290 
    291     private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
    292         BluetoothGattCharacteristic characteristic = null;
    293 
    294         BluetoothGattService service = getService();
    295         if (service != null) {
    296             characteristic = service.getCharacteristic(uuid);
    297             if (characteristic == null) {
    298                 showMessage("Characteristic not found");
    299             }
    300         }
    301         return characteristic;
    302     }
    303 
    304     private void writeCharacteristic(UUID uid, String writeValue) {
    305         BluetoothGattCharacteristic characteristic = getCharacteristic(uid);
    306         if (characteristic != null){
    307             characteristic.setValue(writeValue);
    308             mBluetoothGatt.writeCharacteristic(characteristic);
    309         }
    310     }
    311 
    312     private void sleep(int millis) {
    313         try {
    314             Thread.sleep(millis);
    315         } catch (InterruptedException e) {
    316             Log.e(TAG, "Error in thread sleep", e);
    317         }
    318     }
    319 
    320     private void showMessage(final String msg) {
    321         mHandler.post(new Runnable() {
    322             public void run() {
    323                 Toast.makeText(BleConnectionPriorityClientService.this, msg, Toast.LENGTH_SHORT).show();
    324             }
    325         });
    326     }
    327 
    328     private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
    329         @Override
    330         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    331             if (DEBUG) Log.d(TAG, "onConnectionStateChange");
    332             if (status == BluetoothGatt.GATT_SUCCESS) {
    333                 if (newState == BluetoothProfile.STATE_CONNECTED) {
    334                     int bond = gatt.getDevice().getBondState();
    335                     boolean bonded = false;
    336                     BluetoothDevice target = gatt.getDevice();
    337                     Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    338                     if (pairedDevices.size() > 0) {
    339                         for (BluetoothDevice device : pairedDevices) {
    340                             if (device.getAddress().equals(target.getAddress())) {
    341                                 bonded = true;
    342                                 break;
    343                             }
    344                         }
    345                     }
    346                     if (mSecure && ((bond == BluetoothDevice.BOND_NONE) || !bonded)) {
    347                         // not pairing and execute Secure Test
    348                         mBluetoothGatt.disconnect();
    349                         notifyMismatchSecure();
    350                     } else if (!mSecure && ((bond != BluetoothDevice.BOND_NONE) || bonded)) {
    351                         // already pairing nad execute Insecure Test
    352                         mBluetoothGatt.disconnect();
    353                         notifyMismatchInsecure();
    354                     } else {
    355                         showMessage("Bluetooth LE connected");
    356                         mBluetoothGatt.discoverServices();
    357                     }
    358                 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
    359                     showMessage("Bluetooth LE disconnected");
    360 
    361                     notifyDisconnect();
    362                 }
    363             } else {
    364                 showMessage("Failed to connect");
    365                 mBluetoothGatt.close();
    366                 mBluetoothGatt = null;
    367             }
    368         }
    369 
    370         @Override
    371         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    372             if (DEBUG){
    373                 Log.d(TAG, "onServiceDiscovered");
    374             }
    375             if ((status == BluetoothGatt.GATT_SUCCESS) && (mBluetoothGatt.getService(SERVICE_UUID) != null)) {
    376                 showMessage("Service discovered");
    377                 Intent intent = new Intent(ACTION_CONNECTION_SERVICES_DISCOVERED);
    378                 sendBroadcast(intent);
    379             }
    380         }
    381 
    382         @Override
    383         public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    384             String value = characteristic.getStringValue(0);
    385             UUID uid = characteristic.getUuid();
    386             if (DEBUG) {
    387                 Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + value + " status=" + status + " uid=" + uid);
    388             }
    389         }
    390     };
    391 
    392     private final ScanCallback mScanCallback = new ScanCallback() {
    393         @Override
    394         public void onScanResult(int callbackType, ScanResult result) {
    395             if (mBluetoothGatt == null) {
    396                 stopScan();
    397                 mBluetoothGatt = BleClientService.connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks);
    398             }
    399         }
    400     };
    401 
    402     private void startScan() {
    403         if (DEBUG) {
    404             Log.d(TAG, "startScan");
    405         }
    406         if (!mBluetoothAdapter.isEnabled()) {
    407             notifyBluetoothDisabled();
    408         } else {
    409             List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
    410                     new ParcelUuid(BleConnectionPriorityServerService.ADV_SERVICE_UUID)).build());
    411             ScanSettings setting = new ScanSettings.Builder()
    412                     .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
    413             mScanner.startScan(filter, setting, mScanCallback);
    414         }
    415     }
    416 
    417     private void stopScan() {
    418         if (DEBUG) {
    419             Log.d(TAG, "stopScan");
    420         }
    421         if (mScanner != null) {
    422             mScanner.stopScan(mScanCallback);
    423         }
    424     }
    425 
    426     private void notifyMismatchSecure() {
    427         Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_SECURE);
    428         sendBroadcast(intent);
    429     }
    430 
    431     private void notifyMismatchInsecure() {
    432         Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_INSECURE);
    433         sendBroadcast(intent);
    434     }
    435 
    436     private void notifyDisconnect() {
    437         Intent intent = new Intent(ACTION_FINISH_DISCONNECT);
    438         sendBroadcast(intent);
    439     }
    440 }
    441