Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/developer-guide/Bastion-Users-Guide.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ image::img/bastion-cards.png["The cards screen with a card, its masked number, i

*Exchange* lists the markets, BTC-USD and ETH-USD, with their last price and the index price the bank values coins at, and the customer's recent trades.

image::img/bastion-exchange.png["The exchange screen listing the BTC/USD and ETH/USD markets with their prices",scaledwidth=40%]

image::img/bastion-order-book.png["The BTC/USD market: the last price, the bid and ask, hourly candles with the volume under them, and the order book",scaledwidth=40%]

A market opens the last day in hourly candles, a rising hour in green and a falling one in red over its high-low range, the volume traded each hour, its order book and a ticket:
Expand Down
18 changes: 18 additions & 0 deletions docs/developer-guide/Desktop-Interop.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,24 @@ when it paints, so it stays sharp at any display density.

=== On a phone and on the desktop

SwingSet2, the JDK's own Swing demo, and the open source JavaFX 2048 game build without source changes beyond what the importer asks for (SwingSet2's classes moved out of the default package):

[cols="1,1",frame=none,grid=none]
|===
a|image::img/desktop-compat-swingset2.png[SwingSet2 running as a Codename One app on an iPad,scaledwidth=95%]
a|image::img/desktop-compat-fx2048.png[The JavaFX 2048 game running as a Codename One app on an Android phone,scaledwidth=70%]
|===

The screens below are the Swing and JavaFX scenes from the screenshot test suite, running unmodified on an iPhone. Each is ordinary desktop code: a `JTable` with a custom renderer, JavaFX controls, and an FXML document styled by its own style sheet.

[cols="1,1",frame=none,grid=none]
|===
a|image::img/desktop-compat-ios-swing-controls.png["Swing text fields, check boxes, radio buttons, a combo box, a slider and a progress bar on an iPhone",scaledwidth=90%]
a|image::img/desktop-compat-ios-swing-tables.png[A JTable with a custom renderer and a striped JXTable on an iPhone,scaledwidth=90%]
a|image::img/desktop-compat-ios-javafx-controls.png["JavaFX text fields, a choice box, a slider, a progress bar and check boxes on an iPhone",scaledwidth=90%]
a|image::img/desktop-compat-ios-javafx-fxml.png[An FXML form with its controller and style sheet on an iPhone,scaledwidth=90%]
|===

==== Logical pixels

Desktop code sizes things in pixels and assumes about 96 of them to the
Expand Down
4 changes: 4 additions & 0 deletions docs/developer-guide/MCP-Headless-API.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ The simulator adds tools of its own, prefixed `sim_`, that exist only on the des
* Android in resize mode: the application gets `sizeChanged` with the height above the keyboard.
* Android in pan mode: the size stays the same and the whole screen shifts up until the field clears the keyboard.

The recording below was made by an agent through these tools: it turned the iOS keyboard on, started a recording, tapped the email field, typed into both fields, moved between them with *Next* and *Done*, and stopped the recording.

image::img/simulator-virtual-keyboard.gif[The simulated iOS keyboard sliding up while a sign up form scrolls its email and password fields into view,scaledwidth=40%]

By default Android chooses between resize and pan for each field, the same way the Android port does. The *Android: Resize* and *Android: Pan* items, or `androidSoftInput`, force one mode. Virtual keyboard listeners fire on show and hide in both cases.

*Touches.* `sim_tap` and `sim_drag` press the screen through the same path as a mouse click, so they reach the simulated keyboard and appear as touch indicators, which `ui_activate` doesn't. `sim_touch_indicators` sets the style (`none`, `dot` or `ripple`), color, size and drag trails. *Simulate > Show Touches* also draws them in the simulator window.
Expand Down
Binary file removed docs/developer-guide/img/bastion-exchange.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: "GrapheneOS, LineageOS, /e/OS and Others Are Now Officially Supported"
slug: android-apps-everywhere-even-without-google
url: /blog/android-apps-everywhere-even-without-google/
date: '2026-11-04'
author: Shai Almog
description: "Codename One now officially supports GrapheneOS, LineageOS, CalyxOS, /e/OS and other Android builds without Google, with a boundary CI verifies and push through UnifiedPush. Also: the Android compatibility layer now runs Room, WorkManager, location and document pickers on iOS and desktop."
feed_html: "<img src=\"https://www.codenameone.com/blog/android-apps-everywhere-even-without-google.jpg\" alt=\"An Android app running on Android and on Codename One side by side\" /> GrapheneOS, LineageOS, CalyxOS and /e/OS are now officially supported, and the Android layer runs Room, WorkManager and document pickers on iOS."
series: ["release-2026-10-30"]
---

![An Android app running on Android and on Codename One side by side](/blog/android-apps-everywhere-even-without-google.jpg)

This post is mostly about phones that don't have Google at all: GrapheneOS, LineageOS, CalyxOS, /e/OS and other Android builds. Codename One now supports them officially. After that, a shorter update on the Android compatibility layer, which runs existing Android apps on iPhone, desktop and the web.

## GrapheneOS, LineageOS, CalyxOS and /e/OS

I was a member of Team OS/2. I know what it's like to use an operating system the big vendors forgot, and to find that half the apps you need just don't start. People choose GrapheneOS, LineageOS and other AOSP builds for good reasons, usually privacy, and they deserve apps that work. There's a business case too: when you support a niche operating system its users have learned to live without, you get the most loyal advocates your product will ever have. They notice, they remember, and they tell each other.

Codename One apps mostly worked on these phones before. But mostly isn't support: call the wrong API and you could hit a failure, or behavior nobody could predict. Now it's official, with a clear boundary. One APK serves both kinds of phone: at run time it checks whether Google Play Services is there. No build hint, no flavor, no second build. The Android port never calls Play services, Play Core or Firebase without checking first, and CI enforces that, so a future change can't quietly break it.

| Feature | With Google Play | Without Google Play |
| --- | --- | --- |
| UI, storage, networking, SQLite, media | Works | Works |
| On device ML Kit models | Works | Works |
| Location and geofences | Fused location | Framework `LocationManager` |
| Maps | Google Maps | Vector `MapView` |
| Google sign in | Native | Browser OAuth flow |
| Push | Firebase | UnifiedPush (ntfy, NextPush, Sunup) |
| In-app purchase | Play Billing | Not available |
| Integrity attestation, in-app review | Works | Not available |

The location fallback fixed something on Google phones too: builds without Play location now get proximity alerts and background location through the framework API.

### Push, with one line

Push needs one line of configuration, because the phone has no Firebase to deliver it. UnifiedPush lets the user pick the app that holds the push connection, called a distributor. A UnifiedPush endpoint is a standard Web Push subscription, so the Codename One push service delivers to it with the Web Push credential your app already has. You copy its public key into a build hint:

```properties
codename1.arg.android.unifiedPush.vapidKey=<your 87 character VAPID public key>
```

Your app code doesn't change, and neither does your server. The registration order is automatic:

{{< mermaid >}}
flowchart TD
S[PushClient registers] --> G{Google Play Services?}
G -->|yes| F[Firebase Cloud Messaging]
G -->|no| H{Huawei HMS?}
H -->|yes| K[Huawei Push Kit]
H -->|no| U{UnifiedPush distributor installed?}
U -->|yes| P[UnifiedPush, delivered as Web Push]
U -->|no| E[pushRegistrationError: a push service is needed]
{{< /mermaid >}}

The one real gap is in-app purchase. Play Billing needs the Play Store. If selling on de-Googled phones matters to you, tell us; it's something we can add.

To test, use the Android emulator's AOSP system images, the `default` target, which have no Google apps. The [Android without Google Play chapter](/developer-guide/android-without-google-play/) has the details.

## Also new: classic Android apps, on iPhone

[Three weeks ago](/blog/android-apps-beyond-android/) we showed that an Android Studio module's `src/main` can build unmodified as a Codename One app. There's no Android runtime inside it. Views are Codename One components, layouts follow Android's measure and layout rules, and resources compile to a binary table at build time. Nothing parses XML or uses reflection on the device. You import a project with one command:

```bash
mvn cn1:import-android-project -Dcn1.android.import=/path/to/MyAndroidApp
mvn cn1:build -Dcodename1.platform=ios
```

{{< guide-block >}}
<div class="release-screenshots" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:16px;align-items:start;margin:24px 0">
<figure style="margin:0"><a href="/developer-guide/img/android-interop-material.png"><img width="736" height="800" src="/developer-guide/img/android-interop-material.png" alt="A Material 3 screen on Android and on Codename One" loading="lazy" style="width:100%;height:auto" /></a><figcaption>Material 3.</figcaption></figure>
<figure style="margin:0"><a href="/developer-guide/img/android-interop-recycler.png"><img width="736" height="800" src="/developer-guide/img/android-interop-recycler.png" alt="A RecyclerView on Android and on Codename One" loading="lazy" style="width:100%;height:auto" /></a><figcaption>RecyclerView.</figcaption></figure>
<figure style="margin:0"><a href="/developer-guide/img/android-interop-kotlin.png"><img width="736" height="800" src="/developer-guide/img/android-interop-kotlin.png" alt="A Kotlin activity on Android and on Codename One" loading="lazy" style="width:100%;height:auto" /></a><figcaption>Kotlin activities.</figcaption></figure>
</div>
{{< /guide-block >}}

Each pair shows Android on the left and Codename One on the right, from the same source.

### What's new: the parts real apps lean on

Toy apps are mostly UI. Real apps store data, run work in the background and talk to the hardware. The layer now covers those:

- **Room.** The build generates the `_Impl` classes, the way Android's annotation processor does. Features we don't support yet (`@Relation`, `suspend` DAOs, Flow, Rx, Paging, `@AutoMigration` and FTS) fail the build and name the member.
- **WorkManager.** Chains, unique work, retries and LiveData status, persisted across app restarts.
- **Location and sensors.** `LocationManager` and `SensorManager`.
- **Services.** Manifest services, including `startForeground()` mapped to Codename One's `ForegroundService`. Notifications from `NotificationManager` and `NotificationCompat` show as local notifications.
- **Keystore.** AES keys from `AndroidKeyStore` with GCM and CBC.
- **Document pickers.** `ACTION_OPEN_DOCUMENT`, `ACTION_CREATE_DOCUMENT` and `ACTION_GET_CONTENT`, plus the ActivityResult contracts, open the platform's native open and save dialogs.

So this is ordinary Android code, and it now runs on an iPhone:

```java {class="cn1-no-try"}
@Dao
public interface NoteDao {
@Query("SELECT * FROM Note ORDER BY created DESC")
List<Note> all();

@Insert
long insert(Note note);
}

Intent save = new Intent(Intent.ACTION_CREATE_DOCUMENT);
save.addCategory(Intent.CATEGORY_OPENABLE);
save.setType("text/plain");
save.putExtra(Intent.EXTRA_TITLE, "notes.txt");
startActivityForResult(save, SAVE_NOTES);
```

### Where iOS says no

Some Android behavior has no iOS equivalent, and the [Android chapter](/developer-guide/android-interop/) has a table for it. Location in a foreground service keeps running in the background when the manifest asks for background location. Other foreground service work gets only the short window iOS allows. Constrained WorkManager jobs go to iOS's background task scheduler, and iOS decides when they run. On iOS, `ACTION_CREATE_DOCUMENT` returns `RESULT_OK` right away and shows the save dialog when your app closes the output stream, so a cancel isn't reported back.

Other limits: Keystore keys aren't hardware backed there, `Parcel` is an in process container, SQLite uses a single connection, there are no system content providers or broadcasts, and reflection based libraries won't run on iOS.

Tomorrow, the last post of the week: {{< post-link path="/blog/backend-caches-certificates-and-a-simulator-that-types" text="Caches, Certificates, Streaming Uploads and a Simulator That Types" >}}.

{{< giscus >}}
Loading
Loading