Skip to content

Examples

One hundred and seven examples are included under examples/, organized by category. The launcher that multi-app boards boot into lives in system-apps/launcher/: built the same way, but linked into the firmware. See the launcher guide.

New to Picodroid? Start with the two guided tutorials below — they walk through building a real app step by step. The rest of the catalog is reference material to copy from.

Tutorials

Companion apps for the step-by-step Tutorials. Build them with the guide open alongside.

ExampleClassDescription
tutorial_screenstutorial_screens.TutorialScreensAppA Home hub that pushes Counter and About screens — Activities, the back stack, lifecycle, and view preservation. Follow Multi-screen app.
tutorial_servicetutorial_service.TutorialServiceAppAn uptime-logging Service that survives navigation, bound from a viewer screen for live snapshots. Follow Background service.

Getting Started

Simple apps to verify your setup and get familiar with the build/flash workflow.

ExampleClassDescription
helloworldhelloworld.HelloWorldPrints “Hello, World!” via Log.i()
blinkyblinky.LedBlinkBlinks the onboard LED on GP25 every 500 ms, and reads GP16 back as an input (Gpio.DIRECTION_IN / getValue())

Peripherals

Hardware interaction through the picodroid.pio.PeripheralManager API. Reference: Peripherals.

ExampleClassDescription
uartuart.UartEchoConfigures UART0 at 115200 baud and echoes received bytes
i2cdemoi2cdemo.I2cDemoScans the I2C0 bus (SDA=GP4, SCL=GP5) and logs the 7-bit address of every ACKing device
spidemospidemo.SpiDemoFull-duplex loopback over SPI0 (SCK=GP2, MOSI=GP3, MISO=GP0): sends 0x00-0x0F and logs received bytes
adcdemoadcdemo.AdcDemoOpens the ADC on GP26 and takes 5 voltage readings, logging each value
pwmdemopwmdemo.PwmDemoFades the onboard LED on GP25 using PWM at 1 kHz — duty cycle sweeps 0%->100%->0% three times
tonedemotonedemo.ToneDemoActivityPlays Android tones and a melody on the buzzer via ToneGenerator; requires an [audio] section in board.toml, and reports false on a board without one

Filesystem and Preferences

On-device persistent storage via LittleFS (picodroid.io) and the DataStore-style key-value API (picodroid.content.SharedPreferences). Reference: Storage.

ExampleClassDescription
bootcountbootcount.BootCountPersists a boot counter across reboots using picodroid.io.File / FileInputStream / FileOutputStream
prefs_demoprefsdemo.PrefsDemoStores typed key/value settings (String, int, long, float, boolean) via SharedPreferences.open() / edit().commit(), and walks the File path helpers (getName, getParent, getParentFile, mkdirs, createNewFile)
filesdemofilesdemo.FilesDemoConformance checks for the storage sandbox and the Context file API: every path the app names lives under its own directory, getFilesDir() is /files, openFileOutput / openFileInput / fileList / deleteFile round-trip, File.list works, and a path that climbs out or is too long is refused
quotademoquotademo.QuotaDemoConformance checks for the storage quota on multi-app boards: StatFs reports the volume and what the app may still write, 16 KB files are written until the board’s per-app cap refuses the next with IOException, a delete and a mkdir move the number by exactly what they cost, and StorageStatsManager agrees with the accounting. Build names the board

Networking

TCP/UDP sockets via picodroid.net. On hardware these require a Pico 2 W (--board testbench_rp2350w); under the simulator they hit the host network stack. Reference: Networking.

ExampleClassDescription
netdemonetdemo.NetDemoChecks NetworkInfo, opens a TCP Socket, sends “Hello” to a localhost echo server on port 7000, and logs the response. On hardware it first waits up to 30 s for WiFi join + DHCP — see WiFi & networking setup
netexceptionnetexception.NetExceptionAsserts the typed network-exception taxonomy — ConnectException on a refused loopback port, SocketTimeoutException from ServerSocket.accept() and from a blocked read, and a dotted-quad InetAddress resolve. Fully local: no external server, network, or resolver needed
connectivityconnectivity.ConnectivityConnectivityManager with a NetworkCallback, the Android way to hear about the link instead of polling NetworkInfo.isConnected(), beside a BroadcastReceiver for CONNECTIVITY_ACTION / NETWORK_STATE_CHANGED_ACTION hearing the same edges: onAvailable / onCapabilitiesChanged for the link up at boot, onLost and a fresh Network when it drops and returns (net down / net up on the simulator’s control channel), and the register-twice / unregister-unknown / unsatisfiable-request contracts. Needs a board with a network (--board testbench_rp2350w)
http_gethttp_get.HttpGetAndroid-style HttpURLConnection demo: performs a GET and a POST against a localhost HTTP/1.1 server, reading the response body through HttpInputStream. On hardware it waits up to 30 s for the network, and BASE_URL must point at an HTTP server reachable on your LAN — see WiFi & networking setup
https_gethttps_get.HttpsGetThe TLS client, and the nightly’s net row for it: HttpURLConnection GETs over https (the platform’s time service has set the clock by then) against the test host’s TLS listener (scripts/tls-listener.py), three handshakes that must be refused (an expired leaf, a chain under no known root, a certificate for another name), and two public hosts whose chains end at an ECDSA and an RSA root. Needs has_tls = true (every RP2350 WiFi board)

Sensors

Environmental / hardware sensors exposed through the Android-compatible SensorManager. Reference: Sensors.

ExampleClassDescription
sensordemosensordemo.SensorDemoActivityRegisters a SensorEventListener on the default ambient-temperature sensor (BME688) and logs each reading; requires a [[sensor]] entry in board.toml

Concurrency

Executors and cross-thread dispatch. Reference: api/system.md → Executors.

ExampleClassDescription
executordemoexecutordemo.ExecutorDemoActivityPosts Runnables via both Executors.mainExecutor() and Executors.backgroundExecutor(); verifies main-thread FIFO ordering and cross-pool dispatch

Services

Background components with lifecycle independent of any Activity. Reference: Services & DI; for a guided build see the Background service tutorial.

ExampleClassDescription
bootdemobootdemo.BootDemoAppA manifest receiver for BOOT_COMPLETED: at boot the framework runs the app headless and the receiver arms a PendingIntent.getBroadcast alarm; four seconds after the launcher is up the alarm wakes the app to deliver it to a second receiver, which opens the Activity. The sim’s boot lane; needs a launcher
alarmdemoalarmdemo.AlarmDemoAppSets an AlarmManager alarm three seconds out, leaves for the launcher, and is started again by the framework to receive it in WokeActivity — the whole point of an alarm over a thread that sleeps. Falls back to waiting in place on a board with no launcher
servicedemoservicedemo.ServiceDemoAppDrives a CounterService through the full Service v1 lifecycle in one non-UI run: two startService calls (one onCreate, two onStartCommand), bindService with a LocalBinder peek, unbindService, then stopService (triggers onDestroy and the foreground-notification cancel). Uses startForeground(id, Notification)

Language Features

Demonstrate Java language features supported by the JVM interpreter. Reference: Core language.

ExampleClassDescription
inheritinherit.InheritDemoDemonstrates class inheritance, field inheritance, method overriding, and super()
interfacedemointerfacedemo.InterfaceDemoDemonstrates interface dispatch (invokeinterface) with Dog and Cat implementing Speakable
defaultmethodsdefaultmethods.DefaultMethodsDemoTest-harness coverage of interface default methods: resolution without an override, sub-interface overrides (whatever the implements order), I.super.f() in a diamond, defaults reached through abstract and builtin superclasses, defaults calling abstract methods on this, interface static methods, and a user Iterable in a for-each loop
floatdemofloatdemo.FloatDemoDemonstrates float, long, and double arithmetic and type conversions (f2i, i2l, i2d, etc.)
exceptiondemoexceptiondemo.ExceptionDemoDemonstrates throw, try/catch, and custom exception classes
threaddemothreaddemo.ThreadDemoDemonstrates spawning concurrent FreeRTOS tasks via picodroid.concurrent.Thread
mainhogmainhog.MainKeeps the main queue non-empty for 600 ms while a child thread counts its sleeps: the JVM run lock must hand off to a waiting sibling even when the UI loop never blocks
mathsdemomathsdemo.MathsDemoDemonstrates integer/long/double arithmetic, bitwise/shift ops, cross-type conversions, tableswitch, instanceof, checkcast, reference arrays, and java.lang.Math
stringdemostringdemo.StringDemoTest-harness coverage of java.lang.String, StringBuilder, and String.format: predicates, search, transforms, valueOf, concat/replace/split/toCharArray/hashCode, "" + obj/append(Object)/valueOf(Object) through toString(), toUpperCase(Locale), plus exhaustive printf-style conversions, flags, widths, and precision; String.join and the java.util.Objects helpers
enumdemoenumdemo.EnumDemoDemonstrates Java enum declarations, values(), name(), ordinal(), and switch over enums
timedemotimedemo.TimeDemoSelf-checking java.time: LocalDate/LocalTime/LocalDateTime arithmetic and ISO text, Instant, Duration, ZoneOffset and TimeZone.setDefault, DateTimeFormatter.ofPattern, Math.floorDiv/floorMod; logs === ALL PASSED ===. RP2350 boards only (the RP2040 image excludes java.time)
trywithresourcesdemotrywithresourcesdemo.TryWithResourcesDemoDemonstrates try-with-resources (AutoCloseable) — opens an ADC pin in a try block and confirms close() is called on exit
lambdademolambdademo.LambdaDemoDemonstrates Java lambdas via invokedynamic: non-capturing, capturing, callbacks, and static method references
anondemoanondemo.AnonDemoDemonstrates anonymous classes implementing interfaces, with local variable capture
clinitdemoclinitdemo.ClinitDemoDemonstrates static class initializers (<clinit>): field initializers, static {} blocks, and cross-class chaining
classlitclasslit.ClassLitClass literals (T.class): demonstrates getName() and that repeated T.class evaluations return the same Class instance
clonedemoclonedemo.CloneDemoObject.clone(): shallow-copy semantics (reference fields stay shared), identity, class preservation, and the canonical (T) super.clone() override inside a Cloneable class
rttidemorttidemo.RttiDemoRuntime type information: instanceof / checkcast over strings, arrays, collections under their interfaces and boxes under Number; transitive superinterfaces; a catchable ClassCastException; the boxed equals / hashCode / compare family; Comparable through Arrays.sort(Object[]); enum identity
syncdemosyncdemo.SyncDemoDemonstrates synchronized blocks (monitorenter/monitorexit) and reentrant locking
threadparitythreadparity.ThreadParityTest-harness coverage of the java.lang.Thread API on picodroid.concurrent.Thread: synchronized methods under real contention, join, sleep/interrupt -> InterruptedException, currentThread().getName(), a second start() -> IllegalThreadStateException, Object.wait/notify producer-consumer, timed wait expiry, subclassed run(), and an uncaught exception routed to a setDefaultUncaughtExceptionHandler
jucdemojucdemo.JucDemoTest-harness coverage of the picodroid.concurrent java.util.concurrent subset: newFixedThreadPool/newSingleThreadExecutor, submit(Runnable)/submit(Callable) and Future.get/cancel/timeout, shutdown/shutdownNow/awaitTermination, contended AtomicInteger/AtomicLong/AtomicBoolean/AtomicReference, and CountDownLatch fan-in (excluded from testbench_rp2040 builds — see the compatibility matrix)
jsondemojsondemo.JsonDemoTest-harness coverage of picodroid.json (org.json parity): parsing the open-meteo weather reply, nesting, escapes and number typing, get/opt coercion, put/remove/accumulate/append, JSONObject.NULL, identity through child wrappers, arrays, toString/quote/numberToString/wrap, the error cases, and a GC-stress loop over the native node pool (needs has_json = true — every RP2350 board, not testbench_rp2040)
protodemoprotodemo.ProtoDemoTest-harness coverage of picodroid.protobuf and protoc-gen-picodroid: a message with every proto3 field kind round-tripped through toByteArray/parseFrom, unknown fields of every wire type skipped, truncated and malformed input throwing InvalidProtocolBufferException from the native codec, packed vs unpacked repeated fields, the streams and their size arithmetic on their own, and 300 parses (needs has_protobuf = true — every RP2350 board, not testbench_rp2040)
collectionsdemocollectionsdemo.CollectionsDemoTest-harness coverage of java.util.*: ArrayList add/get/set/remove/contains/clear plus Integer/Boolean autoboxing, Arrays.sort/copyOf/fill/toString, Arrays.sort(Object[]) and Collections.sort/reverse over Comparable<T>, explicit Iterator and enhanced for-each over lists/maps, HashMap and HashSet, entrySet()/Map.Entry, the LinkedHashMap/LinkedHashSet aliases and toArray
randomdemorandomdemo.RandomDemoDemonstrates java.util.Random — nextInt, nextLong, nextFloat, seeded reproducibility
clockdemoclockdemo.ClockDemoDemonstrates System.currentTimeMillis() for boot-elapsed wallclock-style timing
langsuitelangsuite.LangSuiteAggregated language-feature test runner — exercises every JVM language feature in one APK
bytecodecoveragebytecodecoverage.BytecodeCoverageJVM bytecode coverage harness — exercises long/double arrays, multianewarray, wide, goto_w, and stack-manipulation opcodes

Kotlin

Kotlin apps compile to the same bytecode and run on the same JVM; a small kotlin-shim supplies the stdlib entry points the compiler emits. What is and is not supported is tabulated in the Android compatibility matrix.

ExampleClassDescription
hellokthellokt.HelloKtThe smallest Kotlin app: a string template, one !! (the Intrinsics.checkNotNull the shim serves), and a SAM lambda for a Java interface
langsuite_ktlangsuitekt.LangSuiteKtKotlin language suite — data classes, sealed classes and when, extension functions, default and named arguments, varargs, null safety, scope functions, lazy/Pair, objects and companions, interface defaults, lambdas, type checks, exceptions, synchronized
langsuite_kt_stdliblangsuitektstdlib.LangSuiteKtStdlibKotlin stdlib suite — collections, maps, sets, arrays, ranges, sorting, strings, and math over the shim
injectdemo_ktinjectdemokt.InjectDemoKtAppThe Kotlin twin of injectdemo: the same compile-time DI graph through kapt — @Inject lateinit var fields, constructor and method injection, Provider<T> / picodroid.di.Lazy<T>, a @Module object with @JvmStatic @Provides and an instance @Module class — across an Application, two Activities, and a Service
gcstress_ktgcstresskt.GcStressKtGC stress through the churn Kotlin codegen mints: per-iteration lambda proxies, Ref$IntRef boxes for captured locals, autoboxing through generic collections, Pair destructuring, string templates, and map entry-view iteration; asserts identity-hashCode slot stability and lambda-capture rooting across collections
picoenvmon_ktpicoenvmonkt.EnvAppThe Kotlin twin of picoenvmon — same screens, Service, RGB LED, and dashboard HTTP server, same @Inject/@Module object graph, written under the class-metadata frugality rules of the Kotlin guide

Graphics and Display

Full graphical UI with touch input, demonstrating the Activity lifecycle and LVGL widget system. Reference: Graphics & UI; see also Embedded gotchas for the UI pitfalls these apps avoid.

ExampleClassDescription
displaydemodisplaydemo.DisplayDemoAppShowcases the full widget set on a 320x240 display: LinearLayout, ScrollView, TextView, Button, ToggleButton, Switch, CheckBox, SeekBar, Spinner, EditText, touch input, event handlers, a moving-average FPS overlay (DisplayDebug.showFps()), and a themed-widgets section using a custom Theme palette with GradientDrawable (gradient header, surface card, pill / ghost buttons)
keydemokeydemo.KeyDemoActivityHardware-button demo: a focusable Button’s OnKeyListener takes DPAD_CENTER and the Activity’s onKeyDown / onKeyLongPress / onKeyUp take the rest — DPAD_UP has a short and a long action (startTracking / isCanceled), DPAD_DOWN shows auto-repeat (getRepeatCount); requires [[button]] entries in board.toml
keynavkeynav.KeyNavActivityEvery stock value widget driven by the four keys: a SeekBar, a Spinner and a TimePicker edited with SELECT then UP/DOWN, a ScrollView of text scrolled by UP/DOWN, a list dialog walked and picked, and a clickable TextView the keys reach because a click listener makes a view focusable on a keypad board. Each change is logged, so the sim’s control channel can drive it. See Button-only navigation
menudemomenudemo.MenuDemoActivityThe options menu as the write-once action surface: three actions declared once in onCreateOptionsMenu, reached by holding SELECT on a four-key board, through the menu control the framework draws bottom-right on a touch board, and by a MENU key anywhere; onPrepareOptionsMenu and onOptionsMenuClosed counted and logged. See the options menu
powerdemopowerdemo.PowerDemoActivityDisplay power: the panel dozes on KEYCODE_SLEEP, wakes on a tap that lands as no click, toggles on KEYCODE_POWER, dozes on the idle timeout the app shortens through Settings.System.SCREEN_OFF_TIMEOUT, and stays lit under View.setKeepScreenOn(true). One button fills the window; each click is numbered in the log
touchprobetouchprobe.TouchProbeActivityA bench probe for the touch calibration: one full-screen view that logs every touch-down in panel pixels (TouchProbe: down x,y) and shows it, so a finger on the four corners and the centre says whether a board’s [touch] cal_* table maps the panel’s edges to the screen’s
callbacktestcallbacktest.CallbackTestActivityRegression harness for widget callback dispatch under both shrink modes — registers a lambda listener on every widget type and synthetically fires its event
dialogdemodialogdemo.DialogDemoAppToast.makeText().show() and AlertDialog.Builder with positive / negative listeners; demonstrates onBackPressed() confirmation pattern
gesturedemogesturedemo.GestureDemoAppGestureDetector with onSingleTap / onLongPress / onFling listeners on a single View
dragdemodragdemo.DragDemoActivityTouch-driven drag using FrameLayout + OnTouchListener; tracks MotionEvent.ACTION_DOWN/MOVE/UP and updates a tile’s absolute position via setPosition(). The only example using FrameLayout for absolute placement (a LinearLayout would re-flow on every layout pass)
animdemoanimdemo.AnimDemoAppview.animate().alpha(…).x(…).rotation(…).scaleX(…).setStartDelay(…).withEndAction(…).start() — to-only property animations (the start value is read from the view, as on Android), easing interpolators, chained end actions, and the View transform getters
keyboarddemokeyboarddemo.KeyboardDemoAppSoft keyboard: both system-on-touch (default) and explicit Keyboard instances bound to EditText. Includes the soft-keyboard polish pass: slide-up animation, OnEditorActionListener for the Done key, dismiss-on-outside-tap
navdemonavdemo.NavDemoAppMulti-Activity back-stack — startActivity() push, finish() pop, lifecycle callbacks (onPause / onStop / onResume)
bundledemobundledemo.BundleDemoAppBundle conformance, Intent.putExtras() / getExtras(), and saved instance state across Activity.recreate() — onSaveInstanceState → onCreate(Bundle) → onRestoreInstanceState
reclaimdemoreclaimdemo.ReclaimDemoAppReclaimed Activities: run with PICODROID_DONT_KEEP_ACTIVITIES=1 and every covered Activity is destroyed with its state saved, then re-created from the Bundle when uncovered — including a startActivityForResult caller that gets its result as a new instance
fragmentdemofragmentdemo.MainActivityFragment conformance: the callback order against the host’s, replace with a back stack, hide / show, detach / attach, a fragment without a view, the transactions the manager refuses, BACK popping the back stack instead of finishing, and — under PICODROID_DONT_KEEP_ACTIVITIES=1 — the host reclaimed and re-created with its fragments, arguments, saved state and back stack restored through a FragmentFactory
pagerdemopagerdemo.PagerDemoActivityThree fragments in a <ViewPager2> from XML with a dots row on OnPageChangeCallback: a smooth turn (SETTLING, the old page destroyed, IDLE), instant turns, a page’s saved state coming back with it, then recreate() and a covering Activity, after each of which the pager is back on the same page with every page’s state
injectdemoinjectdemo.InjectDemoAppCompile-time DI end to end: @Inject constructor / field / method injection, @Singleton identity across an Application, two Activities and a Service (all injected automatically before onCreate), an unscoped class per injection site, a leaf Activity that only inherits its @Inject members, Foo_Factory.get() from plain code, Provider<T> / Lazy<T> wrappers, a @Module providing an interface and a @Singleton value, and coexistence with a hand-written ApplicationComponent
imagedemoimagedemo.ImageDemoAppImageView.setImageSource("name.png") resolving against the PAPK ASSETS section (v1.1 bundled images). Demonstrates setScaleType / setTint / setScale. See Bundled image assets
resdemoresdemo.ResDemoAppThe resource system end to end: res/values through getResources(), setContentView(R.layout.activity_main), findViewById, LayoutInflater.inflate into a parent, @drawable images, and the laid-out geometry of match_parent / layout_weight. Checks itself and logs ResDemo PASS. See Resources, R and XML layouts
layoutdemolayoutdemo.LayoutDemoActivityWhat an Android page layout is written with, checked end to end: layout_margin in a LinearLayout, layout_gravity plus margins in a FrameLayout (from XML and from code), a <shape> background, <include>, a style and the theme (?attr/…, setTheme), a view class of the app’s own made by Activity.onCreateView and sized by onMeasure, and the same layout inflated a slice per tick by AsyncLayoutInflater; with them the setters that do nothing when nothing changes, setBackgroundTintList, getString with arguments, runOnUiThread and ComponentName. Logs === ALL PASSED ===. RP2350 boards (the custom view draws itself). See Resources, R and XML layouts
pickerdemopickerdemo.PickerDemoAppDatePicker (lv_calendar binding) and TimePicker (lv_roller binding) with 12-hour / AM-PM mode and value-changed listeners
ellipsizedemoellipsizedemo.EllipsizeDemoAppConformance for TextView.setSingleLine / setEllipsize / setMaxLines: a long text in a 96 px label is one line tall with an ellipsis while getText() still returns the whole text, a max-lines label is that many lines tall, and lifting the limit restores the wrapped height. Logs === ALL PASSED === or one FAIL line per check
snackbardemosnackbardemo.SnackbarDemoAppSnackbar.make().setAction().show() — toast with a clickable action lozenge, auto-dismiss, click-through-to-listener
swipedemoswipedemo.SwipeDemoAppOnSwipeListener (UP / DOWN / LEFT / RIGHT direction constants) on a single view; SwipeRefreshLayout pull-to-refresh container
calculatorcalculator.CalculatorActivityFour-function touch calculator built for the 320x480 pico_touch_kit panel: a 4x5 key grid of nested LinearLayout rows, Gravity.BOTTOM/Gravity.RIGHT to right-align the two display lines, GradientDrawable key faces and flat layout backgrounds, and an arithmetic model (CalculatorEngine) kept free of any framework call. Every box is derived from the Display size, so it fills any panel

Performance and Testing

Benchmarks and stress tests for the JVM runtime and allocator. Reference: System & concurrency (Runtime.gcCount(), Runtime.gcTimeNanos()); for the numbers these probe, see Limits & memory budgets and JVM tunables.

ExampleClassDescription
benchmarkbenchmark.BenchmarkJVM performance benchmark: times int/long/float/double arithmetic, method dispatch, interface dispatch, object allocation, array ops, string ops, and control flow; logs per-category and total elapsed time
perfbenchperfbench.PerfBenchUnified speed + memory benchmark — rolls execution-time and heap-usage measurements into a single composite SCORE for tracking runtime regressions
graphicsbenchgraphicsbench.GraphicsBenchLVGL render-pipeline benchmark — exercises the draw/refresh path across several test cases and reports a composite SCORE
gcstressgcstress.GcStressGC stress test: exercises the mark-sweep collector under object churn, linked chains, circular references, string churn, and array churn; reports cycle count, freed entries, and GC time via picodroid.os.Runtime
heapstressheapstress.HeapStressAllocation/fragmentation stress test exercising the array arena allocator and emergency-GC path
tracedemotracedemo.TraceDemoStack-trace showcase — a three-deep uncaught RuntimeException prints at tracedemo.TraceDemo.deepest(TraceDemo.java:41) in the sim and in debug firmware; release firmware prints (pc=N), which scripts/retrace.sh --app tracedemo resolves on the host
bugbashbugbash.BugBashPure-logic regression checks for the 2026-08-30 bug bash — each check names the defect id it pins, proving through real bytecode what the unit tests guard in Rust
bugbash_uibugbashui.BugBashUiAppLifecycle half of the bug-bash regression app — self-driving Activity/Service walk covering finish() idempotence, service bind limits, and pending-op delivery
executorstressexecutorstress.ExecutorStressGC-rooting stress for Runnables in flight in the executor queues: posts lambdas whose only reference is the queued executor word, then forces collections before the queue drains
threadstressthreadstress.ThreadStressConcurrent-allocation stress for the compound-heap atomic sections — three child threads plus the main task churn the shared heap; run under --mem-diag with offensive checks armed
postlooppostloop.MainHop-timing probe for the main executor: a fresh child Thread per hop sleeps 80 ms and posts the next hop, every tenth hop commits a SharedPreferences write, and each hop logs its per-stage millisecond costs (start, up, slept, post, deliver) so a slow hop names the stage that ate the time
qa_langqa_lang.QaLangQA suite, language and bytecode semantics: numeric conversions and overflow, shift masking, switch forms, evaluation order, try/finally ordering and the catchable exception family. A section that throws is reported and the run continues
qa_ioqa_io.QaIoQA suite, the java.io stream hierarchy: the byte-array streams, the base-class defaults an app’s own InputStream / OutputStream inherits, BufferedReader.readLine across every line ending and past the buffer, writers, and the file streams through their java.io supertypes, try-with-resources and instanceof
qa_oopqa_oop.QaOopQA suite, object model: construction order, field shadowing, static hiding, virtual dispatch from constructors, inner / anonymous / local classes, lambdas and method references, enums with bodies, static initialisation order, generics bridges, varargs, and the equals / hashCode / toString / compareTo upcalls the collections rely on
qa_collqa_coll.QaCollQA suite, java.util / java.lang under load and at the edges: identity hash codes across a compacting GC, HashMap growth and views, ArrayList growth, boxed keys of different types, string and String.format corner cases, interleaved StringBuilders, wrapper parsing and printing, and java.util.Random’s exact stream
qa_thrqa_thr.QaThrQA suite, threads: start / join / isAlive, interrupt and the flag, wait / notify hand-offs and timeouts, monitor exclusion on instance and class locks, uncaught-exception handlers, latches, atomics, the ExecutorService / Future contract, and clock and sleep accuracy
qa_storeqa_store.QaStoreQA suite, storage: File predicates and renames, stream offsets and EOF, partial reads, truncation, directory listing, refused paths, the Context private-file helpers, SharedPreferences types, limits and editor semantics, StatFs. Run it twice against the same volume to check persistence across a reboot
qa_bcastqa_bcast.QaBcastAppQA suite, broadcasts: the registry’s rules, in-app sendBroadcast to registered and explicit receivers, a receiver unregistered mid-round, one that throws, the leak reap at an Activity’s destroy, TIME_CHANGED → TIME_TICK from a clock set half a second before the minute, TIMEZONE_CHANGED, SCREEN_OFF / SCREEN_ON from a shortened timeout, and two PendingIntent.getBroadcast alarms. Multi-app boards
qa_lifeqa_life.QaLifeAppQA suite, lifecycle: Intent extras, an Activity started for a result, and a Service walked through unbind / rebind / stop / restart one step per frame
qa_uiqa_ui.QaUiAppQA suite, the view tree and widget state machine driven without a finger: child order after add / remove, visibility and enabled state, listener replacement, compound buttons and radio groups, seek / progress clamping, adapters, focus hand-off, then sizes, animation end actions, dialogs, toasts, snackbars and a view churn under GC

Feature Showcase

End-to-end apps that combine multiple subsystems. These are the closest reference for the layout and structure of a “real” picodroid app.

ExampleClassDescription
picoenvmonpicoenvmon.EnvAppEnvironmental monitor for the Pimoroni Enviro+ Pack (on the Pico 2 W variant: a web dashboard, an NTP-anchored clock and an open-meteo weather row fetched over HTTPS). Multi-Activity (HomeActivity, settings, history, network) with a sub-package layout (ui/, service/, net/, hardware/, data/, util/); customizes the global Theme palette in Application.onCreate(); runs a SensorLoggerService ring-buffering BME688 + LTR559 readings; drives an RGB LED. Wired with @Inject / @Singleton throughout — app-scoped ThresholdConfig, Formatter, LatestReadings, RgbLed, NetworkManager, plus SharedPreferences from an @Provides method in EnvModule, injected into every Activity and the Service
picoclockpicoclock.ClockAppClock and alarms for the 320x480 pico_touch_kit panel. Five screens over a shared BaseActivity (clock face, alarm list, editor, set-time, full-screen ring); an AlarmService whose 4 Hz thread is the whole app’s heartbeat, so no screen owns a timer; a seven-segment face built from plain views, a few per UI tick, because there is no text-size API; alarms in SharedPreferences and the wall clock set by hand, the board having no RTC; the carrier’s buzzer on GP13 through Pwm. The date and schedule arithmetic is pure and self-checked at startup
claudeusageclaudeusage.ui.MainActivityA desk display for Claude usage limits on the 320x240, four-button pico_display2_w: 5-hour session, weekly and per-model caps, burn rate and token history, fetched as protobuf over plain HTTP from a small bridge on the LAN (bridge/claude_usage_bridge.py, with a --demo mode for the simulator). One Activity declared in the manifest; the whole UI in res/: the chrome and every page are XML layouts (layout_weight, FrameLayout margins, <include>, styles), cards and dots are <shape> drawables, colours come from the theme (setTheme, ?attr/…); four screens as Fragments in a <ViewPager2> with a dots row driven by OnPageChangeCallback, each page inflated over several UI ticks by AsyncLayoutInflater and fading in, the status screen a fragment laid over the pager; the numerals through android:textSize; the two limits as ring gauges and the charts as custom views named in the layouts, made by Activity.onCreateView, sized by onMeasure and drawn in onDraw(Canvas), the per-model meters as severity-coloured, tinted ProgressBars; the data flow of an Android app: a started Service with a poll thread and a ConnectivityManager.NetworkCallback fills a repository, a ViewModel turns that and the clock into one immutable UI state published through LiveData only when it would paint differently, and the Activity and the pages observe it; every failure (PC off, bridge down, login expired, stale data, a window resetting while offline) has its own presentation
askclaudeaskclaude.MainActivityAsk Claude from a Pico, the HTTPS showcase: A/B pick a prompt, X POSTs it to the Claude Messages API (HttpsURLConnection, setFixedLengthStreamingMode, request headers, getErrorStream()), the reply fills the screen with the round trip and token usage on the status line; on the Enviro+ board the prompt carries the room readings. The API key and model are BuildConfig constants (picodroidBuildConfig, Android’s buildConfigField); the nightly points the endpoint at the test listener’s canned /v1/messages
weatherweather.MainActivityA weather app in the shape of the phone ones, for the four-button pico_enviro_mon_w: the place, the temperature in the 64 px face with the condition glyph beside it, high and low, the next hours in a strip; a page of the week with a range bar per day; a details page (feels-like, humidity, wind, pressure, sunrise/sunset, and the room’s own readings from the Enviro+ sensors). One open-meteo request over HTTPS every 15 minutes (the platform’s time service sets the clock the certificate check needs), parsed with picodroid.json; the glyphs, the hourly strip and the seven-day list are View.onDraw(Canvas) drawings, one view each; the sky is a GradientDrawable that follows the conditions. Place and units are BuildConfig constants; the nightly runs it against the test listener’s canned /v1/forecast

Running an Example

Terminal window
./scripts/build.sh --app <name>
./scripts/flash.sh --app <name>

Or test on the host simulator without hardware:

Terminal window
./scripts/sim.sh --app <name>

See Build & flash for full build and flash options.