Web Activities on KaiOS

Posted by Tom Barrasso on (updated on )

Sharing, selecting, and viewing data cross-app with Web Activities and deep links

Web Activities

User Flow of Configure Activity
Example User Flow of configure Activity

Like App Intents on iOS and Intents on Android, Web Activities are the way to share, pick, and view across apps on KaiOS. The interfaces are very similar across platforms, consisting of two components: a name (or action in Android) and an arbitrary data payload. Again similar to Android’s Intents, Web Activities are registered on KaiOS within an app’s static manifest.

Web Activites have many uses, including:

  • Launching a specific Settings page
  • Sending an email or SMS
  • Sharing text or an image across apps
  • Opening a specific file format like a PDF
  • Selecting a photo from the Gallery
  • Viewing a website
  • Setting the default ringtone
  • Responding to alarms via the Alarm API

Web Activities are available in all KaiOS app types, including hosted web apps and packaged privileged or certified apps. Furthermore, Web Activities are even available to websites! In 2020 this is how I stumbled upon a universal jailbreak for KaiOS that let’s users open the hidden Developer menu from their browser.

Differences in KaiOS 2.5 and KaiOS 3.0

On KaiOS 2.5, this API is MozActivity and on KaiOS 3.0, it’s WebActivity. This feature has been called deep linking and it’s one of the most common ways to interact with System apps. On KaiOS 2.5, the MozActivity inherits from DOMRequest while on KaiOS 3.0 the WebActivity class has a start method that returns a Promise. Here’s a simple exmaple on both versions that would open a website in the Browser app.

KaiOS 2.5KaiOS 3.0
 1let openURL = new MozActivity({
 2    name: "view",
 3    data: {
 4        type: "url",
 5        url: "https://kaios.dev"
 6    }
 7});
 8
 9openURL.onsuccess = () =>
10    console.log('Opened!');
11openURL.onerror = () =>
12    console.warn('Error!');
 1let openURL = new WebActivity(
 2    "view", {
 3        type: "url",
 4        url: "https://kaios.dev"
 5    }
 6);
 7
 8openURL.start()
 9    .then(() =>
10        console.log('Opened!'))
11    .catch(() =>
12        console.warn('Error!'));

If you’re building an app using WebActivities, it’s easy to standardize the APIs for consistent, cross-version support. Here’s a function that works on both KaiOS 2.5 and KaiOS 3.0 with a consistent Promise return type.

 1function startActivity(name, data) {
 2    // KaiOS 2.5 uses MozActivity
 3    if (typeof MozActivity !== 'undefined') {
 4        return new Promise((resolve, reject) => {
 5            let activity = new MozActivity({ name, data });
 6            activity.onsuccess = () => resolve(activity);
 7            activity.onerror = (e) => reject(e);
 8        });
 9    // KaiOS 3.0 uses WebActivity
10    } else if (typeof WebActivity !== 'undefined') {
11        let activity = new WebActivity(name, data);
12        return activity.start();
13    }
14
15    // Not KaiOS?
16    return Promise.resolve(null);
17}

Registering your app for an activity

So far we’ve covered launching Web Activities, but it’s also possible to respond to activity requests from within your app. To do so, you need to register an activity handler.

Static activity registration is declared in the manifest.webapp or manifest.webmanifest.

 1{
 2    "name": "My App",
 3    "description": "Awesome app",
 4    "activities": {
 5       "view": {
 6            "filters": {
 7                "type": "url",
 8                "url": {
 9                    "required": true,
10                    "regexp":"/^https?:/"
11                }
12            },
13            "returnValue": false
14        }
15    }
16}

Note: returnValue is false by default. Set it to true to allow your activity to return a value via postResult().

KaiOS 2.5

Then within app code, an activity handler callback can be registered and invoked when the system sends a message to your app. In KaiOS 2.5 the API is simple:

1let register = navigator.mozSetMessageHandler('view', (e) => {
2    if (e.source.name === 'view') {
3        console.log(e.source.data);
4    }
5});
6
7register.onerror = () => {
8    console.log("Failed to register");
9};

You can check if your application has queued messages by calling navigator.mozHasPendingMessage() with a given name as the parameter. When a message handler is set, all queued messages will be asynchronously delivered to the application.

Registering to receive system messages like activity or serviceworker-notification (covered in Push Notifications) requires an additional property be set in manifest.webapp:

1{
2    "name": "My App",
3    "description": "Awesome app",
4    "messages": [
5    { "serviceworker-notification": "/index.html" },
6    { "activity": "/index.html" }
7  ],
8}

There’s also one other important function, navigator.mozSetMessageHandlerPromise, which accepts a Promise as input. This method can only be called from the activity handler callback allows the page to set a Promise to keep alive the app until an asynchronous operation is fully completed. This could be used to process data or access a remote resource before returning a response.

 1let register = navigator.mozSetMessageHandler('view', (e) => {
 2    let url = e.source.data.url;
 3    if (url.startsWith('https')) {
 4        navigator.mozSetMessageHandlerPromise(fetch(url))
 5            .then((response) => response.text())
 6            .then((responseText) => e.postResult(responseText)));
 7    } else {
 8        e.postError(new Error('Insecure URL.'));
 9    }
10});

For instance, in this trivial example above an activity handler is registered for the view event that requires a url be provided in the data payload. If the URL is insecure (doesn’t start with HTTPS), then an error is posted. Otherwise, the resource is downloaded via fetch and the response text is passed as the result to the activity caller.

Note: a real-world example should perform better input validation and sanitization before downloading a remote resource.

KaiOS 3.0

For KaiOS 3.0, activity registration is declared in the b2g_features, section of manifest.webmanifest.

 1{
 2    "name": "My App",
 3    "description": "Awesome app",
 4    "b2g_features": {
 5        "activities": {
 6        "view": {
 7                "filters": {
 8                    "type": "url",
 9                    "url": {
10                        "required": true,
11                        "regexp":"/^https?:/"
12                    }
13                }
14            }
15        }
16    }
17}

The process for handling activity requests from within a KaiOS 3.0 app is more complicated than on KaiOS 2.5. First, it requires an active and registered ServiceWorker. Second, the ServiceWorker needs to handle the KaiOS-specific systemmessage event.

 1// Context: application code
 2if (swRegistration.systemMessageManager) {
 3    swRegistration.systemMessageManager.subscribe('view');
 4}
 5
 6// Context: ServiceWorker
 7self.addEventListener('systemmessage', (e) => {
 8    if (e.name === 'view') {
 9        console.log(e.data);
10    }
11});

Often this means posting messages and opening windows using the Clients interface to respond to the message within the app’s UI.

Note: see SystemMessageService.cpp for a list of system messages that can be received like media-button.

Common Activities

KaiOS ships with a number of pre-installed apps including Settings, Browser, Music, FM Radio, Gallery, and more. Each of these apps has a manifest with registered activities. Additionally, popular third-party apps like WhatsApp often register activity handlers as well. Here’s a selection of popular activities you might want to include in your apps.

Settings

The Settings app can be launched using the configure name and an optional section for specific pages.

1let request = new MozActivity({
2    name: "configure",
3    data: {
4        target: "device",
5        section: "TODO"
6    },
7});

There’s easily over 100 different values for section, but here’s a few popular ones:

  • developer - Developer menu
  • volume - Volume control
  • downloads - Downloads list
  • battery - Battery info
  • appPermissions - Permissions page
  • dateTime - Date & Time
  • bluetooth - Bluetooth
  • wifi - WiFi
  • geolocation - Geolocation & GPS

Launching the Settings app to a specific page can be a great UX enhancement! For instance, if your app requires an internet connection and you detect none is available, you can prompt the user to connect and easily navigate back to your app.

Note: it’s important to test these activities on real devices because it’s not uncommon for the Settings app to launch a blank page even for sections is specifically accepts.

Share

Share text or an image via SMS, WhatsApp, etc.

 1let shareText = new MozActivity({
 2  name: "share",
 3  data: {
 4    type: "text/plain",
 5    blobs: [ "This is a message to share" ]
 6  }
 7});
 8
 9let shareImage = new MozActivity({
10  name: "share",
11  data: {
12    type: "image/png",
13    number: 1,
14    blobs: [ blob ], // i.e. Canvas.toBlob
15    filenames: [ "myimage.png" ]
16  }
17});

Pick

Pick Activity
Pick Activity

Launch the Gallery or Camera app to select an image.

1let pick = new MozActivity({
2    name: "pick",
3    data: {
4        type: ["image/png", "image/jpg", "image/jpeg"]
5    }
6});

Launch the Contacts app to select a contact.

1let pick = new MozActivity({
2    name: "pick",
3    data: {
4        type: ["webcontacts/contact"],
5    },
6});

Note: when more than one app is able to respond to an activity request, a list is presented to the user to select.

Dial

Dial Activity
Dial Activity

Launch the phone dialer to call a number (requires user confirmation).

1let dial = new MozActivity({
2    name: "dial",
3    data: {
4        number: "*345#"
5    }
6});

View

Launch a website in the Browser.

1let view = new MozActivity({
2  name: "view",
3  data: {
4    type: "url",
5    url: "https://kaios.dev"
6  }
7});

Open

The "open" action will always launch an activity using the original content type to allow for third party applications to handle arbitrary types of content.

1let open = new MozActivity({
2    name: "open",
3    data: {
4        type: contentType,
5        blob: openBlob,
6        filename: null
7    }
8});

New

New Contact Activity
New Contact Activity

Create a new contact.

 1let newActivity = new MozActivity({
 2    name: "new",
 3    data: {
 4        type: "webcontacts/contact",
 5        params: {
 6            giveName: "Tom",
 7            familyName: "Barrasso",
 8            tel: "+1234567890",
 9            email: "[email protected]",
10            address: "USA",
11            note: "Memo",
12            company: "PodLP"
13        }
14    }
15});

Create a new text message (SMS).

1let newActivity = new MozActivity({
2    name: "new",
3    data: {
4        type: "websms/sms",
5        number: "1234567890",
6    },
7});

Save

Bookmark Activity
Bookmark Activity

Save a website as a bookmark on the homescreen.

1let saveBookmark = new MozActivity({
2    name: "save-bookmark",
3    data: {
4        type: "url",
5        url: "https://kaios.dev",
6        name: "KaiOS.dev",
7        icon: "https://kaiostech.com/favicon.ico"
8    }
9});

Set Ringtone

Set Ringtone Activity
Set Ringtone Activity

Set an audio file as the system ringtone.

 1let ringtoneActivity = new MozActivity({
 2    name: "setringtone",
 3    data: {
 4        blobs: [ audioBlob ],
 5        type: "audio/*",
 6        metadata: [{
 7            title: "Umbrella",
 8            artist: "Rihanna",
 9            picture: pictureBlog
10        }]
11    }
12});

Set Wallpaper

Set an image blob as the system wallpaper.

 1let wallpaperActivity = new MozActivity({
 2    name: "setwallpaper",
 3    data: {
 4        blobs: [ imageBlob ],
 5        type: "image/*",
 6        number: 1
 7    }
 8});
 9
10let wallpaperActivity = new WebActivity("set-wallpaper", {
11    blobs: [ imageBlob ],
12    type: "image/*",
13    number: 1
14});

Note: this is available on KaiOS 3.0 and later KaiOS 2.5 devices. It requires the pre-installed Wallpaper (Gaia Wallpaper Picker) app, and may be named "setwallpaper" or "set-wallpaper".

Cancelling and Error Handling

On both KaiOS 2.5 and 3.0, to cancel a started activity, use cancel(). You can cancel an activity before or after it’s been loaded. On KaiOS 2.5, the pending DOMRequest of new MozAcitivity will trigger onerror, and on KaiOS 3.0 the pending promise of start() will be rejected.

The onerror callback returns a DOMError on KaiOS 2.5, and by far the most common error name you’ll receive is "NO_PROVIDER", which means there is no available app to respond to that activity request. When an activity is cancelled the error name will be ActivityCanceled.

Additional Activities

Open Page

Open Page Activity
Open Page Activity

The open-page activity is supported by the KaiStore and JioStore. It lets you launch the app store to a specific app detail page for quicker installation, and can be used with Install banners on websites. This is how the KaiStore’s web directory prompts for app installations on KaiOS phones. Below is an example that would open PodLP on the KaiStore.

1let openPage = new MozActivity({
2    name: "open-page",
3    data: {
4        type: "url",
5        url: "https://api.kaiostech.com/apps/manifest/UxappJMyyWGDpPORzsyl"
6    }
7});

Note: app identifiers are consistently-generated hashes between the KaiStore and JioStore, but Manifest URLs are different. For instance, PodLP’s ID is UxappJMyyWGDpPORzsyl but the prefix for JioStore Manifest URLs is https://api.kai.jiophone.net/v2.0/apps/manifest/.

Deep Linking

Deep linking is the ability for an app to respond to a URL loaded within the system. For instance, the KaiStore supports deep linking on KaiOS with declarations in the manifest.webapp file.

 1{
 2    "deeplinks": {
 3        "regex": "^(app://)(kaios-store|kaios-plus)(.kaiostech.com)($|/$|/\\?(apps|postResult)=)",
 4        "action": "open-deeplink"
 5    },
 6    "activities": {
 7        "open-deeplink": {
 8            "href": "./index.html",
 9            "disposition": "window",
10            "filters": {
11                "type": "url",
12                "url": {
13                    "required": true,
14                    "pattern": "(app|rtsp|data):.{1,16384}"
15                }
16            },
17            "returnValue": true
18        }
19    }
20}

Deep links require two parts: a deeplinks section with a regular expression for a specific URL and an action activity that’s typically named "open-deeplink". This feature is supported in later versions of KaiOS 2.5, as well as all versions of KaiOS 3.0. Implementation details for deep link registration and handling can be found in AppsServiceDelegate.jsm and nsURILoader.cpp.

Conclusion

There are many ways to use web activities to develop high-quality user experiences on KaiOS apps and websites. Activities can increase install conversation, nudge users to easily grant permissions, and conveniently share content. If you’re looking for a partner to ensure the best possible user experience on KaiOS, you can find the author’s contact info on the About page.