Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeviceControlActivity pausing and thus not reveiving ACTION_GATT_SERVICES_DISCOVERED broadcast #276

Open
0x15feed opened this issue Sep 25, 2022 · 1 comment

Comments

@0x15feed
Copy link

0x15feed commented Sep 25, 2022

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:

  1. Overview: https://developer.android.com/guide/topics/connectivity/bluetooth/ble-overview
  2. Bluetooth Permission: https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#java
  3. Setup Bluetooth: https://developer.android.com/guide/topics/connectivity/bluetooth/setup#java
  4. Find BLE Device: https://developer.android.com/guide/topics/connectivity/bluetooth/find-ble-devices
  5. Connect to a GATT Server: https://developer.android.com/guide/topics/connectivity/bluetooth/connect-gatt-server#java
  6. Transfer BLE Data: https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#java

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?

onPause issue with one device in DeviceControlActivity

Logcat with my device shows onPause issue:

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/LMBluetoothLeService: Trying to create a new connection.
2022-09-25 17:54:54.070 23463-23463/com.example.android.bluetoothlegatt V/LMBluetoothLeService: 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/LMBluetoothLeService: BluetoothGattCallback: onConnectionStateChange - status: 0 newState: 2
2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt I/LMBluetoothLeService: mConnectionState - 2
2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt V/LMBluetoothLeService: broadcastUpdate:  -> action: com.example.bluetooth.le.ACTION_GATT_CONNECTED
2022-09-25 17:54:54.184 23463-23479/com.example.android.bluetoothlegatt I/LMBluetoothLeService: 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/LMDeviceControlActivity: 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/LMBluetoothLeService: Attempting to start service discovery:true
2022-09-25 17:54:54.216 23463-23463/com.example.android.bluetoothlegatt V/LMDeviceControlActivity: 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/LMBluetoothLeService: BluetoothGattCallback: onServicesDiscovered - status: 0
2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt V/LMBluetoothLeService: broadcastUpdate:  -> action: com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED
2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt W/LMBluetoothLeService: 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/LMDeviceControlActivity: DeviceControlActivity - onResume(): 
2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt V/LMDeviceControlActivity: 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/LMBluetoothLeService: 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/LMBluetoothLeService: 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/LMBluetoothLeService: mConnectionState: 1
2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt D/LMDeviceControlActivity: 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

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/LMBluetoothLeService: Trying to create a new connection.
2022-09-25 17:54:54.070 23463-23463/com.example.android.bluetoothlegatt V/LMBluetoothLeService: 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/LMBluetoothLeService: BluetoothGattCallback: onConnectionStateChange - status: 0 newState: 2
2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt I/LMBluetoothLeService: mConnectionState - 2
2022-09-25 17:54:54.183 23463-23479/com.example.android.bluetoothlegatt V/LMBluetoothLeService: broadcastUpdate:  -> action: com.example.bluetooth.le.ACTION_GATT_CONNECTED
2022-09-25 17:54:54.184 23463-23479/com.example.android.bluetoothlegatt I/LMBluetoothLeService: 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/LMDeviceControlActivity: 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/LMBluetoothLeService: Attempting to start service discovery:true
2022-09-25 17:54:54.216 23463-23463/com.example.android.bluetoothlegatt V/LMDeviceControlActivity: 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/LMBluetoothLeService: BluetoothGattCallback: onServicesDiscovered - status: 0
2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt V/LMBluetoothLeService: broadcastUpdate:  -> action: com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED
2022-09-25 17:54:55.008 23463-23479/com.example.android.bluetoothlegatt W/LMBluetoothLeService: 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/LMDeviceControlActivity: DeviceControlActivity - onResume(): 
2022-09-25 17:54:55.992 23463-23463/com.example.android.bluetoothlegatt V/LMDeviceControlActivity: 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/LMBluetoothLeService: 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/LMBluetoothLeService: 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/LMBluetoothLeService: mConnectionState: 1
2022-09-25 17:54:56.019 23463-23463/com.example.android.bluetoothlegatt D/LMDeviceControlActivity: 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

/*
 * 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<ScanFilter> 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<BluetoothDevice> mLeDevices;
        private LayoutInflater mInflator;

        public LeDeviceListAdapter() {
            super();
            mLeDevices = new ArrayList<BluetoothDevice>();
            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

/*
 * 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<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics =
            new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
    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<BluetoothGattService> 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<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            Log.v(TAG, "gattServide: " + gattService.getCharacteristics().size());
            HashMap<String, String> currentServiceData = new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();

            // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                Log.v(TAG, "gattCharacteristic: " + gattCharacteristic);
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData = new HashMap<String, String>();
                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

/*
 * 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<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;
Log.v(TAG, "mBluetoothGatt.getServices() => " + mBluetoothGatt.discoverServices());
        return mBluetoothGatt.getServices();
    }
}

SampleGattAttributes.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<String, String> 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

<?xml version="1.0" encoding="UTF-8"?>
<!--
 Copyright 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.
-->



<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.bluetoothlegatt"
    android:versionCode="1"
    android:versionName="1.0">

    <!-- Min/target SDK versions (<uses-sdk>) managed by build.gradle -->
    
    <!-- Declare this required feature if you want to make the app available to BLE-capable
    devices only.  If you want to make your app available to devices that don't support BLE,
    you should omit this in the manifest.  Instead, determine BLE capability by using
    PackageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE) -->
<!--    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>-->


    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


    <!-- Request legacy Bluetooth permissions on older devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH"   android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"   android:maxSdkVersion="30" />

    <!-- Needed only if your app looks for Bluetooth devices.
         If your app doesn't use Bluetooth scan results to derive physical
         location information, you can strongly assert that your app
         doesn't derive physical location. -->
        <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>

    <!-- Needed only if your app communicates with already-paired Bluetooth devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />



    <application android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@android:style/Theme.Holo.Light">
        <activity android:name=".DeviceScanActivity"
            android:label="@string/app_name"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".DeviceControlActivity"/>
        <service android:name=".BluetoothLeService" android:enabled="true"/>
    </application>

</manifest>

@0x15feed
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant