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

Foreground Service on Android #643

Open
rainerfritz opened this issue Apr 9, 2024 · 8 comments
Open

Foreground Service on Android #643

rainerfritz opened this issue Apr 9, 2024 · 8 comments
Labels
enhancement New feature or request

Comments

@rainerfritz
Copy link

Is your feature request related to a problem? Please describe.
On Android the communication is interrupted after a few minutes Minutes when the app (Ionic/React) is in background. This comes from the Android OS power saving. On iOS this is not the case when BLE service has the permission to run in background. On Android the BLE service needs to be moved to a foreground service, otherwise it gets interrupted. For applications which need to stay connected in background to acquire sensor data for example or want to do a reconnect if the ble device gets lost, this is not currently not possible.
When the app comes from background, it seems that all the messages are spit out at one time, overloading nearly my backend. So it seems, that somehow it is running in BG, but the callback is not thrown.

Describe the solution you'd like
It would be great, if there would be a function to put at least the callback of the startNotifications() service into foreground, that we can still send and receive ble packets to the client. This would be also great for the scanning function as well as connect and disconnect if the connection of the ddevice was lost and to be able to do a reconnect in background.

Describe alternatives you've considered
I was looking into the https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/android-foreground-service
plugin, but it seems it is not suited to do the task.

Additional context
Here it went into "standby mode":
Bildschirmfoto 2024-04-09 um 19 41 12

Here it comes out of the background:
Bildschirmfoto 2024-04-09 um 19 42 31

@rainerfritz rainerfritz added the enhancement New feature or request label Apr 9, 2024
@omercnet
Copy link

I had the same problem for a while and your solution worked well :)

The capawesome plugin works like a charm - why did you suggest it's not the right solution ?

@rainerfritz
Copy link
Author

Hi!

Do you have a working example that works together with with the community-bluetooth le plugin? The startForegroundService() function parameters from their interface are only with regards to (local) notifications. So I thought this would not work with the whole BLE plugin. Or do you use the moveToForeground() function?

BR
Rainer

@omercnet
Copy link

so turns out a foreground service (while it may work) is not the right approach for BLE background scanning

a foreground service should not be running at all times, the documentation actually highlights more appropriate alternative, which is using a Pending Intent as a scan result and using a receiver that is assigned to the application that will make the OS launch the receiver even if the app was previously terminated

see more here https://developer.android.com/develop/connectivity/bluetooth/ble/background

I'll be opening a PR soon that handles the pending intent and receiver

@omercnet
Copy link

see #700

@rainerfritz
Copy link
Author

That's great thanks a lot! Will this then also hold a connection active to the BLE client, which was established when the app was in foreground and went to background?

@omercnet
Copy link

Depends what you're trying to do

See the docs for further information on how to handle background activities

They suggest subscribing to characteristic notifications if you're looking to get information from the device

@rainerfritz
Copy link
Author

rainerfritz commented Oct 21, 2024

Yes thats exactly what I do with the plugin in my app:

await BleClient.startNotifications(
        devID,
        RAK_BLE_UART_SERVICE,
        RAK_BLE_UART_RXCHAR,
        (value) => {
          parseMsg(value).then(async (res) => {
            if (res !== undefined && 'msgTXT' in res) {
              // escape all quotation marks
              console.log("Connect: Text Message: " + res.msgTXT);
              await DatabaseService.writeTxtMsg(res);
              // do the notification if from another callsign
              const curr_call = ConfigObject.getConf().CALL;
              if (res.fromCall !== curr_call && (!res.msgTXT.startsWith("--")) && canNotify.current) {
                console.log("Connect: Notification from: " + res.fromCall);
                NotifyMsgState.update(s => {
                  s.notifyMsg = res;
                });
              }
            } 
            if (res !== undefined && 'temperature' in res) {
                console.log("Connect: Pos Msg from: " + res.callSign);
                await DatabaseService.writePos(res);
            }
            if (res !== undefined && 'mh_nodecall' in res) {
              console.log("Connect: Mheard Node Call: " + res.mh_nodecall);
              res.mh_nodecall = ConfigObject.getConf().CALL;
              MheardStaticStore.setMhArr(res);
            }

          })}).then(async () => {

        console.log('connected to device', devID);

And this part should kept running when the app is going to background. I had a look into the docs. I am familiar with Java a bit, but did not develop in native android. Would be awesome if the plugin has an option to put the notifications on a forground service.

@peitschie
Copy link
Collaborator

Hi @omercnet

Unfortunately, the pending intent is not sufficient to ensure reliable background scanning occurs on Android. In fact, using a pending intent is no better than the existing callback-based scan here.

The Problem

To explain a bit more:

A capacitor app is a Process that runs an Activity which contains the web browser used to execute your application's javascript code. That is, in order for your javascript to be executed, android needs to run a Process -> launch an Activity -> render your code in the web browser.

The Activity part here is important, because as noted for the In the background section in Android's docs, Android no longer allows Activities to be started from the background.

The key difference between using the callback-based scan and the pending intent based scan is that the pending intent scan can relaunch your applications process if Android has terminated the process for power reasons or due to the user navigating too far away. But with a hybrid capacitor app, the Activity is not restarted and therefore your javascript won't be executed.

The Solution

Unfortunately, there is no easy answer here.

  1. For highly controlled environments (e.g., business apps deployed with help from an MDM or EMM system), there are "dangerous" runtime permissions on Android you can turn on to allow Activities to be relaunched from the background: https://developer.android.com/guide/components/activities/background-starts. Specifically, you need the SYSTEM_ALERT_WINDOW permission to be granted by the user
  2. For less controlled environments, you likely need to write native code (e.g., some kind of service) that can execute without the capacitor ecosystem going. There is an interesting effort underway at https://github.com/ionic-team/capacitor-background-runner/, but unfortunately this does not yet have a suitable mechanism for adding Bluetooth support

Side-note 1: The 5 minute pause

There is another likely issue you will see that can "appear" like the scan is stopping. 5 minutes after the phone screen is turned off, an power optimization in the webview kicks in, and pauses all javascript processing (among other things). To ensure your javascript code continues operating beyond this 5min mark after the screen is off (or the app is put into the background), you need to use a plugin such as capacitor-background-mode, specifically the disableWebViewOptimizations() feature.

Please note however, that this can dramatically increase the power usage of your app if you have any kinds of animations or frequent javascript executions.

Side-note 2: The 40-50 minute pause

Roughly 40-50 minutes after the phone screen is turned off (assuming the phone is unplugged), the phone usually enters a deep doze mode. This generally pauses all javascript execution. Some phone manufacturers will also kill some active processes at this point. See dontkillmyapp for details about these issues.

It can help sometimes to disable battery optimizations for your application to continue operating beyond this point. However... at some point, the phone will still go to sleep. Or... if your app uses too much power, Android will choose to kill it in order to conserve power.

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

No branches or pull requests

3 participants