Skip to content

Android compatibility matrix

Picodroid’s goal is that Android code and intuition transfer directly: class names, method signatures, and semantics track android.*. Embedded constraints force some divergences, and a few Android subsystems are intentionally absent. This page is the authoritative list of what’s full, partial, renamed-only, or unsupported — and the picodroid alternative for every gap.

The SDK is imported as picodroid.* — every class below mirrors its android.* counterpart’s name, so the API reads the same; you just import picodroid.* (e.g. import picodroid.view.View;). Apps always use picodroid.* imports.

Status legend

StatusMeaning
FullAPI surface and semantics match Android closely enough to port unchanged.
PartialPresent, but a subset of methods/overloads or a documented behavior difference.
RenamedSame shape, but only reachable as picodroid.* (or via the android alias) — not a real Android class.
UnsupportedNo equivalent; use the listed alternative.

By package

android.app

APIStatusNotes / alternative
ActivityFullLifecycle (onCreate/onStart/onResume/onPause/onStop/onRestart/onDestroy), startActivity, startActivityForResult + onActivityResult, setResult, getIntent, finish.
ApplicationFullonCreate entry point.
ServicePartialStarted + bound services, onRebind, stopSelfResult. No IntentService, no foreground-service notification contract.
AlertDialog / AlertDialog.BuilderPartialPositive/negative/neutral buttons, setItems, single- and multi-choice. List variants cap at ~12 rows (LVGL renderer limit) and a message set alongside items wins (items are dropped, with a Log.w) — matching Android’s message-vs-items precedence.
Notification / NotificationManagerPartialBasic post/cancel. No channels, styles, or actions.
Fragment, Loader, PendingIntentUnsupportedNo Fragment system — compose with Activities + Views.

android.view

APIStatusNotes / alternative
ViewPartialGeometry/visibility/enabled/tag/id, setTranslationX/Y, setRotation, setScaleX/Y + getters (getX() == getLeft() + getTranslationX()), OnClickListener, OnLongClickListener + performLongClick, OnTouchListener, OnKeyListener. No findViewById (no resource IDs — keep references or use setTag/getTag); no post/postDelayed (use Executors.mainExecutor() or animation timers).
ViewGroup / ViewPropertyAnimatorPartialanimate() with alpha, x/y, translationX/Y, rotation, scaleX/Y, setDuration, setStartDelay, setInterpolator (the four built-in curves), withEndAction — to-only, as on Android. Rotation/scale render through an off-screen layer of the view’s size; keep transformed views small.
MotionEventPartialgetX/getY are view-relative, getRawX/getRawY are screen-absolute, matching Android. Coordinates are int, not float (no FPU).
GestureDetectorPartialOnGestureListener + SimpleOnGestureListener; slop/fling use raw coordinates.
KeyEventPartialD-pad / button codes for button-only boards.
LayoutInflater, XML layouts, MenuUnsupportedNo resource/XML layout system — build View trees programmatically.

android.widget

APIStatusNotes / alternative
TextView, Button, LinearLayout, ImageView, Switch, CheckBox, ToggleButton, RadioButton/RadioGroup, ProgressBar, SeekBar, Toast, Spinner, NumberPicker, EditText, ListViewPartial–FullCore widgets present. See specific divergences below.
ProgressBarPartialindeterminate() is creation-time onlysetIndeterminate(boolean) after construction is unsupported (LVGL can’t morph bar↔spinner).
Spinner.OnItemSelectedListenerPartialFull 4-arg onItemSelected(parent, view, position, id); view is always null (LVGL rows have no Java wrapper) and parent is the Spinner (no AdapterView).
ImageViewPartialSCALE_FIT_CENTER, SCALE_CENTER. Source is a bundled asset name (see assets).
EditText + TextWatcherPartialTextWatcher takes String (no CharSequence/Editable); only afterTextChanged fires in v1.

android.util

APIStatusNotes / alternative
Log (v/d/i/w/e)FullMaps to defmt levels on device; the simulator prints every level as [Tag] msg. Filter by tag/level with pdb logcat --stdin.

android.graphics

APIStatusNotes / alternative
ColorFullNamed constants + ARGB ints.
drawable.GradientDrawablePartialSolid/gradient fills, corner radius.
Canvas, Paint, BitmapUnsupportedDrawing is via LVGL widgets, not an immediate-mode Canvas.

android.content

APIStatusNotes / alternative
IntentPartialExplicit (class-targeted) intents + extras. No implicit intents / IntentFilter resolution.
ContextPartialgetMainExecutor, getDisplay, service access. No getSystemService (services are exposed directly), no getResources (bundle files under assets/ → generated AssetConstants), no registerReceiver (no BroadcastReceiver).
SharedPreferences / EditorFullBacked by LittleFS.
DialogInterfaceFullOnClickListener, OnDismissListener, OnMultiChoiceClickListener, button constants.

android.os

APIStatusNotes / alternative
SystemClockFulluptimeMillis/elapsedRealtime.
Handler / Looper / MessageUnsupportedUse Executors.mainExecutor().execute(Runnable) for “post to UI”; for delayed work, a Thread that sleeps then posts, or the animation engine’s withEndAction. There is no postDelayed.
BundlePartialIntent extras only.

android.hardware

APIStatusNotes / alternative
Sensor / SensorManager / SensorEvent / SensorEventListenerPartialBoard-dependent sensors; registration + event callbacks.

Concurrency

APIStatusNotes / alternative
Thread (picodroid.concurrent.Thread)Fullstart() spawns a real FreeRTOS task on device and in the simulator (the sim runs the real kernel). run() override or Runnable target, sleep, join/join(ms), interrupt/isInterrupted/interrupted, isAlive, currentThread, get/setName, getId, yield, UncaughtExceptionHandler (per-thread and default), IllegalThreadStateException on a second start(). synchronized blocks and methods are real kernel mutexes; Object.wait/notify/notifyAll work (timed, interruptible, IllegalMonitorStateException when not owner). Divergences: setPriority and setDaemon are advisory (every Java task runs at one RTOS priority — see the system reference), and a compute-bound thread holds the core until it blocks (no time slicing).
Executor / Executors (mainExecutor / backgroundExecutor)FullThe recommended concurrency primitive — this is how you “post to the UI thread”.
ExecutorService / Future / Callable / TimeUnit (picodroid.concurrent)PartialExecutors.newFixedThreadPool(n) and newSingleThreadExecutor(): execute, submit(Runnable/Callable), Future.get/get(timeout, unit)/cancel/isDone/isCancelled, shutdown/shutdownNow/isShutdown/isTerminated/awaitTermination; ExecutionException (with getCause()), CancellationException, TimeoutException, RejectedExecutionException under their java.util.concurrent names. Pure Java over Thread + wait/notify; each worker costs a 16 KiB task stack. Not built into testbench_rp2040 (framework_class_excludes, flash headroom) — nor are the atomics and CountDownLatch below. No invokeAll/invokeAny, no scheduled or cached pools, no CompletableFuture.
AtomicInteger / AtomicLong / AtomicBoolean / AtomicReference (picodroid.concurrent)Partialget/set/getAndSet/compareAndSet/incrementAndGet/getAndIncrement/decrementAndGet/addAndGet/getAndAdd. synchronized underneath (one core, one JVM priority). No updateAndGet/lambdas, no AtomicIntegerArray.
CountDownLatch (picodroid.concurrent)FullcountDown, await, await(timeout, unit), getCount. No Semaphore, CyclicBarrier, ReentrantLock, ConcurrentHashMap, BlockingQueue.

java.* standard library

Enforced at build time: every app’s verifyApiContract Gradle task (part of assemblePapk, so build-apk.sh, sim.sh, build.sh and flash.sh all run it) rejects a java.* class or member pico-jvm does not serve, naming the call site and an alternative. sdk/api-contract.tsv, generated from the runtime’s own tables (scripts/gen-api-contract.sh), is the machine-readable form of this section. With --board <name> (or -Ppicodroid.board=<name>) the same task also rejects classes that board drops from its framework (framework_class_excludes). -Ppicodroid.apiContract=warn downgrades the failure to a report while experimenting.

APIStatusNotes / alternative
Object.clone() / CloneablePartialShallow copy works, but the Cloneable check is skippedclone() never throws CloneNotSupportedException.
Interface default methodsFullResolved per JVMS §5.4.3.3 (sub-interface overrides win whatever the implements order; I.super.f() works; defaults are found through abstract and builtin superclasses). Exception: a lambda’s own interface defaults — calling a default method on a lambda object runs the lambda body instead, so keep lambda-typed interfaces to their single abstract method.
StringBuilder.append(Object) / String.valueOf(Object) / "" + objFullThe argument’s toString() runs first — a Java override, else the builtin one for boxes/enums, else the identity form pkg.Cls@hhhh; null prints null.
Object.equals/hashCode/toString defaultsPartialIdentity semantics as in Java, but hashCode() is the object’s heap slot index (stable for the object’s lifetime, reused after GC) and builtin collections use identity too (new ArrayList<>().hashCode() is not content-based; list.toString() prints java.util.ArrayList@…, not the elements).
instanceof / checkcastPartialStrings, arrays, builtin collections under List/Collection/Iterable/Set/Map, boxes under Number/Comparable, lambdas under their interface, and transitive superinterfaces all work; a failed cast throws a catchable ClassCastException (with a null message). A reference array’s element class is not recorded, so (String[]) someObjectArray succeeds where Java would throw.
Boxed wrappers (Integer, Float, …)Partialequals/hashCode/compareTo, the compare/hashCode(x) statics and Float.floatToIntBits follow Java 8. The xxxValue() accessors return the box’s own value unconverted (Float.valueOf(2.5f).intValue() yields a float; call the matching accessor, or unbox and convert). Character has isDigit/isLetter/toUpperCase/toLowerCase only, ASCII-only (strings are byte-backed).
Enum.valueOf(Class, String)UnsupportedThe static every enum’s synthetic valueOf(String) delegates to has no builtin (a heap scan cost ~800 B of RP2040 flash). Look the constant up yourself: for (Color c : Color.values()) if (c.name().equals(s)) …. Enum.hashCode() is the ordinal.
ThrowablePartialaddSuppressed/getSuppressed/getCause stored; ExceptionInInitializerError wraps <clinit> throws. A failed <clinit> does not poison the class (no NoClassDefFoundError on re-access).
Comparator + Collections.sort(List, Comparator)FullLambda comparators supported.
Interface-typed collections (List/Set/Collection/Map/Iterable)FullMap<String,String> m = new HashMap<>(); and interface-typed fields, parameters, returns, casts, instanceof and enhanced-for all work — the JVM dispatches on the receiver’s runtime class, so the interfaces need no class file. Caveat: your app compiles against the JDK’s full interfaces, so members picodroid does not implement (map.forEach, list.removeIf, Map.putAll, new TreeMap<>()) compile and then fail at run time. The rows in this table are the served surface.
HashMap.entrySet() / Map.EntryPartialentrySet(), keySet() and values() views answer iterator() and size(); the key and value views also contains() (k in map.keys). Entries answer getKey()/getValue() (no setValue). Iteration order is the map’s internal order.
HashSet.iterator()Fullfor (x : set) and every iterating idiom on a set; hash order, like the map views. Iterators and map views keep a temporary collection alive for as long as they do (for (String w : text.split(" ")), for (e : makeMap().entrySet())) — the GC pins the iterator’s source.
Map.putAll / List.listIterator / Arrays.equalsUnsupportedNo builtin arm: copy with an entrySet() loop, iterate by index, compare arrays element-wise. Kotlin: map += otherMap, list.last { } / indexOfLast { } / findLast { } (they walk a listIterator) and contentEquals hit these.
Float.isNaN(f) / Double.isNaN(d) / isInfinite staticsUnsupportedUse f != f for NaN and a magnitude compare for infinity (Kotlin’s isNaN()/isInfinite()/isFinite() inline to these statics).
String(char[])UnsupportedOnly the byte[] constructors exist; build with StringBuilder.append(char) (Kotlin: chars.concatToString() does this).
Math.roundPartialRounds half away from zero (Math.round(-2.5f) is -3; Java gives -2). Kotlin’s roundToInt()/roundToLong() shim spells out Java’s floor(x + 0.5) and is exact.
String.replace(target, replacement)PartialAn empty target leaves the string unchanged (Java interleaves the replacement between every char).
LinkedHashMap / LinkedHashSetPartialAliases of HashMap / HashSet: insertion order is not preserved. instanceof HashMap/Map/Set hold.
Collection.toArray() / toArray(T[])PartialAlways returns a fresh Object[] of exactly the collection’s size — the array argument is neither filled nor returned (list.toArray(new String[0]) is the idiom to use; a cast to String[] succeeds because reference arrays carry no element class).
java.util.LocalePartialName-only: Locale.ROOT/US/… read as null, and toUpperCase(Locale)/toLowerCase(Locale) ignore the argument (ASCII only). This is what Kotlin’s uppercase()/lowercase() compile to. No Locale instances.
Class.getName()FullReturns dot-form (pkg.Class) per the Java spec.
javax.inject.Inject / Singleton / ScopePartialCompile-time only: an annotation processor generates Foo_Factory / Foo_MembersInjector, and Application / Activity / Service are injected automatically before onCreate. SOURCE retention (JSR-330 says RUNTIME) — no runtime annotations, no reflection. javax.inject.Provider<T> / picodroid.di.Lazy<T> (≙ dagger.Lazy) inject anywhere a T can; picodroid.di.Module / picodroid.di.Provides (≙ dagger.Module / dagger.Provides) bind SDK types and interfaces, every module auto-installed. No @Component, @Binds, @Named/qualifiers, custom scopes. Java and Kotlin apps (the Kotlin plugin runs the same processor through kapt; Kotlin shapes: @Inject lateinit var, @Inject constructor, @Module object + @JvmStatic @Provides, @Module class for instance @Provides). See Services & DI.
String.splitPartialLiteral delimiters only — no regex.
BufferedReader / InputStreamReaderUnsupportedUse the byte-oriented picodroid.io streams (FileInputStream/FileOutputStream); there is no char-stream reader layer.

Cross-cutting divergences

  • Coordinates and sizes are int px. There is no float MotionEvent coordinate, and no density-independent units — no dp/sp, no getResources().getDisplayMetrics(). Lay out in pixels.
  • No resources system. No R class, no res/ directory, no XML layouts, drawables, or strings. Bundle binary assets under assets/ and reference them through the generated AssetConstants (see assets).
  • No Handler/Looper. The main loop is an executor-driven dispatcher; use Executors.mainExecutor() and animation timers.
  • Custom Interpolators fall back to linear. Standard interpolators (linear/accelerate/decelerate/accelerate-decelerate) map to native easing; an app-defined Interpolator can’t be up-called from the native tick, so it falls back to linear with a Log.w.

Imports

Always import the picodroid.* classes directly — e.g. import picodroid.view.View;, import picodroid.widget.TextView;. There is no android.* import compatibility layer: import android.view.View; will not compile or load. The package names below mirror Android’s only so the API reads the same and intuition transfers; the namespace you import is picodroid.