Zygote
Notifications & SystemUIintermediate

The launcher

The home screen is an app with a category filter, not a part of the system. That single fact explains widgets, the recents gesture, why you can replace it, and why a launcher crash does not break the phone.

11 minAPI 21–37 · 17interactivequizverified 2026-10-27

There is no "home screen" component in the Android framework. There is an Intent with CATEGORY_HOME, and whatever app the user has chosen to handle it.

TL;DR#

  • A launcher is an ordinary app that declares an activity with CATEGORY_HOME in an intent filter.
  • Widgets are AppWidgetProvider broadcasts plus RemoteViews descriptions that the launcher inflates in its own process.
  • Glance is the Compose-style API for widgets; it compiles down to RemoteViews.
  • Shortcuts and notification dots are launcher-side features with app-side APIs.
  • On modern Android, the recents/overview gesture is implemented by the launcher through the Quickstep integration, not by SystemUI.
  • A launcher can be replaced, and on a user device usually is: the OEM ships one, the user can install another.

Mental model#

Think of a shopping mall's directory board.

The board is the launcher: it shows where the shops are, and it is maintained by the mall, not by the shops. A shop can rent a display case (a widget) and put a fixed arrangement of items in it, but the shop cannot put a live employee behind the glass — the mall's staff maintains the case.

The mall management is the system: it decides which boards exist, and it will let a different company install and run the board if the tenant prefers.

Where the analogy breaks#

A directory board is passive. A launcher is an app that receives the home intent, draws its own UI, handles gestures, hosts widgets from other apps, and (on modern devices) runs the recents animation. It does far more than display a grid, while still being replaceable.

The big picture#

Loading diagram…
Text description of this diagram

A nine-step sequence between your app, AppWidgetManager, AppWidgetService in system_server, the launcher host, and a RemoteViews object. The provider declares its configuration in the manifest and an appwidget-provider XML. The launcher asks for a widget and the service allocates an id. APPWIDGET_UPDATE is broadcast and onUpdate runs in the app's process. The app builds a RemoteViews, which is a list of method calls to replay later rather than a live view tree. updateAppWidget parcels it and sends it over Binder. The service hands it to the hosting process. The host inflates the layout with its own Context and replays the actions. A click fires a PendingIntent, which is the only path that runs the app's code.

The step that matters is the last one. Everything before it is data. The only code execution the launcher can trigger in your app is a PendingIntent you supplied.

Deep dive#

Being a launcher#

A launcher declares:

<activity
    android:name=".HomeActivity"
    android:exported="true"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

When the user presses Home, the system resolves ACTION_MAIN with CATEGORY_HOME and starts the selected activity. If multiple apps match, the user is asked which to use, and the choice can be changed in Settings.1

That is the entire mechanism. There is no launcher service, no privileged API, and no framework support beyond the intent. A launcher's special powers — reading the installed app list, showing widgets — come from ordinary APIs plus, in some cases, a privileged permission held by the preinstalled one.

Why widgets cannot run your code#

A widget lives in the launcher's process. If the launcher inflated your layout and ran your code, your app's code would execute inside another app's process — with that app's memory and that app's life.

RemoteViews is the workaround. It is a serialised description:1

val views = RemoteViews(context.packageName, R.layout.widget_today).apply {
    setTextViewText(R.id.widget_title, "Today")
    setTextViewText(R.id.widget_count, "3 tasks")
    setOnClickPendingIntent(
        R.id.widget_root,
        PendingIntent.getActivity(
            context,
            0,
            Intent(context, MainActivity::class.java),
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
        ),
    )
}

setTextViewText is not a method call on a view. It is a record: "when you inflate this, call setText on the view with id widget_count". The launcher replays it.

Three consequences:

  • Only a small allowlist of views works. Custom views, ConstraintLayout and most of the View system are unavailable.
  • The layout is inflated with the host's theme, so colours and dimensions may not match what you see in the Android Studio preview.
  • Updates are pushes. AppWidgetManager.updateAppWidget() sends a new description; there is no live binding.

Glance#

Glance is the Compose-style API for widgets.2 It gives you a composable function that looks like Compose:

class TodayWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        val tasks = loadTasks(context)
 
        provideContent {
            Column(modifier = GlanceModifier.fillMaxSize().padding(16.dp)) {
                Text("Today", style = TextStyle(fontWeight = FontWeight.Bold))
                Text("${tasks.size} tasks")
            }
        }
    }
}

Underneath, Glance composes to RemoteViews.2 The composable is not retained; it runs once to produce a description, and the description is what the launcher inflates. That is why Glance widgets cannot hold state the way a Composable in an Activity can, and why the API is provideGlance rather than a @Composable entry point.

Shortcuts, dots and badges#

Three launcher-adjacent features have app-side APIs:

  • Static shortcuts are declared in XML; dynamic shortcuts are published through ShortcutManager. The launcher displays them on a long press.
  • Notification dots appear on an icon when the app has a notification with a matching channel or a direct share target.
  • Badges are launcher-specific; there is no platform API for a numeric badge count. Apps that show one use a notification, and the launcher decides how to render it.

The pattern is consistent: the app publishes a signal, and the launcher decides what to do with it.

Recents and Quickstep#

On modern Android, the overview/recents gesture is handled by the launcher, not SystemUI. The mechanism is a system integration called Quickstep: the launcher implements the recents animation and the app-switching UI, while the system provides the gesture and the task data.

That is why replacing the launcher can change the recents experience, and why a launcher crash can take the recents gesture with it.

The launcher on AOSP#

AOSP's reference launcher is Launcher3, in packages/apps/Launcher3/. It is a real, buildable app: the home screen, the workspace, the all-apps drawer, widget hosting and the Quickstep integration. Reading it is the most direct way to understand how widgets, shortcuts and the recents gesture are actually implemented.

What the launcher boundary teaches#

The launcher is the clearest example on the platform of a component that feels like the system and is not. Three lessons follow from it.

A category is an architecture. CATEGORY_HOME is a single string in an intent filter, and it is the entire contract between the system and the home screen. Everything else — the workspace, the drawer, the widgets, the search bar — is the launcher's own design.

Other apps' code cannot run in your process. The widget model is the platform's answer, and it is a general one: describe the UI, send the description, let the host inflate it. The same idea appears in notifications and in media controls.

The visible seam moves. Recents was SystemUI's for years and is now the launcher's. A component that feels permanent can be reassigned in a release, and the only warning is a change in which process draws the pixels.

Widgets as a design constraint#

The widget model is old, and its limits are the reason widgets look the way they do: a small set of views, no live binding, updates pushed from the app. Glance modernises the authoring experience without changing the underlying constraint, because the constraint is not an API design choice — it is what "another process will inflate this" requires.

Understanding that makes the widget guidelines read differently. They are not arbitrary restrictions; they are the shape of the boundary.

Shortcuts as a contract#

A long-press shortcut is a small promise: this action is available without opening the app. The platform enforces a limit on how many dynamic shortcuts an app can publish, which is a useful constraint — it forces a choice about what the top four or five actions actually are.

Static shortcuts are declared in XML and fixed at build time; dynamic ones are published through ShortcutManager and can change with state. The launcher decides how to display them, and a conversation shortcut also unlocks the conversation notification treatment, which is why messaging apps care about them disproportionately.

The icons are part of the contract#

A launcher displays whatever icon the app declares, across densities and shapes, and the adaptive icon format exists because a square bitmap does not survive the variety of masks launchers apply. An app that ships only a legacy icon will be masked, letterboxed, or shown shrunken on some launchers, and the launcher is not doing anything wrong.

The same is true of the label, the badge and the shortcut order: they are inputs to another app's UI, and the other app decides how to render them. Designing for the launcher means designing for a consumer you do not control.

How it evolved#

2008–2013Launcher2workspace and app drawer, no widget redesign
2013–2018Launcher3the AOSP reference launcher; widget hosting mature
2018–nowQuickstepthe launcher owns the recents animation
2021–nowGlancea Compose-style API that compiles to RemoteViews
Interface changes16 releases
APIVersionWhat changed
11.0
  • Pull-down notification shade
  • Home screen with widgets
31.5 Cupcake
  • Home screen widgets and folders
92.3 Gingerbread
  • New UI with dark theme and green accents
113.0 Honeycomb
  • The Holo design language arrives
123.1 Honeycomb
  • Resizable widgets
164.1 Jelly Bean
  • Project Butter: vsync-driven, smooth UI
  • Expandable notifications
204.4W KitKat (Wear)
  • Voice-driven card UI
215.0 Lollipop
  • Material Design
  • Notifications on the lock screen
247.0 Nougat
  • Quick Settings tiles
  • Direct Reply in notifications
268.0 Oreo
  • Notification channels
289 Pie
  • Dark theme groundwork
2910
  • System-wide dark theme
3011
  • Conversation notifications and bubbles
  • Media controls in Quick Settings
3112
  • Material You dynamic colour
3313 Tiramisu
  • Themed app icons
  • Runtime notification permission
3616 Baklava
  • Material 3 Expressive
  • Live Updates notifications

Under the hood#

Curiosity corner#

  • Widgets are older than Android's public SDK. They shipped in Android 1.5 as "home screen widgets", before the modern widget API existed.
  • A widget's update period is a hint. updatePeriodMillis is a minimum, and the system will not wake the device more often than it allows — which is why most serious widgets also update from a WorkManager job or a push.
  • The launcher can be replaced by any app, and on a user device there is no privileged lock-in. The preinstalled launcher wins by default, not by force.

Try it#

Find the current launcher

adb shell dumpsys activity activities | grep -i home

Example output
mHomeConsumed=true
mHomeProcess=com.android.launcher3
List installed launchers

adb shell cmd package query-activities -a android.intent.action.MAIN -c android.intent.category.HOME

Example output
com.android.launcher3/.uioverrides.QuickstepLauncher
Inspect widget providers

adb shell dumpsys appwidget | head -40

Example output
com.example.app/.TodayWidget

Build it#

Write a widget with GlanceAppWidget and one with a raw RemoteViews, and compare what works. The RemoteViews version will show you the allowlist; the Glance version will show you how much of Compose's mental model survives the translation. The answer is "less than you expect".

Check yourself#

Check yourself

1 / 12

Put these notification features in the order they were introduced.

Drag, or use the arrows to reorder.

  1. 1Expandable styles and actions
  2. 2Direct reply and bundled groups
  3. 3Lock screen notifications and heads-up peeks
  4. 4POST_NOTIFICATIONS as a runtime permission
  5. 5Mandatory notification channels
0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsCreate an app widgetdeveloper.android.comaccessed 2026-10-27↩
  2. docsGlance for app widgetsdeveloper.android.comaccessed 2026-10-27↩
  3. docsNotifications overviewdeveloper.android.comaccessed 2026-10-27↩
  4. docsModular system componentssource.android.comaccessed 2026-10-27↩
  5. docsBehavior changes: Apps targeting Android 14developer.android.comaccessed 2026-10-27↩