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.* — 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), onKeyDown/onKeyLongPress/onKeyUp (KeyEvent.Callback; the fallback after the focused view’s OnKeyListener, with Android’s BACK tracking and long-press cancel), onBackPressed, startActivity, startActivityForResult + onActivityResult, setResult, getIntent, finish, recreate with onSaveInstanceState / onRestoreInstanceState, setContentView(View) / setContentView(int), findViewById, getSupportFragmentManager (FragmentActivity’s, on Activity itself), getLifecycle / getViewModelStore / getDefaultViewModelProviderFactory (ComponentActivity’s), runOnUiThread, the options menu (onCreateOptionsMenu / onPrepareOptionsMenu / onOptionsItemSelected / onOptionsMenuClosed, openOptionsMenu / closeOptionsMenu / invalidateOptionsMenu; opened by a MENU key, a held SELECT on a four-key board, or the framework’s menu control on a touch board). onKeyMultiple is declared and never called. No dispatchKeyEvent, onContextItemSelected or action bar.
ApplicationFullonCreate entry point.
ServicePartialStarted + bound services, onRebind, stopSelf / stopSelfResult, startForeground / stopForeground (the notification is a top-of-screen banner; nothing kills a background service, so “foreground” is about the banner). No IntentService.
AlertDialog / AlertDialog.BuilderPartialPositive/negative/neutral buttons, setItems, single- and multi-choice. Titles, messages and labels are String. A dismissed dialog is freed and cannot be shown again (show() throws IllegalStateException). No setView, setCancelable, setOnDismissListener or setIcon. 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.
AlarmManagerPartialsetTime(long) and setTimeZone(String) on every board (a fixed-offset id such as GMT+05:30; no region ids, no tz database; SET_TIME / SET_TIME_ZONE are not enforced). Alarms on multi-app boards only: set / setExact (both exact) and cancel, on all four RTC* / ELAPSED_REALTIME* clocks. An alarm outlives the app that set it and starts it again to deliver it; alarms live in RAM and are lost at a reset, so re-register them at startup as an Android app does after BOOT_COMPLETED. No setRepeating, setWindow, setAlarmClock or OnAlarmListener.
PendingIntentPartialMulti-app boards only. getActivity and getBroadcast (an explicit receiver class, delivered to a new instance of it, or an action, delivered to the registered receivers), carrying at most two int extras under keys of at most 15 characters; FLAG_NO_CREATE is honoured, the other flags are accepted and ignored (a set always replaces by identity). No getService.
Fragment / FragmentManager / FragmentTransaction (androidx.fragment.app, in picodroid.app)PartialAndroid’s lifecycle callbacks in Android’s order, driven by the host Activity; add / replace / remove / hide / show / detach / attach, commit (deferred) and commitNow, addToBackStack / popBackStack (BACK pops before finish()), findFragmentById / ByTag, setArguments, onSaveInstanceState saved with the Activity’s Bundle. Deviations: a view given up in onDestroyView is freed at once (never re-added); lifecycle states are ints (setMaxLifecycle(Fragment, int), no Lifecycle.State); setInitialSavedState(Bundle) in place of SavedState; after a re-creation fragments come back only through a FragmentFactory the app sets (instantiate(String), no ClassLoader, no reflection — without one nothing is restored, with a warning). getViewLifecycleOwner() for LiveData; the fragment itself is not a LifecycleOwner or ViewModelStoreOwner. A skipped super in a lifecycle override is tolerated (no SuperNotCalledException). No child fragment managers, animations, startActivityForResult, setRetainInstance, menus.
ViewModel / ViewModelProvider (androidx.lifecycle, in picodroid.lifecycle)Partialnew ViewModelProvider(owner[, factory]).get(X.class) / get(key, X.class), ViewModelProvider.Factory.create(Class), ViewModel.onCleared(), ViewModelStore, ViewModelStoreOwner and HasDefaultViewModelProviderFactory (both implemented by Activity). No reflection, so no default factory: override Activity.getDefaultViewModelProviderFactory() or pass one. A ViewModel lives as long as its Activity instance and is cleared on destroy, recreate() included (there are no configuration changes to keep it across). No SavedStateHandle, AndroidViewModel, viewModelScope; a fragment is not an owner.
LiveData / MutableLiveData / Observer / Lifecycle / LifecycleOwner (androidx.lifecycle, in picodroid.lifecycle)Partialobserve(owner, observer) (active from STARTED, removed at DESTROYED), observeForever, removeObserver(s), setValue (synchronous, main thread), postValue (any thread, last value wins), getValue, hasObservers / hasActiveObservers, onActive / onInactive. Owners: Activity, and Fragment.getViewLifecycleOwner(). Lifecycle.getCurrentState() is an int (no Lifecycle.State enum) and Lifecycle has setCurrentState (it is its own LifecycleRegistry). No LifecycleObserver / DefaultLifecycleObserver, Lifecycle.Event, Transformations, MediatorLiveData.
LoaderUnsupportedLoad on a Thread or ExecutorService and post to the main executor.

android.view

APIStatusNotes / alternative
ViewPartialGeometry/visibility/enabled/tag/id, setTranslationX/Y, setRotation, setScaleX/Y + getters (getX() == getLeft() + getTranslationX()), OnClickListener + performClick, OnLongClickListener + performLongClick, OnTouchListener, OnKeyListener, OnFocusChangeListener with setFocusable / requestFocus, setAlpha, setPadding, setLayoutParams, getParent, setBackground / getBackground, setBackgroundColor, setBackgroundTintList / getBackgroundTintList (the tint replaces the background’s colour and keeps its shape; its alpha is ignored). A setter given the value the view already has (setVisibility, setAlpha, setEnabled, the same Drawable or an equal tint; TextView.setText and setTextColor) does nothing. findViewById (depth first, over setId / android:id). VISIBLE / INVISIBLE / GONE have Android’s values (0, 4, 8). Additions with no Android counterpart: setPosition, setSize, close() (frees the widget) and setOnSwipeListener (OnSwipeListener, View.SWIPE_*). onMeasure / setMeasuredDimension / measure / getMeasuredWidth / getMeasuredHeight / resolveSize / getDefaultSize and View.MeasureSpec: asked of a view that draws itself, for a wrap_content dimension. No post/postDelayed (use Executors.mainExecutor() or animation timers), no onLayout / onTouchEvent / getContext.
ViewGroupPartialaddView(child), addView(child, LayoutParams), removeView, removeAllViews, getChildCount, getChildAt, onViewAdded, LayoutParams (MATCH_PARENT, WRAP_CONTENT) and MarginLayoutParams (setMargins; read when the child is added). addView(child) applies the LayoutParams the child carries. removeView frees the child’s widget, so a removed view cannot be added again (IllegalStateException), and addView of a view that has a parent moves it (Android throws). No addView(child, index), indexOfChild or onInterceptTouchEvent.
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). ACTION_DOWN / ACTION_UP / ACTION_MOVE, plus ACTION_LONG_PRESS (3), which has no Android counterpart. Single touch: no pointer ids or getPointerCount.
GestureDetectorPartialOnGestureListener + SimpleOnGestureListener with three callbacks, named and typed differently from Android’s: void onSingleTap(MotionEvent), void onLongPress(MotionEvent), void onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY). The constructor takes the listener alone, and the detector is itself an OnTouchListener (no onTouchEvent). No onDown, onScroll, onShowPress or double tap; slop/fling use raw coordinates.
KeyEventPartialD-pad codes, KEYCODE_BACK and KEYCODE_HOME, and the KEYCODE_BUTTON_A / _B / _X / _Y constants (no board maps a button to those yet: a board’s buttons arrive as the D-pad / BACK codes its board.toml names); getAction, getKeyCode, getRepeatCount, getFlags, isLongPress, isCanceled, startTracking/isTracking, getDownTime/getEventTime, dispatch with KeyEvent.Callback and KeyEvent.DispatcherState. A held key auto-repeats after ViewConfiguration.getKeyRepeatTimeout() (400 ms) every getKeyRepeatDelay() (50 ms); the first repeat is the long-press. Both special keys are handled by the framework before an app sees them: BACK falls through the soft keyboard, a showing dialog, the focused View, then Activity.onKeyDown/onKeyUp, whose defaults run onBackPressed; HOME goes straight to the launcher and cannot be intercepted, as on Android. No ACTION_MULTIPLE, meta state or characters. Which physical buttons exist is board.toml’s business — ask KeyCharacterMap.deviceHasKey.
KeyCharacterMapPartialdeviceHasKey(int) / deviceHasKeys(int[]): the buttons board.toml maps to key codes, plus BACK and HOME where the board synthesises them (the on-screen control on a button-less touch board; HOME from holding BACK). No character map, load, getKeyboardType or key-to-character lookups: no board has text keys.
MenuPartialadd (four overloads), removeItem, clear, findItem, size, getItem, hasVisibleItems, NONE, FIRST; items keep insertion order within their order. The Activity’s options menu only (onCreateOptionsMenu), presented as a list. No icons, sub-menus, groups beyond the id, shortcuts, checkable items or MenuInflater.
MenuItemPartialgetItemId, getGroupId, getOrder, getTitle / setTitle (text or resource), setEnabled / isEnabled, setVisible / isVisible, setOnMenuItemClickListener. No icon, shortcut, checkable state, action view or sub-menu.
ViewConfigurationPartialgetLongPressTimeout, getKeyRepeatTimeout, getKeyRepeatDelay (static, fixed per build). No touch slop, fling velocities or get(Context).
LayoutInflaterPartialfrom, inflate(int, ViewGroup), inflate(int, ViewGroup, boolean), and Activity.setContentView(int) / getLayoutInflater(). Layouts are compiled to binary at build time over a fixed attribute set. <include>, style= and ?attr/ are resolved at build time. A view class of the app’s own is made by a LayoutInflater.Factory (setFactory / getFactory; every Activity is one, through onCreateView(String, Context, AttributeSet)), since there is no reflection; its AttributeSet is always empty. No <merge>, Factory2 or custom attributes. See resources.
AsyncLayoutInflater (picodroid.view)Partialandroidx’s inflate(int, ViewGroup, OnInflateFinishedListener). Inflates on the main thread in slices of a few milliseconds per tick (Android: on a worker thread) and calls back on a tick of its own; retries after an OutOfMemoryError. A callback that no longer wants the view should close() it. Needed only for a layout that overruns the 250 ms build budget; a plain inflate is the shape.

android.widget

APIStatusNotes / alternative
TextView, Button, LinearLayout, FrameLayout, ScrollView, ImageView, Switch, CheckBox, ToggleButton, RadioButton/RadioGroup, ProgressBar, SeekBar, Toast, Spinner, NumberPicker, DatePicker, TimePicker, EditText, ListViewPartial–FullCore widgets present, each with the (Context) constructor and a no-argument one. Text setters take String. See specific divergences below.
LinearLayoutPartialsetOrientation, setGravity (an axis the gravity does not name keeps centring; FILL places at the start), LayoutParams with weight and margins (space kept clear around the child). LayoutParams.gravity is stored, not applied. Adds setSpacing(int), which has no Android counterpart (a margin on the children is the Android spelling). No setWeightSum, dividers or baseline alignment.
CompoundButtonPartialisChecked, setChecked, setOnCheckedChangeListener. toggle() is on Switch and ToggleButton only, not on CheckBox or RadioButton. setChecked does not fire the listener (Android’s does).
RadioGroupPartialcheck, clearCheck, getCheckedRadioButtonId, OnCheckedChangeListener; buttons without an id are given one.
NumberPickerPartialsetMinValue / setMaxValue / setValue and getters, setOnValueChangedListener. Drawn as a focusable box that the keys step, not a scroll wheel. Adds setStep(int), which has no Android counterpart. No setDisplayedValues, setWrapSelectorWheel, setFormatter.
DatePickerPartialA calendar (lv_calendar) with Android’s method names: updateDate(year, month, day) and init(year, month, day, listener) (month 0-based, as Android’s), getYear / getMonth / getDayOfMonth, setOnDateChangedListener. No min/max date, no spinner mode.
TimePickerPartialRollers. getHour / getMinute (hour always 0..23), setIs24HourView / is24HourView, setOnTimeChangedListener; setTime(hour, minute) in place of setHour / setMinute.
SeekBarPartialsetMax, setProgress, getProgress, OnSeekBarChangeListener (the two tracking callbacks have default bodies, so a lambda works; fromUser is false for a programmatic change). A class of its own, not a ProgressBar subclass: no setMin, tints or getMax.
ToastPartialmakeText(Context, String, int), show, cancel, setDuration / getDuration. No setGravity, setView, or the resource-id makeText.
Snackbar (Material Components, in picodroid.widget)Partialmake(View, String, int), setAction(String, View.OnClickListener), show, dismiss, the three LENGTH_* constants. Always at the bottom of the screen; the action’s onClick receives a null view. No callbacks, setActionTextColor, setAnchorView.
SwipeRefreshLayout (androidx.swiperefreshlayout.widget, in picodroid.widget)PartialsetOnRefreshListener, setRefreshing. A pull-down anywhere over the child refreshes (no check that the child is scrolled to its top). No isRefreshing, colour schemes or progress offsets.
TextView.setTextSize / getTextSize / getLineHeightPartialBoth setTextSize overloads and every TypedValue.COMPLEX_UNIT_*. The faces are bitmaps, so a size snaps to the nearest compiled face (14, 20, 28, 64 px on the RP2350 boards; 14 alone on the RP2040 testbench) — getLineHeight() reports the face in use, getTextSize() the size set. One density: px = dp = sp. No setTypeface, setTextScaleX or autosizing.
TextView.setGravity / getGravityPartialThe horizontal field (LEFT/START, CENTER_HORIZONTAL, RIGHT/END) aligns the text inside a view wider than it, as on Android; the vertical field is stored and returned but not drawn — an LVGL label is its text’s height — so centre a label in a taller row through the parent LinearLayout’s gravity. android:gravity on TextView in layouts.
LinearLayout / FrameLayout / RadioGroup defaultsFullFlat like Android’s: no background, border, corner radius or padding until set. (Until 2026-09-24 they carried the LVGL theme’s card border, fill and padding.) setBackgroundColor honours the colour’s alpha, so Color.TRANSPARENT clears a background.
TextView.setSingleLine / setEllipsize / setMaxLinesPartialOver LVGL’s label long modes: the ellipsis is ASCII ..., START/MIDDLE render like END, MARQUEE scrolls circularly whether or not the view is selected, and a single-line / max-lines view is at most that many lines tall — a taller explicit height shrinks to the limit.
ProgressBarPartialsetMax / setMin, setProgress(int, boolean animate), incrementProgressBy and the progress, progress-background and indeterminate tint lists are present. A ColorStateList is one colour (no state sets); setProgressBackgroundTintList and View.setBackgroundColor colour the same LVGL part; no setSecondaryProgress, setProgressDrawable, setInterpolator or tint mode. indeterminate() is creation-time only — there is no setIndeterminate(boolean) (LVGL can’t morph bar↔spinner).
CircularProgressIndicator (Material Components, in picodroid.widget)PartialDeterminate only; one indicator colour (getIndicatorColor() returns int); no indicatorInset. Adds setStartAngle / setSweepAngle (Canvas.drawArc convention), which have no Material counterpart.
ViewPager2 (androidx.viewpager2.widget, in picodroid.widget)PartialsetAdapter(FragmentStateAdapter), setCurrentItem(item[, smoothScroll]), getCurrentItem, registerOnPageChangeCallback (onPageSelected, onPageScrollStateChanged, onPageScrolled), setUserInputEnabled, setOrientation / getOrientation, getScrollState, <ViewPager2> in XML. One page alive at a time: the outgoing page’s fragment is removed (state kept) and its widgets freed before the incoming page’s are made. No scroller: smoothScroll is a fade of the incoming page, the state goes SETTLING → IDLE (never DRAGGING), onPageScrolled fires once per turn with a zero offset; setOffscreenPageLimit is accepted and logged. A swipe over the page turns it, one that starts on a clickable child included. saveState() / restoreState(Bundle) are explicit (no view-state hierarchy). No page transformers, fake drags, item decorations, RecyclerView.Adapter.
FragmentStateAdapter (androidx.viewpager2.adapter, in picodroid.widget)PartialcreateFragment, getItemCount, getItemId, containsItem, notifyDataSetChanged; pages tagged "f" + itemId (Android’s spelling, and as there an implementation detail: let a page observe its data rather than look it up by tag). Constructors take the Activity or the FragmentManager (no Lifecycle, no (Fragment) host). Page states are Bundles, saved through ViewPager2.saveState(), not with the Activity’s other fragments.
AdapterView / Adapter / BaseAdapter / ArrayAdapterPartialListView and Spinner extend AdapterView<Adapter>. Adapter is getCount / getItem / getItemId / getView(int, View, ViewGroup): ListView builds every row through getView, keeps the rows as its children in position order and offers each back as convertView on notifyDataSetChanged() (re-bind in place; setAdapter starts fresh). No off-screen recycling — every row is a live widget, so there is no scrap pool and no RecyclerView. Spinner renders toString() and never calls getView. ArrayAdapter takes a T[] or List<T>, Android’s (Context, int resource[, int textViewResourceId], …) layout-resource constructors, add / addAll / insert / remove / clear / getPosition. ListView.addItem(String) and Spinner.setItems(String) are additions with no Android counterpart.
AdapterView.OnItemClickListener / OnItemSelectedListenerPartialFull 4-arg onItemClick / onItemSelected(parent, view, position, id); view is always null (LVGL rows have no Java wrapper). onNothingSelected is declared and never called.
ImageViewPartialsetImageResource(R.drawable.*), setImageDrawable, and setImageSource(String) for a bundled asset name (see assets), which has no Android counterpart. Scale types are int constants, not the ScaleType enum: SCALE_FIT_CENTER, SCALE_CENTER_CROP, SCALE_FIT_XY, SCALE_CENTER, plus SCALE_TILE; no FIT_START. setTint(int) and setScale(int) (256 = 1.0×) in place of setColorFilter / setImageMatrix.
EditText + TextWatcherPartialSingle-line. getText() returns String. TextWatcher takes String (no CharSequence/Editable); only afterTextChanged fires in v1. setInputType with picodroid.text.InputType: the class picks the keyboard (number and phone open the digit pad), the password variations mask the field, the rest is accepted and ignored. OnEditorActionListener is a top-level picodroid.widget interface taking the EditText; actionId is always EditorInfo.IME_ACTION_DONE and event always null.

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.
TypedValue.COMPLEX_UNIT_* / applyDimensionPartialThe six units and the conversion; nothing else of TypedValue (no TYPE_*, no getDimension). One density, so PX, DIP and SP are identities; PT / IN / MM use the panel’s real pitch (xdpi), so they are ruler measurements.
DisplayMetricsPartialwidthPixels, heightPixels, density (1), densityDpi (160), scaledDensity (1), xdpi / ydpi (the panel’s physical pixels per inch, from the board file), setToDefaults(); from Resources.getDisplayMetrics() or Display.getMetrics(DisplayMetrics).

android.graphics

APIStatusNotes / alternative
ColorFullNamed constants + ARGB ints.
drawable.GradientDrawablePartialsetColor, setCornerRadius(int), setStroke(width, color), each returning the drawable for chaining (Android’s return void). A two-colour linear gradient through setGradient(start, end, orientation), which has no Android counterpart, with Orientation.TOP_BOTTOM / LEFT_RIGHT as int constants. No shapes other than the rectangle, per-corner radii, radial or sweep gradients.
drawable.Drawable / drawable.BitmapDrawablePartialWhat View.setBackground and ImageView.setImageDrawable accept. BitmapDrawable is the icon PackageManager.getApplicationIcon returns; apps do not build one. A res/drawable/*.xml <shape> used as an android:background arrives as a GradientDrawable. No draw(Canvas), bounds, state or Drawable.Callback.
View.onDraw(Canvas), View.invalidate() / postInvalidate()PartialA view made with new View(Context) (or a subclass) draws what its onDraw draws. onDraw runs on the main thread after invalidate(), not every frame; invalidate() on a framework widget does nothing. Every RP2350 board; not the RP2040 testbench (has_canvas = false, flash).
CanvasPartialdrawColor, drawRect, drawRoundRect, drawCircle, drawLine, drawArc, drawText, getWidth/getHeight. Recorded, not rasterised: about 60 calls per view. No Path, Bitmap, save/restore, clipping, transforms or Rect/RectF overloads; ovals and arcs are circular.
PaintPartialColour and alpha, Style, stroke width, Cap (SQUARE draws as BUTT), text size (snaps to a compiled face) and align, ascent, descent, measureText. No shaders, path effects or typefaces; always anti-aliased.
BitmapUnsupportedNo pixel buffers: a full-screen bitmap is 150 KB of RAM.

android.content

APIStatusNotes / alternative
IntentPartialExplicit (class-targeted) intents, broadcast intents (new Intent(String action), setAction / getAction, getParcelableExtra, the ACTION_TIME_TICK / ACTION_TIME_CHANGED / ACTION_TIMEZONE_CHANGED + EXTRA_TIMEZONE / ACTION_SCREEN_ON / ACTION_SCREEN_OFF / ACTION_BOOT_COMPLETED / ACTION_MAIN strings with Android’s values), new Intent(Context, Class) or picodroid’s new Intent(Class), + extras (int, long, boolean, String, Bundle; putExtras / getExtras), setClassName, and on a multi-app board setPackage, which launches another installed app (extras do not cross over). No implicit intents / IntentFilter resolution, no Parcelable / Serializable extras.
ContextPartialgetSystemService (SENSOR_SERVICE, NOTIFICATION_SERVICE, ALARM_SERVICE, STORAGE_STATS_SERVICE, CONNECTIVITY_SERVICE, WIFI_SERVICE), getPackageManager, getPackageName, getResources, getString(int) / getString(int, Object...), getColor(int), getSharedPreferences, the private-file API (getFilesDir, getDataDir, openFileInput / openFileOutput, fileList, deleteFile), startService / stopService, bindService(Intent, ServiceConnection, int) with BIND_AUTO_CREATE / unbindService, getMainExecutor. ServiceConnection has Android’s callbacks (ComponentName, IBinder); os.Binder is the class a LocalBinder extends. startActivity on every Context, registerReceiver(BroadcastReceiver, IntentFilter) / the int flags form (RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED accepted, without effect) / unregisterReceiver / sendBroadcast(Intent) (asynchronous, in-app).
BroadcastReceiverPartialonReceive(Context, Intent) on the main thread for the system actions (Intent.ACTION_TIME_TICK every minute, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED, ACTION_SCREEN_ON / OFF, WifiManager’s and ConnectivityManager’s actions on a network board), an app’s own sendBroadcast, and PendingIntent.getBroadcast alarms. At most 8 registered per app; a leaked one is unregistered at the Activity’s destroy with Android’s warning. Manifest <receiver> for BOOT_COMPLETED / TIME_SET / TIMEZONE_CHANGED (delivered by a headless run of the app) and for explicit targets. No ordered or sticky broadcasts, goAsync, setResult*, peekService.
IntentFilterPartialActions only: IntentFilter() / (String) / (IntentFilter), addAction, hasAction, matchAction, countActions, getAction(int); at most 8 actions. No data, type, category or priority matching.
pm.PackageManagerPartialhasSystemFeature (FEATURE_WIFI, FEATURE_ETHERNET, FEATURE_TOUCHSCREEN), getInstalledPackages, getPackageInfo, getLaunchIntentForPackage, getApplicationLabel, getApplicationIcon, getPackageInstaller; flags arguments are ignored. No permissions, resolveActivity or queryIntentActivities.
res.ResourcesPartialgetString(int) / getString(int, Object...), getText, getColor, getDimension, getDimensionPixelSize, getDimensionPixelOffset, getInteger, getBoolean, getDisplayMetrics, getConfiguration, Resources.NotFoundException. No getDrawable, arrays, plurals or getIdentifier. See resources.
res.ConfigurationPartialscreenWidthDp / screenHeightDp / smallestScreenWidthDp (the app’s window — its <supports-screens> design size, else the panel — in dp, which is a pixel), densityDpi (always 160), orientation, touchscreen (FINGER / NOTOUCH), navigation (DPAD where the board has the four keys, else NONAV), keyboard (always NOKEYS), isLayoutSizeAtLeast, setTo, setToDefaults, toString. A snapshot from Resources.getConfiguration(): nothing changes while an app runs, so no onConfigurationChanged. No locale, uiMode, fontScale, screenLayout, colorMode or mcc/mnc.
res.ColorStateListPartialOne colour: valueOf, getDefaultColor, withAlpha, isStateful (false), getColorForState. No state sets.
SharedPreferences / EditorPartialBacked by LittleFS. getString/getInt/getLong/getFloat/getBoolean, getAll, contains, and the matching put*, remove, clear, commit, apply. Context.getSharedPreferences returns one instance per file, safe from any thread; apply() updates memory at once and writes the file on a background thread (finished when the Activity stops), commit() writes before returning. A failed write leaves the new values in memory, as on Android. SharedPreferences.open(name) is picodroid’s own: a separate instance read from the file. No getStringSet/putStringSet, no OnSharedPreferenceChangeListener.
DialogInterfacePartialdismiss, cancel, OnClickListener, OnMultiChoiceClickListener, the three button constants. OnDismissListener is declared, but nothing takes one yet; no OnCancelListener or OnShowListener.

android.net

APIStatusNotes / alternative
ConnectivityManagerPartialgetSystemService(CONNECTIVITY_SERVICE); registerDefaultNetworkCallback, registerNetworkCallback(NetworkRequest, cb), requestNetwork, unregisterNetworkCallback, getActiveNetwork, getNetworkCapabilities, the TYPE_* constants. No getActiveNetworkInfo (NetworkInfo’s methods are static), getLinkProperties, bindProcessToNetwork, isActiveNetworkMetered.
ConnectivityManager.NetworkCallbackPartialonAvailable, onCapabilitiesChanged, onLost delivered on the main thread by the Activity event loop; onLosing / onUnavailable declared, never called. No onLinkPropertiesChanged, onBlockedStatusChanged.
NetworkPartialOne per link-up; getNetworkHandle, openConnection(URL), equals/hashCode/toString. No getSocketFactory, bindSocket, getAllByName.
NetworkCapabilitiesPartialhasTransport, hasCapability; the TRANSPORT_* / NET_CAPABILITY_* constants a board can show. VALIDATED means up with an address (no internet probe). No Builder, getLinkDownstreamBandwidthKbps, getTransportInfo.
NetworkRequest / BuilderPartialaddTransportType, removeTransportType, addCapability, removeCapability, clearCapabilities, build; hasTransport, hasCapability. No setNetworkSpecifier.
NetworkInfoPartialStatic isConnected(), getIpAddress() (packed int), getType(); no instances.
SntpClientFullrequestTime(String host, int timeoutMs), getNtpTime, getNtpTimeReference, getRoundTripTime. Public here (hidden API on Android). Apps need not use it: the platform’s time service anchors the clock from pool.ntp.org once the link is up and every six hours after (Settings.Global.AUTO_TIME turns it off).
wifi.WifiManagerPartialgetSystemService(WIFI_SERVICE); startScan, getScanResults, registerScanResultsCallback / unregisterScanResultsCallback, getConnectionInfo, getConfiguredNetworks, addNetwork / updateNetwork / enableNetwork / removeNetwork, disconnect / reconnect / reassociate, isWifiEnabled, getWifiState, calculateSignalLevel, compareSignalLevel. One saved network (its networkId is 0); setWifiEnabled is accepted and ignored. SUPPLICANT_STATE_CHANGED_ACTION (+ EXTRA_NEW_STATE, EXTRA_SUPPLICANT_ERROR), NETWORK_STATE_CHANGED_ACTION and SCAN_RESULTS_AVAILABLE_ACTION as broadcasts. Also getLastError(), which has no Android counterpart: the same verdict, read rather than heard. No WifiNetworkSuggestion / WifiNetworkSpecifier, no locks.
wifi.ScanResult / wifi.WifiInfo / wifi.WifiConfiguration / wifi.SupplicantStatePartialScanResult’s public fields (SSID, BSSID, level, frequency, capabilities, timestamp always 0). WifiInfo: getSSID, getBSSID (always 02:00:00:00:00:00), getRssi, getIpAddress, getNetworkId, getSupplicantState. WifiConfiguration: SSID, BSSID, preSharedKey, networkId, status, hiddenSSID; no allowedKeyManagement. SupplicantState has DISCONNECTED, ASSOCIATING, COMPLETED only.

java.net and javax.net.ssl

The networking classes live in picodroid.net; the exceptions are the real java.net and javax.net.ssl ones. See Networking.

APIStatusNotes / alternative
URL (picodroid.net)Partialhttp and https; getProtocol, getHost, getPort (the scheme’s default when omitted), getPath (includes the query string), openConnection. No getQuery, getFile, openStream, URI.
HttpURLConnection (picodroid.net)PartialGET / POST / PUT; setConnectTimeout / setReadTimeout, setRequestProperty / addRequestProperty / getRequestProperty (16 headers at most), setDoOutput, setFixedLengthStreamingMode (required for a body; no chunked upload), getResponseCode, getResponseMessage, getHeaderField / getHeaderFieldKey, getContentLength, getInputStream / getErrorStream / getOutputStream, disconnect, the HTTP_* constants. The streams are HttpInputStream / HttpOutputStream, which extend java.io.InputStream / OutputStream, so new BufferedReader(new InputStreamReader(conn.getInputStream())) works as written. Connection: close always; no redirects followed for you, caching or cookies. Adds AutoCloseable.
HttpsURLConnection (javax.net.ssl, in picodroid.net.ssl)PartialTLS 1.3 against a compiled-in root store, on boards with has_tls = true; getCipherSuite. No setSSLSocketFactory, setHostnameVerifier, getServerCertificates.
Socket / ServerSocket (picodroid.net)RenamedNot java.net’s method surface: connect(int addr, int port) takes a packed IPv4 address, the receive timeout is setTimeout(int), and raw I/O is send(byte[], int, int) / recv(byte[], int, int). getInputStream() / getOutputStream() return java.io streams over them (each call a new wrapper on the same socket; close on either closes the socket), so PrintWriter / BufferedReader over a socket work as on Android. ServerSocket(int port), accept, setSoTimeout, close.
DatagramSocket / DatagramPacket (picodroid.net)Partialsend, receive (whole datagrams, up to 65,507 bytes), setSoTimeout, setBroadcast / getBroadcast (recorded, not enforced), close. DatagramPacket has the window API: the offset constructors, getOffset, setData, setLength, setAddress, setPort; getAddress() returns an InetAddress. No connect, multicast or getLocalPort.
InetAddress (picodroid.net)PartialIPv4 only. getByName, getByAddress(byte[]), getAddress(), getHostAddress; also picodroid’s getByAddress(int a, int b, int c, int d), new InetAddress(int) and getRawAddress() for the packed form the sockets take. No getAllByName, getHostName, isReachable.
java.net / javax.net.ssl exceptionsFullConnectException, BindException, NoRouteToHostException, SocketException, SocketTimeoutException, UnknownHostException, ProtocolException, SSLException, SSLHandshakeException, SSLPeerUnverifiedException, with Java’s hierarchy.

android.os

APIStatusNotes / alternative
SystemClockPartialelapsedRealtime, elapsedRealtimeNanos, sleep(int), setCurrentTimeMillis (always succeeds). No uptimeMillis or currentThreadTimeMillis.
Handler / Looper / MessageUnsupportedUse Executors.mainExecutor().execute(Runnable) for “post to UI”; for delayed or repeating work on the main thread, Executors.mainScheduledExecutor() (schedule, scheduleAtFixedRate). Context.getMainExecutor() and Activity.runOnUiThread(Runnable) exist. There is no postDelayed.
PowerManagerPartialisInteractive() (whether the display is on), from getSystemService(POWER_SERVICE). No wake locks: a dark panel does not stop the CPU, the app keeps running (docs/designs/app-portability-2026-10.md K5). Related: View.setKeepScreenOn / getKeepScreenOn, Activity.setTurnScreenOn, KeyEvent.KEYCODE_POWER / SLEEP / WAKEUP.
BundlePartialIntent extras, saved instance state and fragment arguments. put / get for boolean, int, long, float, double, String, int[], byte[], String[] and nested Bundle, with the default-taking getters; get, containsKey, remove, clear, size, isEmpty, keySet, putAll, the copy constructor. No Parcelable / Serializable, no short / char / byte scalars or ArrayList values.

android.provider

APIStatusNotes / alternative
Settings.SystemPartialgetInt / putInt for one setting, SCREEN_OFF_TIMEOUT (milliseconds, 0 never), stored at /system/display and read by the display’s idle timer. Any other name reads as its default and refuses a write. Context.getContentResolver() returns the handle they take; there are no content providers.
Settings.GlobalPartialgetInt / putInt for one setting, AUTO_TIME (1 by default: the platform time service anchors the clock from the network), stored at /system/time with the zone. No AUTO_TIME_ZONE (SNTP carries no zone).

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 cached pools, no CompletableFuture; scheduling is Executors.newSingleThreadScheduledExecutor() (its own thread, as in the JDK) or Executors.mainScheduledExecutor() (the main thread; picodroid’s own).
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.

org.json

APIStatusNotes / alternative
JSONObject / JSONArray / JSONException (picodroid.json)PartialAndroid’s org.json method surface for the two classes: constructors from text, Map, Collection and arrays; get/opt with Android’s coercions; put/putOpt/accumulate/append/remove; keys/names/keySet; toString/toString(indent); quote/numberToString/wrap; NULL. Documents live in a native node pool (2048 nodes, 16 KiB of strings, 32 levels) reclaimed with the GC; child wrappers share nodes but are not ==. Strict RFC 8259 parser (no lenient JSONTokener extras), no JSONTokener/JSONStringer, keySet() unordered. Only on boards with has_json = true in board.toml — every RP2350 board; not testbench_rp2040, where the API contract rejects the classes at build time. See JSON.

com.google.protobuf

APIStatusNotes / alternative
CodedInputStream / CodedOutputStream / MessageLite / WireFormat / InvalidProtocolBufferException (picodroid.protobuf)Partialprotobuf-javalite’s stream surface: readTag, every readXxx, skipField, pushLimit/popLimit, readString/readBytes (a byte[], no ByteString); every writeXxx/writeXxxNoTag and computeXxxSize. Varints, fixed-width values and skipping are native (micropb); strings, byte arrays, zigzag and limits are Java. Message classes come from protoc-gen-picodroid: mutable, setters instead of Builders, enums as int constants, arrays instead of Lists, proto3 only, no map/oneof/groups/extensions/reflection. Only on boards with has_protobuf = true in board.toml — every RP2350 board; not testbench_rp2040, where the API contract rejects the classes at build time. See Protocol Buffers.

android.media

APIStatusNotes / alternative
ToneGenerator (picodroid.media)PartialAndroid’s constant names, values and CEPT cadences for the DTMF, TONE_SUP_* and TONE_PROP_* families (0-28, plus TONE_SUP_CONFIRM and TONE_SUP_PIP); startTone(int), startTone(int, int), stopTone, release. The output is one piezo on a PWM pad, so tones are monophonic square waves: each plays the lowest component of Android’s multi-frequency tone, and DTMF therefore will not decode. No CDMA tone range, no intercept tones, no getAudioSessionId. Adds startToneSequence(int[], int[]), which has no Android counterpart. Segments advance on the UI frame tick. Needs an [audio] section in board.toml; without one the class is still present and every method is a safe no-op. See Audio.
AudioManager (picodroid.media)PartialThe STREAM_* constants only, with Android’s values, so a ToneGenerator reads as it does on Android. No mixer, no volume control, no playSoundEffect, no focus API.
MediaPlayer / AudioTrack / SoundPool / MediaRecorderNoneNo hardware path: a piezo buzzer has no DAC, no I2S and no amplifier behind it. There is no alternative for sampled audio; use ToneGenerator for tones.

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 skipped — clone() 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). A default method called on a lambda object runs the interface’s body, which reaches the lambda through its single abstract method (until 2026-09-13 it ran the lambda body instead).
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). HashMap/HashSet/ArrayList.contains/remove(Object) compare keys with your equals(Object) override when the class has one (identity otherwise; hashCode() is never consulted — the buffers are linear), and boxes by class and value (Integer(1) is not Short(1); Double keys follow Double.equals). The builtin collections’ own hashCode()/toString() are the identity forms (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 convert as Java does (Float.valueOf(2.5f).intValue() is 2, Integer.valueOf(300).byteValue() is 44), and valueOf shares one box per value across the JLS §5.1.7 range, so Integer a = 127, b = 127; a == b holds as on Android (new Integer(5) is always distinct). Character has isDigit/isLetter/toUpperCase/toLowerCase only, ASCII-only (strings are byte-backed).
Enum.valueOf(Class, String)FullServed by the interpreter, so every enum’s own valueOf(String) works: the constants are the enum class’s static fields of its own type, matched by name; an unknown name throws IllegalArgumentException("No enum constant …"), a null name NullPointerException. 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; list.sort(null) and Collections.sort(list) are natural ordering (compareTo).
OutOfMemoryErrorFullA failed allocation — new, an array, a growing collection or builder, a box — collects first and throws a catchable OutOfMemoryError only when that frees nothing; one error object is kept in reserve for the moment the heap cannot even hold the exception. clear() on a builtin collection releases its buffer, so it is the way to give memory back from a handler; the arena is small and fragments, so hold no more than a few hundred boxed entries per collection.
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.joinPartialjoin(delim, a, b, …) / join(delim, String[]) and join(delim, ArrayList); elements must be String or null (a StringBuilder element throws). Any other Iterable is rejected.
java.util.ObjectsFullequals, hashCode, hash, toString(o), toString(o, default), requireNonNull (both forms), isNull, nonNull. A real class, so equals/hashCode/toString reach your overrides.
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.
Math.floorDiv / floorMod / addExact / subtractExact / multiplyExact / toIntExactFullThe int and long forms, as bodied Java in the SDK; the *Exact ones throw ArithmeticException on overflow.
java.time — LocalDate, LocalTime, LocalDateTime, Instant, Duration, ZoneOffset, ZoneId, Month, DayOfWeek, Year, DateTimeFormatter, ChronoUnitPartialAn SDK port of the JDK classes with their JDK descriptors, so Instant.now().plus(Duration.ofMinutes(5)), LocalDate.of(…).plusMonths(1), ChronoUnit.DAYS.between(a, b), LocalDateTime.ofInstant(instant, zone), date.format(DateTimeFormatter.ofPattern("EEE d MMM")) and ISO parse/toString all behave as on Android. No tz database: every ZoneId is a fixed offset (ZoneId.of("UTC+05:30"), ZoneOffset.ofHours(-8); a region id throws DateTimeException), ZoneId.systemDefault() is UTC until TimeZone.setDefault(TimeZone.getTimeZone("GMT+05:30")), and there is no ZonedDateTime, OffsetDateTime, Period, TemporalField/ChronoField, TemporalAdjusters or locale-aware formatting (DateTimeFormatter.ofPattern covers y u M L d D E a H h m s S and '…' literals, with English month and day names; parse reads ISO-8601 only). now() reads System.currentTimeMillis(), which counts from boot until SystemClock.setCurrentTimeMillis. Not on testbench_rp2040 (framework_class_excludes; ~67 KB of class files). See java.time.
java.util.TimeZonePartialFixed-offset zones only: getTimeZone("GMT+05:30"), getTimeZone(ZoneId), getDefault/setDefault (null restores UTC), getID, getRawOffset, getOffset(long), toZoneId, useDaylightTime (false). An unknown id is GMT, as in the JDK.
String.replace(target, replacement)FullAn empty target interleaves the replacement between every char and at both ends, as in Java ("ab".replace("", "-") is -a-b-).
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.
java.io streams — InputStream, OutputStream, ByteArrayInputStream, ByteArrayOutputStream, Reader, Writer, InputStreamReader, BufferedReader, OutputStreamWriter, PrintWriter, CloseablePartialPure-Java SDK classes with the JDK surface (BufferedReader.readLine, PrintWriter.println, the base-class defaults an app’s own stream inherits). picodroid.io.FileInputStream/FileOutputStream, HttpInputStream/HttpOutputStream and Socket.getInputStream()/getOutputStream() are java.io streams. Strings are UTF-8 bytes: the readers decode UTF-8 and the writers encode it whatever charset name they were given; ByteArrayOutputStream.toString() keeps valid UTF-8 and maps each byte of an invalid sequence to ?. No FileReader/FileWriter (wrap the file streams), DataInputStream, BufferedInputStream, StringReader/StringWriter, java.nio. testbench_rp2040 drops the reader/writer classes (Reader, Writer, InputStreamReader, BufferedReader, OutputStreamWriter, PrintWriter); the byte streams stay. See java.io streams.

Cross-cutting divergences

  • Coordinates and sizes are int px. There is no float MotionEvent coordinate and one display density: dp and sp in a resource file are accepted and mean one logical pixel each, and getResources().getDisplayMetrics() reports density 1 at 160 dpi, with xdpi/ydpi the panel’s real pitch. Every board is at least 240×240 logical pixels; lay out against that floor and let match_parent, weights and margins take up the rest (docs/designs/app-portability-2026-10.md).
  • Resources have no configurations. res/values, res/layout and res/drawable compile into the PAPK with a generated R class (see resources), but there is one display, density and locale: qualified directories (values-night/, drawable-hdpi/) are a build error, and plurals and string arrays do not exist. Styles and the theme are resolved at build time: there is one theme per app (the style the manifest’s android:theme names, else the one called AppTheme), applied before the first onCreate when the manifest names it, else by Context.setTheme, which hands its colours to the widgets.
  • No Handler/Looper. The main loop is an executor-driven dispatcher; use Executors.mainExecutor(), and the scheduled executor for delayed work.
  • 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.