/* * Copyright (C) 2017 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.pmc; import android.app.AlarmManager; import android.app.PendingIntent; 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.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.os.SystemClock; import android.util.Log; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * Class to provide Receiver for AlarmManager to start Gatt Client alarms */ public class GattClientListener extends BroadcastReceiver { public static final String TAG = "GATTC"; public static final String GATTCLIENT_ALARM = "com.android.pmc.GATTClient.ALARM"; private static final int MILLSEC = 1000; private static final int INIT_VALUE = 0; private Context mContext; private final AlarmManager mAlarmManager; private BluetoothAdapter mBluetoothAdapter; private BluetoothGatt mBluetoothGatt; private GattCallback mGattCallback; private MyBleScanner mMyBleScanner; private String mMacAddress; private BluetoothDevice mDevice; private int mWriteTime; private int mIdleTime; private int mCycles; /** * Constructor * @param context - system will provide a context to this function * @param alarmManager - system will provide a AlarmManager to this function */ public GattClientListener(Context context, AlarmManager alarmManager) { Log.d(TAG, "Start GattClientListener()"); mContext = context; mAlarmManager = alarmManager; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "BluetoothAdapter is Null"); return; } else { if (!mBluetoothAdapter.isEnabled()) { Log.d(TAG, "BluetoothAdapter is NOT enabled, enable now"); mBluetoothAdapter.enable(); if (!mBluetoothAdapter.isEnabled()) { Log.e(TAG, "Can't enable Bluetooth"); return; } } } mMyBleScanner = new MyBleScanner(mBluetoothAdapter); mGattCallback = new GattCallback(); mBluetoothGatt = null; mMacAddress = null; mDevice = null; Log.d(TAG, "End GattClientListener"); } /** * Function to be called to start alarm by PMC * * @param startTime - time (sec) when next GATT writing needs to be started * @param writeTime - how long (sec) to write GATT characteristic * @param idleTime - how long (sec) it doesn't need to wait * @param numCycles - how many of cycles of writing with idle time */ public void startAlarm(int startTime, int writeTime, int idleTime, int numCycles, Intent intent) { int currentAlarm = 0; if (intent == null) { // Start Scan here when this func is called for the first time mMyBleScanner.startScan(); mWriteTime = writeTime; mIdleTime = idleTime; mCycles = numCycles; } else { // Get alarm number inside the intent currentAlarm = intent.getIntExtra("com.android.pmc.GATTClient.CurrentAlarm", 0); } Log.d(TAG, "Current Cycle Num: " + currentAlarm); if (currentAlarm >= mCycles) { Log.d(TAG, "All alarms are done"); return; } Intent alarmIntent = new Intent(GattClientListener.GATTCLIENT_ALARM); alarmIntent.putExtra("com.android.pmc.GATTClient.CurrentAlarm", ++currentAlarm); long triggerTime = SystemClock.elapsedRealtime() + startTime * MILLSEC; mAlarmManager.setExactAndAllowWhileIdle( AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)); } /** * Receive function will be called for AlarmManager to connect GATT * and then to write characteristic * * @param context - system will provide a context to this function * @param intent - system will provide an intent to this function */ @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceiver: " + intent.getAction()); if (!intent.getAction().equals(GATTCLIENT_ALARM)) { return; } if (mMacAddress == null) mMacAddress = mMyBleScanner.getAdvMacAddress(); if (mMacAddress == null || mMacAddress.isEmpty()) { Log.e(TAG, "Remote device Mac Address is not set"); return; } if (mDevice == null) mDevice = mBluetoothAdapter.getRemoteDevice(mMacAddress); if (mBluetoothGatt == null) { mBluetoothGatt = mDevice.connectGatt(mContext, false, mGattCallback, BluetoothDevice.TRANSPORT_LE); } else { mBluetoothGatt.discoverServices(); } // Start next alarm to connect again then to write startAlarm((mWriteTime + mIdleTime), mWriteTime, mIdleTime, mCycles, intent); } /** * Callback for GATT Writing */ class GattCallback extends BluetoothGattCallback { public static final int MAX_MTU = 511; public static final int MAX_BYTES = 508; private long mStartWriteTime; GattCallback() {} @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Log.d(TAG, "onConnectionStateChange " + status); if (newState == BluetoothProfile.STATE_CONNECTED) { Log.d(TAG, "State Connected to mac address " + gatt.getDevice().getAddress() + " status " + status); // Discover services in advertiser, callback will be called mBluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d(TAG, "State Disconnected from mac address " + gatt.getDevice().getAddress() + " status " + status); try { mBluetoothGatt.close(); } catch (Exception e) { Log.e(TAG, "Close Gatt: " + e); } mBluetoothGatt = null; } else if (newState == BluetoothProfile.STATE_CONNECTING) { Log.d(TAG, "State Connecting to mac address " + gatt.getDevice().getAddress() + " status " + status); } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { Log.d(TAG, "State Disconnecting from mac address " + gatt.getDevice().getAddress() + " status " + status); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.d(TAG, "onServicesDiscovered Status " + status); mBluetoothGatt.requestMtu(MAX_MTU); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.d(TAG, "onCharacteristicRead: " + status); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.d(TAG, "onCharacteristicWrite: " + status); long timeElapse = SystemClock.elapsedRealtime() - mStartWriteTime; if (timeElapse < (mWriteTime * MILLSEC)) { writeCharacteristic(gatt, (int) (timeElapse / MILLSEC)); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.d(TAG, "onCharacteristicChanged"); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { Log.d(TAG, "onServicesDiscovered: " + status); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { Log.d(TAG, "onDescriptorWrite: " + status); } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { Log.d(TAG, "onReliableWriteCompleted: " + status); } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { Log.d(TAG, "onReadRemoteRssi: " + rssi + " status: " + status); } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { // Every time it disconnects/reconnects it needs to re-set MTU Log.d(TAG, "onMtuChanged " + mtu + " status: " + status); // First time to write a characteristic to GATT server mStartWriteTime = SystemClock.elapsedRealtime(); writeCharacteristic(gatt, INIT_VALUE); } /** * Function to be called to write a new GATT characteristic * * @param gatt - BluetoothGatt object to write * @param value - value to be set inside GATT characteristic */ private void writeCharacteristic(BluetoothGatt gatt, int value) { UUID sUuid = UUID.fromString(GattServer.TEST_SERVICE_UUID); BluetoothGattService service = gatt.getService(sUuid); if (service == null) { Log.e(TAG, "service not found!"); return; } UUID cUuid = UUID.fromString(GattServer.WRITABLE_CHAR_UUID); BluetoothGattCharacteristic characteristic = service.getCharacteristic(cUuid); if (characteristic == null) { Log.e(TAG, "Characteristic not found!"); return; } byte[] byteValue = new byte[MAX_BYTES]; for (int i = 0; i < MAX_BYTES; i++) { byteValue[i] = (byte) value; } characteristic.setValue(byteValue); characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); gatt.writeCharacteristic(characteristic); } } /** * Class to provide Ble Scanner functionalities */ class MyBleScanner { private BluetoothLeScanner mBLEScanner; private ScanSettings mScanSettings; private List mScanFilterList; private MyScanCallback mScanCallback; private String mAdvMacAddress = null; /** * Constructor * @param context - system will provide a context to this function */ MyBleScanner(BluetoothAdapter bluetoothAdapter) { mBLEScanner = bluetoothAdapter.getBluetoothLeScanner(); mScanFilterList = new ArrayList(); mScanSettings = new ScanSettings.Builder().setScanMode( ScanSettings.SCAN_MODE_LOW_LATENCY).build(); mScanCallback = new MyScanCallback(); } /** * Wrapper function to start BLE Scanning */ public void startScan() { // Start Scan here when this func is called for the first time if (mBLEScanner != null) { mBLEScanner.startScan(mScanFilterList, mScanSettings, mScanCallback); } else { Log.e(TAG, "BLEScanner is null"); } } /** * Wrapper function to stop BLE Scanning */ public void stopScan() { if (mBLEScanner != null) { mBLEScanner.stopScan(mScanCallback); } else { Log.e(TAG, "BLEScanner is null"); } } /** * function to get Mac Address for BLE Advertiser device */ public String getAdvMacAddress() { // Return Mac address for Advertiser device return mAdvMacAddress; } /** * Class to provide callback for BLE Scanning */ class MyScanCallback extends ScanCallback { @Override public void onScanResult(int callbackType, ScanResult result) { Log.d(TAG, "Bluetooth scan result: " + result.toString()); BluetoothDevice device = result.getDevice(); if (mAdvMacAddress == null) { mAdvMacAddress = device.getAddress(); Log.d(TAG, "Bluetooth Address: " + mAdvMacAddress); } mBLEScanner.stopScan(mScanCallback); Log.d(TAG, "Bluetooth scan result: end "); } @Override public void onScanFailed(int errorCode) { Log.e(TAG, "Scan Failed: " + errorCode); } } } }