/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.cts.verifier.bluetooth; import static android.content.Context.RECEIVER_EXPORTED; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.android.cts.verifier.R; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.UUID; public class BleClientService extends Service { public static final boolean DEBUG = true; public static final String TAG = "BleClientService"; // Android N (2016 July 15, currently) BluetoothGatt#disconnect() does not work correct. // (termination signal will not be sent) // This flag switches to turn Bluetooth off instead of BluetoothGatt#disconnect(). // If true, test will turn Bluetooth off. Otherwise, will call BluetoothGatt#disconnect(). public static final boolean DISCONNECT_BY_TURN_BT_OFF_ON = (Build.VERSION.SDK_INT > Build.VERSION_CODES.M); // for Version 1 test // private static final int TRANSPORT_MODE_FOR_SECURE_CONNECTION = BluetoothDevice // .TRANSPORT_AUTO; // for Version 2 test private static final int TRANSPORT_MODE_FOR_SECURE_CONNECTION = BluetoothDevice.TRANSPORT_LE; public static final int COMMAND_CONNECT = 0; public static final int COMMAND_DISCONNECT = 1; public static final int COMMAND_DISCOVER_SERVICE = 2; public static final int COMMAND_READ_RSSI = 3; public static final int COMMAND_WRITE_CHARACTERISTIC = 4; public static final int COMMAND_WRITE_CHARACTERISTIC_BAD_RESP = 5; public static final int COMMAND_READ_CHARACTERISTIC = 6; public static final int COMMAND_WRITE_DESCRIPTOR = 7; public static final int COMMAND_READ_DESCRIPTOR = 8; public static final int COMMAND_SET_NOTIFICATION = 9; public static final int COMMAND_BEGIN_WRITE = 10; public static final int COMMAND_EXECUTE_WRITE = 11; public static final int COMMAND_ABORT_RELIABLE = 12; public static final int COMMAND_SCAN_START = 13; public static final int COMMAND_SCAN_STOP = 14; public static final String BLE_BLUETOOTH_MISMATCH_SECURE = "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_MISMATCH_SECURE"; public static final String BLE_BLUETOOTH_MISMATCH_INSECURE = "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_MISMATCH_INSECURE"; public static final String BLE_BLUETOOTH_DISABLED = "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_DISABLED"; public static final String BLE_BLUETOOTH_CONNECTED = "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_CONNECTED"; public static final String BLE_BLUETOOTH_DISCONNECTED = "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_DISCONNECTED"; public static final String BLE_SERVICES_DISCOVERED = "com.android.cts.verifier.bluetooth.BLE_SERVICES_DISCOVERED"; public static final String BLE_MTU_CHANGED_23BYTES = "com.android.cts.verifier.bluetooth.BLE_MTU_CHANGED_23BYTES"; public static final String BLE_MTU_CHANGED_512BYTES = "com.android.cts.verifier.bluetooth.BLE_MTU_CHANGED_512BYTES"; public static final String BLE_CHARACTERISTIC_READ = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ"; public static final String BLE_CHARACTERISTIC_WRITE = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_WRITE"; public static final String BLE_CHARACTERISTIC_CHANGED = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_CHANGED"; public static final String BLE_CHARACTERISTIC_INDICATED = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_INDICATED"; public static final String BLE_DESCRIPTOR_READ = "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_READ"; public static final String BLE_DESCRIPTOR_WRITE = "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_WRITE"; public static final String BLE_RELIABLE_WRITE_COMPLETED = "com.android.cts.verifier.bluetooth.BLE_RELIABLE_WRITE_COMPLETED"; public static final String BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED = "com.android.cts.verifier.bluetooth.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED"; public static final String BLE_READ_REMOTE_RSSI = "com.android.cts.verifier.bluetooth.BLE_READ_REMOTE_RSSI"; public static final String BLE_PHY_READ = "com.android.cts.verifier.bluetooth.BLE_PHY_READ"; public static final String BLE_PHY_READ_SKIPPED = "com.android.cts.verifier.bluetooth.BLE_PHY_READ_SKIPPED"; public static final String BLE_PHY_UPDATE = "com.android.cts.verifier.bluetooth.BLE_PHY_UPDATE"; public static final String BLE_PHY_UPDATE_SKIPPED = "com.android.cts.verifier.bluetooth.BLE_PHY_UPDATE_SKIPPED"; public static final String BLE_ON_SERVICE_CHANGED = "com.android.cts.verifier.bluetooth.BLE_ON_SERVICE_CHANGED"; public static final String BLE_CHARACTERISTIC_READ_NOPERMISSION = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_NOPERMISSION"; public static final String BLE_CHARACTERISTIC_WRITE_NOPERMISSION = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_WRITE_NOPERMISSION"; public static final String BLE_DESCRIPTOR_READ_NOPERMISSION = "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_READ_NOPERMISSION"; public static final String BLE_DESCRIPTOR_WRITE_NOPERMISSION = "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_WRITE_NOPERMISSION"; public static final String BLE_CHARACTERISTIC_READ_NEED_ENCRYPTED = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_NEED_ENCRYPTED"; public static final String BLE_CHARACTERISTIC_WRITE_NEED_ENCRYPTED = "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_WRITE_NEED_ENCRYPTED"; public static final String BLE_DESCRIPTOR_READ_NEED_ENCRYPTED = "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_READ_NEED_ENCRYPTED"; public static final String BLE_DESCRIPTOR_WRITE_NEED_ENCRYPTED = "com.android.cts.verifier.bluetooth.BLE_DESCRIPTOR_WRITE_NEED_ENCRYPTED"; public static final String BLE_CLIENT_ERROR = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ERROR"; public static final String EXTRA_COMMAND = "com.android.cts.verifier.bluetooth.EXTRA_COMMAND"; public static final String EXTRA_WRITE_VALUE = "com.android.cts.verifier.bluetooth.EXTRA_WRITE_VALUE"; public static final String EXTRA_BOOL = "com.android.cts.verifier.bluetooth.EXTRA_BOOL"; // Literal for Client Action public static final String BLE_CLIENT_ACTION_CLIENT_CONNECT = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_CONNECT"; public static final String BLE_CLIENT_ACTION_CLIENT_CONNECT_SECURE = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_CONNECT_SECURE"; public static final String BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE"; public static final String BLE_CLIENT_ACTION_REQUEST_MTU_23 = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_REQUEST_MTU_23"; public static final String BLE_CLIENT_ACTION_REQUEST_MTU_512 = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_REQUEST_MTU_512"; public static final String BLE_CLIENT_ACTION_READ_CHARACTERISTIC = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_CHARACTERISTIC"; public static final String BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC"; public static final String BLE_CLIENT_ACTION_RELIABLE_WRITE = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_RELIABLE_WRITE"; public static final String BLE_CLIENT_ACTION_RELIABLE_WRITE_BAD_RESP = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_RELIABLE_WRITE_BAD_RESP"; public static final String BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC"; public static final String BLE_CLIENT_ACTION_INDICATE_CHARACTERISTIC = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_INDICATE_CHARACTERISTIC"; public static final String BLE_CLIENT_ACTION_READ_DESCRIPTOR = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_DESCRIPTOR"; public static final String BLE_CLIENT_ACTION_WRITE_DESCRIPTOR = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR"; public static final String BLE_CLIENT_ACTION_READ_RSSI = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_RSSI"; public static final String BLE_CLIENT_ACTION_READ_PHY = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_PHY"; public static final String BLE_CLIENT_ACTION_SET_PREFERRED_PHY = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_SET_PREFERRED_PHY"; public static final String BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED"; public static final String BLE_CLIENT_ACTION_CLIENT_DISCONNECT = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_DISCONNECT"; public static final String BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION = "com.android.cts.verifier.bluetooth" + ".BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION"; public static final String BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION = "com.android.cts.verifier.bluetooth" + ".BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION"; public static final String BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION"; public static final String BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION"; public static final String BLE_CLIENT_ACTION_READ_AUTHENTICATED_CHARACTERISTIC = "com.android.cts.verifier.bluetooth" + ".BLE_CLIENT_ACTION_READ_AUTHENTICATED_CHARACTERISTIC"; public static final String BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_CHARACTERISTIC = "com.android.cts.verifier.bluetooth" + ".BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_CHARACTERISTIC"; public static final String BLE_CLIENT_ACTION_READ_AUTHENTICATED_DESCRIPTOR = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_AUTHENTICATED_DESCRIPTOR"; public static final String BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR = "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR"; public static final String EXTRA_CHARACTERISTIC_VALUE = "com.android.cts.verifier.bluetooth.EXTRA_CHARACTERISTIC_VALUE"; public static final String EXTRA_DESCRIPTOR_VALUE = "com.android.cts.verifier.bluetooth.EXTRA_DESCRIPTOR_VALUE"; public static final String EXTRA_RSSI_VALUE = "com.android.cts.verifier.bluetooth.EXTRA_RSSI_VALUE"; public static final String EXTRA_TX_PHY_VALUE = "com.android.cts.verifier.bluetooth.EXTRA_TX_PHY_VALUE"; public static final String EXTRA_RX_PHY_VALUE = "com.android.cts.verifier.bluetooth.EXTRA_RX_PHY_VALUE"; public static final String EXTRA_ERROR_MESSAGE = "com.android.cts.verifier.bluetooth.EXTRA_ERROR_MESSAGE"; public static final String WRITE_VALUE_512BYTES_FOR_MTU = createTestData("REQUEST_MTU", 512); public static final String WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE = createTestData("RELIABLE_WRITE", 507); private static final UUID SERVICE_UUID = UUID.fromString("00009999-0000-1000-8000-00805f9b34fb"); private static final UUID CHARACTERISTIC_UUID = UUID.fromString("00009998-0000-1000-8000-00805f9b34fb"); private static final UUID CHARACTERISTIC_RESULT_UUID = UUID.fromString("00009974-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID = UUID.fromString("00009997-0000-1000-8000-00805f9b34fb"); private static final UUID DESCRIPTOR_UUID = UUID.fromString("00009996-0000-1000-8000-00805f9b34fb"); private static final UUID SERVICE_UUID_ADDITIONAL = UUID.fromString("00009995-0000-1000-8000-00805f9b34fb"); // Literal for registration permission of Characteristic private static final UUID CHARACTERISTIC_NO_READ_UUID = UUID.fromString("00009984-0000-1000-8000-00805f9b34fb"); private static final UUID CHARACTERISTIC_NO_WRITE_UUID = UUID.fromString("00009983-0000-1000-8000-00805f9b34fb"); private static final UUID CHARACTERISTIC_NEED_ENCRYPTED_READ_UUID = UUID.fromString("00009982-0000-1000-8000-00805f9b34fb"); private static final UUID CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID = UUID.fromString("00009981-0000-1000-8000-00805f9b34fb"); // Literal for registration permission of Descriptor private static final UUID DESCRIPTOR_NO_READ_UUID = UUID.fromString("00009973-0000-1000-8000-00805f9b34fb"); private static final UUID DESCRIPTOR_NO_WRITE_UUID = UUID.fromString("00009972-0000-1000-8000-00805f9b34fb"); private static final UUID DESCRIPTOR_NEED_ENCRYPTED_READ_UUID = UUID.fromString("00009969-0000-1000-8000-00805f9b34fb"); private static final UUID DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID = UUID.fromString("00009968-0000-1000-8000-00805f9b34fb"); // Literal for registration upper limit confirmation of Characteristic private static final UUID UPDATE_CHARACTERISTIC_UUID_1 = UUID.fromString("00009989-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_2 = UUID.fromString("00009988-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_3 = UUID.fromString("00009987-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_4 = UUID.fromString("00009986-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_5 = UUID.fromString("00009985-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_6 = UUID.fromString("00009979-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_7 = UUID.fromString("00009978-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_8 = UUID.fromString("00009977-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_9 = UUID.fromString("00009976-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_10 = UUID.fromString("00009975-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_11 = UUID.fromString("00009959-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_12 = UUID.fromString("00009958-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_13 = UUID.fromString("00009957-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_14 = UUID.fromString("00009956-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_CHARACTERISTIC_UUID_15 = UUID.fromString("00009955-0000-1000-8000-00805f9b34fb"); private static final UUID SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID = UUID.fromString("00009949-0000-1000-8000-00805f9b34fb"); private static final UUID UPDATE_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); private static final UUID INDICATE_CHARACTERISTIC_UUID = UUID.fromString("00009971-0000-1000-8000-00805f9b34fb"); public static final String WRITE_VALUE = "CLIENT_TEST"; private static final String NOTIFY_VALUE = "NOTIFY_TEST"; public static final String WRITE_VALUE_BAD_RESP = "BAD_RESP_TEST"; public static final String SERVICE_CHANGED_VALUE = "CLIENT_SVC_CHG"; // current test category private String mCurrentAction; private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothDevice mDevice; private BluetoothGatt mBluetoothGatt; private BluetoothLeScanner mScanner; private Handler mHandler; private Context mContext; private boolean mSecure; private int mNotifyCount; private boolean mValidityService; private ReliableWriteState mExecReliableWrite; private byte[] mBuffer; private boolean mWriteAfterMtuChangeDone = false; // Handler for communicating task with peer. private TestTaskQueue mTaskQueue; // Lock for synchronization during notification request test. private final Object mRequestNotificationLock = new Object(); // Lock for triggering service changed event on server side. private final Object mServiceChangedLock = new Object(); private static final int SERVICE_CHANGED_FLAG_INIT = 0x00; private static final int SERVICE_CHANGED_FLAG_TRIGGER_ACTION = 0x01; private static final int SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED = 0x02; private static final int SERVICE_CHANGED_FLAG_ALL = 0x03; private static final int SERVICE_CHANGED_FLAG_IGNORE = 0xFF; private int mServiceChangedFlag; private enum ReliableWriteState { RELIABLE_WRITE_NONE, RELIABLE_WRITE_WRITE_1ST_DATA, RELIABLE_WRITE_WRITE_2ND_DATA, RELIABLE_WRITE_EXECUTE, RELIABLE_WRITE_BAD_RESP } @Override public void onCreate() { super.onCreate(); registerReceiver( mBondStatusReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED), RECEIVER_EXPORTED); mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = mBluetoothManager.getAdapter(); mScanner = mBluetoothAdapter.getBluetoothLeScanner(); mHandler = new Handler(); mContext = this; mNotifyCount = 0; mTaskQueue = new TestTaskQueue(getClass().getName() + "_taskHandlerThread"); } @Override public int onStartCommand(final Intent intent, int flags, int startId) { if (!mBluetoothAdapter.isEnabled()) { notifyBluetoothDisabled(); } else { mTaskQueue.addTask(new Runnable() { @Override public void run() { onTestFinish(intent.getAction()); } }, 1500); } return START_NOT_STICKY; } private void onTestFinish(String action) { mCurrentAction = action; if (mCurrentAction != null) { switch (mCurrentAction) { case BLE_CLIENT_ACTION_CLIENT_CONNECT_SECURE: mSecure = true; mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_NONE; startScan(); break; case BLE_CLIENT_ACTION_CLIENT_CONNECT: mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_NONE; startScan(); break; case BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE: if (mBluetoothGatt != null && mBleState == BluetoothProfile.STATE_CONNECTED) { mBluetoothGatt.discoverServices(); } else { showMessage("Bluetooth LE not cnnected."); } break; case BLE_CLIENT_ACTION_REQUEST_MTU_23: /* Test the long write before changing MTU. * Starting from Android U, MTU is set to 517, no matter what * user request. To keep same test scope, send first 512 write * before MTU change, so it make use of default MTU = 23. * Later, requestMtu is called twice and test if MTU Exchange will * execute only once over the air. */ writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE_512BYTES_FOR_MTU); break; case BLE_CLIENT_ACTION_REQUEST_MTU_512: // fall through /* Call it twice to verify single MTU Exchange procedure over the link */ requestMtu(); requestMtu(); break; case BLE_CLIENT_ACTION_READ_CHARACTERISTIC: readCharacteristic(CHARACTERISTIC_UUID); break; case BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC: writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE); break; case BLE_CLIENT_ACTION_RELIABLE_WRITE: case BLE_CLIENT_ACTION_RELIABLE_WRITE_BAD_RESP: // fall through mTaskQueue.addTask(new Runnable() { @Override public void run() { reliableWrite(); } }); break; case BLE_CLIENT_ACTION_INDICATE_CHARACTERISTIC: setNotification(INDICATE_CHARACTERISTIC_UUID, true); break; case BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC: // Registered the notify to characteristics in the service mTaskQueue.addTask(new Runnable() { @Override public void run() { mNotifyCount = 16; setNotification(UPDATE_CHARACTERISTIC_UUID, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_1, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_2, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_3, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_4, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_5, true); waitForDisableNotificationCompletion(); setNotification(UPDATE_CHARACTERISTIC_UUID_6, true); waitForDisableNotificationCompletion(); setNotification(UPDATE_CHARACTERISTIC_UUID_7, true); waitForDisableNotificationCompletion(); setNotification(UPDATE_CHARACTERISTIC_UUID_8, true); waitForDisableNotificationCompletion(); setNotification(UPDATE_CHARACTERISTIC_UUID_9, true); waitForDisableNotificationCompletion(); setNotification(UPDATE_CHARACTERISTIC_UUID_10, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_11, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_12, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_13, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_14, true); waitForDisableNotificationCompletion(); setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_15, true); waitForDisableNotificationCompletion(); } }); break; case BLE_CLIENT_ACTION_READ_DESCRIPTOR: readDescriptor(DESCRIPTOR_UUID); break; case BLE_CLIENT_ACTION_WRITE_DESCRIPTOR: writeDescriptor(DESCRIPTOR_UUID, WRITE_VALUE); break; case BLE_CLIENT_ACTION_READ_RSSI: if (mBluetoothGatt != null) { mBluetoothGatt.readRemoteRssi(); } break; case BLE_CLIENT_ACTION_CLIENT_DISCONNECT: if (mBluetoothGatt != null) { mBluetoothGatt.disconnect(); } break; case BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION: readCharacteristic(CHARACTERISTIC_NO_READ_UUID); break; case BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION: writeCharacteristic(CHARACTERISTIC_NO_WRITE_UUID, WRITE_VALUE); break; case BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION: readDescriptor(DESCRIPTOR_NO_READ_UUID); break; case BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION: writeDescriptor(DESCRIPTOR_NO_WRITE_UUID, WRITE_VALUE); break; case BLE_CLIENT_ACTION_READ_AUTHENTICATED_CHARACTERISTIC: readCharacteristic(CHARACTERISTIC_NEED_ENCRYPTED_READ_UUID); break; case BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_CHARACTERISTIC: writeCharacteristic(CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID, WRITE_VALUE); break; case BLE_CLIENT_ACTION_READ_AUTHENTICATED_DESCRIPTOR: readDescriptor(CHARACTERISTIC_RESULT_UUID, DESCRIPTOR_NEED_ENCRYPTED_READ_UUID); break; case BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR: writeDescriptor(CHARACTERISTIC_RESULT_UUID, DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID, WRITE_VALUE); break; case BLE_CLIENT_ACTION_READ_PHY: if (mBluetoothGatt != null) { mBluetoothGatt.readPhy(); } break; case BLE_CLIENT_ACTION_SET_PREFERRED_PHY: if (mBluetoothGatt != null) { mBluetoothGatt.setPreferredPhy(BluetoothDevice.PHY_LE_1M_MASK, BluetoothDevice.PHY_LE_1M_MASK, BluetoothDevice.PHY_OPTION_NO_PREFERRED); } break; case BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED: initializeServiceChangedEvent(); writeCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, WRITE_VALUE); break; } } } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (mBluetoothGatt != null) { mBluetoothGatt.disconnect(); mBluetoothGatt.close(); mBluetoothGatt = null; } stopScan(); unregisterReceiver(mBondStatusReceiver); mTaskQueue.quit(); } /** * Connect to GATT Server hosted by this device. Caller acts as GATT client. * The callback is used to deliver results to Caller, such as connection status as well * as any further GATT client operations. * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct * GATT client operations. * * @param callback GATT callback handler that will receive asynchronous callbacks. * @param autoConnect Whether to directly connect to the remote device (false) or to * automatically connect as soon as the remote device becomes available (true). * @param isSecure Whether to use transport mode for secure connection (true or false) * @throws IllegalArgumentException if callback is null */ public static BluetoothGatt connectGatt(BluetoothDevice device, Context context, boolean autoConnect, boolean isSecure, BluetoothGattCallback callback) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (isSecure) { if (TRANSPORT_MODE_FOR_SECURE_CONNECTION == BluetoothDevice.TRANSPORT_AUTO) { Toast.makeText(context, "connectGatt(transport=AUTO)", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "connectGatt(transport=LE)", Toast.LENGTH_SHORT).show(); } return device.connectGatt(context, autoConnect, callback, TRANSPORT_MODE_FOR_SECURE_CONNECTION); } else { Toast.makeText(context, "connectGatt(transport=LE)", Toast.LENGTH_SHORT).show(); return device.connectGatt(context, autoConnect, callback, BluetoothDevice.TRANSPORT_LE); } } else { Toast.makeText(context, "connectGatt", Toast.LENGTH_SHORT).show(); return device.connectGatt(context, autoConnect, callback); } } private void requestMtu() { if (mBluetoothGatt != null) { // MTU request test does not work on Android 6.0 in secure mode. // (BluetoothDevice#createBond() does not work on Android 6.0. // So devices can't establish Bluetooth LE pairing.) boolean mtuTestExecutable = true; if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { mtuTestExecutable = !mSecure; } if (mtuTestExecutable) { int mtu = 0; if (BLE_CLIENT_ACTION_REQUEST_MTU_23.equals(mCurrentAction)) { mtu = 23; } else if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction)) { mtu = 512; } else { throw new IllegalStateException("unexpected action: " + mCurrentAction); } mBluetoothGatt.requestMtu(mtu); } else { showMessage("Skip MTU test."); notifyMtuChanged(); } } } private void writeCharacteristic(BluetoothGattCharacteristic characteristic, String writeValue) { if (characteristic != null) { // Note: setValue() should not be necessary when using writeCharacteristic(byte[]) which // is added on Android T, but here we call the method in order to make the test // easier to read. Otherwise, we should verify the written value by calling // readCharacteristic() but that makes the whole test hard to read. characteristic.setValue(writeValue); mBluetoothGatt.writeCharacteristic(characteristic, writeValue.getBytes(), characteristic.getWriteType()); } } private void writeCharacteristic(UUID uid, String writeValue) { BluetoothGattCharacteristic characteristic = getCharacteristic(uid); if (characteristic != null) { writeCharacteristic(characteristic, writeValue); } } private void readCharacteristic(UUID uuid) { BluetoothGattCharacteristic characteristic = getCharacteristic(uuid); if (characteristic != null) { mBluetoothGatt.readCharacteristic(characteristic); } } private void writeDescriptor(UUID uid, String writeValue) { BluetoothGattDescriptor descriptor = getDescriptor(uid); if (descriptor != null) { // Note: setValue() should not be necessary when using writeDescriptor(byte[]) which // is added on Android T, but here we call the method in order to make the test // easier to read. Otherwise, we should verify the written value by calling // readDescriptor() but that makes the whole test hard to read. descriptor.setValue(writeValue.getBytes()); mBluetoothGatt.writeDescriptor(descriptor, writeValue.getBytes()); } } private void readDescriptor(UUID uuid) { BluetoothGattDescriptor descriptor = getDescriptor(uuid); if (descriptor != null) { mBluetoothGatt.readDescriptor(descriptor); } } private void writeDescriptor(UUID cuid, UUID duid, String writeValue) { BluetoothGattDescriptor descriptor = getDescriptor(cuid, duid); if (descriptor != null) { // Note: setValue() should not be necessary when using writeDescriptor(byte[]) which // is added on Android T, but here we call the method in order to make the test // easier to read. Otherwise, we should verify the written value by calling // readDescriptor() but that makes the whole test hard to read. descriptor.setValue(writeValue.getBytes()); mBluetoothGatt.writeDescriptor(descriptor, writeValue.getBytes()); } } private void readDescriptor(UUID cuid, UUID duid) { BluetoothGattDescriptor descriptor = getDescriptor(cuid, duid); if (descriptor != null) { mBluetoothGatt.readDescriptor(descriptor); } } private void notifyDisableNotificationCompletion() { synchronized (mRequestNotificationLock) { mRequestNotificationLock.notify(); } } private void waitForDisableNotificationCompletion() { synchronized (mRequestNotificationLock) { try { mRequestNotificationLock.wait(); } catch (InterruptedException e) { Log.e(TAG, "Error in waitForDisableNotificationCompletion" + e); } } } private void initializeServiceChangedEvent() { synchronized (mServiceChangedLock) { mServiceChangedFlag = SERVICE_CHANGED_FLAG_INIT; } } private void sendServiceChangedEventIfReady(int flag) { boolean shouldSend = false; synchronized (mServiceChangedLock) { mServiceChangedFlag |= flag; if (mServiceChangedFlag == SERVICE_CHANGED_FLAG_ALL) { mServiceChangedFlag |= SERVICE_CHANGED_FLAG_IGNORE; shouldSend = true; } } if (shouldSend) { // This is to send result to the connected GATT server. writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), SERVICE_CHANGED_VALUE); } } private void setNotification(BluetoothGattCharacteristic characteristic, boolean enable) { if (characteristic != null) { mBluetoothGatt.setCharacteristicNotification(characteristic, enable); BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UPDATE_DESCRIPTOR_UUID); if (enable) { if (characteristic.getUuid().equals(INDICATE_CHARACTERISTIC_UUID)) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); } else { descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); } } else { descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); } mBluetoothGatt.writeDescriptor(descriptor); } } private void setNotification(UUID serviceUid, UUID characteristicUid, boolean enable) { BluetoothGattCharacteristic characteristic = getCharacteristic(serviceUid, characteristicUid); if (characteristic != null) { setNotification(characteristic, enable); } } private void setNotification(UUID uid, boolean enable) { BluetoothGattCharacteristic characteristic = getCharacteristic(uid); if (characteristic != null) { setNotification(characteristic, enable); } } private void notifyError(String message) { showMessage(message); Log.i(TAG, message); Intent intent = new Intent(BLE_CLIENT_ERROR); sendBroadcast(intent); } private void notifyMismatchSecure() { Intent intent = new Intent(BLE_BLUETOOTH_MISMATCH_SECURE); sendBroadcast(intent); } private void notifyMismatchInsecure() { Intent intent = new Intent(BLE_BLUETOOTH_MISMATCH_INSECURE); sendBroadcast(intent); } private void notifyBluetoothDisabled() { Intent intent = new Intent(BLE_BLUETOOTH_DISABLED); sendBroadcast(intent); } private void notifyConnected() { showMessage("Bluetooth LE connected"); Intent intent = new Intent(BLE_BLUETOOTH_CONNECTED); sendBroadcast(intent); } private void notifyDisconnected() { showMessage("Bluetooth LE disconnected"); Intent intent = new Intent(BLE_BLUETOOTH_DISCONNECTED); sendBroadcast(intent); } private void notifyServicesDiscovered() { showMessage("Service discovered"); Intent intent = new Intent(BLE_SERVICES_DISCOVERED); sendBroadcast(intent); } private void notifyMtuChanged() { Intent intent; if (BLE_CLIENT_ACTION_REQUEST_MTU_23.equals(mCurrentAction)) { intent = new Intent(BLE_MTU_CHANGED_23BYTES); } else if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction)) { intent = new Intent(BLE_MTU_CHANGED_512BYTES); } else { throw new IllegalStateException("unexpected action: " + mCurrentAction); } sendBroadcast(intent); } private void notifyCharacteristicRead(String value) { showMessage("Characteristic read: " + value); Intent intent = new Intent(BLE_CHARACTERISTIC_READ); intent.putExtra(EXTRA_CHARACTERISTIC_VALUE, value); sendBroadcast(intent); } private void notifyCharacteristicWrite(String value) { showMessage("Characteristic write: " + value); Intent intent = new Intent(BLE_CHARACTERISTIC_WRITE); sendBroadcast(intent); } private void notifyCharacteristicReadNoPermission() { showMessage("Characteristic not read: No Permission"); Intent intent = new Intent(BLE_CHARACTERISTIC_READ_NOPERMISSION); sendBroadcast(intent); } private void notifyCharacteristicWriteNoPermission(String value) { showMessage("Characteristic write: No Permission"); Intent intent = new Intent(BLE_CHARACTERISTIC_WRITE_NOPERMISSION); sendBroadcast(intent); } private void notifyCharacteristicReadNeedEncrypted(String value) { showMessage("Characteristic read with encrypted: " + value); Intent intent = new Intent(BLE_CHARACTERISTIC_READ_NEED_ENCRYPTED); sendBroadcast(intent); } private void notifyCharacteristicWriteNeedEncrypted(String value) { showMessage("Characteristic write with encrypted: " + value); Intent intent = new Intent(BLE_CHARACTERISTIC_WRITE_NEED_ENCRYPTED); sendBroadcast(intent); } private void notifyCharacteristicChanged() { showMessage("Characteristic changed"); Intent intent = new Intent(BLE_CHARACTERISTIC_CHANGED); sendBroadcast(intent); } private void notifyCharacteristicIndicated() { showMessage("Characteristic Indicated"); Intent intent = new Intent(BLE_CHARACTERISTIC_INDICATED); sendBroadcast(intent); } private void notifyDescriptorRead(String value) { showMessage("Descriptor read: " + value); Intent intent = new Intent(BLE_DESCRIPTOR_READ); intent.putExtra(EXTRA_DESCRIPTOR_VALUE, value); sendBroadcast(intent); } private void notifyDescriptorWrite(String value) { showMessage("Descriptor write: " + value); Intent intent = new Intent(BLE_DESCRIPTOR_WRITE); sendBroadcast(intent); } private void notifyDescriptorReadNoPermission() { showMessage("Descriptor read: No Permission"); Intent intent = new Intent(BLE_DESCRIPTOR_READ_NOPERMISSION); sendBroadcast(intent); } private void notifyDescriptorWriteNoPermission() { showMessage("Descriptor write: NoPermission"); Intent intent = new Intent(BLE_DESCRIPTOR_WRITE_NOPERMISSION); sendBroadcast(intent); } private void notifyDescriptorReadNeedEncrypted(String value) { showMessage("Descriptor read with encrypted: " + value); Intent intent = new Intent(BLE_DESCRIPTOR_READ_NEED_ENCRYPTED); sendBroadcast(intent); } private void notifyDescriptorWriteNeedEncrypted(String value) { showMessage("Descriptor write with encrypted: " + value); Intent intent = new Intent(BLE_DESCRIPTOR_WRITE_NEED_ENCRYPTED); sendBroadcast(intent); } private void notifyReliableWriteCompleted() { showMessage("Reliable write complete"); Intent intent = new Intent(BLE_RELIABLE_WRITE_COMPLETED); sendBroadcast(intent); } private void notifyReliableWriteBadRespCompleted(String err) { showMessage("Reliable write(bad response) complete"); Intent intent = new Intent(BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED); if (err != null) { intent.putExtra(EXTRA_ERROR_MESSAGE, err); } sendBroadcast(intent); } private void notifyReadRemoteRssi(int rssi) { showMessage("Remote rssi read: " + rssi); Intent intent = new Intent(BLE_READ_REMOTE_RSSI); intent.putExtra(EXTRA_RSSI_VALUE, rssi); sendBroadcast(intent); } private void notifyPhyRead(int txPhy, int rxPhy) { if (BLE_CLIENT_ACTION_READ_PHY.equals(mCurrentAction)) { showMessage("Phy read: txPhy=" + txPhy + ", rxPhy=" + rxPhy); Intent intent = new Intent(BLE_PHY_READ); intent.putExtra(EXTRA_TX_PHY_VALUE, txPhy); intent.putExtra(EXTRA_RX_PHY_VALUE, rxPhy); sendBroadcast(intent); } else { Log.d(TAG, "notifyPhyRead arrived without BLE_CLIENT_ACTION_READ_PHY"); } } private void notifyPhyReadSkipped() { showMessage("Phy read not supported. Skipping the test."); Intent intent = new Intent(BLE_PHY_READ_SKIPPED); sendBroadcast(intent); } private void notifyPhyUpdate(int txPhy, int rxPhy) { if (BLE_CLIENT_ACTION_SET_PREFERRED_PHY.equals(mCurrentAction)) { showMessage("Phy update: txPhy=" + txPhy + ", rxPhy=" + rxPhy); Intent intent = new Intent(BLE_PHY_UPDATE); intent.putExtra(EXTRA_TX_PHY_VALUE, txPhy); intent.putExtra(EXTRA_RX_PHY_VALUE, rxPhy); sendBroadcast(intent); } else { Log.d(TAG, "notifyPhyUpdate arrived without BLE_CLIENT_ACTION_SET_PREFERRED_PHY"); } } private void notifyPhyUpdateSkipped() { showMessage("Phy update not supported. Skipping the test."); Intent intent = new Intent(BLE_PHY_UPDATE_SKIPPED); sendBroadcast(intent); } private void notifyServiceChanged() { showMessage("Remote service changed"); Intent intent = new Intent(BLE_ON_SERVICE_CHANGED); sendBroadcast(intent); } private BluetoothGattService getService(UUID serverUid) { BluetoothGattService service = null; if (mBluetoothGatt != null) { service = mBluetoothGatt.getService(serverUid); if (service == null) { showMessage("Service not found"); } } return service; } private BluetoothGattService getService() { BluetoothGattService service = null; if (mBluetoothGatt != null) { service = mBluetoothGatt.getService(SERVICE_UUID); if (service == null) { showMessage("Service not found"); } } return service; } private BluetoothGattCharacteristic getCharacteristic(UUID serverUid, UUID characteristicUid) { BluetoothGattCharacteristic characteristic = null; BluetoothGattService service = getService(serverUid); if (service != null) { characteristic = service.getCharacteristic(characteristicUid); if (characteristic == null) { showMessage("Characteristic not found"); } } return characteristic; } private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { BluetoothGattCharacteristic characteristic = null; BluetoothGattService service = getService(); if (service != null) { characteristic = service.getCharacteristic(uuid); if (characteristic == null) { showMessage("Characteristic not found"); } } return characteristic; } private BluetoothGattDescriptor getDescriptor(UUID uid) { BluetoothGattDescriptor descriptor = null; BluetoothGattCharacteristic characteristic = getCharacteristic(CHARACTERISTIC_UUID); if (characteristic != null) { descriptor = characteristic.getDescriptor(uid); if (descriptor == null) { showMessage("Descriptor not found"); } } return descriptor; } private BluetoothGattDescriptor getDescriptor(UUID cuid, UUID duid) { BluetoothGattDescriptor descriptor = null; BluetoothGattCharacteristic characteristic = getCharacteristic(cuid); if (characteristic != null) { descriptor = characteristic.getDescriptor(duid); if (descriptor == null) { showMessage("Descriptor not found"); } } return descriptor; } private void showMessage(final String msg) { mHandler.post(new Runnable() { public void run() { Toast.makeText(BleClientService.this, msg, Toast.LENGTH_SHORT).show(); } }); } private void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { Log.e(TAG, "Error in thread sleep", e); } } private void reliableWrite() { // aborting test mBluetoothGatt.beginReliableWrite(); sleep(1000); mBluetoothGatt.abortReliableWrite(); // writing test sleep(2000); mBluetoothGatt.beginReliableWrite(); sleep(1000); if (BLE_CLIENT_ACTION_RELIABLE_WRITE.equals(mCurrentAction)) { mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_WRITE_1ST_DATA; writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE); } else { mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_BAD_RESP; writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE_BAD_RESP); } } private int mBleState = BluetoothProfile.STATE_DISCONNECTED; private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (DEBUG) { Log.d(TAG, "onConnectionStateChange: status= " + status + ", newState= " + newState); } if (status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { mBleState = newState; int bond = gatt.getDevice().getBondState(); boolean bonded = false; BluetoothDevice target = gatt.getDevice(); Set pairedDevices = mBluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { for (BluetoothDevice device : pairedDevices) { if (device.getAddress().equals(target.getAddress())) { bonded = true; break; } } } if (mSecure && ((bond == BluetoothDevice.BOND_NONE) || !bonded)) { // not pairing and execute Secure Test mBluetoothGatt.disconnect(); notifyMismatchSecure(); } else if (!mSecure && ((bond != BluetoothDevice.BOND_NONE) || bonded)) { // already pairing nad execute Insecure Test mBluetoothGatt.disconnect(); notifyMismatchInsecure(); } else { notifyConnected(); } } else if (status == BluetoothProfile.STATE_DISCONNECTED) { mBleState = newState; mSecure = false; mBluetoothGatt.close(); notifyDisconnected(); } } else { showMessage("Failed to connect: " + status + " , newState = " + newState); mBluetoothGatt.close(); mBluetoothGatt = null; } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); if (BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE.equals(mCurrentAction)) { if (DEBUG) { Log.d(TAG, "onServiceDiscovered"); } if ((status == BluetoothGatt.GATT_SUCCESS) && (mBluetoothGatt.getService(SERVICE_UUID) != null)) { notifyServicesDiscovered(); } } else { Log.d(TAG, "onServicesDiscovered without BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE"); } } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status); if (DEBUG) { Log.d(TAG, "onMtuChanged"); } if (status == BluetoothGatt.GATT_SUCCESS) { // verify MTU size which now is always equal to max 517 int expectedNewMtu = 517; if (mtu != expectedNewMtu) { String msg = String.format(getString(R.string.ble_mtu_mismatch_message), expectedNewMtu, mtu); showMessage(msg); } if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) && !mWriteAfterMtuChangeDone) { /* Verify write characteristic on bigger MTU */ writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE_512BYTES_FOR_MTU); mWriteAfterMtuChangeDone = true; } } else { notifyError("Failed to request mtu: " + status); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, final int status) { super.onCharacteristicWrite(gatt, characteristic, status); String value = characteristic.getStringValue(0); final UUID uid = characteristic.getUuid(); if (DEBUG) { Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + value + " status=" + status); } if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) { if (SERVICE_CHANGED_VALUE.equals(value)) { if (status == BluetoothGatt.GATT_SUCCESS) { // Waits until the GATT server sends the response, we can then do notify. notifyServiceChanged(); } else { notifyError("Failed to send result for service changed event"); } } else { // The reason not to check the status code is that we know there is a service // changed event coming later, sometimes the status code will be modified by // bt stack (133), but it's ok, as long as onServiceChanged is called, we then // know the request is successfully sent during this test session. sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_TRIGGER_ACTION); } } else if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) || BLE_CLIENT_ACTION_REQUEST_MTU_23.equals(mCurrentAction)) { if (status == BluetoothGatt.GATT_SUCCESS) { notifyMtuChanged(); } else { notifyError("Failed to write characteristic: " + status + " : " + uid); } } else { switch (mExecReliableWrite) { case RELIABLE_WRITE_NONE: if (status == BluetoothGatt.GATT_SUCCESS) { if (characteristic.getUuid().equals( CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID)) { notifyCharacteristicWriteNeedEncrypted(value); } else if (!characteristic.getUuid().equals( CHARACTERISTIC_RESULT_UUID)) { // verify if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), characteristic.getValue())) { notifyCharacteristicWrite(value); } else { notifyError("Written data is not correct"); } } } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) { if (uid.equals(CHARACTERISTIC_NO_WRITE_UUID)) { writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.WRITE_NO_PERMISSION); notifyCharacteristicWriteNoPermission(value); } else { notifyError("Not Permission Write: " + status + " : " + uid); } } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { notifyError("Not Authentication Write: " + status + " : " + uid); } else { notifyError("Failed to write characteristic: " + status + " : " + uid); } break; case RELIABLE_WRITE_WRITE_1ST_DATA: // verify if (WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE.equals(value)) { // write next data mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_WRITE_2ND_DATA; writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE); } else { notifyError("Failed to write characteristic: " + status + " : " + uid); } break; case RELIABLE_WRITE_WRITE_2ND_DATA: // verify if (WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE.equals(value)) { // execute write mTaskQueue.addTask(new Runnable() { @Override public void run() { mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_EXECUTE; if (!mBluetoothGatt.executeReliableWrite()) { notifyError("reliable write failed"); } } }, 1000); } else { notifyError("Failed to write characteristic: " + status + " : " + uid); } break; case RELIABLE_WRITE_BAD_RESP: mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_NONE; // verify response // Server sends empty response for this test. // Response must be empty. String err = null; if (!TextUtils.isEmpty(value)) { err = "response is not empty"; showMessage(err); } // finish reliable write final String errValue = err; mTaskQueue.addTask(new Runnable() { @Override public void run() { mBluetoothGatt.abortReliableWrite(); notifyReliableWriteBadRespCompleted(errValue); } }, 1000); break; } } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); // Note: Both this method and onCharacteristicRead(byte[]) will be called. UUID uid = characteristic.getUuid(); if (DEBUG) { Log.d(TAG, "onCharacteristicRead (deprecated): status=" + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, int status) { super.onCharacteristicRead(gatt, characteristic, value, status); UUID uid = characteristic.getUuid(); if (DEBUG) { Log.d(TAG, "onCharacteristicRead (memory safe version): status=" + status); } if (status == BluetoothGatt.GATT_SUCCESS) { String stringValue = new String(value); if (characteristic.getUuid().equals(CHARACTERISTIC_NEED_ENCRYPTED_READ_UUID)) { notifyCharacteristicReadNeedEncrypted(stringValue); } else { // verify if (BleServerService.WRITE_VALUE.equals(stringValue)) { notifyCharacteristicRead(stringValue); } else { notifyError("Read data is not correct"); } } } else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) { if (uid.equals(CHARACTERISTIC_NO_READ_UUID)) { writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.READ_NO_PERMISSION); notifyCharacteristicReadNoPermission(); } else { notifyError("Not Permission Read: " + status + " : " + uid); } } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { notifyError("Not Authentication Read: " + status + " : " + uid); } else { notifyError("Failed to read characteristic: " + status + " : " + uid); } } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); if (DEBUG) { Log.d(TAG, "onDescriptorWrite"); } UUID uid = descriptor.getUuid(); if ((status == BluetoothGatt.GATT_SUCCESS)) { if (uid.equals(UPDATE_DESCRIPTOR_UUID)) { Log.d(TAG, "write in update descriptor."); if (Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) { notifyDisableNotificationCompletion(); } } else if (uid.equals(DESCRIPTOR_UUID)) { // verify if (Arrays.equals(WRITE_VALUE.getBytes(), descriptor.getValue())) { notifyDescriptorWrite(new String(descriptor.getValue())); } else { notifyError("Written data is not correct"); } } else if (uid.equals(DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID)) { notifyDescriptorWriteNeedEncrypted(new String(descriptor.getValue())); } } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) { if (uid.equals(DESCRIPTOR_NO_WRITE_UUID)) { writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.DESCRIPTOR_WRITE_NO_PERMISSION); notifyDescriptorWriteNoPermission(); } else { notifyError("Not Permission Write: " + status + " : " + descriptor.getUuid()); } } else { notifyError("Failed to write descriptor"); } } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status); // Note: Both this method and onDescriptorRead(byte[]) will be called. if (DEBUG) { Log.d(TAG, "onDescriptorRead (deprecated)"); } } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status, byte[] value) { super.onDescriptorRead(gatt, descriptor, status, value); if (DEBUG) { Log.d(TAG, "onDescriptorRead (memory safe version)"); } UUID uid = descriptor.getUuid(); String stringValue = new String(value); if ((status == BluetoothGatt.GATT_SUCCESS)) { if ((uid != null) && (uid.equals(DESCRIPTOR_UUID))) { // verify if (BleServerService.WRITE_VALUE.equals(stringValue)) { notifyDescriptorRead(stringValue); } else { notifyError("Read data is not correct"); } } else if (uid.equals(DESCRIPTOR_NEED_ENCRYPTED_READ_UUID)) { notifyDescriptorReadNeedEncrypted(stringValue); } } else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) { if (uid.equals(DESCRIPTOR_NO_READ_UUID)) { writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.DESCRIPTOR_READ_NO_PERMISSION); notifyDescriptorReadNoPermission(); } else { notifyError("Not Permission Read: " + status + " : " + descriptor.getUuid()); } } else { notifyError("Failed to read descriptor: " + status); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); UUID uid = characteristic.getUuid(); // Note: Both this method and onCharacteristicChanged(byte[]) will be called. if (DEBUG) { Log.d(TAG, "onCharacteristicChanged (deprecated): uid=" + uid); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) { super.onCharacteristicChanged(gatt, characteristic, value); UUID uid = characteristic.getUuid(); if (DEBUG) { Log.d(TAG, "onCharacteristicChanged (memory safe version): uid=" + uid); } String stringValue = new String(value); if (uid != null) { if (uid.equals(INDICATE_CHARACTERISTIC_UUID) && BleServerService.INDICATE_VALUE.equals(stringValue)) { setNotification(characteristic, false); notifyCharacteristicIndicated(); } else if (BleServerService.NOTIFY_VALUE.equals(stringValue)) { mNotifyCount--; setNotification(characteristic, false); if (mNotifyCount == 0) { notifyCharacteristicChanged(); } } } } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { super.onReliableWriteCompleted(gatt, status); if (DEBUG) { Log.d(TAG, "onReliableWriteComplete: " + status); } if (mExecReliableWrite != ReliableWriteState.RELIABLE_WRITE_NONE) { if (status == BluetoothGatt.GATT_SUCCESS) { notifyReliableWriteCompleted(); } else { notifyError("Failed to complete reliable write: " + status); } mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_NONE; } } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { super.onReadRemoteRssi(gatt, rssi, status); if (DEBUG) { Log.d(TAG, "onReadRemoteRssi"); } if (status == BluetoothGatt.GATT_SUCCESS) { notifyReadRemoteRssi(rssi); } else { notifyError("Failed to read remote rssi"); } } @Override public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { super.onPhyRead(gatt, txPhy, rxPhy, status); if (DEBUG) { Log.d(TAG, "onPhyRead status=" + status); } if (status == BluetoothGatt.GATT_SUCCESS) { notifyPhyRead(txPhy, rxPhy); } else if (status == BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED) { notifyPhyReadSkipped(); } else { notifyError("Failed to read phy"); } } @Override public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { super.onPhyUpdate(gatt, txPhy, rxPhy, status); if (DEBUG) { Log.d(TAG, "onPhyUpdate status=" + status); } if (status == BluetoothGatt.GATT_SUCCESS) { notifyPhyUpdate(txPhy, rxPhy); } else if (status == BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED) { notifyPhyUpdateSkipped(); } else { notifyError("Failed to update phy"); } } @Override public void onServiceChanged(BluetoothGatt gatt) { super.onServiceChanged(gatt); if (DEBUG) { Log.d(TAG, "onServiceChanged"); } if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) { sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED); } } }; private final ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { if (mBluetoothGatt == null) { // verify the validity of the advertisement packet. mValidityService = false; List uuids = result.getScanRecord().getServiceUuids(); for (ParcelUuid uuid : uuids) { if (uuid.getUuid().equals(BleServerService.ADV_SERVICE_UUID)) { mValidityService = true; break; } } if (mValidityService) { stopScan(); BluetoothDevice device = result.getDevice(); if (mSecure) { if (device.getBondState() != BluetoothDevice.BOND_BONDED) { if (!device.createBond()) { notifyError("Failed to call create bond"); } } else { mBluetoothGatt = connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks); } } else { mBluetoothGatt = connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks); } } else { notifyError("There is no validity to Advertise servie."); } } } }; private void startScan() { if (DEBUG) Log.d(TAG, "startScan"); List filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid( new ParcelUuid(BleServerService.ADV_SERVICE_UUID)).build()); ScanSettings setting = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); mScanner.startScan(filter, setting, mScanCallback); } private void stopScan() { if (DEBUG) Log.d(TAG, "stopScan"); if (mScanner != null) { mScanner.stopScan(mScanCallback); } } private static String createTestData(String prefix, int length) { StringBuilder builder = new StringBuilder(); builder.append(prefix); int len = length - builder.length(); for (int i = 0; i < len; ++i) { builder.append("" + (i % 10)); } return builder.toString(); } private final BroadcastReceiver mBondStatusReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); switch (state) { case BluetoothDevice.BOND_BONDED: if ((mBluetoothGatt == null) && (device.getType() != BluetoothDevice.DEVICE_TYPE_CLASSIC)) { if (DEBUG) { Log.d(TAG, "onReceive:BOND_BONDED: calling connectGatt device=" + device + ", mSecure=" + mSecure); } mBluetoothGatt = connectGatt(device, mContext, false, mSecure, mGattCallbacks); } break; case BluetoothDevice.BOND_NONE: notifyError("Failed to create bond."); break; case BluetoothDevice.BOND_BONDING: default: // fall through // wait for next state break; } } } }; }