Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: PR Check

on:
pull_request_target:
pull_request:
branches: [main]

permissions:
Expand Down
21 changes: 21 additions & 0 deletions LICENSE-TRANSIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Christian Ferreira (CJFData)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
95 changes: 45 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,59 @@
# light-sdk
or: a tool for building Tools
# 🚌 Pico Transit (ALPHA TESTING)

## tl;dr
This repository contains the scaffolding for building simple tools for the Light Phone III. Included are a library ([:sdk:client](./sdk/client)) and placeholder application ([:tool](./tool)) that depends on it. To create a tool that is fully compatible with LightOS, you must write your application code within the `tool` module, using the primitives provided by the sdk client library.
Pico Transit is a friendly little companion for getting around on public transit. Real schedules, real-time arrivals, live connections at any stop, and a live map that shows exactly where your ride actually is — no ads, no clutter, no infinite scroll. Just "where's my bus," answered nicely. 🚏✨

You can and should use current Android best practices: Kotlin for all source code, Compose for UI, Coroutines for async programming, and MVVM architecture. **Although this is appears to be a fairly standard Android dev environment, you will quickly find out that we are (gently but broadly) restricting which Android APIs and third-party libraries can be used. This is in an effort to provide a secure and distinctly _light_ experience for our users. These restrictions are _not_ set in stone and should ease up over time. If there is a stable, open-source library that you'd like us to allow, please let us know! More on this later.**
Right now Pico Transit knows its way around **MBTA** and **RIPTA**, with more agencies hopefully hopping aboard down the road. It's built on the [Light SDK](../) for the Light Phone III, so it stays just as calm and un-distracting as the rest of your Light experience.

## IMPORTANT!! July 1, 2026 Update
If you're reading this, welcome! You're early! (in a cool way)
This repo is a work-in-progress and will remain so for a while. Things are going to change _fast_ in the coming weeks. If you're going to start building right away, be sure to `git pull` frequently.
Before you do, though, please be aware that **while we feel good about letting everybody start to explore and build, we are still working on the infrastructure to properly deploy your new tools.**
The currently builds of LightOS in the wild are not yet ready to "play nice" with the tools built here. If you're someone who's already comfortable working with ADB to sideload APKs on your
Light Phone III, you can totally do that with whatever you do here! But we're shooting to make these tools feel as seamless as the ones already available in LightOS, and that's going to take a bit more work.
We're hoping to have an update on that front later this month. In the meantime, the best way to start working is to use an Android emulator running our new [LightOS Emulator](sdk/emulator). The instructions for getting that up and running
are [right here](docs/system_app).
## 🗺️ What can it do?

## Quickstart
### Grabbing a token
We're currently hosting our library builds with GitHub Packages so each artifact can live beside its source. The tradeoff is that you'll need to add a GitHub token with package read access to your local build environment. **We are considering migrating to Maven Central to avoid this requirement when everything goes public.**
For now, you can either add environment variables with your username and token:
```
GITHUB_ACTOR=your_username
GITHUB_TOKEN=your_token
```
or you can add them to your `local.properties` file:
```
gpr.user=your_username
gpr.key=your_token
```
- 🏠 **Pick your agency** — MBTA or RIPTA — and Pico Transit downloads their schedule right onto your phone.
- ⚙️ **Settings** — a default agency to skip the picker, light/dark map tiles, and on/off toggles (tap-and-hold a stop to jump to its arrivals, double-tap a station to zoom into its platforms, track tapped-open stops' own vehicles on the map, and the home screen's trip progress bar).
- 📅 **Explore Schedules** — browse by Subway 🚇, Commuter Rail 🚆, or Bus 🚌, pick a route, a direction, and a stop, and see every departure today.
- 🔗 **Connections** — tap any stop along a trip to see what else comes through there next, across every platform of a station, not just the one your trip happened to use. Great for planning a transfer on the fly.
- 📍 **Leave Now** — type where you are (or let Pico Transit take a quick IP-based guess 🛰️) and get the closest stops, nearest first — it remembers your last search, so ducking into a stop's arrivals and back doesn't make you search all over again.
- ⏱️ **Live ETAs** — real-time predictions with On Time / Late / Early badges, whenever the agency's live feed is playing along nicely.
- 🗺️ **Map** — your stop, pinned on a live map, with nearby stops you can tap to reveal their names. Live vehicles show up right where they actually are, with a matching icon for their mode (subway/light rail, commuter rail, bus).
- 🚉 **Stations** — browse every real multi-platform station an agency has, and open a zoomed-in map of just that station's own platforms.
- ▶️ **Board a trip** — from any Trip Detail screen, tap Play to make it your current trip. Tap a stop along the way to mark where you're getting off — reach it, and Pico Transit throws a little "You've reached your stop! 🎉" celebration and jumps you to that stop's upcoming arrivals, whether you were looking at the trip or just sitting on the home screen.
- 🚦 **Home screen trip status** — while a trip is boarded, the home screen swaps its agency picker for your route, live ETA, and stops remaining, plus an optional progress bar with a little vehicle marker crawling from your boarding stop toward your alight stop.
- ↩️ **Jump back anytime** — a Play icon shows up in the corner of every screen while a trip's boarded, one tap from wherever you are back to its live tracking; a plain circle in the footer does the same for the home screen itself.
- ℹ️ **About** — a full legend of every icon and mode Pico Transit uses, reachable right from the home screen.

### Running your Tool
**You can test your tool on any Android device or emulator**, but certain functionality (receiving push notifications, requesting special permissions) can only be tested with:
A) Real Light Phone hardware running LightOS
B) An Android emulator (on your computer) set up to run our LightOS emulator app as a _system app_ ([see advanced instructions](docs/system_app))
## 🛠️ Building & running it

You can quickly [create an emulator](https://developer.android.com/studio/run/managing-avds) that generally feels like an LPIII by using the following settings:
* 1080 X 1240, 3.92" display
* Android API 34
* NO Google Play Services installed
Pico Transit lives inside the [light-sdk](../) monorepo — check the [root README](../README.md) first for one-time setup (GitHub token, Android Studio, etc). Once that's done:

### Start Building
1. Fork and/or clone this repository into your local dev environment.
2. Install Android Studio and open this project within it. (IntelliJ IDEA should also work)
1. Open the whole `light-sdk` project in Android Studio.
2. Run the `:tool` module on an emulator, or better yet, [the LightOS emulator](../docs/system_app) — that's this app! 🎉
3. Tap an agency, grab a coffee ☕ while it downloads the schedule, and you're off.

3. Edit the code in `HomeScreen` and `HomeScreenViewModel` to get started. `Homescreen` surfaces a `@Composable` method named `Content`. This is the UI that is shown when the tool first boots. You'll notice this UI sources data from it's `viewModel` field, which is an instance of `HomeScreenViewModel`. Edit that class with your screen's logic and expose the data to the UI using either Compose `State` or Coroutine `Flow`s. If you want to create a new screen, create a new Screen/ViewModel pair: your screen should extend from `LightScreen` and your VM from `LightScreenViewModel`. Your screen implementation will need:
1. A direct reference to your ViewModel's class type
2. A factory method for creating a new instance of your ViewModel.
## 📱 Getting it onto a *real* Light Phone III

Look at `HomeScreen` as an example for how this is done. To navigate to your new screen, use the `navigateTo` function built into `LightScreen` - just pass it a lambda to create an instance of your new screen. Note that the `LightScreen` constructor takes in a `SealedLightActivity`. The lambda is provided an instance of this as a default parameter.
Light's official "build it, sign it, share it" pipeline for community tools isn't quite ready yet — vetting is expected around August/September 2026, with the full sharing platform following in October. So for now, sideloading via ADB is the way, and Light's own docs say that's totally fine for the adventurous! 🤠

Since LightOS does not use Android system navigation, we provide a back button for you. As long as you use `navigateTo` to move between screens, our back button should work great. If need be, you can override the `onBackPressed` method in your `LightViewModel`.
1. In [`lighttool.toml`](./lighttool.toml), point `serverPackage` at the real LightOS package instead of the emulator:
```toml
serverPackage = "com.lightos"
```
2. Build a debug APK:
```bash
./gradlew :tool:assembleDebug
```
3. Turn on Developer Options + USB debugging on your Light Phone III (same as any Android device), plug it in, then:
```bash
adb install -r tool/build/outputs/apk/debug/tool-debug.apk
```
4. On the phone, allow "Any tools" in LightOS's tool settings — it'll warn you this one isn't Light-vetted yet, which is expected for a homemade build like this. 🚧

### Sharing Your Tool
**As of July 1, 2026, there's no "easy" way to share your tool with a Light Phone III user. We're working hard on that. This is how we believe it's going to look.**
That's it — happy transit-ing! 🚏🚌🚆

Given our relatively limited resources and desire to keep our users safe, we're requiring that all community tools be open source (including our own!). We will be building and signing these tools directly from a publicly available git commit, and we'll be archiving the source at build time. You're free to build and share privately, but LightOS won't let you install tools that are not signed by us without acknowledging privacy and performance risks. We won't block users from performing these "dangerous" sideloads, but we're not going to encourage it either. In the near future, you'll be able to queue up a build of your tool on our servers, and if it follows our guidelines and compiles cleanly, we will hand you back a signed, shareable APK.
## 🧪 A couple of nerdy notes

Once we release a version of LightOS that supports community tools, users will have an option to choose what kind of tools they want to be able to run on their device:
- **Light-approved tools**: These include tools that are either built internally by the Light team, or built by the community and officially tested/signed-off by the Light team. We don't know _exactly_ what that sign-off process is going to look like, but as a heads-up: we're going to be looking pretty hard at whether a submitted tool matches the Light ethos both functionally and aesthetically. We've included a UX/UI library to make this as easy as possible! From a technical standpoint, these approved tools are both signed by us _and_ added to an "allow-list" within LightOS. Phones with this option selected will only install and display tools that meet both criteria.
- **SDK-built tools**: This is a slightly more permissive choice. Phones with this option selected will install and launch any tool that was built and signed by Light. These don't require any manual approval by us (though we can block them in extreme cases). If a user wants to be able to install a tool that was shared locally or somewhere outside of Light's dashboard, but they still want to be confident that it will run well and integrate nicely with LightOS, they might choose this option!
- **Any tools**: A user will have the option to make any APK launchable from LightOS, but they will own the responsibility of getting them un/installed. When a user selects this option, we will be warning them that they are potentially opening their device up to security risks, and in doing so will limit our ability to support them if something goes wrong.
- **RIPTA's live feeds are HTTP-only** (no HTTPS), which Android blocks by default. There's a small, clearly-labeled `:netconfig` module that grants just that one narrow exception — see its own `build.gradle.kts` for exactly what it does and how to remove it if you'd rather stay HTTPS-only everywhere.
- **No device GPS is used anywhere** — the SDK doesn't expose it to tools yet. Nearby-stop and location search are powered by Nominatim (OpenStreetMap) and IP-based geolocation instead. Be kind to their free APIs! 🙏
- **Stations are deduplicated using GTFS's `parent_station`** — a big station with several platforms (subway entrances, commuter rail tracks, etc.) shows up as one marker/entry, not one per platform, while still resolving to the right platform's `stop_id` under the hood for schedule lookups.
- **Boarding a trip is a saved reference, not a background tracker** — Pico Transit never polls a live feed while the app itself isn't open. "You've reached your stop" detection only runs while Trip Detail or the home screen is actually visible and polling, the same way every other bit of live tracking in the app works.

## [Complete Documentation](./docs)
## 📄 License

The [`tool/`](tool/) directory (Pico Transit itself) is licensed separately from the rest of the monorepo — see [`LICENSE-TRANSIT`](LICENSE-TRANSIT) (MIT, © Christian Ferreira / CJFData). The rest of `light-sdk` remains under its own [`LICENSE`](LICENSE) (MIT, © The Light Phone).
40 changes: 40 additions & 0 deletions netconfig/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// --- BEGIN removable cleartext exception for RIPTA realtime feeds ---
//
// This module exists solely to grant realtime.ripta.com a Network Security Config cleartext
// exception (see src/main/res/xml/network_security_config.xml). RIPTA's realtime TripUpdates/
// VehiclePositions feeds are served plain-HTTP-only with no HTTPS equivalent, and Android blocks
// cleartext traffic by default — this module's manifest merges the exception into :tool's final
// packaged manifest.
//
// Deliberately does NOT apply the com.thelightphone.light-sdk plugin — that plugin's manifest
// generation has no field for network security config, and hand-editing an
// AndroidManifest.xml in a plugin-applying module is rejected outright. A plain sibling library
// module sidesteps that: the plugin's own dependency validator explicitly exempts same-build
// project dependencies (see LightSdkPlugin.isProjectDependency), and since this module never
// applies the plugin, none of its restrictions apply to it either. Verified against a real forced
// rebuild that the merged attribute survives into :tool's final packaged manifest — confirmed via
// tool/build/intermediates/packaged_manifests/.../AndroidManifest.xml, not just the intermediate
// merge blame log.
//
// TO REMOVE THIS EXCEPTION (restore HTTPS-only enforcement everywhere):
// 1. Delete this module (the netconfig/ directory).
// 2. Remove `include(":netconfig")` from settings.gradle.kts.
// 3. Remove `implementation(project(":netconfig"))` from tool/build.gradle.kts.
// 4. In GtfsAgency.kt, set RIPTA's realtimeTripUpdatesUrl/realtimeVehiclePositionsUrl back to
// null (the original, HTTPS-only-safe state).
//
// UNVERIFIED: whether Light's official build/signing pipeline (builder/) accepts a sibling module
// built this way — only confirmed against local Gradle builds so far.
plugins {
alias(libs.plugins.android.library)
}

android {
namespace = "com.thelightphone.netconfig"
compileSdk = rootProject.ext["compileSdk"] as Int

defaultConfig {
minSdk = rootProject.ext["minSdk"] as Int
}
}
// --- END removable cleartext exception for RIPTA realtime feeds ---
9 changes: 9 additions & 0 deletions netconfig/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
REMOVABLE cleartext exception (see netconfig/build.gradle.kts header for full context).
This attribute is merged into :tool's final AndroidManifest.xml by AGP's manifest merger.
To remove: delete this module entirely (see build.gradle.kts for the full removal steps).
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:networkSecurityConfig="@xml/network_security_config" />
</manifest>
11 changes: 11 additions & 0 deletions netconfig/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
REMOVABLE: scoped narrowly to realtime.ripta.com only — no blanket cleartext allowance for
the rest of the app. Remove this file (and the whole netconfig/ module) to restore
HTTPS-only enforcement everywhere; see netconfig/build.gradle.kts for full removal steps.
-->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">realtime.ripta.com</domain>
</domain-config>
</network-security-config>
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ include(":sdk:client")
include(":sdk:server")
include(":sdk:emulator")
include(":tool")
// REMOVABLE: see netconfig/build.gradle.kts for what this is and how to fully remove it.
include(":netconfig")
include(":examples:ui-demo")
project(":examples:ui-demo").projectDir = file("examples/ui-demo")
include(":examples:weather")
Expand Down
Loading
Loading