Codename One exposes a cross-platform API for rich local notifications, runtime notification permission, notification channels, constraint-aware background work, foreground services, deferrable background processing, receiving content shared from other apps, and push topic subscriptions. Each capability degrades on platforms that can’t support it — calls become documented no-ops rather than errors — and the JavaSE simulator implements them so you can exercise the flows without a device.

Because these features build on platform-specific behavior, every section below lists the support per platform. In the matrices: Yes = fully supported, Partial = supported with documented limitations, No-op = the call is accepted but does nothing, Sim = exercised in the simulator.

Notification permission

On iOS, and on Android 13+ (POST_NOTIFICATIONS), an app must obtain permission before its notifications are shown. Request it through Display:

NotificationPermissionRequest req = new NotificationPermissionRequest()
        .provisional(true)      // iOS: deliver quietly without an explicit prompt
        .timeSensitive(true);   // iOS: allow the time-sensitive interruption level

Display.getInstance().requestNotificationPermission(req, result -> {
    if (result.isGranted()) {
        // schedule notifications
    }
    AuthorizationLevel level = result.getAuthorizationLevel();
});

NotificationPermissionResult.getAuthorizationLevel() returns an AuthorizationLevel enum (NOT_DETERMINED, DENIED, AUTHORIZED, PROVISIONAL, EPHEMERAL). The values are ordered from least to most permissive, so isGranted() is "more permissive than DENIED." The callback is delivered on the EDT.

Request optioniOSAndroidSimulator

alert / sound / badge

Yes

Implied by POST_NOTIFICATIONS

Granted

provisional

Yes (iOS 12+)

No-op

Granted

timeSensitive

Yes (iOS 15+, needs entitlement)

No-op

Granted

critical

Yes (needs Apple entitlement)

No-op

Granted

carPlay / announcement

Yes

No-op

Granted

The timeSensitive and critical options require an Apple capability on the App ID. Enable injection of the matching entitlement with the ios.timeSensitiveNotifications=true and ios.criticalAlerts=true build hints (they’re opt-in because an entitlement the provisioning profile doesn’t carry breaks code signing).

Local notifications

LocalNotification is scheduled through Display.scheduleLocalNotification(…​) as before; the bean has been extended with fluent, backward-compatible setters:

LocalNotification n = new LocalNotification()
        .setChannelId("messages")                 // Android channel (see below)
        .setGroup("chat-42")                       // bundle related notifications
        .setProgress(100, 40)                      // determinate progress bar (Android)
        .setTimeSensitive(true);                   // break through Focus / elevated importance
n.setId("msg-42");
n.setAlertTitle("New message");
n.setAlertBody("Tap to reply");
n.addAction("open", "Open");
n.addInputAction("reply", "Reply", "Type a message", "Send");  // inline quick reply
Display.getInstance().scheduleLocalNotification(n, System.currentTimeMillis() + 1000,
        LocalNotification.REPEAT_NONE);

When the user taps an action, the selected action id (and any quick-reply text) are delivered through com.codename1.push.PushContent — getActionId(), getActionTitle() and getTextResponse() — before your LocalNotificationCallback.localNotificationReceived(id) runs, so push and local notification actions are handled identically.

CapabilityiOSAndroidSimulator

title / body / badge / sound

Yes

Yes

Sim (panel)

image attachment

Yes (UNNotificationAttachment)

Yes (BigPicture)

Sim (thumbnail)

action buttons

Yes (UNNotificationAction)

Yes

Sim (buttons)

quick-reply input action

Yes (UNTextInputNotificationAction)

Yes (RemoteInput)

Sim (text field)

grouping

Yes (thread identifier)

Yes (group + summary)

Partial

progress (ongoing)

No-op

Yes

Sim (progress bar)

full-screen intent

No-op

Yes (needs permission, see hints)

No-op

time-sensitive

Yes (iOS 15+)

Approximated (category + priority)

Sim

messaging / conversation style

Partial

Yes (MessagingStyle)

Sim

custom view

Notification content extension

RemoteViews

Sim (render)

Resource names for sound, image and action icons

These string parameters are platform resource names, not numeric ids (Codename One has no R.drawable constants). They’re resolved at runtime per platform:

ParameterWhat to provideAndroid resolutioniOS resolution

alertSound / channel sound

a file whose name starts with notification_sound, bundled in the app

res/raw/<name> via android.resource:// URI

UNNotificationSound from the app bundle

alertImage

image path placed in the app root

loaded from assets/

UNNotificationAttachment from the bundle

Action icon

a drawable name bundled in android/src/main/resources

getResources().getIdentifier(name, "drawable", pkg)

not displayed

The file extension is optional for the action icon and is ignored. iOS notification action buttons don’t render icons, so the icon value is ignored there.

Notification channels (Android O+)

Channels let the user control importance, sound, vibration, lights, lock screen visibility and badge display for a group of notifications. Build and register one at startup, then reference it from a notification with setChannelId(…​):

new NotificationChannelBuilder("messages", "Messages")
        .description("Incoming chat messages")
        .importance(NotificationChannelBuilder.IMPORTANCE_HIGH)
        .sound("/notification_sound_ping.mp3")
        .enableVibration(true)
        .lightColor(0xff0000)
        .register();

Channels are an Android concept. On iOS and the simulator register() is a no-op (the simulator stores the channel so its name can be shown in the notification panel); the channel id you assign is still carried so the notification behaves consistently.

Constraint-aware background work

BackgroundWork schedules deferrable work that the OS runs when its constraints are met. Implement BackgroundWorker in a class with a public no-argument constructor. You schedule the class, never an instance: the request records its name, and the scheduler constructs a fresh worker for every run, even when the process was never killed. Its fields are always at their defaults, so pass state through the input data:

/// The platform may rebuild this after the app process was killed, so it
/// needs a public no-argument constructor and can rely on nothing from the
/// foreground app -- state arrives through the input data.
public static class SyncWorker implements BackgroundWorker {
    public void performWork(String id, java.util.Map<String, String> inputData,
            long deadline, com.codename1.util.Callback<Boolean> onComplete) {
        // the real work goes here; inputData carries whatever the request
        // was scheduled with, because no foreground state survives
        String account = inputData.get("account");
        boolean ok = account != null;
        // TRUE for done. FALSE asks for a retry on Android; the iOS port
        // ignores the value, so do not rely on it to recover from failure
        onComplete.onSucess(Boolean.valueOf(ok));
    }
}

void scheduleSync() {
    // on iOS the id must appear in BGTaskSchedulerPermittedIdentifiers, and
    // it gets there only if you ask: ios.usesBackgroundProcessing=true
    // declares <packageName>.processing, which is what this derives
    String taskId = Display.getInstance().getProperty("package_name", "") + ".processing";
    WorkRequest req = WorkRequest.builder(taskId, SyncWorker.class)
            .setRequiresNetwork(true)
            .setRequiresCharging(true)
            .setPeriodic(6 * 60 * 60 * 1000L)
            .putInputData("account", "primary")
            .build();
    BackgroundWork.schedule(req);
}

iOS needs the identifier declared at build time before BGTaskScheduler will accept the request, and nothing detects BackgroundWork on the classpath for you. Set ios.usesBackgroundProcessing=true, which declares <packageName>.processing — the id the sample derives — or list your own ids with ios.backgroundProcessingIds. Without one of those the scheduling call is rejected on the device.

The life of a background work request
iOSAndroidSimulator

backend

BGTaskScheduler (iOS 13+)

JobScheduler

Timer

requiresNetwork / unmetered

Yes

Yes

Toggle in Simulate menu

requiresCharging

Yes (requiresExternalPower)

Yes

Toggle

requiresIdle / batteryNotLow

No-op

Yes

Toggle

periodic

Approximated (resubmit)

Yes (>= 15 min)

Yes

The iOS approximation of a periodic request doesn’t carry the request forward. After a run completes, the port resubmits the task with an earliest date one minute out and with both the network and power requirements cleared, so the work can recur far more often than the period you asked for and outside the constraints you set. Treat a periodic request on iOS as "sometime after the app is next backgrounded," and re-check the conditions you care about at the top of the worker. The completion value is Android-only for the same reason: the iOS port resubmits periodic work whichever value you report, and never resubmits one-shot work, so a FALSE there doesn’t buy you a retry.

BackgroundTask.scheduleProcessing(id, earliestBeginDate, requiresNetwork, requiresPower, runnable) is a one-shot variant for occasional maintenance work. On iOS the identifiers must be declared via the ios.backgroundProcessingIds build hint (comma separated, default <packageName>.processing); the build also adds the processing UIBackgroundMode and links BackgroundTasks.framework automatically.

Foreground service (Android)

ForegroundService runs a long task while a persistent notification is shown. The first argument names a channel, but the Android service currently routes its notification through the app-wide channel — android.NotificationChannel.id, defaulting to cn1-channel — so the id here doesn’t place the notification in a channel of its own today:

ForegroundService svc = ForegroundService.start("downloads", "Downloading", "Please wait",
        null, service -> {
            for (int i = 0; i <= 100; i++) {
                service.updateNotification("Downloading", i + "%");
                // ... do a chunk of work ...
            }
        });
// auto-stops when the task returns, or call svc.stop()
AndroidiOSSimulator

persistent-notification service

Yes

No (best-effort background window + notification)

Sim (status indicator)

When the app references ForegroundService the build registers the service and injects FOREGROUND_SERVICE plus the per-type FOREGROUND_SERVICE_<TYPE> permission (Android 14). Override the type with the android.foregroundServiceType build hint (default dataSync).

Receiving shared content

Override onReceivedSharedContent(SharedContent) on your Lifecycle subclass to receive text, URLs, files or images shared into your app from other apps:

public class MyApp extends Lifecycle {
    public void onReceivedSharedContent(SharedContent content) {
        for (SharedContent.Item item : content.getItems()) {
            if (item.getType() == SharedContent.TYPE_IMAGE) {
                // your own import; the path is a FileSystemStorage path
                importImage(item.getFilePath());
            }
        }
    }
}
AndroidiOSSimulator

receive shared content

Yes (SEND / SEND_MULTIPLE)

Yes (share extension + App Group)

Sim ("Send shared content")

On Android, declare the accepted MIME types with the android.shareFilter build hint (comma separated, for example text/plain,image/*); the build registers the share-receiver activity. On iOS this reuses the share-extension authoring pipeline; set the ios.shareAppGroup build hint to the App Group the extension writes to. File and image items are copied into Codename One FileSystemStorage so your code is platform neutral.

Push topics

Push.subscribeToTopic("news") / unsubscribeFromTopic("news") subscribe the device to a fan-out topic.

AndroidiOS

topic subscribe / unsubscribe

Yes (Firebase Cloud Messaging)

No-op (raw APNs has no topics; fan out server-side)

Server-side topic delivery is handled by the Codename One push server.

Simulator

The JavaSE simulator implements all the above so you can develop without a device. Use the Simulate → Notifications and Background menu to toggle the background constraints (network / charging / idle / battery), run scheduled work immediately, inspect registered channels, and inject shared content. Posted notifications appear in a panel at the top right; tapping an action (or submitting a quick-reply) routes back through your callback exactly as on device.