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
17package com.android.pmc;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothGatt;
24import android.bluetooth.BluetoothGattCallback;
25import android.bluetooth.BluetoothGattCharacteristic;
26import android.bluetooth.BluetoothGattDescriptor;
27import android.bluetooth.BluetoothGattService;
28import android.bluetooth.BluetoothProfile;
29import android.bluetooth.le.BluetoothLeScanner;
30import android.bluetooth.le.ScanCallback;
31import android.bluetooth.le.ScanFilter;
32import android.bluetooth.le.ScanResult;
33import android.bluetooth.le.ScanSettings;
34import android.content.BroadcastReceiver;
35import android.content.Context;
36import android.content.Intent;
37import android.os.SystemClock;
38import android.util.Log;
39
40import java.util.ArrayList;
41import java.util.List;
42import java.util.UUID;
43
44/**
45 * Class to provide Receiver for AlarmManager to start Gatt Client alarms
46 */
47public 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     */
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     */
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
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
181        GattCallback() {}
182
183        @Override
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
213        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
214            Log.d(TAG, "onServicesDiscovered Status " + status);
215            mBluetoothGatt.requestMtu(MAX_MTU);
216        }
217
218        @Override
219        public void onCharacteristicRead(BluetoothGatt gatt,
220                BluetoothGattCharacteristic characteristic, int status) {
221            Log.d(TAG, "onCharacteristicRead: " + status);
222        }
223
224        @Override
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
235        public void onCharacteristicChanged(BluetoothGatt gatt,
236                    BluetoothGattCharacteristic characteristic) {
237            Log.d(TAG, "onCharacteristicChanged");
238        }
239
240        @Override
241        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
242                    int status) {
243            Log.d(TAG, "onServicesDiscovered: " + status);
244        }
245
246        @Override
247        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
248                int status) {
249            Log.d(TAG, "onDescriptorWrite: " + status);
250        }
251
252        @Override
253        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
254            Log.d(TAG, "onReliableWriteCompleted: " + status);
255        }
256
257        @Override
258        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
259            Log.d(TAG, "onReadRemoteRssi: " + rssi + " status: " + status);
260        }
261
262        @Override
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         */
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         */
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         */
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         */
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         */
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
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
378            public void onScanFailed(int errorCode) {
379                Log.e(TAG, "Scan Failed: " + errorCode);
380            }
381        }
382    }
383}
384
385