deep-foundation / deep-memo-app

The Unlicense
11 stars 8 forks source link

Let the android app to work when app is in closed and/or screen is off #25

Closed FreePhoenix888 closed 1 year ago

FreePhoenix888 commented 1 year ago

Background Service Limitations

FreePhoenix888 commented 1 year ago

cordova-plugin-foreground-service

NOTE: This plugin does not on its own allow the user to execute javascript while the app is in the background. This must be accompanied by another plugin that will create a background process that will give the application background cycles. This plugin mainly exists to solve the problem outlined here. An app will have restricted background processing if it is not considered a "foreground app" for android API 26+ and will prevent background pluggins from functioning properly. With this plugin your application will be a foreground app and let your background services run properly. This plugin may also help with android doze where an application may not run in the background unless it also has a foreground process. Do not expect this plugin on its own to allow your app to execute javascript while in the background.

FreePhoenix888 commented 1 year ago

This plugin allows for android devices to continue running services in the background, using a foreground ongoing notification. This is targeted towards use with plugins such as 'cordova-geolocation' that will not run while the app is in the background on android API 26+.

FreePhoenix888 commented 1 year ago

According to this example it is enough to have foreground service to do something inside it when the app in background and/or screen is off

FreePhoenix888 commented 1 year ago

There is a lot of confusing info about foreground services. For someone it works perfect, for someone it is not. I have these problems with SaveMyLife android application when I was trying to keep my foreground service alive. Sometimes it is just killed even if the service returns START_STICKY and there are no battery optimizations set for my app it settings

FreePhoenix888 commented 1 year ago

I think we need our app to save something always so our background work is long-running Android documentation tells the recommended solution is to use WorkManager

FreePhoenix888 commented 1 year ago

According to this info I guess we need a foreground service. We have some JS code in our components and I guess we do not need to use useDeepSubscription there to make some work when a specific link is created/updated/deleted in deep. Subscription and following actions must be executed in foreground service How can we achieve this? Should we write code inside foreground services ?

FreePhoenix888 commented 1 year ago

I am learning code from capacitor-community and I have found background-geolocation plugin It created a foreground service I see this part of code but I still do understand how we should use useDeepSubrciption. Where should it be located to work even if the app is closed and foreground service is active? What our functional components converted to? Can we make our functional component to "work" when foreground service is active?

FreePhoenix888 commented 1 year ago

I have found out how capacitor plugins are used in android project. They are added as projects to capacitor.settings.gradle and included to dependencies in capacitor.build.gradle

FreePhoenix888 commented 1 year ago

This is not something missing in Ionic. This is something missing the native frameworks. Ionic cannot make plugins for something that Apple and Google don’t support. Basically they do not want you to be able to use all the battery of the device, so they don’t allow this. For Android, some hacks exist, but for iOS they do not. There are background workers that will work for a while, but the OS will eventually kill your background thread. You need remote push notifications sent from a server for this purposes. It is possible to send notifications that are invisible to the user but are only meant to trigger some event in the app, like a background thread fetching data. I participated on building the Covid detection app in my country, and here there is a specific API (Exposure notification API) which was necessary to keep the app running in the background. We tried for a long time to build this for our selves, but it cannot be done. You cannot keep a background thread alive infinitely in a custom app. The purpose is to protect the users.

Source

FreePhoenix888 commented 1 year ago

Cordova Background Plugin

Plugin for the Cordova framework to perform infinite background execution. Most mobile operating systems are multitasking capable, but most apps dont need to run while in background and not present for the user. Therefore they pause the app in background mode and resume the app before switching to foreground mode. The system keeps all network connections open while in background, but does not deliver the data until the app resumes.

...

The system keeps all network connections open while in background, but does not deliver the data until the app resumes.

... πŸ€”

FreePhoenix888 commented 1 year ago

People on https://forum.ionicframework.com/t/post-location-updates-even-when-screen-is-off-other-application-is-in-the-foreground-ionic-angular/204098 have the same problem but there is no solution :(

FreePhoenix888 commented 1 year ago

https://github.com/capawesome-team/capacitor-background-task#beforeexit allows to do work on background and it says

On iOS this method should be finished in less than 30 seconds.

But no info about android. Can we use it without a foreground service and disabling battery optimizations for our app? Will it even work if we run a foreground service and disable battery optimizations for our app?

FreePhoenix888 commented 1 year ago

https://developer.android.com/topic/performance/memory-overview#SwitchingApps

For more information about how processes are cached while not running in the foreground and how Android decides which ones can be killed, see the Processes and Threads guide.

FreePhoenix888 commented 1 year ago

https://developer.android.com/training/monitoring-device-state/doze-standby#:~:text=Note%3A%20You,app%20is%20idle. The amount of information supporting the hypothesis that we need a foreground service is increasing

Ok... Let us imagine we need a foreground service. How can we use javascript functionality inside it? For example use useDeepSubscription hook, create some links like we must do in our functional component. For example when useDeepSubscription "sees" new Vibrate link which is not Vibrated yet it will rerender functional component and useEffect that depends on the values returned from useDeepSubscription must vibrate uses's phone.

How can we do this by using foreground service?

FreePhoenix888 commented 1 year ago

Trying to answer on my questions:

Ok... Let us imagine we need a foreground service. How can we use javascript functionality inside it? For example use useDeepSubscription hook, create some links like we must do in our functional component. For example when useDeepSubscription "sees" new Vibrate link which is not Vibrated yet it will rerender functional component and useEffect that depends on the values returned from useDeepSubscription must vibrate uses's phone. How can we do this by using foreground service?

The easiest way to communicate between JavaScript and native code is to build a custom Capacitor plugin that is local to your app.

Source - Custom Native Android Code

Example:

package com.example.myapp;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

@CapacitorPlugin(name = "Echo")
public class EchoPlugin extends Plugin {

    @PluginMethod()
    public void echo(PluginCall call) {
        String value = call.getString("value");

        JSObject ret = new JSObject();
        ret.put("value", value);
        call.resolve(ret);
    }
}

Problem: but I need a foreground service that... Hm πŸ€” Should it call our javascript code someway or can we just create a foreground service and code in our functional components will just work? I think no... Reason:

NOTE: This plugin does not on its own allow the user to execute javascript while the app is in the background. This must be accompanied by another plugin that will create a background process that will give the application background cycles. This plugin mainly exists to solve the problem outlined here. An app will have restricted background processing if it is not considered a "foreground app" for android API 26+ and will prevent background pluggins from functioning properly. With this plugin your application will be a foreground app and let your background services run properly. This plugin may also help with android doze where an application may not run in the background unless it also has a foreground process. Do not expect this plugin on its own to allow your app to execute javascript while in the background.

Source - cordova-plugin-foreground-service

This must be accompanied by another plugin that will create a background process that will give the application background cycles. πŸ€” Should I ask them in an issue which plugin sholud I use?

I guess I should try to create a foreground service by using cordova-plugin-foreground-service and use capacitor-background-task
πŸ€” How should I use capacitor-background-task? Example from

import { App } from '@capacitor/app';
import { BackgroundTask } from '@capawesome/capacitor-background-task';

App.addListener('appStateChange', async ({ isActive }) => {
  if (isActive) {
    return;
  }
  // The app state has been changed to inactive.
  // Start the background task by calling `beforeExit`.
  const taskId = await BackgroundTask.beforeExit(async () => {
    // Run your code...
    // Finish the background task as soon as everything is done.
    BackgroundTask.finish({ taskId });
  });
});

I cannot use our hook right there. What should I do instead?

FreePhoenix888 commented 1 year ago

I have found out that useDeepSubscription uses useSubscription from apollo/client

It is possible to subscribe by using kotlin: image If we are going to use that we must convert our java code to kotlin

πŸ€” Why do we need that at all? Will not our useDeepSubscription work in our functional component when a screen is off? I think it is not because the activity can be just closed and foreground service will not make our activity to live forever. I have not found information that is against my hypothesis: https://www.google.com/search?q=foreground+service+is+activity+working&oq=foreground+service+is+activity+working&aqs=chrome..69i57l2j0i271l2j69i60l3.5482j0j1&sourceid=chrome&ie=UTF-8

FreePhoenix888 commented 1 year ago

According to this issue I am not even sure if capacitor-background-task even working. The author says:

A short notice: There is now an Android Foreground Service plugin that allows you to run Capacitor apps (Android only, iOS does not support this without further conditions) permanently in the background (unless they are force closed). This plugin can be combined very well with other plugins like this or https://github.com/transistorsoft/capacitor-background-fetch.

FreePhoenix888 commented 1 year ago

Should we subscribe to changes? We can use capacitor-background-fetch#example ant fetch data every N minutes. Problem:

Background Fetch is a very simple plugin which attempts to awaken an app in the background about every 15 minutes. There is no way to increase the rate which a fetch-event occurs and this plugin sets the rate to the most frequent possible β€” you will never receive an event faster than 15 minutes. The operating-system will automatically throttle the rate the background-fetch events occur based upon usage patterns. Eg: if user hasn't turned on their phone for a long period of time, fetch events will occur less frequently or if an iOS user disables background refresh they may not happen at all.

πŸ€”

FreePhoenix888 commented 1 year ago

I have tried the query [capacitor subscribe to apollo](https://www.google.com/search?q=capacitor+subscribe+to+apollo&oq=capacitor+subscribe+to+apollo&aqs=chrome..69i57j33i160.4930j0j4&sourceid=chrome&ie=UTF-8]

I have found the answer that tells us to use push-notification:

I think what you need is Push Notifications (@capacitor/push_notifications).

When a subscription happens in your server (eg. new message) you then send a push notification to your devices. The push notification will be displayed in the device and when you click/open the app you can fetch new data from the server.

I have seen that answer already somewhere else. Looks wise. Intuition tells me that Telegram uses this to deliver us notifications about new messages so fast. Should we use this solution?

Then we do not need useDeepSubscription and we need to create a handler that is going to send a push notification a device

UPD: I have read the answer fully:

I think what you need is Push Notifications (@capacitor/push_notifications). When a subscription happens in your server (eg. new message) you then send a push notification to your devices. The push notification will be displayed in the device and when you click/open the app you can fetch new data from the server.

And I think is not the solution to accomplish our goal

FreePhoenix888 commented 1 year ago

Avout fetching data every N minutes. It will workwith https://github.com/transistorsoft/capacitor-background-fetch It limets us to fetch data to at least every 15 minutes ONLY on IOS but there are no restrictions for android therefore we can fetch data every minute πŸ€”

FreePhoenix888 commented 1 year ago

According to this I think we can reduce battery consumption if we will be able to do most of work on server and just notify a device to do something πŸ€” https://branch.io/glossary/background-push-notification/#:~:text=A%20background%20push%20notification%20is,when%20received%20by%20your%20app.

FreePhoenix888 commented 1 year ago

We are able to subscribe to push notifications and do some work https://capacitorjs.com/docs/apis/push-notifications#addlistenerpushnotificationreceived-

How I see it for example for the Vibrator package

  1. Server handler that is triggered when a Vibrate link is inserted will send a push notification to a device
  2. The device that is subscribed to push notifications will be able to do some work according what push notification is send

Question: will pushNotificationReceived subscription from capacitor work when the app is closed and a screen is off?

FreePhoenix888 commented 1 year ago

In [capacitor push notifications plugin]() I have found data only notifications section. It says

This plugin does support data-only notifications, but will NOT call pushNotificationReceived if the app has been killed. To handle this scenario, you will need to create a service that extends FirebaseMessagingService, see Handling FCM Messages.

The most interesting place - override onMessageReceived

FreePhoenix888 commented 1 year ago

One more answer that tells that we should use push-notifications - https://stackoverflow.com/a/73130700/13545849

FreePhoenix888 commented 1 year ago

I have tried to find info whether firebase can send notifcations to the APP that are not showed to a user. According to this answer it works this way and you are up to show notification if you want. You can do what you want when you gen push-notification

FreePhoenix888 commented 1 year ago

According to the info from How can I send notifications to Android devices in Doze / power saving mode? we have to use ignore battery optimization feature or FCM high-priority fallback delivery [Set and manage message priority](FCM high-priority fallback delivery,) page says

You have two options for assigning delivery priority to downstream messages on Android: normal and high priority. Delivery of normal and high priority messages works like this:

  • Normal priority. This is the default priority for data messages. - Normal priority messages are delivered immediately when the device is not sleeping. When the device is in Doze mode, delivery may be delayed to conserve battery until the device exits doze. For less time-sensitive messages, such as notifications of new email, keeping your UI in sync, or syncing app data in the background, choose normal delivery priority. When receiving a normal priority message on Android that requests a background data sync for your app, you can schedule a task with WorkManager to handle it when the network is available.
  • High priority. FCM attempts to deliver high priority messages immediately, allowing FCM to wake a sleeping device when necessary and to run some limited processing (including very limited network access). High priority messages generally should result in user interaction with your app or its notifications.

allowing FCM to wake a sleeping device when necessary and to run some limited processing

πŸ€”

Does this meet our requirements? What is limited processing? How mush is it limited?

FreePhoenix888 commented 1 year ago

High priority messages on Android are meant for time sensitive, user visible content, and should result in user-facing notifications. If FCM detects a pattern in which messages do not result in user-facing notifications, your messages may be deprioritized to normal priority. FCM uses 7 days of message behavior when determining whether to deprioritize messages; it makes this determination independently for every instance of your application. If, in response to high priority messages, notifications are displayed in a way that is visible to the user, then your future high-priority messages will not be deprioritized. This applies whether the notification is displayed by the FCM SDK via a notification message, or a developer-generated notification via a data message.

...... Fuck? πŸ€” Is it fine or should we use foreground service and make our apollo subscription to work in background?

FreePhoenix888 commented 1 year ago

I have created discussion on apollo graphQL website - https://community.apollographql.com/t/how-to-use-apollo-subscription-in-a-foreground-service-in-android/5349?u=freephoenix888

FreePhoenix888 commented 1 year ago

Error

freephoenix888@freephoenix888:~/node-test-project$ npm start

> test-project@1.0.0 start
> node index.js

Error sending message: Error: secretOrPrivateKey must be an asymmetric key when using RS256
    at Object.module.exports [as sign] (/home/freephoenix888/node-test-project/node_modules/jsonwebtoken/sign.js:124:22)
    at ServiceAccountCredential.createAuthJwt_ (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/credential-internal.js:105:20)
    at ServiceAccountCredential.getAccessToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/credential-internal.js:77:28)
    at FirebaseAppInternals.refreshToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/firebase-app.js:45:49)
    at FirebaseAppInternals.getToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/firebase-app.js:37:25)
    at AuthorizedHttpClient.getToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/utils/api-request.js:617:34)
    at AuthorizedHttpClient.send (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/utils/api-request.js:605:21)
    at FirebaseMessagingRequestHandler.invokeRequestHandler (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/messaging/messaging-api-request-internal.js:64:32)
    at /home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/messaging/messaging.js:195:49

Code

var admin = require("firebase-admin");
require('dotenv').config()

var serviceAccount = require("./deep-97e93-firebase-adminsdk-rnzyo-9dddc99b8b.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

const deviceRegistrationToken = process.env.DEVICE_REGISTRATION_TOKEN;

const payload = {
  notification: {
    title: 'Notification Title',
    body: 'This is an example notification',
  }
};

const options = {
  priority: 'high',
  timeToLive: 60 * 60 * 24, // 1 day
};

firebase.messaging().sendToDevice(deviceRegistrationToken , payload, options);

Where I get device registration token from - https://capacitorjs.com/docs/apis/push-notifications#addlistenerregistration-

Where I get code example and private key from

  1. Image
  2. https://www.woolha.com/tutorials/node-js-send-push-notification-message-to-firebase

Note

I use the same registration token on firebase's console to send "test cloud notification" and it is working. I do not think the problem is because device registratin token is not valid

FreePhoenix888 commented 1 year ago

I have been looking for information about "is Firebase really the solution we should use?" and I have found information about wake locks. Some people use them to not let CPU sleet and do some work. I have tried to google that too and I came to Keep the device awake documentation page where it says:

Alternatives to using wake locks Before adding wakelock support to your app, consider whether your app's use cases support one of the following alternative solutions:

If your app is performing long-running HTTP downloads, consider using DownloadManager.

If your app is synchronizing data from an external server, consider creating a sync adapter.

If your app relies on background services, consider using JobScheduler or Firebase Cloud Messaging to trigger these services at specific intervals.

If you need to keep your companion app running whenever a companion device is within range, use Companion Device Manager.

I was trying to figure out is Sync adapters really what we need. The Transfer data using sync adapters documentation page says

Note: We recommended WorkManager as the recommended solution for most background processing use cases. Please reference the background processing guide to learn which solution works best for you.

The information that make me think we need long-running operations - Background Work Overview

Under the hood, WorkManager manages and runs a foreground service on your behalf to execute the WorkRequest, while also showing a configurable notification.

Again the documentation says that we need a foreground service

Replace Android Foreground Service with Work Manager article convinces me we need a foreground service with apollo subscription

Replace foreground services says:

Android 12 restricts launching foreground services from the background. For most cases, you should use setForeground() from WorkManager rather than handle foreground services yourself. This allows WorkManager to manage the lifecycle of the foregound service, ensuring efficiency.

You still should use foreground services to perform tasks that are long running and need to notify the user that they are ongoing. If you use foreground services directly, ensure you shut down the service correctly to preserve resource efficiency.

Some use cases for using foreground services directly are as follows:

Media playback Activity tracking Location sharing Voice or video calls

FreePhoenix888 commented 1 year ago

Restricted access to location, camera, and microphone How is our app going to be working if the foreground service that is restarted after phone boot cannot access microphone?

To help protect user privacy, Android 11 (API level 30) introduces limitations to when a foreground service can access the device's location, camera, or microphone. When your app starts a foreground service while the app is running in the background, the foreground service has the following limitations:

Unless the user has granted the ACCESS_BACKGROUND_LOCATION permission to your app, the foreground service cannot access location. The foreground service cannot access the microphone or camera.

In some situations, even if a foreground service is started while the app is running in the background, it can still access location, camera, and microphone information while the app is running in the foreground ("while-in-use"). In these same situations, if the service declares a foreground service type of location and is started by an app that has the ACCESS_BACKGROUND_LOCATION permission, this service can access location information all the time, even when the app is running in the background. πŸ€” Should we create foreground services for different purposes? Geolocation package will have its own foreground service, microphone package will have its own service

FreePhoenix888 commented 1 year ago

Exemptions from the restrictions

In some situations, even if a foreground service is started while the app is running in the background, it can still access location, camera, and microphone information while the app is running in the foreground ("while-in-use"). In these same situations, if the service declares a foreground service type of location and is started by an app that has the ACCESS_BACKGROUND_LOCATION permission, this service can access location information all the time, even when the app is running in the background.

The following list contains these situations:

  • The service is started by a system component.
  • The service is started by interacting with app widgets.
  • The service is started by interacting with a notification.
  • The service is started as a PendingIntent that is sent from a different, visible app.
  • The service is started by an app that is a device policy controller that is running in device owner mode.
  • The service is started by an app which provides the VoiceInteractionService.
  • The service is started by an app that has the START_ACTIVITIES_FROM_BACKGROUND privileged permission.

    The solution above with START_ACTIVITIES_FROM_BACKGROUND permission does not meet our requirements because of this answer:

your app needs to be part of the firmware build (or be installed on the privileged partition by a user of a rooted device).

FreePhoenix888 commented 1 year ago

https://stackoverflow.com/a/42593885/13545849

This is a pattern you should avoid because:

1) The device may disconnect the network from your Android app in doze mode in newer versions of Android. If you're trying to get notified at any time of any change, this is not going to work.

2) Firebase Cloud Messaging is a better way to reliably wake a device to handle a change that your app needs to know about.

Firebase Realtime Database uses a websocket to let a client know when things change. At least the drain on battery from an open socket is not a big deal. You just shouldn't count on that connection unless your app is visible and has a reliable network connection.

FreePhoenix888 commented 1 year ago

https://stackoverflow.com/a/11789809/13545849

Keeping an idle TCP socket connection open (with no data being sent or received) will not (or at least, should not) consume any more battery than having it closed. That is because an idle TCP connection uses no bandwidth or CPU cycles(*).

That said, keeping a TCP connection open for extended periods may not be a good option for a mobile device, because TCP connections don't interact well with computers that go to sleep. The problem scenario would be this: your Android user puts his Android device to sleep while your app is running, and then the remote user's program (or whatever is at the other end of the TCP connection) sends some data over the TCP stream. The remote user's program never gets any ACKs back from the Android device, because of course the Android device is asleep, so the remote device's TCP stack assumes that the TCP packets it sent must have been lost, and it responds by increasing its timeout period, decreasing its TCP window size (aka number-of-TCP-packets-allowed-in-flight-at-once), and resending the TCP packets. But the Android device is still asleep, and thus the same thing happens again. The upshot is that a few minutes later, the remote end of the TCP connection has slowed down to the point where even if the Android device was to wake up, the TCP connection will likely be too slow to be usable -- at which point your program will need to close the bogged-down TCP connection and start up a fresh one anyway, so why bother trying to keep it open?

So my recommendation would be to go with option (a), with the stipulation that you close the TCP connection as part of your device-is-going-to-sleep-now routine.

One possible caveat would be if Android has a feature where keeping a TCP connection open causes the WiFi or cell-network hardware to remain powered up in a situation where it could otherwise be put to sleep -- if that is the case, then the Android device would pay a battery cost for powering the antenna, which it wouldn't otherwise have had to pay. I'm not aware of any Android logic like that, but I've only used Android a little so that might just be ignorance on my part. It might be worth testing for, at least.

(*) Well, technically TCP does send a "keepalive" packet every so often while a TCP connection is open, and that does use some CPU cycles and antenna power... but the default interval for sending keepalive packets on Android is two hours, so I doubt the power used for that would be noticeable.

FreePhoenix888 commented 1 year ago

Doze restrictions

The following restrictions apply to your apps while in Doze:

Network access is suspended. The system ignores wake locks. Standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window. If you need to set alarms that fire while in Doze, use setAndAllowWhileIdle() or setExactAndAllowWhileIdle(). Alarms set with setAlarmClock() continue to fire normally β€” the system exits Doze shortly before those alarms fire. The system does not perform Wi-Fi scans. The system does not allow sync adapters to run. The system does not allow JobScheduler to run.

FreePhoenix888 commented 1 year ago

Doze checklist If possible, use Firebase Cloud Messaging (FCM) for downstream messaging. If your users must see a notification right away, make sure to use an FCM high priority message. Be sure to only use high priority for messages that will result in a notification. For more guidance, refer to FCM's documentation on message priority for Android. Provide sufficient information within the initial message payload, so subsequent network access is unnecessary. Set critical alarms with setAndAllowWhileIdle() and setExactAndAllowWhileIdle(). Test your app in Doze.

FreePhoenix888 commented 1 year ago

https://stackoverflow.com/questions/32286797/wakelock-and-doze-mode

FreePhoenix888 commented 1 year ago

https://stackoverflow.com/a/33757158/13545849:

Also this is actually our recommended practice for this situation -- if you have a long-running foreground service, it should be in a separate process from the activity, so it doesn't force all of the memory associated with the activity to be kept around. (This is also why this bug got through, all of our apps use this pattern.)ο»Ώ

FreePhoenix888 commented 1 year ago

FCM attempts to deliver high priority messages immediately even if the device is in Doze mode.

How?

FreePhoenix888 commented 1 year ago

i_hate_firebase

https://stackoverflow.com/questions/73135325/android-app-not-waking-up-from-doze-mode-even-with-high-priority-message

FreePhoenix888 commented 1 year ago

image

FreePhoenix888 commented 1 year ago

Using FCM to interact with your app while the device is idle

Firebase Cloud Messaging (FCM) is a cloud-to-device service that lets you support real-time downstream messaging between backend services and apps on Android devices. FCM provides a single, persistent connection to the cloud; all apps needing real-time messaging can share this connection. This shared connection significantly optimizes battery consumption by making it unnecessary for multiple apps to maintain their own, separate persistent connections, which can deplete the battery rapidly. For this reason, if your app requires messaging integration with a backend service, we strongly recommend that you use FCM if possible, rather than maintaining your own persistent network connection.

FCM is optimized to work with Doze and App Standby idle modes. FCM high priority messages let you reliably wake your app to engage the user. In Doze or App Standby mode, the system delivers the message and gives the app temporary access to network services and partial wakelocks, then returns the device or app to the idle state. For time sensitive, user-visible notifications, consider using high priority messages to enable delivery in Doze mode. High priority messages are expected to result in notifications. See FCM's guidance on high priority messages for more information.

For messages that don't result in notifications, such as keeping application content up to date in the background or initiating data syncs, normal priority FCM messages are the correct choice. Normal priority messages are delivered immediately if the device is not in doze. If the device is in Doze mode, they are delivered during the periodic doze maintenance windows or as soon as the user wakes the device.

As a general best practice, if your app requires downstream messaging, it should use FCM. If your app already uses FCM, make sure that it uses high priority messages only for messages that result in user-facing notifications.

Alternative

image

FreePhoenix888 commented 1 year ago

Error

freephoenix888@freephoenix888:~/node-test-project$ npm start

> test-project@1.0.0 start
> node index.js

Error sending message: Error: secretOrPrivateKey must be an asymmetric key when using RS256
    at Object.module.exports [as sign] (/home/freephoenix888/node-test-project/node_modules/jsonwebtoken/sign.js:124:22)
    at ServiceAccountCredential.createAuthJwt_ (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/credential-internal.js:105:20)
    at ServiceAccountCredential.getAccessToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/credential-internal.js:77:28)
    at FirebaseAppInternals.refreshToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/firebase-app.js:45:49)
    at FirebaseAppInternals.getToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/firebase-app.js:37:25)
    at AuthorizedHttpClient.getToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/utils/api-request.js:617:34)
    at AuthorizedHttpClient.send (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/utils/api-request.js:605:21)
    at FirebaseMessagingRequestHandler.invokeRequestHandler (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/messaging/messaging-api-request-internal.js:64:32)
    at /home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/messaging/messaging.js:195:49

Code

var admin = require("firebase-admin");
require('dotenv').config()

var serviceAccount = require("./deep-97e93-firebase-adminsdk-rnzyo-9dddc99b8b.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

const deviceRegistrationToken = process.env.DEVICE_REGISTRATION_TOKEN;

const payload = {
  notification: {
    title: 'Notification Title',
    body: 'This is an example notification',
  }
};

const options = {
  priority: 'high',
  timeToLive: 60 * 60 * 24, // 1 day
};

firebase.messaging().sendToDevice(deviceRegistrationToken , payload, options);

Where I get device registration token from - https://capacitorjs.com/docs/apis/push-notifications#addlistenerregistration-

Where I get code example and private key from

  1. Image
  2. https://www.woolha.com/tutorials/node-js-send-push-notification-message-to-firebase

Note

I use the same registration token on firebase's console to send "test cloud notification" and it is working. I do not think the problem is because device registratin token is not valid

This problem was happening because of old node version:

freephoenix888@freephoenix888:~/node-test-project$ npm start

> test-project@1.0.0 start
> node index.js

Error sending message: Error: secretOrPrivateKey must be an asymmetric key when using RS256
    at Object.module.exports [as sign] (/home/freephoenix888/node-test-project/node_modules/jsonwebtoken/sign.js:124:22)
    at ServiceAccountCredential.createAuthJwt_ (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/credential-internal.js:105:20)
    at ServiceAccountCredential.getAccessToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/credential-internal.js:77:28)
    at FirebaseAppInternals.refreshToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/firebase-app.js:45:49)
    at FirebaseAppInternals.getToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/app/firebase-app.js:37:25)
    at AuthorizedHttpClient.getToken (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/utils/api-request.js:617:34)
    at AuthorizedHttpClient.send (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/utils/api-request.js:605:21)
    at FirebaseMessagingRequestHandler.invokeRequestHandler (/home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/messaging/messaging-api-request-internal.js:64:32)
    at /home/freephoenix888/node-test-project/node_modules/firebase-admin/lib/messaging/messaging.js:195:49
freephoenix888@freephoenix888:~/node-test-project$ nvm use 18.12.1
Now using node v18.12.1 (npm v8.19.2)
freephoenix888@freephoenix888:~/node-test-project$ npm start

> test-project@1.0.0 start
> node index.js

Successfully sent message: projects/deep-97e93/messages/0:1672834849350838%a6e672eea6e672ee

And because I was using old api. The way it works:

var admin = require('firebase-admin');
require('dotenv').config();

var serviceAccount = require('./deep-97e93-firebase-adminsdk-rnzyo-9dddc99b8b.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

const deviceRegistrationToken = process.env.DEVICE_REGISTRATION_TOKEN;

const message = {
  notification: {
    title: 'Hello',
    body: `Hello!`,
  },
  token: deviceRegistrationToken,
};

admin
  .messaging()
  .send(message)
  .then((response) => {
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });
FreePhoenix888 commented 1 year ago

addListener('pushNotificationReceived', ...) does not work if the application is closed, but notifications are showed

Answer about it: https://github.com/ionic-team/capacitor/issues/2722#issuecomment-610652252

Silent Push Notifications / Data-only Notifications](https://capacitorjs.com/docs/apis/push-notifications#silent-push-notifications--data-only-notifications) tells that we should extend FirebaseMessagingService class Yes the application receive data-notifications when the screen is off and the application is closed.

I was thinking about Vibrator package and (https://developer.android.com/reference/kotlin/android/os/VibratorManager?hl=en#vibrate) documentation says:

The app should be in foreground for the vibration to happen.

Another VibratorManager.vibrate documentation says we should use notification with ringtone to vibrate when an application is in background

FreePhoenix888 commented 1 year ago

If we have two services extending FirebaseMessagingService only one works image

It means every package must change the code of this service to handle specific data-only push-notification

FreePhoenix888 commented 1 year ago

The listener does not work when the application is closed. In order for a notification to call a listener in the background, we need data-only notifications, but for them to call a listener, we need to inherit their service class. More information: https://capacitorjs.com/docs/apis/push-notifications#silent-push-notifications--data-only-notifications

Problem

We can call plugin capacitor methods from there.But it is not enough. For example, we will need to save something in deep.

Possible solutions

1.There is no client deep, but we can do it with apollo (we will do almost the same work as creating kotlin/java deepclient :D)

  1. Use addListener('pushNotificationActionPerformed', ...) We will be able to not use data-only notifications and use notifications with payload which must be clicked by the user to do something. Example: "Save audio recordings" notification will appear and user must click on it. After that pushNotificationActionPerformed listener will be executed with notifications's data, payload and will start audio recording. Problem: Is it fine that a user will have to click notification to do something? Another problem: push notifications are good but while I was writing this comment I get thought about... How is audio recording going to be executed in the background when the application is killed? It means we should use functions of capacitor plugins inside our foreground service which is going to be alive all the time
FreePhoenix888 commented 1 year ago

Push notifications

We do not need to only get push notifications from a deep server. We need every package to get push-notifications to be aware of changed in deep.

How do we get push notifications for every package or how to make them aware of them?

I have tried to have two services which extends FirebaseMessagingService and only one of them w.... Wait... I do not remember if I added the second one to the manifest

Work when app is closed and screen is off

We need a lot of packages not only get push notifications but to work all the time when they are enabled - for example geolocation and audio-recording packages To prevent doze mode - we can disable battery optimizations for our application and every package can run its own foreground service

FreePhoenix888 commented 1 year ago

No answers:

Bad:

Possible solution - https://stackoverflow.com/questions/47659458/android-can-an-android-app-have-multiple-firebasemessagingservices But nothing about it in the documentation. How much reliable is it?

FreePhoenix888 commented 1 year ago

Are all packages will be installed in the applications? If so we can have just one firebase cloud messaging service