1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.pmc;
18 
19 import android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothGatt;
24 import android.bluetooth.BluetoothGattCallback;
25 import android.bluetooth.BluetoothGattCharacteristic;
26 import android.bluetooth.BluetoothGattDescriptor;
27 import android.bluetooth.BluetoothGattService;
28 import android.bluetooth.BluetoothProfile;
29 import android.bluetooth.le.BluetoothLeScanner;
30 import android.bluetooth.le.ScanCallback;
31 import android.bluetooth.le.ScanFilter;
32 import android.bluetooth.le.ScanResult;
33 import android.bluetooth.le.ScanSettings;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.os.SystemClock;
38 import android.util.Log;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.UUID;
43 
44 /**
45  * Class to provide Receiver for AlarmManager to start Gatt Client alarms
46  */
47 public class GattClientListener extends BroadcastReceiver {
48 
49     public static final String TAG = "GATTC";
50     public static final String GATTCLIENT_ALARM =
51                            "com.android.pmc.GATTClient.ALARM";
52     private static final int MILLSEC = 1000;
53     private static final int INIT_VALUE = 0;
54     private Context mContext;
55     private final AlarmManager mAlarmManager;
56 
57     private BluetoothAdapter mBluetoothAdapter;
58 
59     private BluetoothGatt mBluetoothGatt;
60     private GattCallback mGattCallback;
61 
62     private MyBleScanner mMyBleScanner;
63     private String mMacAddress;
64     private BluetoothDevice mDevice;
65     private int mWriteTime;
66     private int mIdleTime;
67     private int mCycles;
68 
69     /**
70      * Constructor
71      * @param context - system will provide a context to this function
72      * @param alarmManager - system will provide a AlarmManager to this function
73      */
GattClientListener(Context context, AlarmManager alarmManager)74     public GattClientListener(Context context, AlarmManager alarmManager) {
75         Log.d(TAG, "Start GattClientListener()");
76         mContext = context;
77         mAlarmManager = alarmManager;
78         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
79 
80         if (mBluetoothAdapter == null) {
81             Log.e(TAG, "BluetoothAdapter is Null");
82             return;
83         } else {
84             if (!mBluetoothAdapter.isEnabled()) {
85                 Log.d(TAG, "BluetoothAdapter is NOT enabled, enable now");
86                 mBluetoothAdapter.enable();
87                 if (!mBluetoothAdapter.isEnabled()) {
88                     Log.e(TAG, "Can't enable Bluetooth");
89                     return;
90                 }
91             }
92         }
93 
94         mMyBleScanner = new MyBleScanner(mBluetoothAdapter);
95         mGattCallback = new GattCallback();
96         mBluetoothGatt = null;
97         mMacAddress = null;
98         mDevice = null;
99         Log.d(TAG, "End GattClientListener");
100     }
101 
102     /**
103      * Function to be called to start alarm by PMC
104      *
105      * @param startTime - time (sec) when next GATT writing needs to be started
106      * @param writeTime - how long (sec) to write GATT characteristic
107      * @param idleTime - how long (sec) it doesn't need to wait
108      * @param numCycles - how many of cycles of writing with idle time
109      */
startAlarm(int startTime, int writeTime, int idleTime, int numCycles, Intent intent)110     public void startAlarm(int startTime, int writeTime, int idleTime, int numCycles,
111                     Intent intent) {
112 
113         int currentAlarm = 0;
114 
115         if (intent == null) {
116             // Start Scan here when this func is called for the first time
117             mMyBleScanner.startScan();
118             mWriteTime = writeTime;
119             mIdleTime = idleTime;
120             mCycles = numCycles;
121         } else {
122             // Get alarm number inside the intent
123             currentAlarm = intent.getIntExtra("com.android.pmc.GATTClient.CurrentAlarm", 0);
124         }
125         Log.d(TAG, "Current Cycle Num: " + currentAlarm);
126         if (currentAlarm >= mCycles) {
127             Log.d(TAG, "All alarms are done");
128             return;
129         }
130 
131         Intent alarmIntent = new Intent(GattClientListener.GATTCLIENT_ALARM);
132         alarmIntent.putExtra("com.android.pmc.GATTClient.CurrentAlarm", ++currentAlarm);
133 
134         long triggerTime = SystemClock.elapsedRealtime() + startTime * MILLSEC;
135         mAlarmManager.setExactAndAllowWhileIdle(
136                               AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime,
137                               PendingIntent.getBroadcast(mContext, 0,
138                                             alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT));
139     }
140 
141     /**
142      * Receive function will be called for AlarmManager to connect GATT
143      *    and then to write characteristic
144      *
145      * @param context - system will provide a context to this function
146      * @param intent - system will provide an intent to this function
147      */
148     @Override
onReceive(Context context, Intent intent)149     public void onReceive(Context context, Intent intent) {
150         Log.d(TAG, "onReceiver: " + intent.getAction());
151         if (!intent.getAction().equals(GATTCLIENT_ALARM)) {
152             return;
153         }
154 
155         if (mMacAddress == null) mMacAddress = mMyBleScanner.getAdvMacAddress();
156         if (mMacAddress == null || mMacAddress.isEmpty()) {
157             Log.e(TAG, "Remote device Mac Address is not set");
158             return;
159         }
160         if (mDevice == null) mDevice = mBluetoothAdapter.getRemoteDevice(mMacAddress);
161 
162         if (mBluetoothGatt == null) {
163             mBluetoothGatt = mDevice.connectGatt(mContext,
164                         false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
165         } else {
166             mBluetoothGatt.discoverServices();
167         }
168         // Start next alarm to connect again then to write
169         startAlarm((mWriteTime + mIdleTime), mWriteTime, mIdleTime, mCycles, intent);
170     }
171 
172     /**
173      * Callback for GATT Writing
174      */
175     class GattCallback extends BluetoothGattCallback {
176 
177         public static final int MAX_MTU = 511;
178         public static final int MAX_BYTES = 508;
179         private long mStartWriteTime;
180 
GattCallback()181         GattCallback() {}
182 
183         @Override
onConnectionStateChange(BluetoothGatt gatt, int status, int newState)184         public void onConnectionStateChange(BluetoothGatt gatt, int status,
185                     int newState) {
186             Log.d(TAG, "onConnectionStateChange " + status);
187             if (newState == BluetoothProfile.STATE_CONNECTED) {
188                 Log.d(TAG, "State Connected to mac address "
189                             + gatt.getDevice().getAddress() + " status " + status);
190                 // Discover services in advertiser, callback will be called
191                 mBluetoothGatt.discoverServices();
192 
193             } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
194                 Log.d(TAG, "State Disconnected from mac address "
195                             + gatt.getDevice().getAddress() + " status " + status);
196                 try {
197                     mBluetoothGatt.close();
198                 } catch (Exception e) {
199                     Log.e(TAG, "Close Gatt: " + e);
200                 }
201                 mBluetoothGatt = null;
202 
203             } else if (newState == BluetoothProfile.STATE_CONNECTING) {
204                 Log.d(TAG, "State Connecting to mac address "
205                             + gatt.getDevice().getAddress() + " status " + status);
206             } else if (newState == BluetoothProfile.STATE_DISCONNECTING) {
207                 Log.d(TAG, "State Disconnecting from mac address "
208                             + gatt.getDevice().getAddress() + " status " + status);
209             }
210         }
211 
212         @Override
onServicesDiscovered(BluetoothGatt gatt, int status)213         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
214             Log.d(TAG, "onServicesDiscovered Status " + status);
215             mBluetoothGatt.requestMtu(MAX_MTU);
216         }
217 
218         @Override
onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)219         public void onCharacteristicRead(BluetoothGatt gatt,
220                 BluetoothGattCharacteristic characteristic, int status) {
221             Log.d(TAG, "onCharacteristicRead: " + status);
222         }
223 
224         @Override
onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)225         public void onCharacteristicWrite(BluetoothGatt gatt,
226                     BluetoothGattCharacteristic characteristic, int status) {
227             Log.d(TAG, "onCharacteristicWrite: " + status);
228             long timeElapse = SystemClock.elapsedRealtime() - mStartWriteTime;
229             if (timeElapse < (mWriteTime * MILLSEC)) {
230                 writeCharacteristic(gatt, (int) (timeElapse / MILLSEC));
231             }
232         }
233 
234         @Override
onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)235         public void onCharacteristicChanged(BluetoothGatt gatt,
236                     BluetoothGattCharacteristic characteristic) {
237             Log.d(TAG, "onCharacteristicChanged");
238         }
239 
240         @Override
onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)241         public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
242                     int status) {
243             Log.d(TAG, "onServicesDiscovered: " + status);
244         }
245 
246         @Override
onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)247         public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
248                 int status) {
249             Log.d(TAG, "onDescriptorWrite: " + status);
250         }
251 
252         @Override
onReliableWriteCompleted(BluetoothGatt gatt, int status)253         public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
254             Log.d(TAG, "onReliableWriteCompleted: " + status);
255         }
256 
257         @Override
onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status)258         public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
259             Log.d(TAG, "onReadRemoteRssi: " + rssi + " status: " + status);
260         }
261 
262         @Override
onMtuChanged(BluetoothGatt gatt, int mtu, int status)263         public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
264             // Every time it disconnects/reconnects it needs to re-set MTU
265             Log.d(TAG, "onMtuChanged " + mtu + " status: " + status);
266             // First time to write a characteristic to GATT server
267             mStartWriteTime = SystemClock.elapsedRealtime();
268             writeCharacteristic(gatt, INIT_VALUE);
269         }
270 
271         /**
272          * Function to be called to write a new GATT characteristic
273          *
274          * @param gatt - BluetoothGatt object to write
275          * @param value - value to be set inside GATT characteristic
276          */
writeCharacteristic(BluetoothGatt gatt, int value)277         private void writeCharacteristic(BluetoothGatt gatt, int value) {
278             UUID sUuid = UUID.fromString(GattServer.TEST_SERVICE_UUID);
279             BluetoothGattService service = gatt.getService(sUuid);
280             if (service == null) {
281                 Log.e(TAG, "service not found!");
282                 return;
283             }
284             UUID cUuid = UUID.fromString(GattServer.WRITABLE_CHAR_UUID);
285             BluetoothGattCharacteristic characteristic = service.getCharacteristic(cUuid);
286 
287             if (characteristic == null) {
288                 Log.e(TAG, "Characteristic not found!");
289                 return;
290             }
291 
292             byte[] byteValue = new byte[MAX_BYTES];
293 
294             for (int i = 0; i < MAX_BYTES; i++) {
295                 byteValue[i] = (byte) value;
296             }
297 
298             characteristic.setValue(byteValue);
299             characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
300             gatt.writeCharacteristic(characteristic);
301         }
302     }
303 
304     /**
305      * Class to provide Ble Scanner functionalities
306      */
307     class MyBleScanner {
308 
309         private BluetoothLeScanner mBLEScanner;
310         private ScanSettings mScanSettings;
311         private List<ScanFilter> mScanFilterList;
312         private MyScanCallback mScanCallback;
313         private String mAdvMacAddress = null;
314 
315         /**
316          * Constructor
317          * @param context - system will provide a context to this function
318          */
MyBleScanner(BluetoothAdapter bluetoothAdapter)319         MyBleScanner(BluetoothAdapter bluetoothAdapter) {
320 
321             mBLEScanner = bluetoothAdapter.getBluetoothLeScanner();
322             mScanFilterList = new ArrayList<ScanFilter>();
323             mScanSettings = new ScanSettings.Builder().setScanMode(
324                                             ScanSettings.SCAN_MODE_LOW_LATENCY).build();
325             mScanCallback = new MyScanCallback();
326         }
327 
328         /**
329          * Wrapper function to start BLE Scanning
330          */
startScan()331         public void startScan() {
332             // Start Scan here when this func is called for the first time
333             if (mBLEScanner != null) {
334                 mBLEScanner.startScan(mScanFilterList, mScanSettings, mScanCallback);
335             } else {
336                 Log.e(TAG, "BLEScanner is null");
337             }
338 
339         }
340 
341         /**
342          * Wrapper function to stop BLE Scanning
343          */
stopScan()344         public void stopScan() {
345             if (mBLEScanner != null) {
346                 mBLEScanner.stopScan(mScanCallback);
347             } else {
348                 Log.e(TAG, "BLEScanner is null");
349             }
350         }
351 
352         /**
353          * function to get Mac Address for BLE Advertiser device
354          */
getAdvMacAddress()355         public String getAdvMacAddress() {
356             // Return Mac address for Advertiser device
357             return mAdvMacAddress;
358         }
359 
360         /**
361          * Class to provide callback for BLE Scanning
362          */
363         class MyScanCallback extends ScanCallback {
364 
365             @Override
onScanResult(int callbackType, ScanResult result)366             public void onScanResult(int callbackType, ScanResult result) {
367                 Log.d(TAG, "Bluetooth scan result: " + result.toString());
368                 BluetoothDevice device = result.getDevice();
369                 if (mAdvMacAddress == null) {
370                     mAdvMacAddress = device.getAddress();
371                     Log.d(TAG, "Bluetooth Address: " + mAdvMacAddress);
372                 }
373                 mBLEScanner.stopScan(mScanCallback);
374                 Log.d(TAG, "Bluetooth scan result: end ");
375             }
376 
377             @Override
onScanFailed(int errorCode)378             public void onScanFailed(int errorCode) {
379                 Log.e(TAG, "Scan Failed: " + errorCode);
380             }
381         }
382     }
383 }
384 
385