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