Open-source project
ProtonLumo/android-lumo avatar
ProtonLumo/android-lumo

ProtonLumo/android-lumo: the Android wrapper around Proton's Lumo web app, and what the noGMS build changes

Android application for Proton Lumo

360 stars25 forksKotlinGPL-3.0

At a glance

What is it?
ProtonLumo/android-lumo is a Kotlin Android shell that renders lumo.proton.me in a WebView and adds native voice input, permissions and theming. Its most interesting decision is shipping a services variant with no Google Play Billing at all.
Who is it for?
Adopt it if you want Proton's Lumo assistant on Android from F-Droid or a self-built APK, or if you are studying how to wrap a web app in a thin native shell with a JavaScript bridge. Do not adopt it if you need an offline assistant or a documented public API, because the README describes a wrapper around lumo.proton.me and nothing else.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What android-lumo actually is, and who it is for

The README opens by describing Lumo as "the privacy-first AI assistant created by Proton", and then states plainly that this repository is "the native Android application wrapper for the Lumo web application (lumo.proton.me) with addition features e.g. voice entry." That sentence sets the scope of everything else. The app does not contain a language model. It contains a WebView, a set of Android managers around it, and a JavaScript bridge that lets the web page call into Kotlin.

The audience follows from that. If you use Proton's Lumo and want it as an installed Android app rather than a browser tab, this is the code that produces that app. The homepage points at a Play Store listing, and the README carries F-Droid badges, so an ordinary user installs a binary rather than building anything. The second audience is narrower: Android developers who want a worked example of a single-activity Compose app whose main screen is a WebView with a two-way JS bridge, runtime permission handling, and a speech recogniser wired into a Material 3 bottom sheet.

The README also notes that Proton is "the team behind encrypted email, VPN, password manager, and cloud storage trusted by over 100 million people." That is marketing copy from the project's own front page, not an independently verified figure, and it tells you about Proton's other products rather than about this repository.

Single activity, WebView, and a JavaScript bridge that varies by build

The architecture diagram in the README is the most informative thing in the repository. It shows one MainActivity using Compose Navigation, with a LumoApplication for initialisation. Around that sit three managers: UIManager for edge-to-edge layout and insets, WebViewManager for the WebView lifecycle, and PermissionManager for runtime permissions.

The data layer is where the design gets interesting. BaseDependencyProvider is an abstract dependency provider with two concrete implementations: DependencyProvider (GMS), which includes billing, and DependencyProvider (NoGMS), which the diagram labels "No Billing". The build variant axis named in the diagram is Services: gms | noGms, alongside Environment: production and Debugging: standard | noWebViewDebug. So the billing question is resolved at compile time, not at runtime.

On the web side, createWebView() assembles LumoWebClient for page lifecycle and JavaScript injection, LumoChromeClient for file chooser handling, and a WebAppInterface as the base JS bridge. The GMS variant swaps in WebAppWithPaymentsInterface, and that in turn talks to BillingManagerWrapper and BillingManager, which the diagram describes as using Google Play Billing with a cache and auto-refresh. JsInjector is listed with "10+ Injection Functions" covering keyboard and payments. Speech runs separately: SpeechInputSheet feeds SpeechRecognitionManager, which uses the native android.speech.SpeechRecognizer and does RMS tracking for the waveform.

The practical consequence is that the noGms build is not a stripped-down afterthought bolted onto a runtime flag. It is a parallel dependency graph that never constructs the billing objects. That is a cleaner arrangement than a single build that disables billing behind an if-statement, though it also means two code paths to keep in step.

Building it: environment variables, a release target, and a first run

Signing configuration comes from a .env file. The repository ships .env.example, which documents the four keys and the exact sequence: copy the file, fill in values, source it, then run the Gradle release task. The example uses LUMO_KEY_ALIAS=lumo and a placeholder path of /path/to/your/keystore.jks.

bash
cp .env.example .env
source .env
./gradlew assembleProductionStandardRelease

The README does not spell out the individual Gradle tasks for the gms and noGms variants, but the variant names in the architecture diagram (Services: gms | noGms) tell you they exist. The release names in this repository's history follow the pattern 2.1.0-nogms, 2.0.6-nogms and 2.0.5-nogms, which indicates the noGms variant is the one being tagged for distribution outside Google Play.

There is also a Makefile with a single release-branch target, which is a maintainer tool rather than something an end user needs. It takes the version as the variable v and calls the release-branch script in ci/scripts with that value. The version value itself is not documented in the README, so check version.properties before relying on the naming convention.

One thing to check before building: the repository has a top-level vosk-model/ directory, and the README's feature list mentions the native android.speech.SpeechRecognizer rather than Vosk. The README does not explain what vosk-model/ contains or how it is fetched, so treat that directory as an unresolved question rather than a documented step.

The noGMS variant is the honest part of the design

Most Android apps that want to be on F-Droid treat Google Play Services as a problem to be worked around at runtime. This repository treats it as a build axis. The diagram marks BillingManagerWrapper as "Graceful Degradation" and routes the noGms provider straight to the base WebAppInterface, so the payment bridge is simply absent from that build.

What that buys is a build with no Play Billing dependency at all. What it costs is that subscription management has to happen somewhere else. The navigation section of the diagram lists PurchaseLinkDialog labelled "Web Payment (NoGMS)" next to PaymentScreen labelled "Payment Dialog (GMS)". So a noGms user is pushed to a web payment path rather than an in-app purchase flow. The README does not say which domains or URLs that dialog points to, and it does not describe how subscription state is read back into the app afterwards. SubscriptionViewModel and SubscriptionRepository are both marked GMS Only in the diagram, which suggests the noGms build has no native view of subscription state at all. That is a real gap, and it is the kind of thing you would want to confirm on a device before promising a user that the F-Droid build is equivalent.

The LumoConfig entry in the diagram is labelled "Domain Management" and "Multi-Env Support", so the target web domain is configurable rather than hardcoded. The README does not list the accepted values.

Where this is the wrong tool

If you want an assistant that works without a network connection, this is not it. Every capability in the app flows through a WebView pointed at lumo.proton.me. The native layer contributes voice capture, file chooser handling, permissions and theming. Nothing in the README suggests any inference happens on the device.

If you want to script Lumo, this is also the wrong entry point. There is no documented HTTP API here, no endpoint list, no authentication scheme. The JavaScript bridge exists so the web page can call Kotlin, not so an external program can call Lumo. The repository topics include ai-assistant, android and privacy-tools, and the README's feature list stays inside the app boundary.

The third case is subtler. If you are building your own product, copying this architecture means inheriting its constraints: a single activity, a WebView as the main surface, and a bridge whose surface area is defined by whatever the remote page expects. The README lists "10+ Injection Functions" in JsInjector without enumerating them. A bridge that exists to satisfy one specific web app is not a general-purpose integration layer, and the coupling runs both ways: change the site and the injected functions may stop matching.

Compared with a native Android client such as ChatterUI

The obvious alternative for someone who wants an AI assistant on Android is a native client that speaks to a model API directly, for example ChatterUI. The difference is architectural, not cosmetic. A native client owns the conversation state, the request format and the rendering, and it can point at whichever backend the user configures, including a locally served model. It works with the network off if the model runs on the device.

android-lumo does the opposite. It delegates the entire conversation surface to a remote web application and keeps the native layer thin. That is why the app can track the web app's features without shipping a new APK for every prompt change, and it is also why the app cannot do anything the web app does not expose. There is no offline mode to configure and no model to select.

The trade-off is legible in the repository layout. A native client's complexity lives in its networking and state layers. Here, complexity lives in the bridge: LumoWebClient, LumoChromeClient, WebAppInterface and JsInjector exist to keep a web page and an Android shell in agreement. If your goal is privacy in the sense of not sending data to a third party, a local model client is the stronger choice. If your goal is using Proton's Lumo specifically, this wrapper is the only Android path the README describes.

Licence, maintenance and what an upgrade costs

The repository is GPL-3.0. For anyone forking or redistributing a modified build, that licence carries obligations that a permissive licence does not, and the standard place to read them is the LICENSE file at the repository root. This is a description of the licence identifier, not legal advice; if you plan to ship a modified APK, read the licence text and, where it matters, take proper advice.

The last push to the default branch was on 2026-09-08, and the most recent release tag in the same window is 2.1.0-nogms. The repository is not archived. The release history shows 2.0.5-nogms, then 2.0.6-nogms, then 2.1.0-nogms, which reads as a steady cadence of patch and minor tags for the noGms line specifically.

Upgrade cost is the interesting part. Because the app wraps a remote web application, a change to lumo.proton.me can alter behaviour without any change to this repository. The bridge is the seam where that shows up. If the web app starts calling a JavaScript interface method that JsInjector does not provide, the failure appears at runtime in the WebView, not at compile time in Gradle. Anyone maintaining a fork should watch JsInjector and the bridge interfaces rather than only watching the release tags. The README does not document a compatibility contract between the app and the web app, and it does not describe a rollback procedure for a release that breaks against a newer site.

Editorial conclusion

Adopt it if you want Proton's Lumo assistant on Android from F-Droid or a self-built APK, or if you are studying how to wrap a web app in a thin native shell with a JavaScript bridge. Do not adopt it if you need an offline assistant or a documented public API, because the README describes a wrapper around lumo.proton.me and nothing else. Before building, verify that vosk-model/ is populated, since the README does not document the speech model's provenance or download step.

Frequently asked questions

What is Lumo?

The README describes Lumo as the privacy-first AI assistant created by Proton, the team behind encrypted email, VPN, password manager and cloud storage. This repository is the native Android application wrapper for the Lumo web application at lumo.proton.me.

What is the app Lumo?

On Android it is a WebView-based app that displays the Lumo web application, with added native features such as voice entry. The README lists a Google Play listing and F-Droid packages for installation.

Is Lumo actually private?

The README states that Lumo is a privacy-first assistant and that it helps without compromising privacy, but it does not document the data flow between the app and lumo.proton.me in technical terms. The repository topics include privacy-tools, which is a label rather than a technical guarantee.

How does Lumo compare to ChatGPT?

The README does not compare Lumo to ChatGPT or any other assistant, so there is no basis in this material for a comparison. What it does describe is the Android wrapper: a WebView around lumo.proton.me with native voice input and a JavaScript bridge.

Official sources

  1. License: GPL-3.0
  2. Project website
  3. ProtonLumo/android-lumo on GitHub
  4. README
  5. Releases
Community notes

Community notes