Running in the Background on KaiOS

Posted by Tom Barrasso on (updated on )

Running in the Background on KaiOS

Despite KaiOS operating on resource-constrained hardware, apps can execute tasks in the background using several approaches.

  • Background Media
  • Activities & System Messages
  • Service Workers
  • Alarm API
  • Request Sync API

Background Media

The simplest way to do anything in the background is by using Audio Channels to continue audio playback while using another app. Using the content audio channel on an audio or video element, when the user presses Back/ EndCall, it will trigger your application to enter the background.

The property can be set as an HTML attribute:

1<audio mozAudioChannelType="content" />
2<video mozAudioChannelType="content" />

Or via a JavaScript property:

1const audio = document.createElement('audio');
2audio.mozAudioChannelType = 'content';

That’s it! When the user exits your app, audio will continue to play in the background.

Note: due to resource constraints, devices that are experiencing memory pressure may close your app, stopping audio playback. It’s best not to use video for background audio playback, since that requires significantly more memory.

Activities and System Messages

The easiest way to perform work in the background is by having your app respond to activities using MozActivity an the messages manifest.webapp property. Responding apps can either

  1. Launch fully and become the foreground app
  2. Launch an interstitial (i.e. to select photos from a gallery)
  3. Perform a task and return a result.

In the case of system messages, some may be triggered by the system without user interaction, while most activities will be triggered by the user directly.

What it’s good for? Responding to user-triggered events or certain system messages.

⚠️ Warning: Activities are exposed to all apps and the web browser. This is how CVE-2023-27108 works: the Communications app exposes an activity that returns the user’s call log without authorization checks.

Service Workers

Like all modern web browsers, one way to performa limited set of background tasks is through Service Workers, most notably via push notifications. That said, service workers execute in a worker scope with no access to the Document Object Model (DOM) and without many common APIs like XMLHttpRequest (XHR). However, service workers can process information off the main thread and can return transferable objects (i.e. String and ArrayBuffer) using message passing.

What it’s good for? Responding to push notifications; basic background processing via message passing.

On KaiOS 3.0, all background processing is routed via a Service Worker that, in some cases (i.e. when called via notificationclick), can launch a UI.

ℹ️ Important: Service Workers have a default 30 second idle timeout for Events like PusgEvent. It’s controlled by dom.serviceWorkers.idle_timeout, and while this may seem short, longer-running tasks are best moved to the main thread.

Alarm API

The Alarm API wakes up the phone at a specified time. The OS dispatches system messages to an app with both the alarm permission and alarm message declared in manifest.webapp. The Alarm API is accessible via the navigator.mozAlarms (KaiOS 2.5) and navigator.b2g.alarmManager (KaiOS 3.0).

ℹ️ Important: unlike the RequestSync API, alarms do not have timeouts. If you use the Alarm API for background synchronization, it’s important to call window.close to terminate your application once completed.

Apps first need to declare the permission and message in their manifest:

1"permissions": {
2    "alarms": { },
3},
4"messages": [
5    { "alarm": "/index.html" }
6]

Registering for Notifications

Then register for incoming system messages via navigator.mozSetMessageHandler, or navigator.mozSetMessageHandlerPromise if they want to wait until some asychronous process is completed. On KaiSO 3.0, you need to register a ServiceWorker than subscribe for the alarm systems messages.

1// KaiOS 2.5
2navigator.mozSetMessageHandler('alarm', (mozAlarm) => {
3    console.log('alarm fired:', JSON.stringify(mozAlarm.data));
4});
5
6// KaiOS 3.0
7navigator.serviceWorker.ready.then(() => {
8    registration.systemMessageManager.subscribe('alarm');
9});

Adding Alarms

Finally, the app then needs to set an alarm at a specific time:

 1let tomorrow = new Date();
 2tomorrow.setMinutes(0);
 3tomorrow.setSeconds(0);
 4tomorrow.setDate(new Date().getDate() + 1);
 5
 6let data = { timestamp: tomorrow.valueOf() };
 7
 8// KaiOS 2.5, returns DOMRequest
 9navigator.mozAlarms.add(
10    tomorrow,
11    'honorTimezone',
12    data
13);
14
15// KaiOS 3.0, returns Promise
16navigator.b2g.alarmManager.add({
17    date: date,
18    data: data,
19    ignoreTimezone: false,
20});

The first parameter is the Date for the alarm to trigger, the second is an enum to honorTimezone or ignoreTimezone, and the third data parameter is optional, and must be JSON-serializable.

Honoring timezones means that the alarm remains the same in UTC Coordinated Universal Time. If the alarm was set for 1PM Pacific Standard Time (PST) but the user has travelled to the east coast, it will run at 4PM Eastern Standard Time (EST).

Ignoring timezones means that the alarm remains at the same time of day. If the alarm was set for 1PM Pacific Standard Time (PST), it will also run at 1PM Eastern Standard Time (EST).

Getting and Removing Alarms

You can get a list of all the alarms that your app set using getAll. It returns a Promise or DOMRequest that resolves to an Array of objects, each with an id property unique to that alarm.

1// KaiOS 2.5, returns DOMRequest
2navigator.mozAlarms.getAll();
3
4// KaiOS 3.0, returns Promise
5navigator.b2g.alarmManager.getAll();

You can remove the alarm with remove by passing the alarm id as the parameter. These methods return void, so there’s no need to wait or check their results.

1// KaiOS 2.5
2navigator.mozAlarms.remove(alarm.id);
3
4// KaiOS 3.0
5navigator.b2g.alarmManager.remove(alarm.id);

Alarm API Use Cases

One downside of the Alarm API is that you don’t have information on the device state, so if you need internet access and your alarm triggers when internet is unavailable, you’ll have to alert the user (likely with a notification) and try again later. I have also found that it can be unreliable when setting intervals, with alarms not running at the time set or not running at all.

Intervals: apps can set multiple alarms, so to replicate regular intervals (i.e. daily, weekly) they can set an alarm for every day in the next X months, or when triggered, set an alarm for the next day. For an example of this, check out my Daily Bing Wallpaper app.

What it’s good for? The Alarm API is good for waking up the device and launching your app at a specific time (i.e. an alarm clock). With a bit more effort, it can also be used for periodic background polling (i.e. auto-update, checking for new emails).

Note: the Alarm API is only available on privileged packaged apps, and excessive use will drain the battery. It’s available based on the dom.mozAlarms.enabled device preference, which is enabled by default.

Request Sync API

The final approach executing background tasks on KaiOS is the Request Sync API. Note that is only works on KaiOS 2. (was removed in KaiOS 3.0) and requires the app be certified. Since there’s more book keeping involved, and the RequestSyncService needs to obtain a cpu wakelock for tasks, RequestSync may drain the battery more than the Alarm API.

That said, it’s a fairly simple API and, in my experience, has proved more reliable than the Alarm API. The RequestSync API was originally introduced in Boot2Gecko (B2G) as a background sync schedule for the email app to periodically poll for new email messages. It was meant as a substitute for the BackgroundSync API, which is only available for installed Progressive Web Apps (PWAs) and Firefox has not implemented to this day.

ℹ️ Important: because the RequestSync API is meant for background synchronization, it has a minimum internal of 100 seconds (customizable via dom.requestSync.minInterval), and a timeout of 2 minutes (customizable via dom.requestSync.maxTaskTimeout).

In your manifest.webapp, declare the requestsync-manager permission and the request-sync message.

1"permissions": {
2    "requestsync-manager": { },
3},
4"messages": [
5    { "request-sync": "/index.html" }
6]

Note: the RequestSync API is available behind the dom.requestSync.enabled device preference, which is enabled by default on KaiOS 2.5.

Register Tasks

Registering RequestSync tasks is done via the navigator.sync API.

1let taskName = 'myapp-dailytask';
2
3// KaiOS 2.5 only, returns Promise
4return navigator.sync.register(taskName, {
5    minInterval: 60 * 60 * 24, // 1 day, in seconds
6    oneShot: false, // run repeatedly
7    wifiOnly: false, // run with or without WiFi
8    wakeUpPage: location.href // launch this page
9});

In this example, we run a task daily. When the app launches, we register the task in the background like in my app Quotez.

Unregister Tasks

Tasks can also be unregistered easily via task name.

1let taskName = 'myapp-dailytask';
2
3// KaiOS 2.5 only
4navigator.sync.unregister(taskName);

Enumerating Tasks

You can easily enumerate all tasks that your app registered. This resolves to an Array of task objects with all the same configuration parameters, as well as some metadata on execution history.

Here is a snippet of the WebIDL definition for a RequestSync Task:

 1interface RequestSyncTask {
 2  // This object describes the app that is owning the task.
 3  readonly attribute RequestSyncApp app;
 4
 5  // These attributes are taken from the configuration of the task:
 6  readonly attribute USVString task;
 7  readonly attribute DOMTimeStamp lastSync;
 8  readonly attribute USVString wakeUpPage;
 9  readonly attribute boolean oneShot;
10  readonly attribute long minInterval;
11  readonly attribute boolean wifiOnly;
12  readonly attribute any data;
13
14  Promise<void> runNow();
15}

Enumerating all registration is simple:

1navigator.sync.registrations()
2    .then((registrations) => {
3        if (Array.isArray(registrations) && registrations.length) {
4            // Registrations are present
5        }
6    });

Note: it’s a good idea to check if you’ve already registered the tasks, to avoid double registration.

What it’s good for? For certified apps targeting KaiOS 2.5 only: running tasks at specific intervals (i.e. daily) or only running tasks when WiFi is available.

ServiceWorkerAlarmRequestSync
TypePush NotificationOne-Off AlarmSync Intervals
Triggerpush eventalarm system messagerequest-sync system message
Min PermissionHostedPrivilegedCertified
Timeout30 secondsN/A2 minutes
ScopeSW Global ScopeWindow (2.5)
SW (3.0)
Window
Works Offline?X☑️☑️
WiFi-Only Option?XX☑️
KaiOS 2.5☑️☑️☑️
KaiOS 3.0☑️☑️X

Conclusion

Despite hardware constraints, background task execution is available in KaiOS. In fact, it’s an important component of certain services like the Email app, which needs to poll for new email messages without the user opening the app. If you’re looking for a partner to ensure the best user experience and deliver background synchronization on KaiOS, you can find the author’s contact info on the About page.