Chapter 43: Widgets, RemoteViews, and RemoteCompose¶
Android widgets are one of the platform's oldest and most architecturally distinctive
features. Unlike normal app UI, a widget's view hierarchy lives in a process that did
not create it -- typically the launcher. This cross-process rendering requirement
drives the entire design of RemoteViews, AppWidgetService, and the new
RemoteCompose subsystem. This chapter traces every layer, from the provider-side
AppWidgetProvider through the system service that brokers updates, through
RemoteViews' action serialization and inflation pipeline, and finally into the
RemoteCompose engine that may eventually replace the XML-layout approach altogether.
Key source paths we will reference throughout:
| Component | Path |
|---|---|
| AppWidget framework | frameworks/base/core/java/android/appwidget/ |
| AppWidget service | frameworks/base/services/appwidget/java/com/android/server/appwidget/ |
| RemoteViews | frameworks/base/core/java/android/widget/RemoteViews.java (10,874 lines) |
| RemoteViewsService | frameworks/base/core/java/android/widget/RemoteViewsService.java (321 lines) |
| RemoteViewsAdapter | frameworks/base/core/java/android/widget/RemoteViewsAdapter.java (1,305 lines) |
| RemoteCompose core | frameworks/base/core/java/com/android/internal/widget/remotecompose/core/ |
| RemoteCompose player | frameworks/base/core/java/com/android/internal/widget/remotecompose/player/ |
| Launcher3 widgets | packages/apps/Launcher3/src/com/android/launcher3/widget/ |
| SystemUI notifications | frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ |
43.1 AppWidget Framework¶
The client-side AppWidget framework is defined in
frameworks/base/core/java/android/appwidget/. It consists of 10 public Java files
that together define the contract between widget providers (apps that supply widget
content) and widget hosts (apps that display them).
43.1.1 Core Classes¶
The framework revolves around five central classes:
| Class | Role | Lines |
|---|---|---|
AppWidgetProvider |
BroadcastReceiver convenience wrapper for providers | 220 |
AppWidgetHost |
Host-side connection to AppWidgetService | 726 |
AppWidgetHostView |
The actual View container that renders RemoteViews | ~800 |
AppWidgetManager |
System-service client proxy (singleton) | ~1,500 |
AppWidgetProviderInfo |
Parcelable metadata describing a widget provider | 647 |
Plus newer additions:
| Class | Role | Lines |
|---|---|---|
AppWidgetEvent |
Engagement metrics for widget interactions | 401 |
PendingHostUpdate |
Queued update types during host reconnection | ~100 |
AppWidgetConfigActivityProxy |
Proxy for cross-profile config activities | ~100 |
AppWidgetManagerInternal |
System-server-internal API surface | ~50 |
43.1.2 AppWidgetProvider -- The Provider Entry Point¶
AppWidgetProvider (220 lines) extends BroadcastReceiver. It is a pure convenience
class: everything it does can be accomplished with a raw receiver. Its onReceive()
method dispatches to hook methods based on the received intent action:
// frameworks/base/core/java/android/appwidget/AppWidgetProvider.java
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) {
this.onReceive(context, new Intent(intent)
.setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED));
this.onReceive(context, new Intent(intent)
.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
} else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(
AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context,
AppWidgetManager.getInstance(context), appWidgetIds);
}
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
// ...extract single ID, call onDeleted()
} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
// ...extract options bundle, call onAppWidgetOptionsChanged()
} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
// ...call onRestored() then onUpdate()
}
}
The dispatch is straightforward, but notice the combined broadcast action
ACTION_APPWIDGET_ENABLE_AND_UPDATE. This is a newer optimization (controlled by
the COMBINED_BROADCAST_ENABLED DeviceConfig flag) that merges the enable and
initial update into a single broadcast, reducing widget startup latency.
The hook methods that subclasses override:
| Callback | When Called |
|---|---|
onUpdate() |
Periodic update timer fires, or widget first bound |
onEnabled() |
First instance of this provider placed on any host |
onDisabled() |
Last instance of this provider removed from all hosts |
onDeleted() |
A specific widget instance is removed |
onAppWidgetOptionsChanged() |
Widget resized or options changed |
onRestored() |
Widget instances restored from backup (followed by onUpdate) |
43.1.3 AppWidgetHost -- The Host Entry Point¶
AppWidgetHost (726 lines) is the host application's handle to the widget system.
Launcher3, for example, creates an AppWidgetHost with a fixed host ID of 1024.
The class has three critical architectural elements:
1. IPC Callback Stub:
// frameworks/base/core/java/android/appwidget/AppWidgetHost.java
static class Callbacks extends IAppWidgetHost.Stub {
private final WeakReference<Handler> mWeakHandler;
public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (isLocalBinder() && views != null) {
views = views.clone();
}
Handler handler = mWeakHandler.get();
if (handler == null) return;
Message msg = handler.obtainMessage(HANDLE_UPDATE,
appWidgetId, 0, views);
msg.sendToTarget();
}
// ... providerChanged, appWidgetRemoved, viewDataChanged, etc.
}
Note the isLocalBinder() check -- when the call originates in the same process
(system_server calling itself), the RemoteViews must be cloned to prevent shared
mutable state corruption.
2. Handler-based message dispatch:
Six message types flow from the callback stub through the Handler:
| Constant | Value | Meaning |
|---|---|---|
HANDLE_UPDATE |
1 | New RemoteViews available |
HANDLE_PROVIDER_CHANGED |
2 | Provider APK updated |
HANDLE_PROVIDERS_CHANGED |
3 | Available widget list changed |
HANDLE_VIEW_DATA_CHANGED |
4 | Collection data invalidated |
HANDLE_APP_WIDGET_REMOVED |
5 | Widget instance removed server-side |
HANDLE_VIEW_UPDATE_DEFERRED |
6 | Deferred update for lazy inflation |
3. Listener registry (SparseArray):
Each widget instance is identified by an integer appWidgetId. The listener
interface (AppWidgetHostListener) defines:
updateAppWidget(RemoteViews views)-- apply new contentonUpdateProviderInfo(AppWidgetProviderInfo)-- provider changedonViewDataChanged(int viewId)-- collection data changedupdateAppWidgetDeferred(String, int)-- lazy evaluation pathcollectWidgetEvent()-- engagement metrics collection
Service binding happens lazily on first construction:
private static void bindService(Context context) {
synchronized (sServiceLock) {
if (sServiceInitialized) return;
sServiceInitialized = true;
// Check for FEATURE_APP_WIDGETS
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
}
43.1.4 AppWidgetProviderInfo -- Widget Metadata¶
AppWidgetProviderInfo (647 lines) is a Parcelable that describes a widget's
capabilities. It is populated from the <appwidget-provider> XML metadata in the
provider's manifest.
Key fields:
| Field | Type | Description |
|---|---|---|
provider |
ComponentName |
Identity of the BroadcastReceiver |
minWidth/minHeight |
int |
Minimum dimensions in pixels |
minResizeWidth/Height |
int |
Minimum resize dimensions |
maxResizeWidth/Height |
int |
Maximum resize dimensions |
targetCellWidth/Height |
int |
Default size in grid cells |
updatePeriodMillis |
int |
Requested update interval (min 30 minutes) |
initialLayout |
int |
Resource ID of the initial layout |
initialKeyguardLayout |
int |
Layout for keyguard display |
configure |
ComponentName |
Configuration activity |
resizeMode |
int |
Bitmask: RESIZE_HORIZONTAL, RESIZE_VERTICAL |
widgetCategory |
int |
HOME_SCREEN, KEYGUARD, SEARCHBOX, NOT_KEYGUARD |
widgetFeatures |
int |
RECONFIGURABLE, HIDE_FROM_PICKER, CONFIGURATION_OPTIONAL |
generatedPreviewCategories |
int |
Categories with generated previews available |
43.1.5 AppWidgetEvent -- Engagement Metrics¶
AppWidgetEvent (401 lines) is a newer addition (Android 16, flagged under
FLAG_ENGAGEMENT_METRICS) that tracks user interactions with widgets:
// frameworks/base/core/java/android/appwidget/AppWidgetEvent.java
public final class AppWidgetEvent implements Parcelable {
public static final int MAX_NUM_ITEMS = 10;
private final int mAppWidgetId;
private final Duration mVisibleDuration;
private final Instant mStart;
private final Instant mEnd;
private final Rect mPosition;
private final int[] mClickedIds; // max 10 view IDs
private final int[] mScrolledIds; // max 10 view IDs
}
The Builder class tracks visibility windows:
public Builder startVisibility() {
long now = System.currentTimeMillis();
if (now < mStart) mStart = now;
mLastVisibilityChangeMillis = SystemClock.uptimeMillis();
return this;
}
public Builder endVisibility() {
long now = System.currentTimeMillis();
if (now > mEnd) mEnd = now;
mDurationMillis += SystemClock.uptimeMillis() - mLastVisibilityChangeMillis;
return this;
}
Events are serialized to PersistableBundle and reported to UsageStatsManager via
AppWidgetHost.reportAllWidgetEvents().
43.1.6 Widget Lifecycle¶
sequenceDiagram
participant User
participant Launcher as Launcher (AppWidgetHost)
participant AWS as AppWidgetService
participant Provider as Widget Provider App
User->>Launcher: Long-press, pick widget
Launcher->>AWS: allocateAppWidgetId()
AWS-->>Launcher: appWidgetId = 42
Launcher->>AWS: bindAppWidgetIdIfAllowed(42, provider)
AWS->>Provider: ACTION_APPWIDGET_ENABLED (first instance)
AWS->>Provider: ACTION_APPWIDGET_UPDATE [ids: 42]
Provider->>Provider: onUpdate() -> build RemoteViews
Provider->>AWS: updateAppWidget(42, remoteViews)
AWS->>Launcher: Callbacks.updateAppWidget(42, views)
Launcher->>Launcher: AppWidgetHostView.updateAppWidget(views)
Note over Launcher: RemoteViews.apply() inflates layout
loop Periodic Updates
AWS->>Provider: ACTION_APPWIDGET_UPDATE
Provider->>AWS: updateAppWidget(42, newViews)
AWS->>Launcher: Callbacks.updateAppWidget(42, newViews)
Launcher->>Launcher: RemoteViews.reapply()
end
User->>Launcher: Remove widget
Launcher->>AWS: deleteAppWidgetId(42)
AWS->>Provider: ACTION_APPWIDGET_DELETED [id: 42]
AWS->>Provider: ACTION_APPWIDGET_DISABLED (last instance)
43.1.7 AppWidgetHostView -- View Container¶
AppWidgetHostView (in frameworks/base/core/java/android/appwidget/AppWidgetHostView.java)
extends FrameLayout and implements AppWidgetHostListener. It is the actual
View placed in the host's layout hierarchy. Key responsibilities:
- Inflate or reapply RemoteViews when
updateAppWidget()is called - Handle error states -- display an error layout when the provider crashes
- Support engagement metrics via
AppWidgetEvent.Builder - Apply color resources for dynamic theming
- Manage keyguard vs. home screen layout switching based on
OPTION_APPWIDGET_HOST_CATEGORY
The updateAppWidget() method decides between apply() (first time) and
reapply() (subsequent updates with compatible layouts):
public void updateAppWidget(RemoteViews remoteViews) {
// ... null checks, error handling ...
if (mView != null) {
mView = remoteViews.reapply(/* ... */);
} else {
mView = remoteViews.apply(/* ... */);
}
}
43.2 AppWidgetService¶
The system-side AppWidgetService manages registration, binding, updates, and
security policy for all widgets across all users. The implementation lives in
frameworks/base/services/appwidget/java/com/android/server/appwidget/.
43.2.1 Service Architecture¶
The service is split into two classes:
| Class | Role |
|---|---|
AppWidgetService.java |
Lifecycle wrapper, registered as APPWIDGET_SERVICE |
AppWidgetServiceImpl.java |
The actual IPC implementation (~7,500 lines) |
AppWidgetServiceImpl extends IAppWidgetService.Stub and implements
WidgetBackupProvider and OnCrossProfileWidgetProvidersChangeListener.
43.2.2 Internal Data Model¶
The service maintains five core data structures, all protected by mLock:
// frameworks/base/services/appwidget/java/.../AppWidgetServiceImpl.java
private final ArrayList<Widget> mWidgets = new ArrayList<>();
private final ArrayList<Host> mHosts = new ArrayList<>();
private final ArrayList<Provider> mProviders = new ArrayList<>();
private final ArraySet<Pair<Integer, String>> mPackagesWithBindWidgetPermission
= new ArraySet<>();
private final SparseBooleanArray mLoadedUserIds = new SparseBooleanArray();
The relationships form a many-to-many graph:
erDiagram
PROVIDER ||--o{ WIDGET : "provides"
HOST ||--o{ WIDGET : "displays"
WIDGET {
int appWidgetId
RemoteViews views
Bundle options
}
PROVIDER {
ProviderId id
AppWidgetProviderInfo info
ArrayList widgets
PendingIntent broadcast
}
HOST {
int hostId
String packageName
IAppWidgetHost callbacks
}
43.2.3 Widget Allocation and Binding¶
When a host requests a new widget:
-
allocateAppWidgetId()assigns a monotonically increasing integer frommNextAppWidgetIds(per-user). Creates aWidgetobject and associates it with the callingHost. -
bindAppWidgetIdIfAllowed()orbindAppWidgetId()links the widget to a specificProvider. This triggers: - Security policy check (caller must have
BIND_APPWIDGETpermission or per-package allowlist inmPackagesWithBindWidgetPermission) - Cross-profile checks via
DevicePolicyManagerInternal - Scheduling the initial
ACTION_APPWIDGET_UPDATEbroadcast
43.2.4 Update Pipeline¶
When a provider calls AppWidgetManager.updateAppWidget():
flowchart TD
A[Provider calls updateAppWidget] --> B[AppWidgetManager.updateAppWidget]
B --> C[IAppWidgetService.updateAppWidgetIds]
C --> D[AppWidgetServiceImpl.updateAppWidgetIds]
D --> E{For each widget ID}
E --> F[Store RemoteViews in Widget.views]
F --> G[Increment UPDATE_COUNTER]
G --> H{Host currently listening?}
H -->|Yes| I[host.callbacks.updateAppWidget]
H -->|No| J[Queue as PendingHostUpdate]
I --> K[Handler dispatches to AppWidgetHostView]
J --> L[Delivered on next startListening]
The PendingHostUpdate mechanism handles the case when a host calls
stopListening() (e.g., activity goes to background). When startListening()
is called again, all queued updates are delivered in order:
public void startListening() {
List<PendingHostUpdate> updates;
updates = sService.startListening(mCallbacks, mContextOpPackageName,
mHostId, idsToUpdate).getList();
for (PendingHostUpdate update : updates) {
switch (update.type) {
case TYPE_VIEWS_UPDATE: updateAppWidgetView(/*...*/); break;
case TYPE_PROVIDER_CHANGED: onProviderChanged(/*...*/); break;
case TYPE_VIEW_DATA_CHANGED: viewDataChanged(/*...*/); break;
case TYPE_APP_WIDGET_REMOVED: dispatchOnAppWidgetRemoved(/*...*/); break;
}
}
}
43.2.5 Periodic Updates via AlarmManager¶
The service schedules periodic updates using AlarmManager with a minimum
period of 30 minutes (enforced by MIN_UPDATE_PERIOD):
// AppWidgetServiceImpl.java
private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000;
43.2.6 Bitmap Memory Limits¶
The service computes a maximum bitmap memory budget based on the display size:
private void computeMaximumWidgetBitmapMemory() {
Display display = mContext.getDisplayNoVerify();
Point size = new Point();
display.getRealSize(size);
// 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h
mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
This limit is enforced during RemoteViews serialization. On a 1080x2400 display,
the budget is approximately 15.5 MB.
43.2.7 State Persistence¶
Widget state is persisted to XML files at
/data/system/appwidgets.xml (per-user variant under /data/system_ce/<user>/):
private static final String STATE_FILENAME = "appwidgets.xml";
private static final int CURRENT_VERSION = 1;
The handleSaveMessage() method converts state to bytes under mLock, then
writes to disk outside the lock to minimize contention.
43.2.8 Generated Previews¶
Widget providers can set generated previews (snapshots of widget content for the
picker) via AppWidgetManager.setWidgetPreview(). These are stored in
/data/system_ce/<user>/appwidget/previews/ and are rate-limited:
private static final long DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS =
Duration.ofHours(1).toMillis();
private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
private static final int DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS = 50;
43.2.9 Engagement Metrics Reporting¶
The reportWidgetEvents() method accepts AppWidgetEvent[] from hosts and
forwards them to UsageStatsManager as USER_INTERACTION events. A
ReportWidgetEventsJob periodically triggers collection:
private static final long DEFAULT_WIDGET_EVENTS_REPORT_INTERVAL_MS =
Duration.ofHours(1).toMillis();
43.2.10 Security Policy and Limits¶
Hard limits prevent abuse:
| Limit | Value |
|---|---|
| Maximum hosts per package | 20 |
| Maximum widgets per host | 200 |
| Minimum update period | 30 minutes |
| Generated preview API calls per hour | 2 |
| Maximum providers with previews | 50 |
43.3 RemoteViews¶
RemoteViews is the central mechanism for cross-process UI in Android. Defined in
frameworks/base/core/java/android/widget/RemoteViews.java (10,874 lines), it
serializes a description of view modifications as Parcelable actions that can be
sent over Binder, then applied (inflated) in the receiving process.
43.3.1 Architecture Overview¶
flowchart LR
subgraph PP["Provider Process"]
A[Build RemoteViews] --> B[Add Actions]
B --> C[Parcel via Binder]
end
subgraph system_server
C --> D[Store in Widget.views]
end
subgraph HP["Host Process"]
D --> E[Unparcel RemoteViews]
E --> F{First time?}
F -->|Yes| G["apply() -> inflate layout + run actions"]
F -->|No| H["reapply() -> run actions on existing views"]
G --> I[Live View Hierarchy]
H --> I
end
43.3.2 Supported Views¶
RemoteViews restricts which View classes can be used. Views must be annotated
with @RemoteView:
Layouts (ViewGroups):
AdapterViewFlipperFrameLayoutGridLayoutGridViewLinearLayoutListViewRelativeLayoutStackViewViewFlipper
Widgets (Leaf Views):
AnalogClock,Button,Chronometer,ImageButton,ImageViewProgressBar,TextClock,TextView
API 31+ additions:
CheckBox,RadioButton,RadioGroup,Switch
The filter is enforced at inflation time:
// RemoteViews.java
private static final LayoutInflater.Filter INFLATER_FILTER =
(clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
43.3.3 Action System¶
Every mutation to a RemoteViews object creates an Action that is appended to
an internal ArrayList<Action>:
Each Action subclass has a unique tag for parceling. Here are the defined tags:
| Tag Constant | Value | Purpose |
|---|---|---|
SET_ON_CLICK_RESPONSE_TAG |
1 | Click handlers |
REFLECTION_ACTION_TAG |
2 | Generic setter via reflection |
SET_DRAWABLE_TINT_TAG |
3 | Drawable tinting |
VIEW_GROUP_ACTION_ADD_TAG |
4 | Add child RemoteViews |
VIEW_CONTENT_NAVIGATION_TAG |
5 | Content navigation |
SET_EMPTY_VIEW_ACTION_TAG |
6 | Set empty view for adapter |
VIEW_GROUP_ACTION_REMOVE_TAG |
7 | Remove child views |
SET_PENDING_INTENT_TEMPLATE_TAG |
8 | Collection click template |
SET_REMOTE_VIEW_ADAPTER_INTENT_TAG |
10 | Legacy collection adapter |
TEXT_VIEW_DRAWABLE_ACTION_TAG |
11 | Compound drawables |
BITMAP_REFLECTION_ACTION_TAG |
12 | Bitmap via reflection |
TEXT_VIEW_SIZE_ACTION_TAG |
13 | Text size |
VIEW_PADDING_ACTION_TAG |
14 | View padding |
SET_REMOTE_INPUTS_ACTION_TAG |
18 | Remote input for notifications |
LAYOUT_PARAM_ACTION_TAG |
19 | Layout parameters |
SET_RIPPLE_DRAWABLE_COLOR_TAG |
21 | Ripple effect color |
SET_INT_TAG_TAG |
22 | Integer tag on view |
REMOVE_FROM_PARENT_ACTION_TAG |
23 | Remove from parent |
RESOURCE_REFLECTION_ACTION_TAG |
24 | Resource-based reflection |
COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG |
25 | Dimension units |
SET_COMPOUND_BUTTON_CHECKED_TAG |
26 | CheckBox/Switch checked |
SET_RADIO_GROUP_CHECKED |
27 | RadioGroup selection |
SET_VIEW_OUTLINE_RADIUS_TAG |
28 | View outline radius |
SET_ON_CHECKED_CHANGE_RESPONSE_TAG |
29 | Checked change handler |
NIGHT_MODE_REFLECTION_ACTION_TAG |
30 | Dark mode variant |
SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG |
31 | Collection items |
ATTRIBUTE_REFLECTION_ACTION_TAG |
32 | Theme attribute |
SET_REMOTE_ADAPTER_TAG |
33 | New-style adapter |
SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG |
34 | Stylus handwriting |
SET_DRAW_INSTRUCTION_TAG |
35 | RemoteCompose instructions |
The Action base class defines the contract:
// RemoteViews.java
private abstract static class Action {
@IdRes int mViewId;
public abstract void apply(View root, ViewGroup rootParent,
ActionApplyParams params) throws ActionException;
public static final int MERGE_REPLACE = 0;
public static final int MERGE_APPEND = 1;
public static final int MERGE_IGNORE = 2;
public int mergeBehavior() { return MERGE_REPLACE; }
public abstract int getActionTag();
public String getUniqueKey() {
return (getActionTag() + "_" + mViewId);
}
// Async variant for background preparation
public Action initActionAsync(ViewTree root, ViewGroup rootParent,
ActionApplyParams params) {
return this;
}
}
43.3.4 Reflection Actions¶
The most general Action type is ReflectionAction, which invokes arbitrary
setter methods on views. When you call RemoteViews.setTextViewText(), it creates
a ReflectionAction that calls setText():
public void setTextViewText(@IdRes int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
The reflection lookup is cached in sMethods (an ArrayMap<MethodKey, MethodArgs>)
using MethodHandle for efficient invocation.
43.3.5 Layout Modes¶
RemoteViews supports three modes for responsive layouts:
| Mode | Constant | Description |
|---|---|---|
| Normal | MODE_NORMAL |
Single layout |
| Landscape/Portrait | MODE_HAS_LANDSCAPE_AND_PORTRAIT |
Two layouts by orientation |
| Sized | MODE_HAS_SIZED_REMOTEVIEWS |
Multiple layouts by size (up to 16) |
The sized mode (API 31+) allows providers to supply multiple RemoteViews at
different breakpoints:
public RemoteViews(Map<SizeF, RemoteViews> remoteViews) {
// Creates a sized RemoteViews with up to MAX_INIT_VIEW_COUNT (16) variants
}
43.3.6 Bitmap Cache¶
Bitmaps are deduplicated via BitmapCache:
Only the root RemoteViews in a hierarchy stores the cache. Nested RemoteViews
(from addView or landscape/portrait) reference the parent's cache via index.
The reduceImageSizes() method enforces maximum dimensions:
public void reduceImageSizes(int maxWidth, int maxHeight) {
ArrayList<Bitmap> cache = mBitmapCache.mBitmaps;
for (int i = 0; i < cache.size(); i++) {
Bitmap bitmap = cache.get(i);
cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight));
}
}
43.3.7 Apply vs. Reapply¶
The two core operations:
apply() -- Full inflation:
- Inflates the XML layout resource using
LayoutInflaterwith theINFLATER_FILTER - Walks the action list and applies each action to the inflated view tree
- Returns the inflated root
View
reapply() -- Incremental update:
- Reuses the existing view hierarchy
- Only applies the new actions, using merge semantics
- Actions with
MERGE_REPLACEoverwrite previous actions for the same view/type - Actions with
MERGE_APPENDaccumulate (e.g., adding children)
The merge logic in mergeRemoteViews():
public void mergeRemoteViews(RemoteViews newRv) {
HashMap<String, Action> map = new HashMap<>();
for (Action a : mActions) {
map.put(a.getUniqueKey(), a);
}
for (Action a : copy.mActions) {
String key = a.getUniqueKey();
int mergeBehavior = a.mergeBehavior();
if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
mActions.remove(map.get(key));
}
if (mergeBehavior == MERGE_REPLACE || mergeBehavior == MERGE_APPEND) {
mActions.add(a);
}
}
reconstructCaches();
}
43.3.8 Async Apply¶
For smoother UI, RemoteViews supports async inflation:
public CancellationSignal applyAsync(Context context, ViewGroup parent,
Executor executor, OnViewAppliedListener listener) {
// Inflate and apply on executor thread, callback on UI thread
}
Actions can override initActionAsync() to perform expensive work (like parsing
RemoteCompose documents) off the UI thread, then return a lightweight RuntimeAction
that runs on UI.
43.3.9 The Serialization Pipeline¶
flowchart TD
subgraph "Provider Process"
A["new RemoteViews(pkg, layoutId)"] --> B["setTextViewText(id, text)"]
B --> C["setOnClickPendingIntent(id, pi)"]
C --> D["setImageViewResource(id, resId)"]
D --> E["Parcel.writeParcelable(rv)"]
end
E -->|"Binder Transaction"| F
subgraph "system_server"
F["IAppWidgetService.updateAppWidget"] --> G["Store in Widget.views"]
end
G -->|"Binder Callback"| H
subgraph "Host Process (Launcher)"
H["Callbacks.updateAppWidget(id, views)"] --> I["Unparcel RemoteViews"]
I --> J["LayoutInflater.inflate(layoutId)"]
J --> K["forEach action: action.apply(root)"]
K --> L["View hierarchy updated"]
end
The parceling process writes:
- Mode byte (normal, landscape/portrait, or sized)
BitmapCache(only at root)RemoteCollectionCache(only at root)ApplicationInfo- Layout ID, View ID, light background layout ID
- Action count, then each action (tag + data)
- Apply flags, provider instance ID, hasDrawInstructions flag
43.3.10 RemoteViewsAdapter and RemoteViewsService¶
For collection widgets (ListView, GridView, StackView), a different mechanism is needed because the adapter data may be large and dynamic.
RemoteViewsService (321 lines) is an abstract Service that hosts
RemoteViewsFactory instances:
// frameworks/base/core/java/android/widget/RemoteViewsService.java
public interface RemoteViewsFactory {
void onCreate();
void onDataSetChanged(); // Heavy work allowed synchronously
void onDestroy();
int getCount();
RemoteViews getViewAt(int position);
RemoteViews getLoadingView();
int getViewTypeCount();
long getItemId(int position);
boolean hasStableIds();
}
RemoteViewsAdapter (1,305 lines) is the host-side adapter that connects
to the RemoteViewsService via IRemoteViewsFactory (AIDL). It manages:
- Service connection lifecycle
- View caching and recycling
- Loading views during async fetch
- Data change notifications via
notifyDataSetChanged()
The newer RemoteCollectionItems API (API 31+) allows inline collection data
without a service, reducing IPC overhead:
new RemoteViews.RemoteCollectionItems.Builder()
.addItem(id, remoteViewsForItem)
.setHasStableIds(true)
.build();
43.3.11 DrawInstructions -- Bridge to RemoteCompose¶
The SET_DRAW_INSTRUCTION_TAG (35) action connects RemoteViews to the new
RemoteCompose system:
// RemoteViews.java
@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
public RemoteViews(@NonNull final DrawInstructions drawInstructions) {
Objects.requireNonNull(drawInstructions);
mHasDrawInstructions = true;
addAction(new SetDrawInstructionAction(drawInstructions));
}
SetDrawInstructionAction applies the draw instructions to a RemoteComposePlayer:
private class SetDrawInstructionAction extends Action {
private final DrawInstructions mInstructions;
@Override
public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
applyAction(root, (player, doc) -> {
player.setDocument(doc);
applyActionListener(player, params);
return ACTION_NOOP;
});
}
@Override
public final Action initActionAsync(ViewTree root, ViewGroup rootParent,
ActionApplyParams params) {
return applyAction(root.mRoot, (player, doc) -> {
PreparedDocument preparedDoc = player.prepareDocument(doc);
return preparedDoc == null ? ACTION_NOOP
: new RunnableAction(() -> {
player.setPreparedDocument(preparedDoc);
applyActionListener(player, params);
});
});
}
}
The initActionAsync variant enables background document parsing, keeping the
UI thread responsive.
43.4 RemoteViews in Notifications¶
Notifications are the other major consumer of RemoteViews. While most notifications
use the platform's standard templates, custom notifications use RemoteViews directly.
43.4.1 Notification Template System¶
The Notification.Builder creates RemoteViews internally for standard templates:
Notification.Builder builder = new Notification.Builder(context, channelId)
.setContentTitle("Title")
.setContentText("Content");
Internally, Notification.Builder.createContentView() constructs a RemoteViews
from system layout resources and populates it with actions for title, text, icon, etc.
Custom notifications provide their own RemoteViews:
RemoteViews customView = new RemoteViews(getPackageName(),
R.layout.notification_custom);
customView.setTextViewText(R.id.title, "Custom Title");
builder.setCustomContentView(customView);
43.4.2 SystemUI's NotifRemoteViewsFactory¶
SystemUI uses NotifRemoteViewsFactory (in
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt)
to intercept view inflation within notification RemoteViews:
// NotifRemoteViewsFactory.kt
interface NotifRemoteViewsFactory {
fun instantiate(
row: ExpandableNotificationRow,
@InflationFlag layoutType: Int,
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View?
}
This factory pattern allows SystemUI to substitute custom implementations for
standard views within notifications. For example, RemoteComposePlayer views
can be injected when the notification uses DrawInstructions.
43.4.3 NotifRemoteViewCache¶
The NotifRemoteViewCacheImpl caches inflated notification views to avoid
re-inflation when a notification is rebound:
// NotifRemoteViewCacheImpl.java
public interface NotifRemoteViewCache {
boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
View getCachedView(NotificationEntry entry, @InflationFlag int flag);
void putCachedView(NotificationEntry entry, @InflationFlag int flag, View v);
void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
}
43.4.4 Security Considerations¶
Notification RemoteViews run in SystemUI's process with elevated privileges. Several security measures apply:
- View filtering: The
INFLATER_FILTERensures only@RemoteView-annotated classes can be instantiated - Nesting limits:
MAX_NESTED_VIEWS = 10prevents stack overflow - Bitmap limits:
reduceImageSizes()is called before display - URI grants:
visitUris()collects all referenced URIs so appropriate permission grants can be issued - PendingIntent validation: Actions with PendingIntents are validated against the calling package
43.5 RemoteCompose Architecture¶
RemoteCompose is a new rendering system within AOSP that provides a
programmatic alternative to XML layouts for cross-process rendering. Located in
frameworks/base/core/java/com/android/internal/widget/remotecompose/, it
comprises 265 Java files totaling over 60,000 lines of code.
43.5.1 Design Goals¶
RemoteCompose addresses fundamental limitations of the XML-based RemoteViews:
- Static layout: XML layouts cannot express animations, data-driven values, or conditional rendering without full re-serialization
- Limited view set: Only
@RemoteView-annotated views are available - Performance: Each update requires full Parcel serialization over Binder
- Expressiveness: Complex visual designs require many actions
RemoteCompose replaces this with a binary bytecode format (WireBuffer) that
encodes draw operations, layout instructions, variables, expressions, and
animations into a compact document that can be rendered by a player.
43.5.2 Architecture Split: core/ and player/¶
flowchart TB
subgraph "core/ (Platform-independent)"
A[CoreDocument] --> B[Operations]
A --> C[WireBuffer]
A --> D[RemoteComposeState]
A --> E[TimeVariables]
B --> F["operations/ (100+ classes)"]
B --> G["operations/layout/"]
B --> H["operations/layout/modifiers/"]
end
subgraph "player/ (Android-specific)"
I[RemoteComposePlayer] --> J[RemoteComposeDocument]
I --> K[RemoteComposeView]
K --> L[AndroidPaintContext]
K --> M[AndroidRemoteContext]
I --> N["accessibility/"]
I --> O["platform/"]
end
A -.->|"document instance"| J
L -.->|"implements"| P[PaintContext]
The core/ package is designed to be platform-independent -- it could theoretically
run on non-Android JVMs. The player/ package binds to Android APIs (Canvas,
Paint, View system, accessibility).
43.5.3 CoreDocument¶
CoreDocument (in core/CoreDocument.java) is the central data structure. It
contains:
// frameworks/base/.../remotecompose/core/CoreDocument.java
public class CoreDocument implements Serializable {
public static final int MAJOR_VERSION = 1;
public static final int MINOR_VERSION = 2;
public static final int PATCH_VERSION = 0;
public static final int DOCUMENT_API_LEVEL = 8;
ArrayList<Operation> mOperations = new ArrayList<>();
RootLayoutComponent mRootLayoutComponent = null;
RemoteComposeState mRemoteComposeState = new RemoteComposeState();
TimeVariables mTimeVariables = new TimeVariables();
Version mVersion;
String mContentDescription;
long mRequiredCapabilities = 0L;
int mWidth = 0;
int mHeight = 0;
int mContentScroll, mContentSizing, mContentMode;
int mContentAlignment = RootContentBehavior.ALIGNMENT_CENTER;
RemoteComposeBuffer mBuffer = new RemoteComposeBuffer();
// ... expression maps, clock, touch operations ...
}
The document lifecycle:
- Construction: Created on the provider side, operations are appended
- Serialization: Written to a
RemoteComposeBuffer(byte stream) - Transport: Embedded in
RemoteViewsasDrawInstructions - Deserialization:
initFromBuffer()reconstructs operations - Initialization:
initializeContext()loads resources, caches bitmaps - Painting:
paint()executes operations via aPaintContext
43.5.4 WireBuffer¶
WireBuffer (in core/WireBuffer.java) is the binary encoding layer:
// frameworks/base/.../remotecompose/core/WireBuffer.java
public class WireBuffer {
private static final int BUFFER_SIZE = 1024 * 1024; // 1 MB default
byte[] mBuffer;
int mIndex = 0;
int mSize = 0;
boolean[] mValidOperations = new boolean[256];
public void start(int type) {
if (!mValidOperations[type]) {
throw new RuntimeException(
"Operation " + type + " is not supported for this version");
}
mStartingIndex = mIndex;
writeByte(type);
}
}
The buffer supports:
- Primitive types:
writeByte,writeInt,writeFloat,writeLong - Strings via length-prefixed encoding
- Auto-resizing when capacity is exceeded
- Version-gated operations via
mValidOperationsarray
43.5.5 PaintContext¶
PaintContext (in core/PaintContext.java) is the abstract rendering interface:
// frameworks/base/.../remotecompose/core/PaintContext.java
public abstract class PaintContext {
public static final int TEXT_MEASURE_MONOSPACE_WIDTH = 0x01;
public static final int TEXT_MEASURE_FONT_HEIGHT = 0x02;
public static final int TEXT_MEASURE_SPACES = 0x04;
public static final int TEXT_COMPLEX = 0x08;
protected RemoteContext mContext;
public abstract void drawBitmap(int imageId,
int srcLeft, int srcTop, int srcRight, int srcBottom,
int dstLeft, int dstTop, int dstRight, int dstBottom, int cdId);
public abstract void scale(float scaleX, float scaleY);
public abstract void translate(float translateX, float translateY);
public abstract void drawArc(float left, float top, float right,
float bottom, float startAngle, float sweepAngle);
public abstract void drawSector(/*...*/);
public abstract void drawCircle(float x, float y, float radius);
public abstract void drawLine(float x1, float y1, float x2, float y2);
public abstract void drawRect(float l, float t, float r, float b);
public abstract void drawRoundRect(/*...*/);
public abstract void drawOval(/*...*/);
public abstract void drawText(/*...*/);
public abstract void drawTextOnPath(/*...*/);
public abstract void drawPath(/*...*/);
// ... matrix operations, clipping, paint state ...
}
This abstraction allows the core to express rendering without depending on
Android's Canvas API directly.
43.5.6 RemoteComposeState¶
RemoteComposeState (in core/RemoteComposeState.java) manages the runtime
state of a document:
// frameworks/base/.../remotecompose/core/RemoteComposeState.java
public class RemoteComposeState implements CollectionsAccess {
public static final int START_ID = 42;
public static final int BITMAP_TEXTURE_ID_OFFSET = 2000;
private final IntMap<Object> mIntDataMap = new IntMap<>();
private final HashMap<Object, Integer> mDataIntMap = new HashMap<>();
private final IntFloatMap mFloatMap = new IntFloatMap();
private final IntIntMap mIntegerMap = new IntIntMap();
private final IntIntMap mColorMap = new IntIntMap();
private final IntMap<DataMap> mDataMapMap = new IntMap<>();
private final IntMap<Object> mPathMap = new IntMap<>();
}
This state holds:
- Named data: Bitmaps, text strings, paths, shaders -- all indexed by integer ID
- Variables: Floats, integers, colors -- with override flags for host-side values
- Collections: Lists and maps for data-driven content
- Path data: Pre-computed path coordinates and winding rules
43.6 RemoteCompose Operations¶
The operations/ directory under core/ contains 100+ operation classes. Each
operation has a unique opcode defined in Operations.java.
43.6.1 Operation Registry¶
Operations.java defines all opcodes and registers their companion
(factory) objects. The opcodes span several categories:
Protocol operations:
| Opcode | Value | Class |
|---|---|---|
HEADER |
0 | Header |
THEME |
63 | Theme |
CLICK_AREA |
64 | ClickArea |
ROOT_CONTENT_BEHAVIOR |
65 | RootContentBehavior |
ROOT_CONTENT_DESCRIPTION |
103 | RootContentDescription |
ACCESSIBILITY_SEMANTICS |
250 | CoreSemantics |
Draw operations:
| Opcode | Value | Class |
|---|---|---|
DRAW_RECT |
42 | DrawRect |
DRAW_TEXT_RUN |
43 | DrawText |
DRAW_BITMAP |
44 | DrawBitmap |
DRAW_CIRCLE |
46 | DrawCircle |
DRAW_LINE |
47 | DrawLine |
DRAW_ROUND_RECT |
51 | DrawRoundRect |
DRAW_SECTOR |
52 | DrawSector |
DRAW_TEXT_ON_PATH |
53 | DrawTextOnPath |
DRAW_OVAL |
56 | DrawOval |
DRAW_ARC |
152 | DrawArc |
DRAW_PATH |
124 | DrawPath |
DRAW_TWEEN_PATH |
125 | DrawTweenPath |
DRAW_TEXT_ANCHOR |
133 | DrawTextAnchored |
DRAW_BITMAP_SCALED |
149 | DrawBitmapScaled |
DRAW_BITMAP_INT |
66 | DrawBitmapInt |
DRAW_CONTENT |
139 | DrawContent |
DRAW_TO_BITMAP |
190 | DrawToBitmap |
DRAW_BITMAP_TEXT_ANCHORED |
184 | DrawBitmapTextAnchored |
Data operations:
| Opcode | Value | Class |
|---|---|---|
DATA_TEXT |
102 | TextData |
DATA_BITMAP |
101 | BitmapData |
DATA_SHADER |
45 | ShaderData |
DATA_PATH |
123 | PathData |
DATA_FLOAT |
80 | FloatConstant |
DATA_INT |
140 | IntegerConstant |
DATA_LONG |
148 | LongConstant |
DATA_BOOLEAN |
143 | BooleanConstant |
DATA_FONT |
189 | FontData |
DATA_BITMAP_FONT |
167 | BitmapFontData |
Matrix operations:
| Opcode | Value | Class |
|---|---|---|
MATRIX_SCALE |
126 | MatrixScale |
MATRIX_TRANSLATE |
127 | MatrixTranslate |
MATRIX_SKEW |
128 | MatrixSkew |
MATRIX_ROTATE |
129 | MatrixRotate |
MATRIX_SAVE |
130 | MatrixSave |
MATRIX_RESTORE |
131 | MatrixRestore |
MATRIX_SET |
132 | MatrixConstant |
MATRIX_FROM_PATH |
181 | MatrixFromPath |
MATRIX_EXPRESSION |
187 | MatrixExpression |
MATRIX_VECTOR_MATH |
188 | MatrixVectorMath |
Clipping operations:
| Opcode | Value | Class |
|---|---|---|
CLIP_PATH |
38 | ClipPath |
CLIP_RECT |
39 | ClipRect |
Paint operations:
| Opcode | Value | Class |
|---|---|---|
PAINT_VALUES |
40 | PaintData |
43.6.2 Draw Operation Detail: DrawText¶
DrawText demonstrates the variable-binding pattern used throughout:
// frameworks/base/.../remotecompose/core/operations/DrawText.java
public class DrawText extends PaintOperation implements VariableSupport {
int mTextID;
float mX, mY; // Source coordinates (may be NaN for variables)
float mOutX, mOutY; // Resolved coordinates
boolean mRtl;
@Override
public void updateVariables(RemoteContext context) {
mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX;
mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY;
}
@Override
public void registerListening(RemoteContext context) {
if (Float.isNaN(mX)) context.listensTo(Utils.idFromNan(mX), this);
if (Float.isNaN(mY)) context.listensTo(Utils.idFromNan(mY), this);
}
}
The NaN-encoding trick is clever: coordinate values that are Float.NaN with
specific bit patterns encode variable IDs. Utils.idFromNan() extracts the ID
from the NaN payload. This allows the same serialization format to hold both
literal values and variable references.
43.6.3 Bitmap Operations¶
BitmapData (opcode 101) loads a bitmap into the document state:
public class BitmapData extends Operation {
int mImageId;
int mWidth, mHeight;
byte[] mBitmapData; // Compressed bitmap bytes
}
Multiple draw variants exist:
DrawBitmap: Source/destination rect mappingDrawBitmapInt: Integer coordinates for pixel-perfect renderingDrawBitmapScaled: Scaled rendering with automatic fittingDrawBitmapTextAnchored: Text rendered from bitmap font glyphs
43.6.4 Path Operations¶
Paths are first-class objects in RemoteCompose:
| Operation | Description |
|---|---|
PathData |
Raw path coordinate data |
PathCreate |
Construct path from primitives |
PathAppend |
Append segments to existing path |
PathCombine |
Boolean operations on paths |
PathTween |
Interpolate between two paths |
PathExpression |
Dynamic path from expressions |
DrawPath |
Render a stored path |
DrawTweenPath |
Render an animated path transition |
43.6.5 Expression and Animation Operations¶
RemoteCompose supports data-driven rendering through expression operations:
| Opcode | Value | Description |
|---|---|---|
ANIMATED_FLOAT |
81 | Animated float value |
COLOR_EXPRESSIONS |
134 | Color computed from expressions |
FLOAT_LIST |
147 | List of float values |
INTEGER_EXPRESSION |
144 | Integer computed from expression |
TEXT_FROM_FLOAT |
135 | Text generated from float value |
TEXT_MERGE |
136 | Concatenate text values |
TEXT_LOOKUP |
151 | Look up text by key |
TEXT_LOOKUP_INT |
153 | Look up text by integer key |
DATA_MAP_LOOKUP |
154 | Look up value in data map |
TOUCH_EXPRESSION |
157 | Expression driven by touch input |
43.6.6 Haptic Feedback¶
The HapticFeedback operation (opcode 177) triggers device haptics from the
document:
// frameworks/base/.../remotecompose/core/operations/HapticFeedback.java
public class HapticFeedback extends Operation {
private int mHapticFeedbackType;
@Override
public void write(WireBuffer buffer) {
apply(buffer, mHapticFeedbackType);
}
}
The player-side HapticSupport class translates feedback type constants to
Android HapticFeedbackConstants.
43.6.7 Particle System¶
RemoteCompose includes a particle system for animated effects:
| Opcode | Value | Description |
|---|---|---|
PARTICLE_DEFINE |
161 | Define particle emitter parameters |
PARTICLE_PROCESS |
162 | Process particle simulation step |
PARTICLE_LOOP |
163 | Loop particle rendering |
IMPULSE_START |
164 | Trigger impulse animation |
IMPULSE_PROCESS |
165 | Process impulse state |
43.6.8 Conditional and Control Flow¶
| Opcode | Value | Description |
|---|---|---|
CONDITIONAL_OPERATIONS |
178 | Execute operations based on condition |
FUNCTION_DEFINE |
168 | Define reusable function |
FUNCTION_CALL |
166 | Call defined function |
DEBUG_MESSAGE |
179 | Emit debug output |
WAKE_IN |
191 | Schedule a repaint after delay |
43.7 RemoteCompose Layout and State¶
43.7.1 Layout System¶
RemoteCompose includes a full layout system with opcodes in the 200+ range:
Layout containers:
| Opcode | Value | Class | Description |
|---|---|---|---|
LAYOUT_ROOT |
200 | RootLayoutComponent |
Document root |
LAYOUT_CONTENT |
201 | LayoutComponentContent |
Content placeholder |
LAYOUT_BOX |
202 | BoxLayout |
Overlay layout (like FrameLayout) |
LAYOUT_FIT_BOX |
176 | FitBoxLayout |
Scale-to-fit layout |
LAYOUT_ROW |
203 | RowLayout |
Horizontal layout |
LAYOUT_COLUMN |
204 | ColumnLayout |
Vertical layout |
LAYOUT_CANVAS |
205 | CanvasLayout |
Free-form canvas |
LAYOUT_CANVAS_CONTENT |
207 | CanvasContent |
Canvas child |
LAYOUT_TEXT |
208 | TextLayout |
Text component |
LAYOUT_STATE |
217 | StateLayout |
State-driven layout |
LAYOUT_IMAGE |
234 | ImageLayout |
Image component |
LAYOUT_COLLAPSIBLE_ROW |
230 | CollapsibleRowLayout |
Collapsible horizontal |
LAYOUT_COLLAPSIBLE_COLUMN |
233 | CollapsibleColumnLayout |
Collapsible vertical |
Structural operations:
| Opcode | Value | Description |
|---|---|---|
COMPONENT_START |
2 | Begin component definition |
CONTAINER_END |
214 | End container scope |
LOOP_START |
215 | Begin loop iteration |
The layout managers (in operations/layout/managers/) implement a measure-layout-draw
cycle similar to Android's View system but entirely within RemoteCompose.
43.7.2 Layout Managers¶
Each layout type has a corresponding manager class:
core/operations/layout/managers/
BoxLayout.java
CanvasLayout.java
ColumnLayout.java
CollapsibleColumnLayout.java
CollapsibleRowLayout.java
FitBoxLayout.java
ImageLayout.java
LayoutManager.java
RowLayout.java
StateLayout.java
TextLayout.java
LayoutManager is the base class. Each manager implements:
- Measurement: Calculate intrinsic and constrained sizes
- Layout: Position children within the allocated space
- Drawing: Delegate to paint operations
43.7.3 Modifier System¶
Modifiers adjust component behavior without changing the layout type. They are applied as a chain, similar to Jetpack Compose's modifier pattern:
Dimension modifiers:
| Opcode | Value | Class |
|---|---|---|
MODIFIER_WIDTH |
16 | WidthModifierOperation |
MODIFIER_HEIGHT |
67 | HeightModifierOperation |
MODIFIER_WIDTH_IN |
231 | WidthInModifierOperation |
MODIFIER_HEIGHT_IN |
232 | HeightInModifierOperation |
Visual modifiers:
| Opcode | Value | Class |
|---|---|---|
MODIFIER_BACKGROUND |
55 | BackgroundModifierOperation |
MODIFIER_BORDER |
107 | BorderModifierOperation |
MODIFIER_PADDING |
58 | PaddingModifierOperation |
MODIFIER_CLIP_RECT |
108 | ClipRectModifierOperation |
MODIFIER_ROUNDED_CLIP_RECT |
54 | RoundedClipRectModifierOperation |
MODIFIER_GRAPHICS_LAYER |
224 | GraphicsLayerModifierOperation |
MODIFIER_RIPPLE |
229 | RippleModifierOperation |
MODIFIER_MARQUEE |
228 | MarqueeModifierOperation |
Layout modifiers:
| Opcode | Value | Class |
|---|---|---|
MODIFIER_OFFSET |
221 | OffsetModifierOperation |
MODIFIER_ZINDEX |
223 | ZIndexModifierOperation |
MODIFIER_SCROLL |
226 | ScrollModifierOperation |
MODIFIER_VISIBILITY |
211 | ComponentVisibilityOperation |
MODIFIER_COLLAPSIBLE_PRIORITY |
235 | CollapsiblePriorityModifierOperation |
Interaction modifiers:
| Opcode | Value | Class |
|---|---|---|
MODIFIER_CLICK |
59 | ClickModifierOperation |
MODIFIER_TOUCH_DOWN |
219 | TouchDownModifierOperation |
MODIFIER_TOUCH_UP |
220 | TouchUpModifierOperation |
MODIFIER_TOUCH_CANCEL |
225 | TouchCancelModifierOperation |
MODIFIER_DRAW_CONTENT |
174 | DrawContentOperation |
Action modifiers:
| Opcode | Value | Class |
|---|---|---|
HOST_ACTION |
209 | HostActionOperation |
HOST_METADATA_ACTION |
216 | HostActionMetadataOperation |
HOST_NAMED_ACTION |
210 | HostNamedActionOperation |
RUN_ACTION |
236 | RunActionOperation |
VALUE_INTEGER_CHANGE_ACTION |
212 | ValueIntegerChangeActionOperation |
VALUE_STRING_CHANGE_ACTION |
213 | ValueStringChangeActionOperation |
VALUE_FLOAT_CHANGE_ACTION |
222 | ValueFloatChangeActionOperation |
The ComponentModifiers class aggregates modifiers into a chain:
43.7.4 State and Variables¶
VariableSupport interface:
// frameworks/base/.../remotecompose/core/VariableSupport.java
public interface VariableSupport {
void registerListening(RemoteContext context);
void updateVariables(RemoteContext context);
void markDirty();
}
Operations that depend on runtime values implement VariableSupport. They
register interest in specific variable IDs via context.listensTo(id, this).
When the variable changes, updateVariables() is called and the operation
recalculates its output values.
TimeVariables:
// frameworks/base/.../remotecompose/core/TimeVariables.java
public class TimeVariables {
public void updateTime(RemoteContext context, ZoneId zoneId,
LocalDateTime dateTime) {
context.loadFloat(RemoteContext.ID_CONTINUOUS_SEC, sec);
context.loadFloat(RemoteContext.ID_TIME_IN_SEC, currentSeconds);
context.loadFloat(RemoteContext.ID_TIME_IN_MIN, currentMinute);
context.loadFloat(RemoteContext.ID_TIME_IN_HR, hour);
context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month);
context.loadFloat(RemoteContext.ID_DAY_OF_MONTH, day_of_month);
context.loadFloat(RemoteContext.ID_WEEK_DAY, day_week);
context.loadFloat(RemoteContext.ID_DAY_OF_YEAR, day_of_year);
context.loadFloat(RemoteContext.ID_YEAR, year);
context.loadFloat(RemoteContext.ID_OFFSET_TO_UTC,
offset.getTotalSeconds());
context.loadInteger(RemoteContext.ID_EPOCH_SECOND, (int) epochSec);
context.loadFloat(RemoteContext.ID_API_LEVEL,
CoreDocument.getDocumentApiLevel() + CoreDocument.BUILD);
}
}
This enables clock-face widgets and time-dependent animations without any Binder round-trips -- the player locally updates time variables and repaints.
Named Variables:
The NamedVariable operation (opcode 137) associates a human-readable name
with a variable ID and type. Types include:
COLOR_TYPESTRING_TYPEFLOAT_TYPEINTEGER_TYPE
This allows hosts to discover and set document variables by name.
RemoteComposeState data maps:
The state uses efficient specialized maps:
private final IntFloatMap mFloatMap = new IntFloatMap();
private final IntIntMap mIntegerMap = new IntIntMap();
private final IntIntMap mColorMap = new IntIntMap();
private final IntMap<DataMap> mDataMapMap = new IntMap<>();
Override flags (mColorOverride[], mFloatOverride[], etc.) track which values
have been set by the host vs. the document itself.
43.7.5 Serialization¶
WireBuffer encoding:
Each operation writes itself to the WireBuffer:
buffer.start(opcode)-- writes the 1-byte opcode- Operation-specific data (primitives, strings, byte arrays)
- No explicit end marker -- the reader knows each operation's exact format
Serializable interface:
// frameworks/base/.../remotecompose/core/serialize/Serializable.java
public interface Serializable {
// serialize to a MapSerializer for JSON/debug output
}
SerializeTags:
// frameworks/base/.../remotecompose/core/serialize/SerializeTags.java
// Tag constants for map-based serialization format
MapSerializer:
// frameworks/base/.../remotecompose/core/serialize/MapSerializer.java
public interface MapSerializer {
MapSerializer addType(String type);
MapSerializer addFloatExpressionSrc(String key, float[] value);
MapSerializer addIntExpressionSrc(String key, int[] value, int mask);
MapSerializer addPath(String key, float[] path);
// ... other typed add methods
}
The MapSerializer provides a structured serialization format for debugging,
testing, and potential JSON export of documents.
43.7.6 Document Flow¶
flowchart TD
A[RemoteComposeBuffer] -->|"Provider builds"| B[Write operations to WireBuffer]
B --> C[Serialize to byte array]
C --> D[Embed in DrawInstructions]
D --> E[Transport via RemoteViews/Binder]
E --> F[RemoteComposeDocument.new]
F --> G[CoreDocument.initFromBuffer]
G --> H[Parse operations from WireBuffer]
H --> I[Build operation list]
I --> J[initializeContext loads resources]
J --> K[paint executes operations]
K --> L[PaintContext renders to Canvas]
43.8 RemoteCompose Player¶
The player/ directory provides the Android-specific rendering implementation.
43.8.1 RemoteComposePlayer¶
RemoteComposePlayer (in player/RemoteComposePlayer.java) extends FrameLayout
and is the primary widget for rendering RemoteCompose documents:
// frameworks/base/.../remotecompose/player/RemoteComposePlayer.java
public class RemoteComposePlayer extends FrameLayout
implements RemoteContextActions {
private RemoteComposeView mInner;
private StateUpdater mStateUpdater;
private final ThemeSupport mThemeSupport = new ThemeSupport();
private final SensorSupport mSensorsSupport = new SensorSupport();
private final HapticSupport mHapticSupport = new HapticSupport();
// Version compatibility check
private static final int MAX_SUPPORTED_MAJOR_VERSION = MAJOR_VERSION;
private static final int MAX_SUPPORTED_MINOR_VERSION = MINOR_VERSION;
// Theme constants
public static final int THEME_UNSPECIFIED = Theme.UNSPECIFIED;
public static final int THEME_LIGHT = Theme.LIGHT;
public static final int THEME_DARK = Theme.DARK;
}
Key capabilities:
- Document loading:
setDocument()orsetPreparedDocument()(for async) - Theme support: Light/dark theme switching
- Sensor integration: Accelerometer, gyroscope values as variables
- Haptic feedback: Translate document haptic requests to device vibrations
- Touch handling: Propagate touch events to document components
- Scroll support:
showOnScreen(),scrollByOffset(),scrollDirection() - Click handling:
performClick()routes to document click areas
43.8.2 RemoteComposeDocument¶
RemoteComposeDocument (in player/RemoteComposeDocument.java) is the public
API for loading documents:
// frameworks/base/.../remotecompose/player/RemoteComposeDocument.java
public class RemoteComposeDocument {
private CoreDocument mDocument;
public RemoteComposeDocument(byte[] inputStream) {
this(new ByteArrayInputStream(inputStream), new SystemClock());
}
public RemoteComposeDocument(InputStream inputStream, Clock clock) {
mDocument = new CoreDocument(clock);
RemoteComposeBuffer buffer = RemoteComposeBuffer.fromInputStream(inputStream);
mDocument.initFromBuffer(buffer);
}
public void paint(RemoteContext context, int theme) {
mDocument.paint(context, theme);
}
public int needsRepaint() {
return mDocument.needsRepaint(); // -1 = no, 0 = ASAP, >0 = delay ms
}
public boolean canBeDisplayed(int majorVersion, int minorVersion,
long capabilities) {
return mDocument.canBeDisplayed(majorVersion, minorVersion, capabilities);
}
}
43.8.3 PreparedDocument and Async Loading¶
For smooth UI, documents can be prepared on a background thread:
public class RemoteComposePlayer extends FrameLayout {
public PreparedDocument prepareDocument(RemoteComposeDocument doc) {
// Parse and initialize on background thread
// Returns PreparedDocument that can be set on UI thread
}
public void setPreparedDocument(PreparedDocument doc) {
// Apply pre-initialized document on UI thread (fast)
}
}
This maps to the SetDrawInstructionAction.initActionAsync() path in RemoteViews.
43.8.4 AndroidPaintContext¶
AndroidPaintContext (in player/platform/AndroidPaintContext.java) is the
concrete PaintContext implementation for Android:
// frameworks/base/.../player/platform/AndroidPaintContext.java
public class AndroidPaintContext extends PaintContext {
Paint mPaint = new Paint();
// Maps to android.graphics.Canvas operations:
// - drawBitmap -> canvas.drawBitmap()
// - drawRect -> canvas.drawRect()
// - drawText -> canvas.drawTextRun()
// - drawPath -> canvas.drawPath()
// - clipRect -> canvas.clipRect()
// - save/restore -> canvas.save()/restore()
// Supports:
// - LinearGradient, RadialGradient, SweepGradient, BitmapShader
// - RuntimeShader (AGSL/SkSL)
// - Custom fonts via FontFamily/FontVariationAxis
// - RenderEffect, BlendMode, PorterDuff
}
43.8.5 Platform Support Classes¶
The player/platform/ directory contains Android integration classes:
| Class | Role |
|---|---|
RemoteComposeView |
Custom View that draws the CoreDocument |
AndroidRemoteContext |
Android implementation of RemoteContext |
AndroidPaintContext |
Canvas-based PaintContext implementation |
ThemeSupport |
Resolves Android theme attributes to RemoteCompose values |
SensorSupport |
Feeds device sensor data into document variables |
HapticSupport |
Translates haptic feedback requests |
FloatsToPath |
Converts float arrays to android.graphics.Path |
ClickAreaView |
Transparent view overlays for click detection |
RemotePreparedDocument |
Async-prepared document holder |
AndroidPlatformServices |
Platform service resolution |
AndroidComputedTextLayout |
Text measurement using Android APIs |
SettingsRetriever |
System settings access |
43.8.6 Accessibility¶
The player/accessibility/ directory implements accessibility support:
| Class | Role |
|---|---|
RemoteComposeTouchHelper |
ExploreByTouchHelper implementation |
PlatformRemoteComposeTouchHelper |
Platform-specific touch accessibility |
CoreDocumentAccessibility |
Extract semantic tree from CoreDocument |
RemoteComposeDocumentAccessibility |
Public accessibility API |
RemoteComposeAccessibilityRegistrar |
Register with accessibility framework |
SemanticNodeApplier |
Apply semantics to accessibility nodes |
AndroidPlatformSemanticNodeApplier |
Android-specific node population |
The accessibility layer traverses the document's component tree and exposes
semantic information (content descriptions, click actions, scroll state) through
Android's AccessibilityNodeInfo framework.
43.8.7 State Management¶
The player/state/ directory contains:
| Class | Role |
|---|---|
StateUpdater |
Interface for external state injection |
StateUpdaterImpl |
Default implementation |
State updates allow the host to inject values (e.g., weather data, notification counts) into document variables without rebuilding the document.
43.8.8 Rendering Pipeline¶
sequenceDiagram
participant Host as Host (Launcher)
participant Player as RemoteComposePlayer
participant View as RemoteComposeView
participant Doc as CoreDocument
participant PC as AndroidPaintContext
Host->>Player: setDocument(remoteComposeDoc)
Player->>View: setDocument(coreDoc)
View->>Doc: initializeContext(remoteContext)
Doc->>Doc: Load bitmaps, fonts, paths
Note over View: onDraw() triggered
View->>Doc: paint(remoteContext, theme)
Doc->>Doc: updateTime(timeVariables)
Doc->>Doc: evaluateExpressions()
loop For each Operation
Doc->>PC: operation.paint(paintContext)
PC->>PC: canvas.draw*(...)
end
alt Has Layout Components
Doc->>Doc: measure(width, height)
Doc->>Doc: layout(0, 0, width, height)
Doc->>Doc: drawComponents(paintContext)
end
PC-->>View: Canvas rendered
View-->>Host: Display updated
alt needsRepaint() >= 0
View->>View: postInvalidateDelayed(delay)
end
43.9 Launcher3 Widget Integration¶
The Launcher3 app (in packages/apps/Launcher3/) is the primary widget host on
most Android devices. Its widget integration layer handles discovery, pinning,
resizing, and lifecycle management.
43.9.1 Key Widget Classes¶
| Class | Path | Role |
|---|---|---|
LauncherWidgetHolder |
widget/LauncherWidgetHolder.java |
AppWidgetHost wrapper for background execution |
LauncherAppWidgetHostView |
widget/LauncherAppWidgetHostView.java |
Custom host view with long-press, auto-advance |
WidgetManagerHelper |
widget/WidgetManagerHelper.java |
AppWidgetManager wrapper |
PendingAddWidgetInfo |
widget/PendingAddWidgetInfo.java |
Widget being added (not yet bound) |
PendingAppWidgetHostView |
widget/PendingAppWidgetHostView.java |
Placeholder during widget load |
WidgetHostViewLoader |
widget/WidgetHostViewLoader.java |
Async widget loading |
WidgetAddFlowHandler |
widget/WidgetAddFlowHandler.java |
Widget add/configure flow |
LauncherAppWidgetProviderInfo |
widget/LauncherAppWidgetProviderInfo.java |
Extended provider info |
DatabaseWidgetPreviewLoader |
widget/DatabaseWidgetPreviewLoader.java |
Preview image loading |
LocalColorExtractor |
widget/LocalColorExtractor.java |
Dynamic theme color extraction |
43.9.2 LauncherWidgetHolder¶
LauncherWidgetHolder wraps AppWidgetHost to run widget operations on a
background thread:
// packages/apps/Launcher3/src/com/android/launcher3/widget/LauncherWidgetHolder.java
public class LauncherWidgetHolder {
public static final int APPWIDGET_HOST_ID = 1024;
protected static final int FLAG_LISTENING = 1;
protected static final int FLAG_STATE_IS_NORMAL = 1 << 1;
protected static final int FLAG_ACTIVITY_STARTED = 1 << 2;
protected static final int FLAG_ACTIVITY_RESUMED = 1 << 3;
private static final int FLAGS_SHOULD_LISTEN =
FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED | FLAG_ACTIVITY_RESUMED;
protected final ListenableAppWidgetHost mWidgetHost;
protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
}
The flag system ensures listening only when the launcher is fully visible and in normal state (not in overview mode or being paused).
43.9.3 LauncherAppWidgetHostView¶
LauncherAppWidgetHostView extends the framework's AppWidgetHostView with
launcher-specific features:
// packages/apps/Launcher3/.../widget/LauncherAppWidgetHostView.java
public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
implements TouchCompleteListener, View.OnLongClickListener,
UpdateDeferrableView, Poppable {
private static final long ADVANCE_INTERVAL = 20000; // 20 seconds
private static final long ADVANCE_STAGGER = 250; // 250ms stagger
private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000;
private final CheckLongPressHelper mLongPressHelper;
private boolean mIsScrollable;
private long mDeferUpdatesUntilMillis = 0;
private RemoteViews mLastRemoteViews;
}
Key features:
- Long-press handling:
CheckLongPressHelperdetects long-press for widget resizing or removal - Auto-advance: Widgets like
StackVieware auto-advanced every 20 seconds with 250ms stagger between widgets - Update deferral: During animations or transitions, updates are deferred for up to 1 second to prevent visual glitches
- Color resources:
setColorResources()applies dynamic theme colors - Scrollability detection: Tracks whether the widget contains scrollable content
43.9.4 Widget Picker¶
The picker (in widget/picker/) presents available widgets to the user:
| Class | Role |
|---|---|
WidgetsListAdapter |
RecyclerView adapter for widget list |
WidgetPagedView |
Paged widget carousel |
WidgetRecommendationsView |
AI/recommendation-based widget suggestions |
WidgetsListHeaderViewHolderBinder |
Bind header entries (app name) |
WidgetsListTableViewHolderBinder |
Bind widget preview tables |
SimpleWidgetsSearchAlgorithm |
Search filtering |
43.9.5 Widget Pinning Flow¶
When a user adds a widget:
sequenceDiagram
participant User
participant Picker as Widget Picker
participant Holder as LauncherWidgetHolder
participant AWS as AppWidgetService
participant ConfigAct as Config Activity
User->>Picker: Select widget
Picker->>Holder: allocateAppWidgetId()
Holder->>AWS: allocateAppWidgetId()
AWS-->>Holder: appWidgetId
Holder->>AWS: bindAppWidgetIdIfAllowed(id, provider)
alt Has Configuration Activity
Holder->>ConfigAct: startActivityForResult
ConfigAct-->>Holder: RESULT_OK + configured
end
Holder->>Holder: createView(context, id, info)
Note over Holder: Creates LauncherAppWidgetHostView
Holder->>Holder: Add to workspace CellLayout
Holder->>AWS: Provider sends initial RemoteViews
AWS->>Holder: updateAppWidget(id, views)
Holder->>Holder: Apply RemoteViews to host view
43.9.6 Widget Resizing¶
Widget resizing involves:
- User enters resize mode (long-press then tap resize handle)
AppWidgetResizeFramedraws the resize handles- User drags to new size
- Launcher calculates new cell dimensions
- Calls
AppWidgetManager.updateAppWidgetOptions()with new size bundle - Service sends
ACTION_APPWIDGET_OPTIONS_CHANGEDto provider - Provider rebuilds RemoteViews for new size (or uses sized RemoteViews)
43.9.7 Widget Utilities¶
| Class | Purpose |
|---|---|
WidgetSizes |
Calculate widget sizes from grid cells |
WidgetsTableUtils |
Arrange widgets in preview table grid |
WidgetDragScaleUtils |
Scale calculations during drag |
43.10 Try It: Build a Custom Widget¶
This section provides a practical exercise demonstrating the concepts covered in this chapter.
43.10.1 XML-Based Widget (Traditional)¶
Step 1: Create the AppWidgetProvider
// src/com/example/widget/MyWidgetProvider.java
public class MyWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.widget_layout);
// Set text
views.setTextViewText(R.id.widget_title, "My Widget");
views.setTextViewText(R.id.widget_subtitle,
new SimpleDateFormat("HH:mm").format(new Date()));
// Set click handler
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_root, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
@Override
public void onEnabled(Context context) {
// First widget instance placed -- start any background work
}
@Override
public void onDisabled(Context context) {
// Last widget instance removed -- clean up
}
}
Step 2: Define the widget layout
<!-- res/layout/widget_layout.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="@drawable/widget_background">
<TextView
android:id="@+id/widget_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/widget_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary" />
</LinearLayout>
Step 3: Define widget metadata
<!-- res/xml/widget_info.xml -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="60dp"
android:targetCellWidth="3"
android:targetCellHeight="1"
android:updatePeriodMillis="1800000"
android:initialLayout="@layout/widget_layout"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:widgetFeatures="reconfigurable|configuration_optional"
android:previewLayout="@layout/widget_layout"
android:description="@string/widget_description" />
Step 4: Register in AndroidManifest.xml
<receiver android:name=".widget.MyWidgetProvider"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
43.10.2 Collection Widget with RemoteViewsService¶
Step 1: Implement the factory
// src/com/example/widget/MyRemoteViewsFactory.java
public class MyWidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new MyRemoteViewsFactory(this, intent);
}
}
class MyRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private List<String> mItems = new ArrayList<>();
@Override
public void onDataSetChanged() {
// Fetch fresh data -- this runs on a binder thread,
// heavy work is safe here
mItems.clear();
mItems.addAll(fetchItems());
}
@Override
public RemoteViews getViewAt(int position) {
RemoteViews rv = new RemoteViews(mPackageName,
R.layout.widget_list_item);
rv.setTextViewText(R.id.item_text, mItems.get(position));
// Fill-in intent for individual item clicks
Intent fillInIntent = new Intent();
fillInIntent.putExtra("item_position", position);
rv.setOnClickFillInIntent(R.id.item_root, fillInIntent);
return rv;
}
@Override
public int getCount() { return mItems.size(); }
@Override
public RemoteViews getLoadingView() { return null; } // Use default
@Override
public int getViewTypeCount() { return 1; }
@Override
public long getItemId(int position) { return position; }
@Override
public boolean hasStableIds() { return true; }
@Override
public void onCreate() { }
@Override
public void onDestroy() { mItems.clear(); }
}
Step 2: Set up the adapter in the provider
@Override
public void onUpdate(Context context, AppWidgetManager manager,
int[] appWidgetIds) {
for (int id : appWidgetIds) {
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.widget_collection);
// Set up the collection adapter
Intent serviceIntent = new Intent(context, MyWidgetService.class);
views.setRemoteAdapter(R.id.widget_list, serviceIntent);
views.setEmptyView(R.id.widget_list, R.id.widget_empty);
// Set up click template
Intent clickIntent = new Intent(context, DetailActivity.class);
PendingIntent clickPending = PendingIntent.getActivity(
context, 0, clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
views.setPendingIntentTemplate(R.id.widget_list, clickPending);
manager.updateAppWidget(id, views);
}
}
43.10.3 Sized RemoteViews for Responsive Layout¶
@Override
public void onUpdate(Context context, AppWidgetManager manager,
int[] appWidgetIds) {
for (int id : appWidgetIds) {
Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
// Small: 2x1 cells -- show only title
RemoteViews small = new RemoteViews(context.getPackageName(),
R.layout.widget_small);
small.setTextViewText(R.id.title, "Title");
viewMapping.put(new SizeF(120f, 40f), small);
// Medium: 3x2 cells -- show title and image
RemoteViews medium = new RemoteViews(context.getPackageName(),
R.layout.widget_medium);
medium.setTextViewText(R.id.title, "Title");
medium.setImageViewResource(R.id.image, R.drawable.preview);
viewMapping.put(new SizeF(200f, 100f), medium);
// Large: 4x3 cells -- show title, image, and description
RemoteViews large = new RemoteViews(context.getPackageName(),
R.layout.widget_large);
large.setTextViewText(R.id.title, "Title");
large.setImageViewResource(R.id.image, R.drawable.preview);
large.setTextViewText(R.id.description, "Full description");
viewMapping.put(new SizeF(300f, 200f), large);
manager.updateAppWidget(id, new RemoteViews(viewMapping));
}
}
43.10.4 RemoteCompose Widget (DrawInstructions)¶
@Override
public void onUpdate(Context context, AppWidgetManager manager,
int[] appWidgetIds) {
for (int id : appWidgetIds) {
// Build a RemoteCompose document
RemoteComposeBuffer buffer = new RemoteComposeBuffer();
// ... add Header, theme, draw operations to buffer ...
byte[] documentBytes = buffer.toByteArray();
List<byte[]> instructions = new ArrayList<>();
instructions.add(documentBytes);
DrawInstructions drawInstructions =
new DrawInstructions.Builder(instructions).build();
// Create RemoteViews from DrawInstructions
RemoteViews views = new RemoteViews(drawInstructions);
manager.updateAppWidget(id, views);
}
}
When the host's AppWidgetHostView receives these RemoteViews, it detects
mHasDrawInstructions == true and uses a RemoteComposePlayer instead of
inflating an XML layout.
43.10.5 Engagement Metrics¶
// In your widget's AppWidgetHostView setup
RemoteViews views = new RemoteViews(packageName, R.layout.widget);
// Tag views for event tracking
views.setAppWidgetEventTag(R.id.button_1, 1001);
views.setAppWidgetEventTag(R.id.scroll_list, 2001);
// Later, query events
List<AppWidgetEvent> events =
AppWidgetManager.getInstance(context)
.queryAppWidgetEvents(appWidgetId);
for (AppWidgetEvent event : events) {
Duration visible = event.getVisibleDuration();
int[] clicked = event.getClickedIds();
int[] scrolled = event.getScrolledIds();
// Analyze engagement...
}
43.10.6 Build and Test¶
To build a widget within the AOSP tree:
# Build the widget app
m MyWidgetApp
# Install on device
adb install -r $OUT/system/app/MyWidgetApp/MyWidgetApp.apk
# Force a widget update (useful for testing)
adb shell am broadcast -a android.appwidget.action.APPWIDGET_UPDATE \
--ei appWidgetIds 42 \
-n com.example.widget/.MyWidgetProvider
# Dump widget service state
adb shell dumpsys appwidget
# Check widget memory usage
adb shell dumpsys meminfo com.example.widget
43.10.7 Debugging Tips¶
-
Widget not appearing in picker: Verify the
<receiver>has the correct intent-filter and<meta-data>in the manifest. Checkadb shell dumpsys appwidgetfor registered providers. -
Updates not arriving: Check
updatePeriodMillis(minimum 30 minutes in production). UseAlarmManagerorWorkManagerfor more frequent updates. -
RemoteViews crash: The
ActionExceptionmessage typically includes the method name and parameter type that failed. Common causes: - Using a non-
@RemoteViewview class - Nesting depth exceeding
MAX_NESTED_VIEWS(10) -
Bitmap size exceeding
mMaxWidgetBitmapMemory -
RemoteCompose not rendering: Ensure the host's
AppWidgetHostViewcreates aRemoteComposePlayerwhenmHasDrawInstructionsis true. Check document version compatibility withcanBeDisplayed(). -
Engagement metrics not collecting: Verify the
FLAG_ENGAGEMENT_METRICSfeature flag is enabled. Check thatsetAppWidgetEventTag()is called before the views are applied.
Summary¶
The Android widget system is a multi-layer architecture connecting app providers to host processes through a system service broker:
flowchart TB
subgraph "Provider App Process"
P1[AppWidgetProvider]
P2[RemoteViews Builder]
P3["RemoteCompose Buffer (new)"]
end
subgraph "system_server"
S1[AppWidgetServiceImpl]
S2["Widget / Host / Provider models"]
S3["State persistence (XML)"]
S4["Engagement metrics pipeline"]
end
subgraph "Host App Process (Launcher)"
H1[AppWidgetHost]
H2[AppWidgetHostView]
H3["RemoteViews.apply() / reapply()"]
H4["RemoteComposePlayer (new)"]
end
P1 -->|"Broadcast"| S1
P2 -->|"updateAppWidget()"| S1
P3 -->|"DrawInstructions"| P2
S1 -->|"IAppWidgetHost callback"| H1
H1 -->|"Handler dispatch"| H2
H2 -->|"XML layout path"| H3
H2 -->|"DrawInstructions path"| H4
The key takeaways:
-
RemoteViews serializes view mutations as an ordered list of typed
Actionobjects (35 action types) that are applied to an inflated XML layout. -
AppWidgetService brokers the relationship between providers and hosts, enforcing security policy, managing state persistence, and handling periodic updates via
AlarmManager. -
RemoteCompose is a significant new addition (265 files, 60,000+ lines) that provides a binary bytecode format for rendering. It supports draw operations, layout containers, modifiers, variables, expressions, animations, haptics, and accessibility -- far exceeding what
RemoteViewscan express. -
The bridge between old and new is the
DrawInstructionsclass andSET_DRAW_INSTRUCTION_TAG(35), which embeds RemoteCompose documents inside traditionalRemoteViewsparcels. -
Launcher3 adds substantial widget-specific logic on top of the framework: background-thread host operations, update deferral during animations, auto-advance for collection widgets, and a full widget picker UI.
Source file reference for further exploration:
frameworks/base/core/java/android/appwidget/AppWidgetProvider.java(220 lines)frameworks/base/core/java/android/appwidget/AppWidgetHost.java(726 lines)frameworks/base/core/java/android/appwidget/AppWidgetEvent.java(401 lines)frameworks/base/core/java/android/appwidget/AppWidgetHostView.javaframeworks/base/core/java/android/appwidget/AppWidgetManager.javaframeworks/base/core/java/android/appwidget/AppWidgetProviderInfo.java(647 lines)frameworks/base/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.javaframeworks/base/core/java/android/widget/RemoteViews.java(10,874 lines)frameworks/base/core/java/android/widget/RemoteViewsService.java(321 lines)frameworks/base/core/java/android/widget/RemoteViewsAdapter.java(1,305 lines)frameworks/base/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.javaframeworks/base/core/java/com/android/internal/widget/remotecompose/core/Operations.javaframeworks/base/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.javaframeworks/base/core/java/com/android/internal/widget/remotecompose/core/PaintContext.javaframeworks/base/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.javaframeworks/base/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.javaframeworks/base/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.javaframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.ktpackages/apps/Launcher3/src/com/android/launcher3/widget/LauncherWidgetHolder.javapackages/apps/Launcher3/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java