/* * Copyright 2018 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 android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.BluetoothLeAdvertiser; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.ParcelUuid; import android.util.Log; import android.widget.Toast; import com.android.cts.verifier.R; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.UUID; public class BleCocServerService extends Service { public static final boolean DEBUG = true; public static final String TAG = "BleCocServerService"; public static final int COMMAND_ADD_SERVICE = 0; public static final int COMMAND_WRITE_CHARACTERISTIC = 1; public static final int COMMAND_WRITE_DESCRIPTOR = 2; public static final int TEST_DATA_EXCHANGE_BUFSIZE = 8 * 1024; 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_ACTION_COC_SERVER_INSECURE = "com.android.cts.verifier.bluetooth.BLE_ACTION_COC_SERVER_INSECURE"; public static final String BLE_ACTION_COC_SERVER_SECURE = "com.android.cts.verifier.bluetooth.BLE_ACTION_COC_SERVER_SECURE"; public static final String BLE_ACTION_SERVER_SECURE = "com.android.cts.verifier.bluetooth.BLE_ACTION_SERVER_SECURE"; public static final String BLE_ACTION_SERVER_NON_SECURE = "com.android.cts.verifier.bluetooth.BLE_ACTION_SERVER_NON_SECURE"; public static final String BLE_LE_CONNECTED = "com.android.cts.verifier.bluetooth.BLE_LE_CONNECTED"; public static final String BLE_COC_LISTENER_CREATED = "com.android.cts.verifier.bluetooth.BLE_COC_LISTENER_CREATED"; public static final String BLE_PSM_READ = "com.android.cts.verifier.bluetooth.BLE_PSM_READ"; public static final String BLE_COC_CONNECTED = "com.android.cts.verifier.bluetooth.BLE_COC_CONNECTED"; public static final String BLE_CONNECTION_TYPE_CHECKED = "com.android.cts.verifier.bluetooth.BLE_CONNECTION_TYPE_CHECKED"; public static final String BLE_DATA_8BYTES_READ = "com.android.cts.verifier.bluetooth.BLE_DATA_8BYTES_READ"; public static final String BLE_DATA_LARGEBUF_READ = "com.android.cts.verifier.bluetooth.BLE_DATA_LARGEBUF_READ"; public static final String BLE_DATA_8BYTES_SENT = "com.android.cts.verifier.bluetooth.BLE_DATA_8BYTES_SENT"; public static final String BLE_LE_DISCONNECTED = "com.android.cts.verifier.bluetooth.BLE_LE_DISCONNECTED"; public static final String BLE_COC_SERVER_ACTION_SEND_DATA_8BYTES = "com.android.cts.verifier.bluetooth.BLE_COC_SERVER_ACTION_SEND_DATA_8BYTES"; public static final String BLE_COC_SERVER_ACTION_EXCHANGE_DATA = "com.android.cts.verifier.bluetooth.BLE_COC_SERVER_ACTION_EXCHANGE_DATA"; public static final String BLE_COC_SERVER_ACTION_DISCONNECT = "com.android.cts.verifier.bluetooth.BLE_COC_SERVER_ACTION_DISCONNECT"; public static final String BLE_SERVER_DISCONNECTED = "com.android.cts.verifier.bluetooth.BLE_SERVER_DISCONNECTED"; public static final String BLE_OPEN_FAIL = "com.android.cts.verifier.bluetooth.BLE_OPEN_FAIL"; public static final String BLE_ADVERTISE_UNSUPPORTED = "com.android.cts.verifier.bluetooth.BLE_ADVERTISE_UNSUPPORTED"; public static final String BLE_ADD_SERVICE_FAIL = "com.android.cts.verifier.bluetooth.BLE_ADD_SERVICE_FAIL"; 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"); public static final UUID ADV_COC_SERVICE_UUID= UUID.fromString("00003334-0000-1000-8000-00805f9b34fb"); private static final UUID SERVICE_UUID_ADDITIONAL = UUID.fromString("00009995-0000-1000-8000-00805f9b34fb"); private static final UUID SERVICE_UUID_INCLUDED = UUID.fromString("00009994-0000-1000-8000-00805f9b34fb"); // Variable 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"); private static final int CONN_INTERVAL = 150; // connection interval 150ms private static final int EXECUTION_DELAY = 1500; // Delay of notification when secure test failed to start. private static final long NOTIFICATION_DELAY_OF_SECURE_TEST_FAILURE = 5 * 1000; public static final String WRITE_VALUE = "SERVER_TEST"; private static final String NOTIFY_VALUE = "NOTIFY_TEST"; private static final String INDICATE_VALUE = "INDICATE_TEST"; public static final String READ_NO_PERMISSION = "READ_NO_CHAR"; public static final String WRITE_NO_PERMISSION = "WRITE_NO_CHAR"; public static final String DESCRIPTOR_READ_NO_PERMISSION = "READ_NO_DESC"; public static final String DESCRIPTOR_WRITE_NO_PERMISSION = "WRITE_NO_DESC"; private BluetoothManager mBluetoothManager; private BluetoothGattServer mGattServer; private BluetoothGattService mService; private BluetoothDevice mDevice; private Handler mHandler; private BluetoothLeAdvertiser mAdvertiser; private boolean mSecure; private int mMtuSize = -1; private BluetoothServerSocket mServerSocket; private int mPsm = -1; private BluetoothGattCharacteristic mLePsmCharacteristic; BluetoothChatService mChatService; private int mNextReadExpectedLen = -1; private String mNextReadCompletionIntent; private int mTotalReadLen = 0; private byte mNextReadByte; private int mNextWriteExpectedLen = -1; private String mNextWriteCompletionIntent = null; // Handler for communicating task with peer. private TestTaskQueue mTaskQueue; // current test category private String mCurrentAction; // Task to notify failure of starting secure test. // Secure test calls BluetoothDevice#createBond() when devices were not paired. // createBond() causes onConnectionStateChange() twice, and it works as strange sequence. // At the first onConnectionStateChange(), target device is not paired (bond state is // BluetoothDevice.BOND_NONE). // At the second onConnectionStateChange(), target devices is paired (bond state is // BluetoothDevice.BOND_BONDED). // CTS Verifier will perform lazy check of bond state. Verifier checks bond state // after NOTIFICATION_DELAY_OF_SECURE_TEST_FAILURE from the first onConnectionStateChange(). private Runnable mNotificationTaskOfSecureTestStartFailure; @Override public void onCreate() { super.onCreate(); mTaskQueue = new TestTaskQueue(getClass().getName() + "_taskHandlerThread"); mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser(); mGattServer = mBluetoothManager.openGattServer(this, mCallbacks); mService = createService(); mDevice = null; mHandler = new Handler(); if (!mBluetoothManager.getAdapter().isEnabled()) { notifyBluetoothDisabled(); } else if (mGattServer == null) { notifyOpenFail(); } else if (mAdvertiser == null) { notifyAdvertiseUnsupported(); } else { // start adding services mSecure = false; if (!mGattServer.addService(mService)) { notifyAddServiceFail(); } } } private void notifyBluetoothDisabled() { Intent intent = new Intent(BLE_BLUETOOTH_DISABLED); sendBroadcast(intent); } private void notifyMismatchSecure() { Intent intent = new Intent(BLE_BLUETOOTH_MISMATCH_SECURE); sendBroadcast(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String action = intent.getAction(); if (action != null) { if (DEBUG) { Log.d(TAG, "onStartCommand: action=" + action); } mTaskQueue.addTask(new Runnable() { @Override public void run() { onTestFinish(intent.getAction()); } }, EXECUTION_DELAY); } return START_NOT_STICKY; } private void startServerTest(boolean secure) { mSecure = secure; if (mBluetoothManager.getAdapter().isEnabled() && (mChatService == null)) { createChatService(); } if (mBluetoothManager.getAdapter().isEnabled() && (mAdvertiser != null)) { startAdvertise(); } } private void sendMessage(byte[] buf) { mChatService.write(buf); } private void sendData8bytes() { if (DEBUG) Log.d(TAG, "sendData8bytes"); final byte[] buf = new byte[]{1,2,3,4,5,6,7,8}; mNextWriteExpectedLen = 8; mNextWriteCompletionIntent = BLE_DATA_8BYTES_SENT; sendMessage(buf); } private void sendDataLargeBuf() { final int len = BleCocServerService.TEST_DATA_EXCHANGE_BUFSIZE; if (DEBUG) Log.d(TAG, "sendDataLargeBuf of size=" + len); byte[] buf = new byte[len]; for (int i = 0; i < len; i++) { buf[i] = (byte)(i + 1); } mNextWriteExpectedLen = len; mNextWriteCompletionIntent = null; sendMessage(buf); } private void onTestFinish(String action) { mCurrentAction = action; if (mCurrentAction != null) { switch (mCurrentAction) { case BLE_ACTION_COC_SERVER_INSECURE: startServerTest(false); break; case BLE_ACTION_COC_SERVER_SECURE: startServerTest(true); break; case BLE_COC_SERVER_ACTION_SEND_DATA_8BYTES: sendData8bytes(); break; case BLE_COC_SERVER_ACTION_EXCHANGE_DATA: sendDataLargeBuf(); break; case BLE_COC_SERVER_ACTION_DISCONNECT: if (mChatService != null) { mChatService.stop(); } notifyDisconnected(); break; default: Log.e(TAG, "Error: Unhandled or invalid action=" + mCurrentAction); } } } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (mChatService != null) { mChatService.stop(); } cancelNotificationTaskOfSecureTestStartFailure(); stopAdvertise(); mTaskQueue.quit(); if (mGattServer == null) { return; } if (mDevice != null) { mGattServer.cancelConnection(mDevice); } mGattServer.clearServices(); mGattServer.close(); } private void notifyOpenFail() { if (DEBUG) { Log.d(TAG, "notifyOpenFail"); } Intent intent = new Intent(BLE_OPEN_FAIL); sendBroadcast(intent); } private void notifyAddServiceFail() { if (DEBUG) { Log.d(TAG, "notifyAddServiceFail"); } Intent intent = new Intent(BLE_ADD_SERVICE_FAIL); sendBroadcast(intent); } private void notifyAdvertiseUnsupported() { if (DEBUG) { Log.d(TAG, "notifyAdvertiseUnsupported"); } Intent intent = new Intent(BLE_ADVERTISE_UNSUPPORTED); sendBroadcast(intent); } private void notifyConnected() { if (DEBUG) { Log.d(TAG, "notifyConnected"); } Intent intent = new Intent(BLE_LE_CONNECTED); sendBroadcast(intent); } private void notifyDisconnected() { if (DEBUG) { Log.d(TAG, "notifyDisconnected"); } Intent intent = new Intent(BLE_SERVER_DISCONNECTED); sendBroadcast(intent); } private BluetoothGattService createService() { BluetoothGattService service = new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_UUID, 0x0A, 0x11); characteristic.setValue(WRITE_VALUE.getBytes()); BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0x11); descriptor.setValue(WRITE_VALUE.getBytes()); characteristic.addDescriptor(descriptor); BluetoothGattDescriptor descriptor_permission = new BluetoothGattDescriptor(DESCRIPTOR_NO_READ_UUID, 0x10); characteristic.addDescriptor(descriptor_permission); descriptor_permission = new BluetoothGattDescriptor(DESCRIPTOR_NO_WRITE_UUID, 0x01); characteristic.addDescriptor(descriptor_permission); service.addCharacteristic(characteristic); // Registered the characteristic of PSM Value mLePsmCharacteristic = new BluetoothGattCharacteristic(BleCocClientService.LE_PSM_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ); service.addCharacteristic(mLePsmCharacteristic); return service; } private void showMessage(final String msg) { mHandler.post(new Runnable() { public void run() { Toast.makeText(BleCocServerService.this, msg, Toast.LENGTH_SHORT).show(); } }); } private synchronized void cancelNotificationTaskOfSecureTestStartFailure() { if (mNotificationTaskOfSecureTestStartFailure != null) { mHandler.removeCallbacks(mNotificationTaskOfSecureTestStartFailure); mNotificationTaskOfSecureTestStartFailure = null; } } private final BluetoothGattServerCallback mCallbacks = new BluetoothGattServerCallback() { @Override public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { if (DEBUG) { Log.d(TAG, "onConnectionStateChange: newState=" + newState); } if (status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { mDevice = device; boolean bonded = false; Set pairedDevices = mBluetoothManager.getAdapter().getBondedDevices(); if (pairedDevices.size() > 0) { for (BluetoothDevice target : pairedDevices) { if (target.getAddress().equals(device.getAddress())) { bonded = true; break; } } } if (mSecure && ((device.getBondState() == BluetoothDevice.BOND_NONE) || !bonded)) { // not pairing and execute Secure Test Log.e(TAG, "BluetoothGattServerCallback.onConnectionStateChange: " + "Not paired but execute secure test"); cancelNotificationTaskOfSecureTestStartFailure(); } else if (!mSecure && ((device.getBondState() != BluetoothDevice.BOND_NONE) || bonded)) { // already pairing and execute Insecure Test Log.e(TAG, "BluetoothGattServerCallback.onConnectionStateChange: " + "Paired but execute insecure test"); } else { cancelNotificationTaskOfSecureTestStartFailure(); } notifyConnected(); } else if (status == BluetoothProfile.STATE_DISCONNECTED) { notifyDisconnected(); mDevice = null; } } } @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { if (mGattServer == null) { if (DEBUG) { Log.d(TAG, "GattServer is null, return"); } return; } if (DEBUG) { Log.d(TAG, "onCharacteristicReadRequest()"); } boolean finished = false; byte[] value = null; if (mMtuSize > 0) { byte[] buf = characteristic.getValue(); if (buf != null) { int len = Math.min((buf.length - offset), mMtuSize); if (len > 0) { value = Arrays.copyOfRange(buf, offset, (offset + len)); } finished = ((offset + len) >= buf.length); if (finished) { Log.d(TAG, "sent whole data: " + (new String(characteristic.getValue()))); } } } else { value = characteristic.getValue(); finished = true; } mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); UUID uid = characteristic.getUuid(); if (uid.equals(BleCocClientService.LE_PSM_CHARACTERISTIC_UUID)) { Log.d(TAG, "onCharacteristicReadRequest: reading PSM"); } } }; private void leCheckConnectionType() { if (mChatService == null) { Log.e(TAG, "leCheckConnectionType: no LE Coc connection"); return; } int type = mChatService.getSocketConnectionType(); if (type != BluetoothSocket.TYPE_L2CAP) { Log.e(TAG, "leCheckConnectionType: invalid connection type=" + type); return; } showMessage("LE CoC Connection Type Checked"); Intent intent = new Intent(BLE_CONNECTION_TYPE_CHECKED); sendBroadcast(intent); } private void readData8bytes() { mNextReadExpectedLen = 8; mTotalReadLen = 0; mNextReadCompletionIntent = BLE_DATA_8BYTES_READ; mNextReadByte = 1; } private void readDataLargeBuf() { mNextReadExpectedLen = BleCocServerService.TEST_DATA_EXCHANGE_BUFSIZE; mTotalReadLen = 0; mNextReadCompletionIntent = BLE_DATA_LARGEBUF_READ; mNextReadByte = 1; } private void processChatStateChange(int newState) { Intent intent; if (DEBUG) { Log.d(TAG, "processChatStateChange: newState=" + newState); } switch (newState) { case BluetoothChatService.STATE_LISTEN: intent = new Intent(BLE_COC_LISTENER_CREATED); sendBroadcast(intent); break; case BluetoothChatService.STATE_CONNECTED: intent = new Intent(BLE_COC_CONNECTED); sendBroadcast(intent); // Check the connection type leCheckConnectionType(); // Prepare the next data read readData8bytes(); break; } } private boolean checkReadBufContent(byte[] buf, int len) { // Check that the content is correct for (int i = 0; i < len; i++) { if (buf[i] != mNextReadByte) { Log.e(TAG, "handleMessageRead: Error: wrong byte content. buf[" + i + "]=" + buf[i] + " not equal to " + mNextReadByte); return false; } mNextReadByte++; } return true; } private void handleMessageRead(Message msg) { byte[] buf = (byte[])msg.obj; int len = msg.arg1; if (len <= 0) { return; } mTotalReadLen += len; if (DEBUG) { Log.d(TAG, "handleMessageRead: receive buffer of length=" + len + ", mTotalReadLen=" + mTotalReadLen + ", mNextReadExpectedLen=" + mNextReadExpectedLen); } if (mNextReadExpectedLen == mTotalReadLen) { if (!checkReadBufContent(buf, len)) { mNextReadExpectedLen = -1; return; } showMessage("Read " + len + " bytes"); if (DEBUG) { Log.d(TAG, "handleMessageRead: broadcast intent " + mNextReadCompletionIntent); } Intent intent = new Intent(mNextReadCompletionIntent); sendBroadcast(intent); mTotalReadLen = 0; if (mNextReadCompletionIntent.equals(BLE_DATA_8BYTES_READ)) { // Keeps the logic same as in the client code, to make sure that the expectation is // set before we receive the next bunch of data. readDataLargeBuf(); } else { mNextReadExpectedLen = -1; mNextReadCompletionIntent = null; } } else if (mNextReadExpectedLen > mTotalReadLen) { if (!checkReadBufContent(buf, len)) { mNextReadExpectedLen = -1; return; } } else if (mNextReadExpectedLen < mTotalReadLen) { Log.e(TAG, "handleMessageRead: Unexpected receive buffer of length=" + len + ", expected len=" + mNextReadExpectedLen); } } private void handleMessageWrite(Message msg) { byte[] buffer = (byte[]) msg.obj; int len = buffer.length; showMessage("LE CoC Server wrote " + len + " bytes" + ", mNextWriteExpectedLen=" + mNextWriteExpectedLen); if (len == mNextWriteExpectedLen) { if (mNextWriteCompletionIntent != null) { Intent intent = new Intent(mNextWriteCompletionIntent); sendBroadcast(intent); } } else { Log.d(TAG, "handleMessageWrite: unrecognized length=" + len); } mNextWriteCompletionIntent = null; mNextWriteExpectedLen = -1; } private class ChatHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (DEBUG) { Log.d(TAG, "ChatHandler.handleMessage: msg=" + msg); } switch (msg.what) { case BluetoothChatService.MESSAGE_STATE_CHANGE: processChatStateChange(msg.arg1); break; case BluetoothChatService.MESSAGE_READ: handleMessageRead(msg); break; case BluetoothChatService.MESSAGE_WRITE: handleMessageWrite(msg); break; } } } /* Start the Chat Service to create the Bluetooth Server Socket for LE CoC */ private void createChatService() { mChatService = new BluetoothChatService(this, new ChatHandler(), true); mChatService.start(mSecure); mPsm = mChatService.getPsm(mSecure); if (DEBUG) { Log.d(TAG, "createChatService: assigned PSM=" + mPsm); } if (mPsm > 0x00ff) { Log.e(TAG, "createChatService: Invalid PSM=" + mPsm); } // Notify that the PSM is read Intent intent = new Intent(BLE_PSM_READ); sendBroadcast(intent); // Set the PSM value in the PSM characteristics in the GATT Server. mLePsmCharacteristic.setValue(mPsm, BluetoothGattCharacteristic.FORMAT_UINT8, 0); } private void startAdvertise() { if (DEBUG) { Log.d(TAG, "startAdvertise"); } AdvertiseData data = new AdvertiseData.Builder() .addServiceData(new ParcelUuid(ADV_COC_SERVICE_UUID), new byte[]{1,2,3}) .addServiceUuid(new ParcelUuid(ADV_COC_SERVICE_UUID)) .build(); AdvertiseSettings setting = new AdvertiseSettings.Builder() .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) .setConnectable(true) .build(); mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback); } private void stopAdvertise() { if (DEBUG) { Log.d(TAG, "stopAdvertise"); } if (mAdvertiser != null) { mAdvertiser.stopAdvertising(mAdvertiseCallback); } } private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){ @Override public void onStartFailure(int errorCode) { // Implementation for API Test. super.onStartFailure(errorCode); if (DEBUG) { Log.d(TAG, "onStartFailure"); } if (errorCode == ADVERTISE_FAILED_FEATURE_UNSUPPORTED) { notifyAdvertiseUnsupported(); } else { notifyOpenFail(); } } @Override public void onStartSuccess(AdvertiseSettings settingsInEffect) { // Implementation for API Test. super.onStartSuccess(settingsInEffect); if (DEBUG) { Log.d(TAG, "onStartSuccess"); } } }; /*protected*/ static void dumpService(BluetoothGattService service, int level) { String indent = ""; for (int i = 0; i < level; ++i) { indent += " "; } Log.d(TAG, indent + "[service]"); Log.d(TAG, indent + "UUID: " + service.getUuid()); Log.d(TAG, indent + " [characteristics]"); for (BluetoothGattCharacteristic ch : service.getCharacteristics()) { Log.d(TAG, indent + " UUID: " + ch.getUuid()); Log.d(TAG, indent + " properties: " + String.format("0x%02X", ch.getProperties())); Log.d(TAG, indent + " permissions: " + String.format("0x%02X", ch.getPermissions())); Log.d(TAG, indent + " [descriptors]"); for (BluetoothGattDescriptor d : ch.getDescriptors()) { Log.d(TAG, indent + " UUID: " + d.getUuid()); Log.d(TAG, indent + " permissions: " + String.format("0x%02X", d.getPermissions())); } } if (service.getIncludedServices() != null) { Log.d(TAG, indent + " [included services]"); for (BluetoothGattService s : service.getIncludedServices()) { dumpService(s, level + 1); } } } }