Open 0x15feed opened 2 years ago
The broadcast is received now as I've moved registerReceiver() and unregisterReceiver() into onCreate() and onDestroy respectively.
It's unclear to me though why a bluetooth peripheral behavior is affecting this onPause() in the activity.
I'm working with BluetoothLeGatt project.
I can scan on GAP , connect at GATT layer and discover services which are, however, cannot be broadcasted to DeviceControlActivity as this activity goes into pause directly after receiving ACTION_GATT_CONNECTED broadcast.
I'm unsure why this happens with the one bluetooth perpheral and not with my smartwatch.
I've followed these official documentation links:
Following changes are done in the original code:
DeviceScanActivity: Deprecated startLeScan() and stopLeScan() replaced with startScan() and stopScan()
BluetoothLeService.java: setCharacteristicNotification() changed to notify on characteristics from my peripheral device (following this post)
Declaration of own services and characteristics in SampleGattAttributes in addition to the existing heart rate unit declarations
DeviceControlActivity: Call to setCharacteristicNotification adapted and extractData added (following this post)
All these changes, however, should not explain why DeviceControlActivity suddently goes to onPause() even though the screen is not changed. The activity is still in foreground.
Mind you this only happens with my own peripheral device and not with my smartwatch.
Any pointers are much appreciated. I'm running out of ideas.
On the left side we see the example where onPause() is called immediately after receiving a broadcast ACTION_GATT_CONNECTED. This seems to be the reason that the services broadcast is not received. Why does it happen depending on the peripheral device?
Logcat with my device shows onPause issue:
```java 2022-09-25 17:54:54.067 23463-23463/com.example.android.bluetoothlegatt D/BluetoothGatt: connect() - device: E0:B5:87:2C:E9:4B, auto: true 2022-09-25 17:54:54.067 23463-23463/com.example.android.bluetoothlegatt I/BluetoothAdapter: isSecureModeEnabled 2022-09-25 17:54:54.068 23463-23463/com.example.android.bluetoothlegatt D/BluetoothGatt: registerApp() 2022-09-25 17:54:54.068 23463-23463/com.example.android.bluetoothlegatt D/BluetoothGatt: registerApp() - UUID=96a2f4bb-a56d-4bae-9fd2-e17b9993f1e0 2022-09-25 17:54:54.070 23463-23463/com.example.android.bluetoothlegatt D/LM: BluetoothLeService: Trying to create a new connection. 2022-09-25 17:54:54.070 23463-23463/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: mBluetoothDeviceAddress: E0:B5:87:2C:E9:4BmConnectionState: 1[] 2022-09-25 17:54:54.070 23463-23512/com.example.android.bluetoothlegatt D/BluetoothGatt: onClientRegistered() - status=0 clientIf=13 2022-09-25 17:54:54.073 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1 2022-09-25 17:54:54.074 23463-23463/com.example.android.bluetoothlegatt D/InputMethodManager: startInputInner - Id : 0 2022-09-25 17:54:54.074 23463-23463/com.example.android.bluetoothlegatt I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus 2022-09-25 17:54:54.176 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=13 device=E0:B5:87:2C:E9:4B 2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: BluetoothGattCallback: onConnectionStateChange - status: 0 newState: 2 2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt I/LM: BluetoothLeService: mConnectionState - 2 2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: broadcastUpdate: -> action: com.example.bluetooth.le.ACTION_GATT_CONNECTED 2022-09-25 17:54:54.184 23463-23479/com.example.android.bluetoothlegatt I/LM: BluetoothLeService: Connected to GATT server. 2022-09-25 17:54:54.184 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: discoverServices() - device: E0:B5:87:2C:E9:4B 2022-09-25 17:54:54.185 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: BroadcastReceiver: mGattUpdateReceiver onReceive()- Intent.action: com.example.bluetooth.le.ACTION_GATT_CONNECTED 2022-09-25 17:54:54.186 23463-23479/com.example.android.bluetoothlegatt I/LM: BluetoothLeService: Attempting to start service discovery:true 2022-09-25 17:54:54.216 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: DeviceControlActivity - onPause(): 2022-09-25 17:54:54.305 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1 2022-09-25 17:54:54.447 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: handleAppVisibility mAppVisible=true visible=false 2022-09-25 17:54:54.455 23463-23486/com.example.android.bluetoothlegatt D/OpenGLRenderer: setSurface called with nullptr 2022-09-25 17:54:54.455 23463-23486/com.example.android.bluetoothlegatt D/OpenGLRenderer: setSurface() destroyed EGLSurface 2022-09-25 17:54:54.455 23463-23486/com.example.android.bluetoothlegatt D/OpenGLRenderer: destroyEglSurface 2022-09-25 17:54:54.458 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: Relayout returned: old=(0,0,1080,2400) new=(0,0,1080,2400) req=(1080,2400)8 dur=1 res=0x5 s={false 0} ch=true fn=550 2022-09-25 17:54:54.458 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: stopped(true) old=false 2022-09-25 17:54:54.459 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: isMultiSplitHandlerRequested: windowingMode=1 isFullscreen=true isPopOver=false isHidden=false skipActivityType=false isHandlerType=false this: DecorView@e11fe2e[DeviceScanActivity] 2022-09-25 17:54:54.459 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: removeMultiSplitHandler: no exist. decor=DecorView@e11fe2e[DeviceScanActivity] 2022-09-25 17:54:54.465 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: Relayout returned: old=(0,0,1080,2400) new=(0,0,1080,2400) req=(1080,2400)8 dur=1 res=0x5 s={false 0} ch=false fn=-1 2022-09-25 17:54:54.925 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onConnectionUpdated() - Device=E0:B5:87:2C:E9:4B interval=6 latency=0 timeout=500 status=0 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onSearchComplete() = Device=E0:B5:87:2C:E9:4B Status=0 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: BluetoothGattCallback: onServicesDiscovered - status: 0 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: broadcastUpdate: -> action: com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt W/LM: BluetoothLeService: onServicesDiscovered received - sending broadcaseUpdte(ACTION_GATT_SERVICES_DISCOVERED)0 2022-09-25 17:54:55.075 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onConnectionUpdated() - Device=E0:B5:87:2C:E9:4B interval=39 latency=0 timeout=500 status=0 2022-09-25 17:54:55.975 23463-23463/com.example.android.bluetoothlegatt D/InsetsSourceConsumer: ensureControlAlpha: for ITYPE_NAVIGATION_BAR on com.example.android.bluetoothlegatt/com.example.android.bluetoothlegatt.DeviceControlActivity 2022-09-25 17:54:55.976 23463-23463/com.example.android.bluetoothlegatt D/InsetsSourceConsumer: ensureControlAlpha: for ITYPE_STATUS_BAR on com.example.android.bluetoothlegatt/com.example.android.bluetoothlegatt.DeviceControlActivity 2022-09-25 17:54:55.983 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1 2022-09-25 17:54:55.984 23463-23463/com.example.android.bluetoothlegatt D/InputMethodManager: startInputInner - Id : 0 2022-09-25 17:54:55.984 23463-23463/com.example.android.bluetoothlegatt I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: stopped(false) old=false 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: DeviceControlActivity - onResume(): 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: makeGattUpdateIntentFilter() 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt I/IntentFilter: all GATT states registered 2022-09-25 17:54:56.014 23463-23463/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: mBluetoothDeviceAddress: E0:B5:87:2C:E9:4Baddress: E0:B5:87:2C:E9:4BmBluetoothGatt: android.bluetooth.BluetoothGatt@fdee42 2022-09-25 17:54:56.014 23463-23463/com.example.android.bluetoothlegatt D/LM: BluetoothLeService: Trying to use an existing mBluetoothGatt for connection. 2022-09-25 17:54:56.014 23463-23463/com.example.android.bluetoothlegatt I/BluetoothAdapter: isSecureModeEnabled 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: mConnectionState: 1 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt D/LM: DeviceControlActivity: Connect request result=true 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/DecorView: notifyKeepScreenOnChanged: keepScreenOn=false 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: isMultiSplitHandlerRequested: windowingMode=1 isFullscreen=true isPopOver=false isHidden=false skipActivityType=false isHandlerType=false this: DecorView@fdadb56[DeviceControlActivity] 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: isMultiSplitHandlerRequested: windowingMode=1 isFullscreen=true isPopOver=false isHidden=false skipActivityType=false isHandlerType=false this: DecorView@fdadb56[DeviceControlActivity] 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: removeMultiSplitHandler: no exist. decor=DecorView@fdadb56[DeviceControlActivity] 2022-09-25 17:54:56.026 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: Relayout returned: old=(0,0,1080,2400) new=(0,0,1080,2400) req=(1080,2400)0 dur=3 res=0x1 s={true -5476376645935329424} ch=false fn=6 ```
Logcat_My_device_with_onPause_issue_in_DeviceControlActivity.txt
```java 2022-09-25 17:54:54.067 23463-23463/com.example.android.bluetoothlegatt D/BluetoothGatt: connect() - device: E0:B5:87:2C:E9:4B, auto: true 2022-09-25 17:54:54.067 23463-23463/com.example.android.bluetoothlegatt I/BluetoothAdapter: isSecureModeEnabled 2022-09-25 17:54:54.068 23463-23463/com.example.android.bluetoothlegatt D/BluetoothGatt: registerApp() 2022-09-25 17:54:54.068 23463-23463/com.example.android.bluetoothlegatt D/BluetoothGatt: registerApp() - UUID=96a2f4bb-a56d-4bae-9fd2-e17b9993f1e0 2022-09-25 17:54:54.070 23463-23463/com.example.android.bluetoothlegatt D/LM: BluetoothLeService: Trying to create a new connection. 2022-09-25 17:54:54.070 23463-23463/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: mBluetoothDeviceAddress: E0:B5:87:2C:E9:4BmConnectionState: 1[] 2022-09-25 17:54:54.070 23463-23512/com.example.android.bluetoothlegatt D/BluetoothGatt: onClientRegistered() - status=0 clientIf=13 2022-09-25 17:54:54.073 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1 2022-09-25 17:54:54.074 23463-23463/com.example.android.bluetoothlegatt D/InputMethodManager: startInputInner - Id : 0 2022-09-25 17:54:54.074 23463-23463/com.example.android.bluetoothlegatt I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus 2022-09-25 17:54:54.176 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=13 device=E0:B5:87:2C:E9:4B 2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: BluetoothGattCallback: onConnectionStateChange - status: 0 newState: 2 2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt I/LM: BluetoothLeService: mConnectionState - 2 2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: broadcastUpdate: -> action: com.example.bluetooth.le.ACTION_GATT_CONNECTED 2022-09-25 17:54:54.184 23463-23479/com.example.android.bluetoothlegatt I/LM: BluetoothLeService: Connected to GATT server. 2022-09-25 17:54:54.184 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: discoverServices() - device: E0:B5:87:2C:E9:4B 2022-09-25 17:54:54.185 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: BroadcastReceiver: mGattUpdateReceiver onReceive()- Intent.action: com.example.bluetooth.le.ACTION_GATT_CONNECTED 2022-09-25 17:54:54.186 23463-23479/com.example.android.bluetoothlegatt I/LM: BluetoothLeService: Attempting to start service discovery:true 2022-09-25 17:54:54.216 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: DeviceControlActivity - onPause(): 2022-09-25 17:54:54.305 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1 2022-09-25 17:54:54.447 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: handleAppVisibility mAppVisible=true visible=false 2022-09-25 17:54:54.455 23463-23486/com.example.android.bluetoothlegatt D/OpenGLRenderer: setSurface called with nullptr 2022-09-25 17:54:54.455 23463-23486/com.example.android.bluetoothlegatt D/OpenGLRenderer: setSurface() destroyed EGLSurface 2022-09-25 17:54:54.455 23463-23486/com.example.android.bluetoothlegatt D/OpenGLRenderer: destroyEglSurface 2022-09-25 17:54:54.458 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: Relayout returned: old=(0,0,1080,2400) new=(0,0,1080,2400) req=(1080,2400)8 dur=1 res=0x5 s={false 0} ch=true fn=550 2022-09-25 17:54:54.458 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: stopped(true) old=false 2022-09-25 17:54:54.459 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: isMultiSplitHandlerRequested: windowingMode=1 isFullscreen=true isPopOver=false isHidden=false skipActivityType=false isHandlerType=false this: DecorView@e11fe2e[DeviceScanActivity] 2022-09-25 17:54:54.459 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: removeMultiSplitHandler: no exist. decor=DecorView@e11fe2e[DeviceScanActivity] 2022-09-25 17:54:54.465 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@d450cf9[DeviceScanActivity]: Relayout returned: old=(0,0,1080,2400) new=(0,0,1080,2400) req=(1080,2400)8 dur=1 res=0x5 s={false 0} ch=false fn=-1 2022-09-25 17:54:54.925 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onConnectionUpdated() - Device=E0:B5:87:2C:E9:4B interval=6 latency=0 timeout=500 status=0 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onSearchComplete() = Device=E0:B5:87:2C:E9:4B Status=0 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: BluetoothGattCallback: onServicesDiscovered - status: 0 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: broadcastUpdate: -> action: com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED 2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt W/LM: BluetoothLeService: onServicesDiscovered received - sending broadcaseUpdte(ACTION_GATT_SERVICES_DISCOVERED)0 2022-09-25 17:54:55.075 23463-23479/com.example.android.bluetoothlegatt D/BluetoothGatt: onConnectionUpdated() - Device=E0:B5:87:2C:E9:4B interval=39 latency=0 timeout=500 status=0 2022-09-25 17:54:55.975 23463-23463/com.example.android.bluetoothlegatt D/InsetsSourceConsumer: ensureControlAlpha: for ITYPE_NAVIGATION_BAR on com.example.android.bluetoothlegatt/com.example.android.bluetoothlegatt.DeviceControlActivity 2022-09-25 17:54:55.976 23463-23463/com.example.android.bluetoothlegatt D/InsetsSourceConsumer: ensureControlAlpha: for ITYPE_STATUS_BAR on com.example.android.bluetoothlegatt/com.example.android.bluetoothlegatt.DeviceControlActivity 2022-09-25 17:54:55.983 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1 2022-09-25 17:54:55.984 23463-23463/com.example.android.bluetoothlegatt D/InputMethodManager: startInputInner - Id : 0 2022-09-25 17:54:55.984 23463-23463/com.example.android.bluetoothlegatt I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: stopped(false) old=false 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: DeviceControlActivity - onResume(): 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt V/LM: DeviceControlActivity: makeGattUpdateIntentFilter() 2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt I/IntentFilter: all GATT states registered 2022-09-25 17:54:56.014 23463-23463/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: mBluetoothDeviceAddress: E0:B5:87:2C:E9:4Baddress: E0:B5:87:2C:E9:4BmBluetoothGatt: android.bluetooth.BluetoothGatt@fdee42 2022-09-25 17:54:56.014 23463-23463/com.example.android.bluetoothlegatt D/LM: BluetoothLeService: Trying to use an existing mBluetoothGatt for connection. 2022-09-25 17:54:56.014 23463-23463/com.example.android.bluetoothlegatt I/BluetoothAdapter: isSecureModeEnabled 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt V/LM: BluetoothLeService: mConnectionState: 1 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt D/LM: DeviceControlActivity: Connect request result=true 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/DecorView: notifyKeepScreenOnChanged: keepScreenOn=false 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: isMultiSplitHandlerRequested: windowingMode=1 isFullscreen=true isPopOver=false isHidden=false skipActivityType=false isHandlerType=false this: DecorView@fdadb56[DeviceControlActivity] 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: isMultiSplitHandlerRequested: windowingMode=1 isFullscreen=true isPopOver=false isHidden=false skipActivityType=false isHandlerType=false this: DecorView@fdadb56[DeviceControlActivity] 2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt I/MSHandlerLifeCycle: removeMultiSplitHandler: no exist. decor=DecorView@fdadb56[DeviceControlActivity] 2022-09-25 17:54:56.026 23463-23463/com.example.android.bluetoothlegatt I/ViewRootImpl@c80921d[DeviceControlActivity]: Relayout returned: old=(0,0,1080,2400) new=(0,0,1080,2400) req=(1080,2400)0 dur=3 res=0x1 s={true -5476376645935329424} ch=false fn=6 ```
DeviceScanActivity .java
```java /* * Copyright (C) 2013 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.example.android.bluetoothlegatt; import android.Manifest; import android.app.Activity; import android.app.ListActivity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; 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.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.ParcelUuid; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * Activity for scanning and displaying available Bluetooth LE devices. */ public class DeviceScanActivity extends ListActivity { private static final String TAG = "MY_DEBUG" + DeviceScanActivity.class.getSimpleName(); private static final int PERMISSIONS_REQUEST_CODE = 100; private LeDeviceListAdapter mLeDeviceListAdapter; private BluetoothAdapter mBluetoothAdapter; private BluetoothLeScanner mbluetoothLeScanner; ScanCallback mBtScanCallback; private boolean mScanning; private Handler mHandler; private static final int REQUEST_ENABLE_BT = 1; // Stops scanning after 10 seconds. private static final long SCAN_PERIOD = 10000; private final String[] permissions = { //only dangerous permissions are run-time permissions Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.RECORD_AUDIO}; // with the SDK 31 and higher (Android 12) we need to have also BLUETOOTH_SCAN and BLUETOOTH_CONNECT permissions @RequiresApi(api = Build.VERSION_CODES.S) private final String[] permissionsForS = { //only dangerous permissions are run-time permissions Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.RECORD_AUDIO}; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handlePermissions(); getActionBar().setTitle(R.string.title_devices); mHandler = new Handler(); // Use this check to determine whether BLE is supported on the device. Then you can // selectively disable BLE-related features. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); } // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to // BluetoothAdapter through BluetoothManager. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); // Checks if Bluetooth is supported on the device. if (mBluetoothAdapter == null) { Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show(); finish(); return; } mbluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); if (!mScanning) { menu.findItem(R.id.menu_stop).setVisible(false); menu.findItem(R.id.menu_scan).setVisible(true); menu.findItem(R.id.menu_refresh).setActionView(null); } else { menu.findItem(R.id.menu_stop).setVisible(true); menu.findItem(R.id.menu_scan).setVisible(false); menu.findItem(R.id.menu_refresh).setActionView( R.layout.actionbar_indeterminate_progress); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_scan: mLeDeviceListAdapter.clear(); scanLeDevice(true); break; case R.id.menu_stop: scanLeDevice(false); break; } return true; } @Override protected void onResume() { super.onResume(); Log.v(TAG, "onResume() " ); // Initializes list view adapter. mLeDeviceListAdapter = new LeDeviceListAdapter(); setListAdapter(mLeDeviceListAdapter); scanLeDevice(true); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // User chose not to enable Bluetooth. if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { finish(); return; } super.onActivityResult(requestCode, resultCode, data); } @Override protected void onPause() { super.onPause(); scanLeDevice(false); mLeDeviceListAdapter.clear(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { Log.v(TAG, "onListItemclick -> sending intent DeviceControlActivity.class"); final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position); if (device == null) return; final Intent intent = new Intent(this, DeviceControlActivity.class); intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName()); intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress()); if (mScanning) { // mBluetoothAdapter.stopLeScan(mLeScanCallback); mbluetoothLeScanner.stopScan(mBtScanCallback); //Todo:MY_DEBUG: Verify; remove the stopLeScan -> just replace with stopScan() enough? mScanning = false; } startActivity(intent); } private void scanLeDevice(final boolean enable) { if (enable) { // Stops scanning after a pre-defined scan period. mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mbluetoothLeScanner.stopScan(mBtScanCallback); invalidateOptionsMenu(); Log.v(TAG, "scanLeDevice - stopped after timeout"); } }, SCAN_PERIOD); Log.v(TAG, "scanLeDevice - started"); mScanning = true; //mBluetoothAdapter.startLeScan(mLeScanCallback); mBtScanCallback = new BTScanCallback(); List scanFilters = new ArrayList<>();
ScanFilter.Builder filter_builder = new ScanFilter.Builder();
filter_builder.setServiceUuid(new ParcelUuid(UUID.fromString(SampleGattAttributes.MY_BTLE_DEVICE_SERVICE_UUID)));
scanFilters.add(filter_builder.build());
//Todo: MY_DEBUG: Remove limitation to first match as it's for testing purpose
//ScanSettings.Builder settings_builder = new ScanSettings.Builder().setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH);
ScanSettings.Builder settings_builder = new ScanSettings.Builder();
//settings_builder.setLegacy(false); //disable finding legacy -> min API 26 required
ScanSettings scanSettings = settings_builder.build();
//mbluetoothLeScanner.startScan(scanFilters, scanSettings, mBtScanCallback);
mbluetoothLeScanner.startScan(mBtScanCallback); //unfiltered rsults
} else {
mScanning = false;
mbluetoothLeScanner.stopScan(mBtScanCallback);
}
invalidateOptionsMenu();
}
// Adapter for holding devices found through scanning.
private class LeDeviceListAdapter extends BaseAdapter {
private ArrayList mLeDevices;
private LayoutInflater mInflator;
public LeDeviceListAdapter() {
super();
mLeDevices = new ArrayList();
mInflator = DeviceScanActivity.this.getLayoutInflater();
}
public void addDevice(BluetoothDevice device) {
if(!mLeDevices.contains(device)) {
mLeDevices.add(device);
}
}
public BluetoothDevice getDevice(int position) {
return mLeDevices.get(position);
}
public void clear() {
mLeDevices.clear();
}
@Override
public int getCount() {
return mLeDevices.size();
}
@Override
public Object getItem(int i) {
return mLeDevices.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
Log.v(TAG, "getView in private class LeDeviceListAdapter called");
ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.listitem_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
BluetoothDevice device = mLeDevices.get(i);
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 0)
viewHolder.deviceName.setText(deviceName);
else
viewHolder.deviceName.setText(R.string.unknown_device);
viewHolder.deviceAddress.setText(device.getAddress());
return view;
}
}
private class BTScanCallback extends ScanCallback {
/**
* Callback when a BLE advertisement has been found.
*
* @param callbackType Determines how this callback was triggered. Could be one of {@link
* ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or
* {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST}
* @param result A Bluetooth LE scan result.
*/
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.v(TAG, "~~~~~~~~~~~~~~~~~ \n \t onScanResult: " + " " + result.getDevice().getName() + " " + result.getDevice().getAddress() + " " + result.getDevice().getUuids() + "\n\tresult.getScanRecord().getServiceUuids(): " + result.getScanRecord().getServiceUuids()+ "\n\n " + result.toString());
if (result.getScanRecord().getServiceUuids()==null) {
Log.v(TAG, "result.getScanRecord().getServiceUuids()==null" +
"<<<---------------------");
return;
}
mLeDeviceListAdapter.addDevice(result.getDevice());
mLeDeviceListAdapter.notifyDataSetChanged();
UUID foundUuid = result.getScanRecord().getServiceUuids().get(0).getUuid();
UUID expectedUUid = UUID.fromString(SampleGattAttributes.MY_BTLE_DEVICE_SERVICE_UUID);
if (foundUuid.equals(expectedUUid)) {
Log.v(TAG, "MY_BTLE_DEVICE with BTLE Nordic UART Service found <<<---------------------");
//gattConnection(result.getDevice());
mLeDeviceListAdapter.addDevice(result.getDevice());
mLeDeviceListAdapter.notifyDataSetChanged();
} else {
Log.v(TAG, "some other device without UART service found");
}
}
/**
* Callback when scan could not be started.
*
* @param errorCode Error code (one of SCAN_FAILED_*) for scan failure.
*/
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.v(TAG, "\t onScanFailed called ..");
}
}
//Todo: MY_DEBUG: The following callback method is deprecated now -> to be removed
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.v(TAG, "BluetoothAdapter.LeScanCallback -> result received");
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
}
private void handlePermissions() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.requestPermissions(this, permissionsForS,
PERMISSIONS_REQUEST_CODE);
} else {
ActivityCompat.requestPermissions(this, permissions,
PERMISSIONS_REQUEST_CODE);
}
} else {
Log.v(TAG, "permission has already been granted");
}
}
/**
* Call back method is used to check for granted permissions and provide information in case
* permissions are not granted but are necessary for the app functionality
*
* @param requestCode requestCode used for requesting the permissions
* @param permissions Permissions that were requested
* @param grantResults result of the permission request
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSIONS_REQUEST_CODE:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "All requested permissions granted. Continue..");
} else {
Log.e(TAG, "Not all permissions are granted. Accept the permissions request for full app functionality..");
}
return;
}
}
}
```
DeviceControlActivity.java
```java /* * Copyright (C) 2013 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.example.android.bluetoothlegatt; import android.app.Activity; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ExpandableListView; import android.widget.SimpleExpandableListAdapter; import android.widget.TextView; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * For a given BLE device, this Activity provides the user interface to connect, display data, * and display GATT services and characteristics supported by the device. The Activity * communicates with {@code BluetoothLeService}, which in turn interacts with the * Bluetooth LE API. */ public class DeviceControlActivity extends Activity { private final static String TAG = "MY_DEBUG: " + DeviceControlActivity.class.getSimpleName(); public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME"; public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS"; private TextView mConnectionState; private TextView mDataField; private String mDeviceName; private String mDeviceAddress; private ExpandableListView mGattServicesList; private BluetoothLeService mBluetoothLeService; private ArrayList> mGattCharacteristics =
new ArrayList>();
private boolean mConnected = false;
private BluetoothGattCharacteristic mNotifyCharacteristic;
private final String LIST_NAME = "NAME";
private final String LIST_UUID = "UUID";
// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
Log.v(TAG, "onServiceConnected() ");
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
if (!mBluetoothLeService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
// Automatically connects to the device upon successful start-up initialization.
mBluetoothLeService.connect(mDeviceAddress);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.v(TAG, "onServiceDiscnnected() ");
mBluetoothLeService = null;
}
};
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read
// or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.v(TAG, "BroadcastReceiver: mGattUpdateReceiver onReceive()- Intent.action: " + intent.getAction());
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
mConnected = true;
updateConnectionState(R.string.connected);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
mConnected = false;
updateConnectionState(R.string.disconnected);
invalidateOptionsMenu();
clearUI();
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
// Show all the supported services and characteristics on the user interface.
mBluetoothLeService.setCharacteristicNotification(SampleGattAttributes.MY_BTLE_DEVICE_SERVICE_UUID, SampleGattAttributes.MY_BTLE_DEVICE_STATUS_UUID, true); //Todo: MY_DEBUG: check whether the characteristic and service is correct for notification!
displayGattServices(mBluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
extractData(intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA));
displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
}
else {
Log.v(TAG, "unhandled action received .." + action);
}
}
};
private void extractData(byte[] input_data){
System.out.println("Extracting Data");;
Log.i("broadcastUpdate", "Data Length = " + input_data.length);
if (input_data != null && input_data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(input_data.length);
for (byte byteChar : input_data) {
stringBuilder.append(String.format("%02X ", byteChar));
}
Log.i("broadcastUpdate", "Received Data = " + stringBuilder.toString());
}
}
// If a given GATT characteristic is selected, check for supported features. This sample
// demonstrates 'Read' and 'Notify' features. See
// http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
// list of supported characteristic features.
private final ExpandableListView.OnChildClickListener servicesListClickListner =
new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
Log.v(TAG, "onChildClick registered on characteristic --> trying to read and get the notification");
if (mGattCharacteristics != null) {
final BluetoothGattCharacteristic characteristic =
mGattCharacteristics.get(groupPosition).get(childPosition);
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) { //Todo: MY_DEBUG: Why is this property doing bitwise OR instead of AND &? -> Should check for read property exclusively, not?
// If there is an active notification on a characteristic, clear
// it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
mBluetoothLeService.setCharacteristicNotification(characteristic.getService().getUuid().toString(), characteristic.getUuid().toString(),
false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(characteristic);
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
Log.v(TAG, "(charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0 ---------> set notification to TRUE .... notifications should come now...");
mNotifyCharacteristic = characteristic;
mBluetoothLeService.setCharacteristicNotification(characteristic.getService().getUuid().toString(),characteristic.getUuid().toString(), true);
}
return true;
}
return false;
}
};
private void clearUI() {
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
mDataField.setText(R.string.no_data);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gatt_services_characteristics);
final Intent intent = getIntent();
mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS);
// Sets up UI references.
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list);
mGattServicesList.setOnChildClickListener(servicesListClickListner);
mConnectionState = (TextView) findViewById(R.id.connection_state);
mDataField = (TextView) findViewById(R.id.data_value);
getActionBar().setTitle(mDeviceName);
getActionBar().setDisplayHomeAsUpEnabled(true);
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
@Override
protected void onResume() {
super.onResume();
Log.v(TAG, "DeviceControlActivity - onResume(): ");
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
if (mBluetoothLeService != null) {
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
Log.d(TAG, "Connect request result=" + result);
}
}
@Override
protected void onPause() {
super.onPause();
Log.v(TAG, "DeviceControlActivity - onPause(): ");
unregisterReceiver(mGattUpdateReceiver);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
mBluetoothLeService = null;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.gatt_services, menu);
if (mConnected) {
menu.findItem(R.id.menu_connect).setVisible(false);
menu.findItem(R.id.menu_disconnect).setVisible(true);
} else {
menu.findItem(R.id.menu_connect).setVisible(true);
menu.findItem(R.id.menu_disconnect).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_connect:
mBluetoothLeService.connect(mDeviceAddress);
return true;
case R.id.menu_disconnect:
mBluetoothLeService.disconnect();
return true;
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void updateConnectionState(final int resourceId) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnectionState.setText(resourceId);
}
});
}
private void displayData(String data) {
if (data != null) {
mDataField.setText(data);
}
}
// Demonstrates how to iterate through the supported GATT Services/Characteristics.
// In this sample, we populate the data structure that is bound to the ExpandableListView
// on the UI.
private void displayGattServices(List gattServices) {
Log.v(TAG, "displayGattServices - called ");
if (gattServices == null || gattServices.size()==0) return;
Log.v(TAG, "displayGattServices - gattServices.size()/first characteristic UUID: " + gattServices.size() + "/" + gattServices.get(0).getCharacteristics().get(0).getUuid());
String uuid = null;
String unknownServiceString = getResources().getString(R.string.unknown_service);
String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
ArrayList> gattServiceData = new ArrayList>();
ArrayList>> gattCharacteristicData
= new ArrayList>>();
mGattCharacteristics = new ArrayList>();
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
Log.v(TAG, "gattServide: " + gattService.getCharacteristics().size());
HashMap currentServiceData = new HashMap();
uuid = gattService.getUuid().toString();
currentServiceData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
ArrayList> gattCharacteristicGroupData =
new ArrayList>();
List gattCharacteristics =
gattService.getCharacteristics();
ArrayList charas =
new ArrayList();
// Loops through available Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
Log.v(TAG, "gattCharacteristic: " + gattCharacteristic);
charas.add(gattCharacteristic);
HashMap currentCharaData = new HashMap();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
this,
gattServiceData,
android.R.layout.simple_expandable_list_item_2,
new String[] {LIST_NAME, LIST_UUID},
new int[] { android.R.id.text1, android.R.id.text2 },
gattCharacteristicData,
android.R.layout.simple_expandable_list_item_2,
new String[] {LIST_NAME, LIST_UUID},
new int[] { android.R.id.text1, android.R.id.text2 }
);
mGattServicesList.setAdapter(gattServiceAdapter);
}
private static IntentFilter makeGattUpdateIntentFilter() {
Log.v(TAG, "makeGattUpdateIntentFilter() ");
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
Log.i("IntentFilter", "all GATT states registered");
return intentFilter;
}
}
```
BluetoothLeService.java
```java /* * Copyright (C) 2013 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.example.android.bluetoothlegatt; import android.app.Service; 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.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; import java.util.List; import java.util.UUID; /** * Service for managing connection and data communication with a GATT server hosted on a * given Bluetooth LE device. */ public class BluetoothLeService extends Service { private final static String TAG = "MY_DEBUG: " + BluetoothLeService.class.getSimpleName(); private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; private int mConnectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); // Implements callback methods for GATT events that the app cares about. For example, // connection change and services discovered. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Log.v(TAG, "BluetoothGattCallback: onConnectionStateChange - " + "status: " + status + " newState: " + newState); String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) { intentAction = ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; Log.i(TAG, "mConnectionState - " + mConnectionState); broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); // Attempts to discover services after successful connection. Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.v(TAG, "BluetoothGattCallback: onServicesDiscovered - " + "status: " + status ); if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); Log.w(TAG, "onServicesDiscovered received - sending broadcaseUpdte(ACTION_GATT_SERVICES_DISCOVERED)" + status); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.v(TAG, "BluetoothGattCallback: onCharacteristicRead: - " + "status: " + status + "characteristic UUID: " + characteristic.getUuid()); Log.v(TAG, "onCharacteristicRead: "); if (status == BluetoothGatt.GATT_SUCCESS) { Log.v(TAG, "onCharacteristicRead: GATT_SUCCESS -> ACTION_DATA_AVAILABLE for characteristic" + characteristic.getUuid()); broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.v(TAG, "onCharacteristicChanged: -> ACTION_DATA_AVAILABLE for characteristic" + characteristic.getUuid()); broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } }; private void broadcastUpdate(final String action) { Log.v(TAG, "broadcastUpdate: -> action: " + action); final Intent intent = new Intent(action); sendBroadcast(intent); } //Todo: MY_DEBUG: Have to write the data in MY_BTLE_DEVICE specific data format private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { final Intent intent = new Intent(action); Log.v(TAG, "broadcastUpdate: -> action: " + action + "characteristic: " +characteristic.getUuid()); // This is special handling for the Heart Rate Measurement profile. Data parsing is // carried out as per profile specifications: // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); } final int heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); } else { Log.v(TAG, "broadcastUpdate: " + action + " - characteristic: " + characteristic.getUuid()); // For all other profiles, writes the data formatted in HEX. final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for(byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); } } sendBroadcast(intent); } public class LocalBinder extends Binder { BluetoothLeService getService() { return BluetoothLeService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public boolean onUnbind(Intent intent) { // After using a given device, you should make sure that BluetoothGatt.close() is called // such that resources are cleaned up properly. In this particular example, close() is // invoked when the UI is disconnected from the Service. close(); return super.onUnbind(intent); } private final IBinder mBinder = new LocalBinder(); /** * Initializes a reference to the local Bluetooth adapter. * * @return Return true if the initialization is successful. */ public boolean initialize() { // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; } /** * Connects to the GATT server hosted on the Bluetooth LE device. * * @param address The device address of the destination device. * * @return Return true if the connection is initiated successfully. The connection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ public boolean connect(final String address) { if (mBluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } // Previously connected device. Try to reconnect. if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.v(TAG, "mBluetoothDeviceAddress: " + mBluetoothDeviceAddress + "address: " + address + "mBluetoothGatt: " +mBluetoothGatt.toString()); Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); if (mBluetoothGatt.connect()) { mConnectionState = STATE_CONNECTING; Log.v(TAG, "mConnectionState: " + mConnectionState); return true; } else { Log.v(TAG, "connect() returned false" ); return false; } } final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } Log.v(TAG, "remote device address: " + device.getAddress() + " name: " + device.getName()); // We want to directly connect to the device, so we are setting the autoConnect // parameter to false. mBluetoothGatt = device.connectGatt(this, true, mGattCallback); //Todo: MY_DEBUG: set autoConnect to true for final solution -> just for manual testing false is used Log.d(TAG, "Trying to create a new connection."); mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; Log.v(TAG,"mBluetoothDeviceAddress: " + mBluetoothDeviceAddress + "mConnectionState: " + mConnectionState + mBluetoothGatt.getServices()); return true; } /** * Disconnects an existing connection or cancel a pending connection. The disconnection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.disconnect(); } /** * After using a given BLE device, the app must call this method to ensure resources are * released properly. */ public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; } /** * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} * callback. * * @param characteristic The characteristic to read from. */ public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.readCharacteristic(characteristic); } /** * Enables or disables notification on a give characteristic. * * @param service_uuid * @param characteristic_uuid Characteristic to act on. * @param enabled If true, enable notification. False otherwise. */ public void setCharacteristicNotification(String service_uuid, String characteristic_uuid, boolean enabled) { Log.v(TAG, "setCharacteristicNotification called - "); if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(service_uuid)); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristic_uuid)); if (characteristic==null) return; Log.v(TAG, "setCharacteristicNotification() called in service with \"enabled = \"" + enabled); mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.MY_BTLE_DEVICE_STATUS_UUID)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); } /** * Retrieves a list of supported GATT services on the connected device. This should be * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. * * @return A {@code List} of supported services. */ public List getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
Log.v(TAG, "mBluetoothGatt.getServices() => " + mBluetoothGatt.discoverServices());
return mBluetoothGatt.getServices();
}
}
```
SampleGattAttributes.java
```java /* * Copyright (C) 2013 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.example.android.bluetoothlegatt; import java.util.HashMap; /** * This class includes a small subset of standard GATT attributes for demonstration purposes. */ public class SampleGattAttributes { private static HashMap attributes = new HashMap();
public static String MY_BTLE_DEVICE_STATUS_UUID = "00002a37-0000-1000-8000-00805f9b34fb";
public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
public static String MY_BTLE_DEVICE_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-00805f9b34fb";
public static String MY_BTLE_DEVICE_RX_UUID = "6e400002-b5a3-f393-e0a9-00805f9b34fb";
public static String MY_BTLE_DEVICE_TX_UUID = "6e400003-b5a3-f393-e0a9-00805f9b34fb";
static {
// Sample Services.
attributes.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate Service");
attributes.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information Service");
// Sample Characteristics.
attributes.put(HEART_RATE_MEASUREMENT, "Heart Rate Measurement");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put(MY_BTLE_DEVICE_SERVICE_UUID, "MY_BTLE_DEVICE Service");
attributes.put(MY_BTLE_DEVICE_RX_UUID, "MY_BTLE_DEVICE Receive");
attributes.put(MY_BTLE_DEVICE_TX_UUID, "MY_BTLE_DEVICE Transmit");
attributes.put(MY_BTLE_DEVICE_STATUS_UUID, "MY_BTLE_DEVICE Status");
}
public static String lookup(String uuid, String defaultName) {
String name = attributes.get(uuid);
return name == null ? defaultName : name;
}
}
```
For the sake of completion, here is also the manifest.
AndroidManifest.xml
```java
```