blob: a99f41cde244cd6b9a533736618be019a9a5fab6 [file] [log] [blame]
/*
* 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<ScanFilter> 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<ScanFilter>();
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);
}
}
}
}