Chapter 49: Launcher3 - The Android Home Screen¶
Launcher3 is the default home screen application in AOSP, responsible for the experience
users see first after unlocking their device. It manages app icons on the workspace,
the all-apps drawer, widgets, folders, drag-and-drop, the taskbar on large screens, and,
through its Quickstep integration, the recent-apps overview. The codebase lives in
packages/apps/Launcher3/ and is split across roughly fifteen top-level directories of
Java and Kotlin source, plus a quickstep/ module for gesture-navigation and recents
features.
This chapter walks through the full architecture of Launcher3, from the model layer that loads workspace data off a background thread, through the view hierarchy that renders icons and widgets, to the drag-and-drop engine that ties it all together. Every section references real AOSP source files and quotes key code constructs.
49.1 Launcher3 Architecture¶
49.1.1 Project Layout¶
The top-level directory of Launcher3 is organized as follows:
packages/apps/Launcher3/
src/ # Core launcher source
quickstep/ # Gesture nav, recents, taskbar
src_no_quickstep/ # Stubs for builds without quickstep
src_plugins/ # Plugin interfaces
shared/ # Code shared across variants (e.g. TestProtocol)
res/ # Resources (layouts, XML configs)
protos/ # Protocol buffer definitions
protos_overrides/ # Per-build proto overrides
modules/ # Compose modules (e.g. modules/widgetpicker)
aconfig/ # aconfig feature-flag definitions
dagger/ # Dagger dependency injection modules
go/ # Android Go variant overrides
checks/ # Lint and error-prone checks
tests/ # Unit and integration tests
tools/ # Build tooling
AndroidManifest.xml # Application manifest
Android.bp # Soong build file
There is no longer a separate compose/ directory; the Compose-based code now
lives under modules/ (notably modules/widgetpicker/).
The primary source tree at src/com/android/launcher3/ contains the following
key subdirectories:
| Subdirectory | Purpose |
|---|---|
allapps/ |
All-apps drawer, alphabetical list, tabs |
dragndrop/ |
Drag controller, drag layer, drag views |
folder/ |
Folder icon, folder paged view, grid organizer |
widget/ |
Widget host views, widget picker |
model/ |
Data model, loader task, database |
icons/ |
Icon cache, icon provider |
graphics/ |
Theme manager, scrim, shape delegate |
statemanager/ |
State machine for launcher states |
celllayout/ |
Cell layout parameters, reorder algorithms |
search/ |
Search algorithm interfaces |
responsive/ |
Responsive grid specifications |
touch/ |
Touch controllers, click handlers |
anim/ |
Animation utilities |
config/ |
Feature flags |
deviceprofile/ |
Device-specific layout profiles |
views/ |
Common view utilities |
popup/ |
Long-press popup menus |
shortcuts/ |
Deep shortcuts |
logging/ |
Stats and event logging |
49.1.2 The Main Activity: Launcher¶
The entry point is Launcher.java, a roughly 2900-line class that extends StatefulActivity<LauncherState>:
// src/com/android/launcher3/Launcher.java
public class Launcher extends StatefulActivity<LauncherState>
implements Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
PluginListener<LauncherOverlayPlugin> {
StatefulActivity is a generic base class that integrates with the StateManager
to handle transitions between launcher states (NORMAL, ALL_APPS, SPRING_LOADED,
EDIT_MODE, and others). The Callbacks interface is defined in BgDataModel and
provides the contract through which the model layer delivers loaded data to the UI.
The key member variables of Launcher establish the view hierarchy:
// src/com/android/launcher3/Launcher.java
Workspace<?> mWorkspace;
DragLayer mDragLayer;
Hotseat mHotseat;
ActivityAllAppsContainerView<Launcher> mAppsView;
AllAppsTransitionController mAllAppsController;
ScrimView mScrimView;
LauncherDragController mDragController;
49.1.3 Launcher Lifecycle: onCreate¶
The onCreate method is the central initialization path. Here is the sequence:
// src/com/android/launcher3/Launcher.java, onCreate()
protected void onCreate(Bundle savedInstanceState) {
// 1. Startup tracing
TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
super.onCreate(savedInstanceState);
mWallpaperThemeManager = new WallpaperThemeManager(this);
// 2. Obtain the application-wide state
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.getModel();
// 3. Initialize device profile and rotation
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
initDeviceProfile(idp);
// 4. Set up drag controller and state manager
initDragController();
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new StateManager<>(this, NORMAL);
// 5. Widget infrastructure
mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHolder = LauncherWidgetHolder.newInstance(this);
// 6. Inflate views
setupViews();
// 7. Start widget listening
mAppWidgetHolder.startListening();
// 8. Start model loading
mModel.addCallbacksAndLoad(this);
}
The flow can be visualized:
sequenceDiagram
participant System
participant Launcher
participant LauncherAppState
participant LauncherModel
participant LoaderTask
participant DB as LauncherProvider
System->>Launcher: onCreate()
Launcher->>LauncherAppState: getInstance()
LauncherAppState-->>Launcher: model, IDP, iconCache
Launcher->>Launcher: initDeviceProfile()
Launcher->>Launcher: setupViews()
Launcher->>LauncherModel: addCallbacksAndLoad()
LauncherModel->>LoaderTask: run() [MODEL_EXECUTOR]
LoaderTask->>DB: loadWorkspace()
LoaderTask->>DB: loadAllApps()
LoaderTask-->>LauncherModel: data ready
LauncherModel-->>Launcher: bindWorkspace()
LauncherModel-->>Launcher: bindAllApps()
LauncherModel-->>Launcher: bindWidgets()
49.1.4 LauncherAppState: The Singleton Hub¶
LauncherAppState (now a Kotlin data class) aggregates the core singletons:
// src/com/android/launcher3/LauncherAppState.kt
@Deprecated("Inject the specific targets directly instead of using LauncherAppState")
data class LauncherAppState
@Inject
constructor(
@ApplicationContext val context: Context,
val iconProvider: LauncherIconProvider,
val iconCache: IconCache,
val model: LauncherModel,
val invariantDeviceProfile: InvariantDeviceProfile,
@Named("SAFE_MODE") val isSafeModeEnabled: Boolean,
)
Note the @Deprecated annotation -- the codebase is migrating toward Dagger injection
of individual components rather than going through this singleton. The companion object
still exposes the legacy INSTANCE and getInstance() accessor for compatibility.
49.1.5 LauncherModel: The Data Backbone¶
LauncherModel is annotated @LauncherAppSingleton and manages all in-memory
launcher data. It is constructed via Dagger injection:
// src/com/android/launcher3/LauncherModel.kt
@LauncherAppSingleton
class LauncherModel
@Inject
constructor(
@ApplicationContext private val context: Context,
private val taskControllerProvider: Provider<ModelTaskController>,
private val iconCache: IconCache,
private val prefs: LauncherPrefs,
private val installQueue: ItemInstallQueue,
@Named("ICONS_DB") dbFileName: String?,
initializer: ModelInitializer,
lifecycle: DaggerSingletonTracker,
val modelDelegate: ModelDelegate,
private val mBgAllAppsList: AllAppsList,
private val mBgDataModel: BgDataModel,
private val loaderFactory: LoaderTaskFactory,
private val binderFactory: BaseLauncherBinderFactory,
val modelDbController: ModelDbController,
dumpManager: DumpManager,
) : LauncherDumpable {
The model maintains two critical data structures:
BgDataModel-- holds workspace items, folders, app widgets, and screen orderAllAppsList-- holds the complete list of launchable activities
Loading runs on MODEL_EXECUTOR (a dedicated background thread). The model tracks load
state with mModelLoaded, mLoaderTask, and lastLoadId:
// src/com/android/launcher3/LauncherModel.kt
fun isModelLoaded() =
synchronized(mLock) { mModelLoaded && mLoaderTask == null && !mModelDestroyed }
49.1.6 Model-View Separation¶
The architecture follows a strict model-view separation:
graph TD
subgraph "Background Thread (MODEL_EXECUTOR)"
LT[LoaderTask]
BDM[BgDataModel]
AAL[AllAppsList]
DB[LauncherProvider / SQLite]
end
subgraph "Main Thread"
L[Launcher Activity]
WS[Workspace]
HA[Hotseat]
AA[AllAppsContainerView]
WP[WidgetPicker]
end
LT -->|reads| DB
LT -->|populates| BDM
LT -->|populates| AAL
BDM -->|bindWorkspace| L
AAL -->|bindAllApps| L
L -->|displays| WS
L -->|displays| HA
L -->|displays| AA
L -->|displays| WP
The Callbacks interface (implemented by Launcher) defines the binding contract:
bindItems()-- delivers workspace items (icons, shortcuts)bindAppWidgets()-- delivers widget instancesbindAllApplications()-- delivers the full app listbindWidgetsModel()-- delivers widget catalog for the picker
Model writes go through ModelWriter, obtained via LauncherModel.getWriter().
All database mutations happen on the model thread, ensuring consistency.
49.1.7 State Machine¶
StateManager is a generic state machine that drives animated transitions between
launcher states. Each state is a subclass of LauncherState:
// src/com/android/launcher3/LauncherState.java
public abstract class LauncherState implements BaseState<LauncherState> {
public static final int HOTSEAT_ICONS = 1 << 0;
public static final int ALL_APPS_CONTENT = 1 << 1;
public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
public static final int FLOATING_SEARCH_BAR = 1 << 7;
// ...
public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
public static final int FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED = BaseState.getFlag(2);
public static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(6);
Each state carries an ordinal field, set from the matching *_STATE_ORDINAL
constant in TestProtocol (shared/src/com/android/launcher3/testing/shared/TestProtocol.java)
that the state's definition in LauncherState.java passes to its constructor. The
values are not a contiguous "UI layer" order; they are the stable identifiers shared
with the test harness:
| State | Ordinal | Description |
|---|---|---|
NORMAL |
0 | Default workspace view |
SPRING_LOADED |
1 | Workspace shrunk during drag |
OVERVIEW |
2 | Recents view (Quickstep) |
OVERVIEW_MODAL_TASK |
3 | Task menu open |
QUICK_SWITCH |
4 | Quick switch gesture |
ALL_APPS |
5 | All-apps drawer open |
BACKGROUND_APP |
6 | App is in foreground |
HINT_STATE |
7 | Swipe-up hint indicator |
HINT_STATE_TWO_BUTTON |
8 | Two-button-nav hint indicator |
OVERVIEW_SPLIT_SELECT |
9 | Split-screen selection |
EDIT_MODE |
10 | Workspace customization mode |
DESKTOP_DRAG_MODE |
11 | Drag into a desktop window |
The StateManager drives transitions with animations:
// src/com/android/launcher3/statemanager/StateManager.java
public class StateManager<S extends BaseState<S>, T extends StatefulContainer<S>> {
private final AnimationState<S> mConfig = new AnimationState<>();
private final T mContainer;
private final ArrayList<StateListener<S>> mListeners = new ArrayList<>();
private S mState;
private S mLastStableState;
private S mCurrentStableState;
State handlers (StateHandler<S>[]) are responsible for applying state-specific
property changes. For example, AllAppsTransitionController adjusts the vertical
position and alpha of the all-apps panel during transitions.
49.1.8 Dependency Injection with Dagger¶
The Launcher3 codebase uses Dagger for dependency injection, with key annotations:
@LauncherAppSingleton-- scoped to the application lifecycle@ApplicationContext-- the applicationContext@Inject-- constructor injection@Named-- qualifier for specific instances (e.g.,"ICONS_DB")
The DI graph is rooted at LauncherAppComponent, which provides singletons like
InvariantDeviceProfile, LauncherModel, IconCache, and ThemeManager.
graph TD
LAC[LauncherAppComponent] --> IDP[InvariantDeviceProfile]
LAC --> LM[LauncherModel]
LAC --> IC[IconCache]
LAC --> TM[ThemeManager]
LAC --> LP[LauncherPrefs]
LM --> MTC[ModelTaskController]
LM --> BDM[BgDataModel]
LM --> AAL[AllAppsList]
LM --> MDC[ModelDbController]
49.2 App Icons and Grid¶
49.2.1 ItemInfo Hierarchy¶
Every element on the launcher home screen -- app icons, shortcuts, widgets, folders --
is represented by a subclass of ItemInfo:
// src/com/android/launcher3/model/data/ItemInfo.java
public class ItemInfo {
public int id = NO_ID;
public int itemType;
public int container = NO_ID;
public int screenId = -1;
public int cellX = -1;
public int cellY = -1;
public int spanX = 1;
public int spanY = 1;
public int minSpanX = 1;
public int minSpanY = 1;
public int rank = 0;
public CharSequence title;
The itemType field determines the concrete type:
| Constant | Value | Meaning |
|---|---|---|
ITEM_TYPE_APPLICATION |
0 | App shortcut |
ITEM_TYPE_FOLDER |
2 | Folder container |
ITEM_TYPE_APPWIDGET |
4 | App widget |
ITEM_TYPE_DEEP_SHORTCUT |
6 | Pinned deep shortcut |
ITEM_TYPE_TASK |
7 | Task (recents) |
ITEM_TYPE_QSB |
8 | Quick search bar slot |
ITEM_TYPE_APP_GROUP |
10 | App pair for split screen (its toString label is still "APP_PAIR") |
ITEM_TYPE_FILE_SYSTEM_FILE |
12 | Home screen file |
The inheritance tree:
classDiagram
class ItemInfo {
+int id
+int itemType
+int container
+int screenId
+int cellX, cellY
+int spanX, spanY
+CharSequence title
}
class ItemInfoWithIcon {
+BitmapInfo bitmap
+int runtimeStatusFlags
}
class WorkspaceItemInfo {
+Intent intent
+int status
+ShortcutInfo shortcutInfo
}
class AppInfo {
+ComponentName componentName
+Intent intent
}
class CollectionInfo {
+add(ItemInfo)* void
+getContents()* List~ItemInfo~
+getAppContents()* List~WorkspaceItemInfo~
}
class FolderInfo {
-ArrayList~ItemInfo~ contents
+FolderNameInfos suggestedFolderNames
}
class LauncherAppWidgetInfo {
+int appWidgetId
+ComponentName providerName
}
class AppPairInfo {
-List~WorkspaceItemInfo~ contents
}
class PackageItemInfo {
+String packageName
}
ItemInfo <|-- ItemInfoWithIcon
ItemInfoWithIcon <|-- WorkspaceItemInfo
ItemInfoWithIcon <|-- AppInfo
ItemInfo <|-- CollectionInfo
CollectionInfo <|-- FolderInfo
ItemInfo <|-- LauncherAppWidgetInfo
CollectionInfo <|-- AppPairInfo
ItemInfoWithIcon <|-- PackageItemInfo
The container field specifies where the item lives:
// src/com/android/launcher3/LauncherSettings.java
public static final int CONTAINER_DESKTOP = -100;
public static final int CONTAINER_HOTSEAT = -101;
public static final int CONTAINER_ALL_APPS = -104;
49.2.2 CellLayout: The Grid Container¶
CellLayout is the fundamental grid container. Every workspace page and the hotseat
are CellLayout instances. It manages a grid of cells where items can be placed:
// src/com/android/launcher3/CellLayout.java
public class CellLayout extends ViewGroup {
@Thunk int mCellWidth;
@Thunk int mCellHeight;
protected Point mBorderSpace;
protected int mCountX;
protected int mCountY;
Each CellLayout maintains a GridOccupancy that tracks which cells are occupied:
// src/com/android/launcher3/util/GridOccupancy.java
public class GridOccupancy {
boolean[][] cells;
int countX;
int countY;
Items are positioned using CellLayoutLayoutParams:
// src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
public class CellLayoutLayoutParams extends MarginLayoutParams {
public int cellX;
public int cellY;
public int cellHSpan;
public int cellVSpan;
public int tmpCellX;
public int tmpCellY;
The CellLayout uses a child container called ShortcutAndWidgetContainer that
performs the actual layout of children. This separation allows CellLayout to
manage the grid logic while the container handles ViewGroup layout mechanics.
49.2.3 Workspace: The Paging Container¶
Workspace extends PagedView and holds multiple CellLayout pages:
// src/com/android/launcher3/Workspace.java
public class Workspace<T extends View & PageIndicator> extends PagedView<T>
implements DropTarget, DragSource, View.OnTouchListener,
LauncherOverlayCallbacks, Insettable {
The workspace supports:
- Horizontal paging between home screen pages
- Drag-and-drop of items between pages
- Page creation and deletion based on content
- Wallpaper scrolling via
WallpaperOffsetInterpolator - Spring-loaded mode where pages shrink during drag operations
49.2.4 BubbleTextView: The Icon View¶
BubbleTextView is the custom TextView subclass that renders app icons:
// src/com/android/launcher3/BubbleTextView.java
public class BubbleTextView extends TextView
implements ItemInfoUpdateReceiver, DraggableView, Poppable {
It renders both the icon (as a compound drawable on top) and the label text below. Key features include:
- Notification dots -- rendered via
DotRendererwhen the app has notifications - Download progress -- overlay progress ring during installation
- Themed icons -- monochrome icon rendering when Material You theming is active
- Running app state -- visual indicator on taskbar icons for running apps
The view supports multiple display contexts via constants:
// src/com/android/launcher3/BubbleTextView.java
public static final int DISPLAY_WORKSPACE = 0;
public static final int DISPLAY_ALL_APPS = 1;
public static final int DISPLAY_FOLDER = 2;
public static final int DISPLAY_TASKBAR = 5;
public static final int DISPLAY_SEARCH_RESULT = 6;
public static final int DISPLAY_SEARCH_RESULT_SMALL = 7;
49.2.5 Hotseat: The Bottom Row¶
The Hotseat is a specialized CellLayout that represents the persistent bottom row:
// src/com/android/launcher3/Hotseat.java
public class Hotseat extends CellLayout implements Insettable {
It differs from workspace CellLayout instances in that:
- It uses a single-row grid (
mCountY = 1) - Items are not associated with a specific screen ID
- It participates in predictions (suggested apps appear here)
49.2.6 DeviceProfile and Grid Configuration¶
Launcher3 adapts its grid to different screen sizes through a two-tier system:
InvariantDeviceProfile (IDP) is the device-independent specification loaded from
res/xml/device_profiles.xml:
<!-- res/xml/device_profiles.xml -->
<grid-option
launcher:name="4_by_4"
launcher:numRows="4"
launcher:numColumns="4"
launcher:numFolderRows="3"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="4"
launcher:numExtendedHotseatIcons="6"
launcher:dbFile="launcher_4_by_4.db"
launcher:defaultLayoutId="@xml/default_workspace_4x4"
launcher:deviceCategory="phone" >
The IDP supports multiple grid sizes (3_by_3, 4_by_4, 5_by_5, 6_by_5)
plus a fixed_landscape_mode profile. Each grid definition includes display
options that specify icon sizes, text sizes, and border spacing for different
screen dimensions.
DeviceProfile is the runtime profile computed for the current display configuration. It incorporates responsive specifications:
// src/com/android/launcher3/DeviceProfile.java
public class DeviceProfile {
public final InvariantDeviceProfile inv;
public final boolean isQsbInline;
public final boolean isLeftRightSplit;
private final boolean mIsScalableGrid;
private final boolean mIsResponsiveGrid;
The device profile delegates layout calculations to sub-profiles:
graph TD
DP[DeviceProfile] --> WSP[WorkspaceProfile]
DP --> HSP[HotseatProfile]
DP --> FP[FolderProfile]
DP --> AAP[AllAppsProfile]
DP --> OP[OverviewProfile]
DP --> TBP[TaskbarProfile]
DP --> DTP[DropTargetProfile]
DP --> BSP[BottomSheetProfile]
The device type classification determines layout behavior:
// src/com/android/launcher3/InvariantDeviceProfile.java
public static final int TYPE_PHONE = 0;
public static final int TYPE_MULTI_DISPLAY = 1;
public static final int TYPE_TABLET = 2;
public static final int TYPE_DESKTOP = 3;
49.2.7 Icon Loading and Caching¶
The IconCache is responsible for loading and caching app icons. Icons are loaded
asynchronously on a background thread and cached in a SQLite database
(app_icons.db by default).
The icon loading pipeline:
sequenceDiagram
participant UI as UI Thread
participant IC as IconCache
participant PM as PackageManager
participant DB as Icons DB
UI->>IC: getTitleAndIcon(info)
IC->>DB: lookup(componentName, user)
alt Cache hit
DB-->>IC: cached BitmapInfo
IC-->>UI: return cached icon
else Cache miss
IC->>PM: getActivityIcon()
PM-->>IC: raw Drawable
IC->>IC: normalize + theme icon
IC->>DB: addOrUpdate(entry)
IC-->>UI: return new icon
end
The LauncherIconProvider handles icon loading with theme support. When themed
icons are enabled, it attempts to load a monochrome icon variant and applies
the user's wallpaper-based color palette.
49.2.8 Responsive Grid System¶
The responsive grid system in src/com/android/launcher3/responsive/ dynamically
adjusts cell sizes and spacing based on available screen space:
responsive/
ResponsiveSpec.kt # Core spec definition
ResponsiveSpecsProvider.kt # Provider for workspace specs
ResponsiveCellSpecsProvider.kt # Provider for cell specs
HotseatSpecsProvider.kt # Provider for hotseat specs
SizeSpec.kt # Individual size specification
ResponsiveSpecGroup.kt # Grouping of specs
ResponsiveSpecsParser.kt # XML parser for spec files
Responsive specs are defined in XML resource files (e.g., spec_col_count_3_row.xml,
spec_handheld_all_apps_3_row.xml) and the system selects the appropriate spec
based on available dimensions at runtime.
49.3 Widget System¶
49.3.1 Widget Architecture Overview¶
Launcher3's widget system bridges the Android AppWidgetManager framework with the
launcher's own view hierarchy. The key classes form a layered architecture:
graph TD
subgraph "Android Framework"
AWM[AppWidgetManager]
AWH_FW[AppWidgetHost]
AWHV_FW[AppWidgetHostView]
end
subgraph "Launcher3 Widget Layer"
LWH[LauncherWidgetHolder]
LAWH[LauncherAppWidgetHost]
LAWHV[LauncherAppWidgetHostView]
LAWI[LauncherAppWidgetInfo]
WMH[WidgetManagerHelper]
end
subgraph "Widget Picker (Compose)"
WPA[WidgetPickerActivity]
WPCW[WidgetPickerComposeWrapper]
VM["Catalog ViewModels<br/>(modules/widgetpicker)"]
WPDP[WidgetPickerDataProvider]
WPD[WidgetPickerData]
end
AWM --> WMH
AWH_FW --> LAWH
AWHV_FW --> LAWHV
LWH --> LAWH
WPA --> WPCW
WPCW --> VM
VM --> WPDP
WPDP --> WPD
LAWI -->|data| LAWHV
49.3.2 LauncherWidgetHolder¶
LauncherWidgetHolder wraps AppWidgetHost to allow widget operations from
background threads:
// 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;
@NonNull protected final Context mContext;
@NonNull protected final ListenableAppWidgetHost mWidgetHost;
@NonNull protected final SparseArray<LauncherAppWidgetHostView> mViews;
The holder tracks activity lifecycle flags to determine when to listen for updates.
Widget views only receive remote view updates when all FLAGS_SHOULD_LISTEN are set
(the activity is in NORMAL state, started, and resumed).
49.3.3 LauncherAppWidgetHost¶
LauncherAppWidgetHost extends ListenableAppWidgetHost and creates
LauncherAppWidgetHostView instances:
// src/com/android/launcher3/widget/LauncherAppWidgetHost.java
class LauncherAppWidgetHost extends ListenableAppWidgetHost {
@Override
@NonNull
public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
ListenableHostView result =
mViewToRecycle != null ? mViewToRecycle : new ListenableHostView(context);
mViewToRecycle = null;
return result;
}
Note the view recycling mechanism: when a widget is reconfigured, the existing view
is passed to recycleViewForNextCreation() to avoid recreating the host view.
49.3.4 Widget Data: LauncherAppWidgetInfo¶
Widgets are represented in the model by LauncherAppWidgetInfo:
// src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
public class LauncherAppWidgetInfo extends ItemInfo {
public int appWidgetId;
public ComponentName providerName;
public int restoreStatus;
public int installProgress;
The restoreStatus field tracks the restore lifecycle:
FLAG_ID_NOT_VALID-- widget ID needs allocationFLAG_PROVIDER_NOT_READY-- provider not yet installedFLAG_UI_NOT_READY-- view not yet inflatedRESTORE_COMPLETED-- fully restored
49.3.5 Widget Pinning Flow¶
When a user adds a widget from the widget picker, this flow executes:
sequenceDiagram
participant User
participant WP as WidgetPreview (Compose)
participant DIL as WidgetPickerDragItemListener
participant PDH as PendingItemDragHelper
participant L as Launcher
participant WMH as WidgetManagerHelper
participant AWM as AppWidgetManager
participant WS as Workspace
User->>WP: Opens picker, long-press widget
WP->>DIL: onWidgetInteraction(WidgetDragInfo)
DIL->>PDH: startDrag() with PendingAddWidgetInfo
PDH->>L: beginDrag once launcher resumes
User->>WS: Drop on workspace
WS->>L: onDropCompleted()
L->>AWM: bindAppWidgetIdIfAllowed()
alt Bind allowed
AWM-->>L: success
L->>L: completeAddAppWidget()
else Needs permission
AWM-->>L: false
L->>AWM: createBindConfirmation()
AWM-->>User: Permission dialog
end
L->>WMH: Configure if needed
L->>WS: Add LauncherAppWidgetHostView
49.3.6 Widget Picker: WidgetPickerActivity (Compose)¶
The widget picker is no longer a RecyclerView-backed bottom sheet. The
WidgetsFullSheet / WidgetsListAdapter / WidgetCell trio was removed and
replaced by a standalone, Jetpack Compose activity. The host is
WidgetPickerActivity:
// src/com/android/launcher3/widgetpicker/WidgetPickerActivity.kt
open class WidgetPickerActivity :
BaseActivity(), OnBackPressedDispatcherOwner, OnBackAnimationCallback, LifecycleOwner {
WidgetPickerActivity inflates a SimpleDragLayer to host drags, then hands the
actual content to WidgetPickerComposeWrapper. The wrapper is an interface with
implementation WidgetPickerComposeWrapperImpl:
// src/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapper.kt
interface WidgetPickerComposeWrapper {
fun showAllWidgets(activity: BaseActivity, widgetPickerConfig: WidgetPickerConfig)
fun showWidgetsFor(packageName: String, user: UserHandle, /* ... */)
fun showWidgetsForPinRequest(/* ... */)
}
The implementation adds a ComposeView, calls setContent { }, builds a
WidgetPickerComponent Dagger subgraph, and wires up the repositories that feed
the catalog (WidgetsRepository, WidgetUsersRepository,
WidgetAppIconsRepository). On the Quickstep side,
QuickstepWidgetPickerActivity
(quickstep/src/com/android/launcher3/QuickstepWidgetPickerActivity.kt) extends
WidgetPickerActivity to host the picker on additional surfaces (for example a
lock-screen widget picker), adding wallpaper blur, zoom, and gesture-blocking on
top of the same Compose content.
The composable catalogs live in the modules/widgetpicker/ module, each driven by
a ViewModel:
| Surface | Composable | ViewModel |
|---|---|---|
| All widgets (landing + search) | FullWidgetsCatalog |
FullWidgetsCatalogViewModel |
| Single-app widgets | SingleAppWidgetsCatalog |
SingleAppWidgetsCatalogViewModel |
| Pin-widget request | PinAppWidgetCatalog |
PinAppWidgetCatalogViewModel |
The full catalog further splits into a LandingScreen (featured widgets, with
single- and two-pane variants for large screens) and a SearchScreen, each with
its own ViewModel under
modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/fullcatalog/screens/.
49.3.7 Widget Preview Rendering and Drag-Out¶
There is no WidgetCell view and no DatabaseWidgetPreviewLoader in the picker
UI anymore. A widget tile is now the WidgetPreview composable
(modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/components/WidgetPreview.kt),
laid out by WidgetsGrid.kt; the preview bitmap is supplied through the
repositories rather than fetched by a dedicated loader class. The data the grid
renders comes from WidgetPickerData, exposed by WidgetPickerDataProvider:
// src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
// holds a WidgetPickerData, whose allWidgets is a List<WidgetsListBaseEntry>
Drag-out still ends in the same model-side machinery as before. In the composable,
a long-press is detected by detectDragGesturesAfterLongPress, whose onDragStart
reports a WidgetInteractionInfo.WidgetDragInfo through the
WidgetPickerEventListeners interface
(modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/WidgetPickerEventListeners.kt).
WidgetPickerComposeWrapperImpl turns that into a WidgetPickerDragItemListener
(src/com/android/launcher3/widgetpicker/listeners/WidgetPickerDragItemListener.kt),
a BaseItemDragListener that converts the picked widget into a
PendingAddWidgetInfo and, once the launcher resumes, starts the drag through the
unchanged PendingItemDragHelper.
49.3.8 Widget Resize¶
Placed widgets can be resized via AppWidgetResizeFrame (now a Kotlin file):
The resize frame draws handles on the widget edges and updates the cell span
as the user drags. Minimum span constraints (minSpanX, minSpanY) and maximum
span constraints (from AppWidgetProviderInfo.minResizeWidth/Height) are enforced.
49.3.9 Widget Visibility Tracking¶
WidgetVisibilityTracker monitors which widgets are currently visible on screen
and notifies the AppWidgetHost accordingly, allowing the system to optimize
resource usage for off-screen widgets:
// src/com/android/launcher3/widget/WidgetVisibilityTracker.kt
// Initialized in Launcher.onCreate():
mWidgetVisibilityTracker = new WidgetVisibilityTracker(
this, mAppWidgetHolder, mWorkspace, mStateManager);
49.4 Drag and Drop¶
49.4.1 Drag-and-Drop Architecture¶
The drag-and-drop system is one of the most complex subsystems in Launcher3, involving multiple coordinating classes:
graph TD
subgraph "Controllers"
DC[DragController]
LDC[LauncherDragController]
SDC[SystemDragController]
SLDC[SpringLoadedDragController]
end
subgraph "Visual Layer"
DL[DragLayer]
DV[DragView]
LDV[LauncherDragView]
end
subgraph "Drop Targets"
WS[Workspace]
FL[Folder]
HS[Hotseat]
DTB[DropTargetBar]
DD[DeleteDropTarget]
SD[SecondaryDropTarget]
end
subgraph "Data"
DO[DragObject]
DI[DragOptions]
end
LDC --> DC
SDC -.->|system drag| DC
DC --> DL
DC --> DV
DV --> LDV
DC -->|dispatches to| WS
DC -->|dispatches to| FL
DC -->|dispatches to| HS
DC -->|dispatches to| DTB
DTB --> DD
DTB --> SD
DC --> DO
DC --> DI
SLDC -->|timer| WS
49.4.2 DragController¶
DragController is the abstract base that manages the drag lifecycle:
// src/com/android/launcher3/dragndrop/DragController.java
public abstract class DragController<T extends ActivityContext>
implements DragDriver.EventListener, TouchController {
private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
protected final T mActivity;
protected DragDriver mDragDriver = null;
public DragOptions mOptions;
protected final Point mMotionDown = new Point();
protected final Point mLastTouch = new Point();
public DropTarget.DragObject mDragObject;
private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
private final ArrayList<DragListener> mListeners = new ArrayList<>();
protected DropTarget mLastDropTarget;
The drag lifecycle:
- Pre-drag -- A long press is detected; the controller enters pre-drag mode
- Drag start -- If the user moves beyond the threshold,
DragViewis created - Drag move -- Touch events update
DragViewposition and find drop targets - Drop -- The item is released; the appropriate
DropTargetreceives it
49.4.3 DragLayer¶
DragLayer is a custom ViewGroup that sits at the root of the launcher's
view hierarchy and intercepts all touch events during a drag:
// src/com/android/launcher3/dragndrop/DragLayer.java
public class DragLayer extends BaseDragLayer<Launcher>
implements LauncherOverlayCallbacks {
public static final int ALPHA_INDEX_OVERLAY = 0;
public static final int ALPHA_INDEX_LOADER = 1;
It coordinates:
- Rendering the
DragViewabove all other content - Forwarding touch events to the
DragController - Playing drop animations
- Managing folder open/close overlay animations
49.4.4 DragView¶
DragView is the floating view that follows the user's finger during a drag:
// src/com/android/launcher3/dragndrop/DragView.java
public abstract class DragView<T extends Context & ActivityContext>
extends FrameLayout {
public static final int VIEW_ZOOM_DURATION = 150;
private final View mContent;
private final int mWidth;
private final int mHeight;
private final int mBlurSizeOutline;
protected final int mRegistrationX;
protected final int mRegistrationY;
private final float mInitialScale;
private final float mEndScale;
protected final float mScaleOnDrop;
The DragView uses spring animations for a natural feel:
The mRegistrationX/Y values represent the offset from the touch point to the
drag view's origin, ensuring the view follows the finger naturally.
49.4.5 Drop Targets¶
The DropTarget interface defines how views accept drops:
// src/com/android/launcher3/DropTarget.java
public interface DropTarget {
boolean acceptDrop(DragObject dragObject);
void onDrop(DragObject dragObject, DragOptions options);
void onDragEnter(DragObject dragObject);
void onDragOver(DragObject dragObject);
void onDragExit(DragObject dragObject);
The main drop targets are:
Workspace-- accepts icons, shortcuts, widgets on workspace pagesHotseat-- accepts icons in the bottom dockFolder-- accepts icons when dragged over a folderDeleteDropTarget-- removes items from the home screenSecondaryDropTarget-- provides "Uninstall" or "App info" actions
49.4.6 SpringLoadedDragController¶
When the user drags an item and hovers over a workspace page, the
SpringLoadedDragController manages page switching with a delay:
// src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
class SpringLoadedDragController(private val launcher: Launcher) : OnAlarmListener {
internal val alarm = Alarm().also { it.setOnAlarmListener(this) }
private var screen: CellLayout? = null
fun setAlarm(cl: CellLayout?) {
cancel()
alarm.setAlarm(
when {
cl == null -> ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
Utilities.isRunningInTestHarness() -> ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
else -> ENTER_SPRING_LOAD_HOVER_TIME
}
)
screen = cl
}
override fun onAlarm(alarm: Alarm) {
if (screen != null) {
with(launcher.workspace) {
if (!isVisible(screen) && launcher.dragController.mDistanceSinceScroll != 0) {
snapToPage(indexOfChild(screen))
}
}
} else {
launcher.dragController.cancelDrag()
}
}
companion object {
private const val ENTER_SPRING_LOAD_HOVER_TIME: Long = 500
private const val ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST: Long = 3000
private const val ENTER_SPRING_LOAD_CANCEL_HOVER_TIME: Long = 950
}
}
The 500ms hover delay before page switching is a deliberate UX choice to prevent accidental page navigation during drag operations.
49.4.7 System Drag Support¶
Launcher3 also supports Android's system drag-and-drop API for cross-app drag:
SystemDragController handles drag events that originate from outside the launcher
(e.g., dragging a file from another app onto the home screen). It creates
SystemDragItemInfo to represent the dragged content and routes it through
the standard drop target mechanism.
49.4.8 The Complete Drag Flow¶
sequenceDiagram
participant User
participant BTV as BubbleTextView
participant LDC as LauncherDragController
participant DV as DragView
participant DL as DragLayer
participant WS as Workspace
participant SLDC as SpringLoadedDragController
participant SM as StateManager
User->>BTV: Long press
BTV->>LDC: beginDrag()
LDC->>DV: create DragView
LDC->>SM: goToState(SPRING_LOADED)
SM->>WS: shrink workspace
loop Drag movement
User->>DL: touchMove(x, y)
DL->>LDC: onDriverDragMove()
LDC->>WS: onDragOver(dragObject)
WS->>WS: showReorderHint()
WS->>SLDC: setAlarm(targetPage)
end
User->>DL: touchUp(x, y)
DL->>LDC: onDriverDragEnd()
LDC->>WS: onDrop(dragObject)
WS->>WS: addItemToCell()
LDC->>SM: goToState(NORMAL)
SM->>WS: unshrink workspace
LDC->>DV: animateDrop()
49.4.9 Reorder Preview Animation¶
During drag, when items need to shift to make room, CellLayout shows reorder
preview animations:
// src/com/android/launcher3/celllayout/ReorderPreviewAnimation.java
// src/com/android/launcher3/celllayout/ReorderAlgorithm.java
The reorder algorithm computes item configurations that minimize displacement
while fitting the dragged item, and ReorderPreviewAnimation smoothly
translates items to their new positions.
49.5 Recents Integration¶
49.5.1 Launcher as Recents Provider¶
In modern Android (since Android 10), Launcher3 serves as both the home screen
and the recent-apps provider when the Quickstep module is included. The class
QuickstepLauncher extends Launcher to add recents functionality:
// quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
public class QuickstepLauncher extends Launcher {
This integration is controlled by the system property and Quickstep's
TouchInteractionService, which intercepts gesture-navigation events and
routes them to either the launcher (for going home or showing recents) or
the foreground app.
49.5.2 Architecture Overview¶
graph TD
subgraph "System UI"
TIS[TouchInteractionService]
SUI[SystemUI]
end
subgraph "Quickstep"
OCH[OverviewCommandHelper]
OCO[OverviewComponentObserver]
ASH[AbsSwipeUpHandler]
TAM[TaskAnimationManager]
end
subgraph "Launcher3 Views"
QL[QuickstepLauncher]
RV[RecentsView]
TV[TaskView]
OAV[OverviewActionsView]
end
SUI -->|gestures| TIS
TIS -->|commands| OCH
TIS -->|swipe up| ASH
OCH -->|toggle/show| RV
OCO -->|component info| OCH
ASH -->|animate| TAM
TAM -->|transition| QL
QL --> RV
RV --> TV
RV --> OAV
49.5.3 OverviewCommandHelper¶
OverviewCommandHelper manages atomic commands for showing/hiding the recents view:
// quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
class OverviewCommandHelper
@Inject
constructor(
private val touchInteractionHandler: Provider<TouchInteractionHandler>,
private val overviewComponentObserver: OverviewComponentObserver,
private val dispatcherProvider: DispatcherProvider,
private val displayRepository: DisplayRepository,
private val taskbarManager: TaskbarManager,
private val taskAnimationManagerRepository: PerDisplayRepository<TaskAnimationManager>,
@ElapsedRealtimeLong private val elapsedRealtime: () -> Long,
private val systemUiProxy: SystemUiProxy,
private val latencyTracker: LatencyTracker,
) {
private val coroutineScope =
CoroutineScope(SupervisorJob() + dispatcherProvider.lightweightBackground)
private val commandQueue = ConcurrentLinkedDeque<CommandInfo>()
In Android 17 the helper is a plain @Inject Dagger type rather than the
assisted-injected one of earlier releases: instead of receiving a
TouchInteractionService and SystemUiProxy directly it pulls a
Provider<TouchInteractionHandler>, a PerDisplayRepository<TaskAnimationManager>,
and a DisplayRepository, all of which are display-aware so a single helper
can drive overview on whichever display the gesture happened. The command
types are:
enum class CommandType {
SHOW_ALT_TAB,
HIDE_ALT_TAB,
/** Toggle between overview and the next task */
TOGGLE, // Navigate to Overview
HOME, // Navigate to Home
/**
* Toggle between Overview and the previous screen before launching Overview, which can
* either be a task or the home screen.
*/
TOGGLE_OVERVIEW_PREVIOUS,
/** Toggle between Overview and the keyboard-focused Overview task. */
TOGGLE_WITH_FOCUS,
}
The standalone SHOW_WITH_FOCUS command was removed in 17; keyboard-focused
overview is now reached through TOGGLE_WITH_FOCUS.
49.5.4 RecentsView¶
RecentsView is a horizontally-scrolling container for recent task thumbnails:
// quickstep/src/com/android/quickstep/views/RecentsView.java
public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>,
STATE_TYPE extends BaseState<STATE_TYPE>>
extends PagedView<PageIndicator> {
Key features of RecentsView:
- Task cards are
TaskViewinstances showing app thumbnails - Clear All button to dismiss all recent tasks
- Split screen initiation by dragging a task to the split placeholder
- Desktop task views for windowed/desktop mode tasks
- Grid-only overview mode where tasks are shown in a grid layout
In Android 17 RecentsView also holds a DesktopRecentsTransitionController
(quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt),
injected through its init path. When a task card is moved into desktop windowing,
RecentsView delegates to that controller's moveToDesktop, and when the
display is an external connected display it calls moveToExternalDisplay; both
run a RemoteTransition named "RecentsToDesktop" so the task animates from the
overview grid into a freeform desktop window.
49.5.5 TaskView¶
TaskView represents a single recent task:
// quickstep/src/com/android/quickstep/views/TaskView.kt
open class TaskView
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
// ... border animators, type, and fullscreen draw params
) : FrameLayout(context, attrs), ViewPool.Reusable {
Each TaskView contains:
- A task thumbnail (rendered from a recent screenshot)
- An icon chip showing the app icon
- An overlay for running state indicators
- Touch handling for launching, dismissing, and split-screen gestures
GroupedTaskView extends TaskView for split-screen task pairs, showing
two thumbnails side by side.
49.5.6 Launcher State Transitions for Recents¶
The OVERVIEW state is added by the Quickstep module:
// quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
public class OverviewState extends LauncherState {
The RecentsViewStateController handles animation between states:
Transitions between NORMAL and OVERVIEW involve:
- Scaling the workspace down
- Fading in the recents view
- Showing/hiding overview action buttons
- Adjusting the taskbar state
49.5.7 Gesture Navigation Flow¶
sequenceDiagram
participant User
participant NavBar as Navigation Bar
participant TIS as TouchInteractionService
participant ASH as AbsSwipeUpHandler
participant QL as QuickstepLauncher
participant RV as RecentsView
User->>NavBar: Swipe up from bottom
NavBar->>TIS: Gesture detected
TIS->>ASH: create handler
ASH->>ASH: Track finger movement
alt Quick swipe (go home)
ASH->>QL: goToState(NORMAL)
QL->>QL: Show home screen
else Slow swipe (show recents)
ASH->>QL: goToState(OVERVIEW)
QL->>RV: Show recents view
RV->>RV: Layout task cards
else Hold (app switch)
ASH->>QL: goToState(QUICK_SWITCH)
QL->>RV: Show task switcher
end
49.5.8 Windowed Recents: RecentsWindowManager¶
Historically overview was hosted by an Activity (RecentsActivity in the
fallback case, or the QuickstepLauncher itself in the launcher case). With
desktop windowing and connected displays, Android 17 introduces a way to host
overview in a standalone window rather than an activity, so recents can live
on a secondary display or float over a desktop without owning a task. The host
is RecentsWindowManager:
// quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt
class RecentsWindowManager
@Inject
constructor(
@WindowContext private val windowContext: Context,
private val fallbackWindowInterface: FallbackWindowInterface,
private val recentsWindowTracker: RecentsWindowTracker,
wallpaperColorHints: WallpaperColorHints,
private val systemUiProxy: SystemUiProxy,
recentsModel: RecentsModel,
private val screenOnTracker: ScreenOnTracker,
desktopState: DesktopState,
displayController: DisplayController,
@Ui private val uiExecutor: LooperExecutor,
invariantDeviceProfile: InvariantDeviceProfile,
lifeCycle: PerDisplayCleanupTask,
@Named(WINDOW_BLUR_STATE) private val blurState: ListenableRef<Boolean>,
) :
RecentsWindowContext(windowContext, wallpaperColorHints.hints, invariantDeviceProfile),
RecentsViewContainer,
StatefulContainer<RecentsState>,
ComponentCallbacks {
Instead of an Activity, RecentsWindowManager builds its own view tree with a
SurfaceControlViewHost driven by a WindowlessWindowManager, owns a
StateManager<RecentsState, RecentsWindowManager> (its own HIDDEN/visible
state machine independent of LauncherState), and implements
RecentsViewContainer so the very same RecentsView/TaskView machinery from
section 49.5.4 renders inside it. Because it is a ComponentCallbacks, it reacts
to its own configuration changes (orientation, screen size) per display.
graph TD
subgraph "Activity-hosted overview"
QL2[QuickstepLauncher / RecentsActivity]
end
subgraph "Window-hosted overview (17)"
RWM[RecentsWindowManager]
SCVH["SurfaceControlViewHost (WindowlessWindowManager)"]
RWRV[FallbackWindowRecentsView]
end
RVC[RecentsViewContainer interface]
RV3[RecentsView / TaskView]
QL2 -.implements.-> RVC
RWM -.implements.-> RVC
RWM --> SCVH
SCVH --> RWRV
RWRV --> RV3
RVC --> RV3
Which host is used is gated by RecentsWindowFlags
(quickstep/src/com/android/quickstep/window/RecentsWindowFlags.kt), whose
enableLauncherOverviewInWindow and enableFallbackOverviewInWindow
DesktopExperienceFlags wrap the enable_launcher_overview_in_window and
enable_fallback_overview_in_window aconfig flags. A per-display
RecentsWindowManager is created and torn down by RecentsWindowTracker
(quickstep/src/com/android/quickstep/window/RecentsWindowTracker.kt, a
ContextTracker) in concert with the DisplayModel/PerDisplayComponent
machinery described in section 49.6, so each display with system decorations can
get its own overview window. The matching gesture handler is
RecentsWindowSwipeHandler
(quickstep/src/com/android/quickstep/window/RecentsWindowSwipeHandler.java),
the window-hosted counterpart to AbsSwipeUpHandler.
49.5.9 Desktop App-Launch Transitions¶
When desktop windowing is active, launching an app from the home screen or
taskbar should animate the new window into a freeform desktop position rather
than full screen. Android 17 adds a dedicated transition package,
com.android.launcher3.desktop. DesktopAppLaunchTransitionManager
(quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt)
registers a RemoteTransition with SystemUI for freeform task opens and for the
window-limit "unminimize" case:
// quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
fun registerTransitions() {
if (!shouldRegisterTransitions()) return
remoteWindowLimitUnminimizeTransition =
RemoteTransition(/* ... unminimize runner ... */)
systemUiProxy.registerRemoteTransition(remoteWindowLimitUnminimizeTransition)
}
private fun shouldRegisterTransitions(): Boolean =
DesktopModeStatus.canEnterDesktopMode(context)
The actual animation is described by DesktopAppLaunchTransition
(quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt),
whose AppLaunchType enum distinguishes a fresh LAUNCH from an UNMINIMIZE,
and DesktopAppLaunchAnimatorHelper
(quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt)
builds the per-window animators. QuickstepTransitionManager wires this in: in
its remote-transition path it checks isDesktopAppLaunch(...) and, when true,
returns createDesktopAppLaunchRemoteTransition(...) so a home-screen icon tap
in desktop mode plays the desktop launch animation. The whole path is gated by
DesktopModeStatus.canEnterDesktopMode() and the
desktop_homescreen_icons_applaunch_transitions flag, so on phones the classic
full-screen launch animation is unchanged.
49.6 Taskbar¶
49.6.1 Taskbar Architecture¶
The taskbar is a persistent navigation element on large screens (tablets,
foldables, desktop mode). It exists as a separate window managed by
TaskbarActivityContext:
// quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
public class TaskbarActivityContext extends BaseTaskbarContext {
On the primary display the taskbar window is of type TYPE_NAVIGATION_BAR,
placing it at the same system UI level as the navigation bar; on a secondary
(connected) display it uses TYPE_NAVIGATION_BAR_PANEL instead
(createAllWindowParams() picks the type via isPrimaryDisplay()). It uses
FLAG_NOT_FOCUSABLE to avoid stealing input focus from foreground apps.
There is one TaskbarActivityContext per display. In Android 17 the higher-level
lifecycle (creating and destroying taskbars as displays come and go) is owned by
the TaskbarManager interface and its DisplayModel-backed implementation;
section 49.6.7 covers that per-display architecture.
49.6.2 Taskbar Controller Architecture¶
The taskbar uses a complex controller architecture where each aspect is managed by a dedicated controller:
graph TD
TAC[TaskbarActivityContext] --> TC[TaskbarControllers]
TC --> TSC[TaskbarStashController]
TC --> SHVC[StashedHandleViewController]
TC --> TDC[TaskbarDragController]
TC --> TDLC[TaskbarDragLayerController]
TC --> NBVC[NavbarButtonsViewController]
TC --> TIC[TaskbarInsetsController]
TC --> TASC[TaskbarAutohideSuspendController]
TC --> LTUC[LauncherTaskbarUIController]
TC --> TDMC[TaskbarDesktopModeController]
TC --> THTTC[TaskbarHoverToolTipController]
Key controllers:
TaskbarStashController-- manages stashing/unstashing the taskbarStashedHandleViewController-- manages the small handle shown when stashedNavbarButtonsViewController-- manages the back/home/recents buttonsTaskbarDragController-- handles drag from taskbar to workspaceTaskbarInsetsController-- reports insets to the system
49.6.3 StashedHandleViewController¶
When the taskbar is stashed (hidden), a small handle is displayed that can be swiped to reveal it:
// quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
public class StashedHandleViewController
implements TaskbarControllers.LoggableTaskbarController, NavHandle {
public static final int ALPHA_INDEX_STASHED = 0;
public static final int ALPHA_INDEX_HOME_DISABLED = 1;
public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2;
public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3;
public static final int ALPHA_INDEX_NUDGED = 4;
public static final int ALPHA_INDEX_ALL_SET_TRANSITION = 5;
private static final int NUM_ALPHA_CHANNELS = 6;
The stashed handle has multiple alpha channels that control its visibility in different scenarios. The handle uses region sampling to adapt its color to the underlying content.
49.6.4 Taskbar on Different Form Factors¶
The taskbar adapts to different device types:
| Form Factor | Behavior |
|---|---|
| Phone | No taskbar; uses gesture nav bar |
| Tablet | Persistent taskbar with app icons |
| Foldable | Taskbar appears in unfolded state |
| Desktop mode | Full-featured taskbar with overflow |
| Connected display | Separate taskbar per display |
TaskbarDesktopModeController
(quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt)
handles desktop-specific behavior. It registers itself as a
DesktopVisibilityController.DesktopVisibilityListener and exposes per-display
queries that the rest of the taskbar reads:
isInDesktopMode(displayId)andisInDesktopModeAndNotInOverview(displayId), delegating to the app-singletonDesktopVisibilityController(quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt)shouldShowDesktopTasksInTaskbar(displayId), which decides whether running desktop tasks appear in the taskbar (true in desktop mode or on a freeform display)onTaskbarCornerRoundingUpdate(...), which animates the taskbar's corner radius when an adjacent desktop window needs rounding- a
DisplayControllerlistener so it re-evaluates state when the display configuration changes
Note that the controller is constructed per TaskbarActivityContext, so each
display's taskbar gets its own desktop-mode controller scoped to that display.
49.6.5 Taskbar-Launcher Communication¶
The taskbar communicates with the launcher through LauncherTaskbarUIController:
This controller synchronizes:
- Icon state between taskbar and launcher
- Stash state based on launcher state changes
- Drag operations between taskbar and workspace
- All-apps page progress for smooth transitions
49.6.6 Taskbar Icon Population¶
Taskbar icons are loaded from the same model as the hotseat. The
TaskbarInteractor manages the data flow:
Running app state is tracked and displayed as a dot indicator under running
app icons, using BubbleTextView.RunningAppState:
// src/com/android/launcher3/BubbleTextView.java
public enum RunningAppState {
NOT_RUNNING,
RUNNING,
MINIMIZED,
}
49.6.7 Per-Display Taskbar¶
On phones there is one taskbar (or none), but desktop windowing and connected
displays mean a device can show several displays with system decorations at once,
each needing its own taskbar. Android 17 makes the taskbar per-display by
splitting the manager into an interface plus an implementation and giving the
implementation a DisplayModel. TaskbarManager
(quickstep/src/com/android/launcher3/taskbar/TaskbarManager.kt) is now an
interface whose methods take a displayId, for example:
// quickstep/src/com/android/launcher3/taskbar/TaskbarManager.kt
interface TaskbarManager {
fun getTaskbarForDisplay(displayId: Int): TaskbarActivityContext?
fun setWallpaperVisible(displayId: Int, isVisible: Boolean)
fun onSystemUiFlagsChanged(@SystemUiStateFlags systemUiStateFlags: Long, displayId: Int)
fun getTaskbarInteractor(displayId: Int): TaskbarInteractor?
// ...
}
The concrete logic lives in TaskbarManagerImpl
(quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java, reached
through the TaskbarManagerImplWrapper), which owns a
DisplayModel<PerDisplayTaskbarResource>:
// quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java
private final DisplayModel<PerDisplayTaskbarResource> mResources;
Each PerDisplayTaskbarResource
(quickstep/src/com/android/launcher3/taskbar/PerDisplayTaskbarResource.kt)
implements DisplayModel.DisplayResource and owns one display's
TaskbarActivityContext, its root layout, window-manager view, and an
isExternalDisplay flag. The DisplayModel
(quickstep/src/com/android/quickstep/DisplayModel.kt) is the generic registry
that creates a resource when a display gains system decorations
(onDisplayAddSystemDecorations) and tears it down on
onDisplayRemoved/onDisplayRemoveSystemDecorations:
// quickstep/src/com/android/quickstep/DisplayModel.kt
class DisplayModel<RESOURCE_TYPE : DisplayResource>
@AssistedInject
constructor(/* ... */) : DisplayDecorationListener, SafeCloseable {
override fun onDisplayAddSystemDecorations(displayId: Int) { storeDisplayResource(displayId) }
override fun onDisplayRemoved(displayId: Int) { deleteDisplayResource(displayId) }
fun getDisplayResource(displayId: Int): RESOURCE_TYPE? { /* ... */ }
fun forEach(callback: Consumer<RESOURCE_TYPE>) { /* ... */ }
interface DisplayResource { fun cleanup(); fun dump(prefix: String, writer: PrintWriter) }
}
This per-display model is shared infrastructure. The taskbar uses it for its
PerDisplayTaskbarResources, and as shown in section 49.5.8 the same kind of
display tracking governs the per-display RecentsWindowManager. Dagger backs it
with a PerDisplayComponent/PerDisplaySingleton scope
(quickstep/src/com/android/launcher3/dagger/PerDisplayComponent.kt) so each
display's controllers are injected into a subgraph scoped to that display and
cleaned up via PerDisplayCleanupTask when the display goes away.
graph TD
TM[TaskbarManager interface] --> TMW[TaskbarManagerImplWrapper]
TMW --> TMI[TaskbarManagerImpl]
TMI --> DM["DisplayModel<PerDisplayTaskbarResource>"]
DM --> R0["PerDisplayTaskbarResource (display 0)"]
DM --> R1["PerDisplayTaskbarResource (external display)"]
R0 --> TAC0[TaskbarActivityContext]
R1 --> TAC1[TaskbarActivityContext]
TAC0 --> C0[TaskbarControllers]
TAC1 --> C1[TaskbarControllers]
Whether a connected display gets its own taskbar at all is gated by
enable_taskbar_connected_displays
(frameworks/base/core/java/android/window/flags/lse_desktop_experience.aconfig,
namespace lse_desktop_experience); when the flag is off, the per-display path
above still runs but only the primary display's taskbar is created.
Two desktop-class taskbar features round this out. When more recent apps are
open than fit on the taskbar, TaskbarOverflowView
(quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java) draws an
overflow item that collapses the surplus icons into one chip and expands them on
tap, gated by enable_taskbar_overflow (same lse_desktop_experience
namespace). The keyboard task switcher is KeyboardQuickSwitchController
(quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java,
with its KeyboardQuickSwitchView and KeyboardQuickSwitchViewController), the
Alt+Tab switcher that cycles through recent tasks. Its
enable_alt_tab_kqs_flatenning flag (read through TaskbarDesktopExperienceFlags)
flattens the switcher so the apps are shown in a single non-grouped list.
49.7 Search Integration¶
49.7.1 Search Architecture¶
The All Apps drawer includes an integrated search system with a pluggable architecture:
graph TD
subgraph "UI Layer"
ACL[AppsSearchContainerLayout]
SRV[SearchRecyclerView]
end
subgraph "Controller"
AASBC[AllAppsSearchBarController]
STC[SearchTransitionController]
end
subgraph "Algorithm"
SA[SearchAlgorithm Interface]
DASA[DefaultAppSearchAlgorithm]
ExtSearch[External Search Provider]
end
subgraph "Adapter"
SAP[SearchAdapterProvider]
DSAP[DefaultSearchAdapterProvider]
end
ACL --> AASBC
AASBC --> SA
SA --> DASA
SA -.-> ExtSearch
SRV --> SAP
SAP --> DSAP
49.7.2 AllAppsSearchBarController¶
The search bar controller manages text input and search dispatching:
// src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
public class AllAppsSearchBarController
implements TextWatcher, OnEditorActionListener,
ExtendedEditText.OnBackKeyListener {
protected SearchAlgorithm<AdapterItem> mSearchAlgorithm;
protected SearchCallback<AdapterItem> mCallback;
protected ExtendedEditText mInput;
protected String mQuery;
Initialization connects the controller to the search algorithm and UI:
public final void initialize(
SearchAlgorithm<AdapterItem> searchAlgorithm,
ExtendedEditText input,
ActivityContext launcher,
SearchCallback<AdapterItem> callback) {
mCallback = callback;
mLauncher = launcher;
mInput = input;
mInput.addTextChangedListener(this);
mInput.setOnEditorActionListener(this);
mInput.setOnBackKeyListener(this);
mSearchAlgorithm = searchAlgorithm;
}
49.7.3 SearchAlgorithm Interface¶
The SearchAlgorithm interface allows different search implementations:
// src/com/android/launcher3/search/SearchAlgorithm.java
public interface SearchAlgorithm<T> {
void doSearch(String query, SearchCallback<T> callback);
void cancel(boolean interruptActiveRequests);
}
49.7.4 DefaultAppSearchAlgorithm¶
The built-in search performs case-insensitive title matching:
// src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
public class DefaultAppSearchAlgorithm implements SearchAlgorithm<AdapterItem> {
private static final int MAX_RESULTS_COUNT = 5;
@Override
public void doSearch(String query, SearchCallback<AdapterItem> callback) {
mAppState.getModel().enqueueModelUpdateTask(
(taskController, dataModel, apps) -> {
ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
if (mAddNoResultsMessage && result.isEmpty()) {
result.add(getEmptyMessageAdapterItem(query));
}
mResultHandler.post(() -> callback.onSearchResult(query, result));
});
}
The search runs on the model thread to safely access AllAppsList.data, then
delivers results back on the main thread. StringMatcherUtility provides the
matching logic, supporting substring matching with word boundary awareness.
49.7.5 Search Transition¶
When the user types a search query, SearchTransitionController animates
the All Apps view from the alphabetical list to search results:
The transition involves:
- Hiding the alphabetical fast scroller
- Switching the RecyclerView adapter to the search adapter
- Animating the tab indicator off-screen
- Adjusting the header height
49.7.6 External Search Providers¶
Launcher3 supports external search via SearchUiManager:
OEMs and Google Search can provide custom search experiences by implementing
the AllAppsSearchUiDelegate interface, which controls:
- The search input layout
- The search result adapter
- The search algorithm implementation
The qsb/ package provides the Quick Search Bar integration on the workspace,
which is a separate search entry point that typically launches Google Search.
49.7.7 App Prediction and the AppPredictionService¶
The suggested apps that fill the prediction row at the top of All Apps and the
predicted slots in the Hotseat do not come from Launcher3. Launcher3 is the client
of a system AppPredictionService; a separate app supplies the predictions.
On the Launcher side, QuickstepModelDelegate opens prediction sessions through
the framework AppPredictor API, one per surface, tagged with a UI surface string:
Source file: quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
// QuickstepModelDelegate.recreatePredictors()
mAllPredictionAppsState.registerPredictor(mContext,
new AppPredictionContext.Builder(mContext)
.setUiSurface("home") // All Apps prediction row
.setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
.build(),
mModel, PredictionUpdateTask::new);
// ... and a second session with setUiSurface("hotseat")
Each launch is reported back to the service as an AppTargetEvent, and the service
pushes a fresh list of AppTargets that Launcher3 renders through
appprediction/PredictionRowView.java (All Apps) and the hotseat predictor.
The service behind these sessions is selectable by the device. AOSP ships a minimal
reference implementation in packages/apps/OnDeviceAppPrediction, package
com.android.apppredictionservice. Its single class, PredictionService, extends
android.service.appprediction.AppPredictionService and is registered for the
android.service.appprediction.AppPredictionService action:
Source file: packages/apps/OnDeviceAppPrediction/src/com/android/apppredictionservice/PredictionService.java
public class PredictionService extends AppPredictionService {
@Override
public void onCreatePredictionSession(
AppPredictionContext context, AppPredictionSessionId sessionId) {
if (context.getUiSurface().equals("home")
|| context.getUiSurface().equals("overview")) {
activeLauncherSessions.add(sessionId);
postPredictionUpdate(sessionId);
}
}
}
Its logic is deliberately simple: it keeps the five most recently launched apps,
seeded on first boot from the default calendar, gallery, maps, email, and browser
handlers, and moves an app to the front of the list on each onAppTargetEvent.
There is no on-device model. A production build replaces this with a Google or OEM
predictor that ranks by usage history and context. The reference app exists so the
prediction row has something to show on a stock AOSP image; its README notes that
the project is unsupported and slated for removal from the manifest.
49.8 Folder System¶
49.8.1 Folder Architecture¶
Folders allow grouping multiple app icons. The system involves three key components:
graph TD
subgraph "Workspace Representation"
FI[FolderIcon]
PB[PreviewBackground]
PIM[PreviewItemManager]
end
subgraph "Open Folder View"
F[Folder]
FPV[FolderPagedView]
FNE[FolderNameEditText]
end
subgraph "Data"
FInfo[FolderInfo]
FGO[FolderGridOrganizer]
FNP[FolderNameProvider]
end
FI -->|on tap| F
FI --> PB
FI --> PIM
F --> FPV
F --> FNE
FI --> FInfo
FPV --> FGO
FNP -->|suggests names| FNE
49.8.2 FolderIcon¶
FolderIcon is the view displayed on the workspace representing a folder:
// src/com/android/launcher3/folder/FolderIcon.java
public class FolderIcon extends FrameLayout implements FloatingIconViewCompanion,
DraggableView, Reorderable, Poppable {
@Thunk ActivityContext mActivity;
@Thunk Folder mFolder;
public FolderInfo mInfo;
static final int DROP_IN_ANIMATION_DURATION = 400;
public static final boolean SPRING_LOADING_ENABLED = true;
private static final int ON_OPEN_DELAY = 800;
The icon displays a preview of up to 4 items (controlled by
MAX_NUM_ITEMS_IN_PREVIEW) in a clipped layout managed by
ClippedFolderIconLayoutRule:
// src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
private static final float MAX_RADIUS_DILATION = 0.25f;
public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f); // 1.125
When an item is dragged over a FolderIcon, spring loading causes the folder
to open after an 800ms delay (ON_OPEN_DELAY).
49.8.3 FolderInfo: The Data Model¶
FolderInfo holds the folder's contents:
// src/com/android/launcher3/model/data/FolderInfo.java
public class FolderInfo extends CollectionInfo {
public FolderNameInfos suggestedFolderNames;
private final ArrayList<ItemInfo> contents = new ArrayList<>();
// ... accessed through getContents()
The contents list is private and reached through getContents();
suggestedFolderNames is a FolderNameInfos (which can hold several ranked label
candidates), not a plain CharSequence. The willAcceptItemType static method
determines which item types can be placed in a folder:
public static boolean willAcceptItemType(int itemType) {
return itemType == ITEM_TYPE_APPLICATION
|| itemType == ITEM_TYPE_DEEP_SHORTCUT
|| itemType == ITEM_TYPE_APP_GROUP;
}
49.8.4 Folder: The Open View¶
Folder is an AbstractFloatingView that appears when a folder icon is tapped:
// src/com/android/launcher3/folder/Folder.java
public class Folder extends AbstractFloatingView implements
ClipPathView, DragSource, DragListener {
The Folder view tracks its own open/close lifecycle with an @IntDef over three
states (there is no notion of distinct "folder types" here):
// src/com/android/launcher3/folder/Folder.java
public static final int STATE_CLOSED = 0;
public static final int STATE_ANIMATING = 1;
public static final int STATE_OPEN = 2;
@IntDef({STATE_CLOSED, STATE_ANIMATING, STATE_OPEN})
public @interface FolderState {}
The folder view includes:
- A
FolderPagedViewfor paging through items - A
FolderNameEditTextfor editing the folder name - Page indicators for multi-page folders
- Drag-and-drop support for reordering items within the folder
49.8.5 FolderPagedView¶
FolderPagedView extends PagedView to display folder contents in a grid:
// src/com/android/launcher3/folder/FolderPagedView.java
public class FolderPagedView extends PagedView<PageIndicatorDots>
implements ClipPathView {
private static final int REORDER_ANIMATION_DURATION = 230;
private static final int START_VIEW_REORDER_DELAY = 30;
private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
private final FolderGridOrganizer mOrganizer;
private int mGridCountX;
private int mGridCountY;
Each page in the folder is a CellLayout with the folder's grid dimensions
(typically 3x4 or 4x4 depending on the device profile).
49.8.6 FolderGridOrganizer¶
FolderGridOrganizer manages item positions based on rank:
// src/com/android/launcher3/folder/FolderGridOrganizer.java
public class FolderGridOrganizer {
private final int mMaxCountX;
private final int mMaxCountY;
private final int mMaxItemsPerPage;
private int mNumItemsInFolder;
private int mCountX;
private int mCountY;
public static FolderGridOrganizer createFolderGridOrganizer(DeviceProfile profile) {
return new FolderGridOrganizer(
profile.getFolderProfile().getNumColumns(),
profile.getFolderProfile().getNumRows()
);
}
The organizer dynamically adjusts the grid size based on content count:
- 1 item: 1x1 grid
- 2-3 items: 2x2 grid
- 4+ items: full grid dimensions
49.8.7 Auto-Organize and Folder Naming¶
When items are dragged together to create a folder, the system automatically
suggests a folder name using FolderNameProvider:
// src/com/android/launcher3/folder/FolderNameProvider.java
public class FolderNameProvider {
public static final int SUGGEST_MAX = 4;
@Inject
public FolderNameProvider() {
Preconditions.assertWorkerThread();
}
The naming algorithm examines the apps in the folder and attempts to find a
common category. It uses information from the model and can provide up to 4
name suggestions. The FolderNameSuggestionLoader coordinates loading suggestions
asynchronously:
49.8.8 Folder Open/Close Animation¶
The folder open animation is managed by FolderAnimationManager:
The animation includes:
- Preview-to-folder -- the small preview icons scale up to the full folder
- Background reveal -- the folder background circle expands
- Content fade-in -- folder items fade in with a stagger
- Scrim darkening -- the background dims behind the folder
Spring animations (FolderSpringAnimatorSet) provide a bouncy, natural feel:
The close animation reverses these steps. FolderOpenCloseAnimationListener
handles callbacks for animation lifecycle events.
49.8.9 Folder Creation via Drag¶
When the user drags one icon over another on the workspace, a folder is created:
sequenceDiagram
participant User
participant WS as Workspace
participant CL as CellLayout
participant L as Launcher
participant MW as ModelWriter
participant FI as FolderIcon
User->>WS: Drag icon A over icon B
WS->>CL: Detect overlap
CL->>WS: Report merge candidate
WS->>L: createFolder(itemA, itemB, cell)
L->>MW: addToFolder(folderInfo)
MW->>MW: Write to database
L->>FI: Create FolderIcon
FI->>FI: Animate preview
WS->>WS: Replace icons with FolderIcon
49.9 Theming¶
49.9.1 ThemeManager¶
The ThemeManager is a Dagger singleton that centralizes icon theming:
// src/com/android/launcher3/graphics/ThemeManager.kt
@LauncherAppSingleton
class ThemeManager
@Inject
constructor(
@ApplicationContext private val context: Context,
@Ui private val uiExecutor: LooperExecutor,
private val prefs: LauncherPrefs,
private val themePreference: ThemePreference,
@Named(ICON_FACTORY_DAGGER_KEY)
private val iconThemeFactories: Map<String, IconThemeFactory>,
@Ui mainExecutor: LooperExecutor,
lifecycle: DaggerSingletonTracker,
) {
private val _iconShapeData = MutableListenableRef(IconShape.EMPTY)
val iconShapeData: ListenableRef<IconShape> = _iconShapeData.asListenable()
var iconState = parseIconState(null)
The ThemeManager manages:
- Icon shape -- the adaptive icon mask shape (circle, squircle, etc.)
- Icon theme -- monochrome/themed icon rendering
- Folder shape -- the shape used for folder backgrounds
- Theme controller -- coordinates icon recoloring
49.9.2 Dynamic Color (Material You)¶
Launcher3 integrates with Android's Material You dynamic color system. The color pipeline extracts colors from the wallpaper and applies them throughout the UI.
WallpaperThemeManager is initialized in Launcher.onCreate():
// src/com/android/launcher3/Launcher.java
mWallpaperThemeManager = new WallpaperThemeManager(this);
The wallpaper colors flow through the system:
graph LR
WP[Wallpaper] -->|color extraction| WCE[WallpaperColors]
WCE -->|to system| DCS[Dynamic Color Scheme]
DCS -->|themed attrs| LA[Launcher Activity]
LA -->|apply| WS[Workspace Scrim]
LA -->|apply| AA[AllApps Background]
LA -->|apply| TB[Taskbar Background]
DCS -->|icon tinting| IC[Icon Cache]
IC -->|mono icons| BTV[BubbleTextView]
49.9.3 Themed Icons¶
When themed icons are enabled, the ThemeManager applies monochrome icon
rendering:
// src/com/android/launcher3/graphics/ThemeManager.kt
@Deprecated("Use [ThemePreference] instead")
var isMonoThemeEnabled
set(value) = themePreference.setValue(if (value) MONO_THEME_VALUE else null)
get() = MONO_THEME_VALUE == themePreference.value
The themed icon pipeline:
- Check if the app provides a monochrome icon in its
AdaptiveIconDrawable - If available, extract the monochrome layer
- Tint it with the wallpaper-derived palette color
- Cache the themed version in the icon database
Apps that do not provide a monochrome layer receive a fallback treatment (the full-color icon may be desaturated or overlaid).
49.9.4 Icon Shapes¶
Icon shapes are defined via ShapeDelegate and loaded from the system overlay:
The ShapesProvider loads available shapes:
Supported shapes include circles, rounded squares, squircles, teardrops, and custom SVG-based paths. The icon shape affects:
- App icon clipping
- Folder icon background
- Widget corner radius
- Notification dot positioning
49.9.5 Scrim and Background Treatment¶
Scrim views provide the visual background treatment:
// src/com/android/launcher3/graphics/Scrim.java
// src/com/android/launcher3/graphics/SysUiScrim.java
SysUiScrim manages the gradient scrim over the system bars, while
the all-apps scrim provides the dark overlay when the drawer opens.
The PillColorProvider generates colors for rounded-pill UI elements:
49.9.6 Wallpaper-Based Colors¶
The LocalColorExtractor extracts colors from the wallpaper behind each widget:
This allows widgets to adapt their appearance to the wallpaper region they cover, providing a cohesive visual experience across the home screen.
49.9.7 Dark Mode Support¶
Launcher3 responds to system dark mode changes via CONFIG_UI_MODE:
// src/com/android/launcher3/Launcher.java (imports)
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
Dark mode affects:
- Workspace page indicators
- All-apps drawer background and text colors
- Folder backgrounds
- Widget background tinting
- Scrim colors and opacity
- Taskbar appearance
The Themes utility class provides helpers for reading themed attributes:
49.10 Try It: Customize the Launcher Grid¶
This section walks through modifying the Launcher3 grid configuration to create a custom layout. We will change the default phone grid from 4x5 to 6x5 and adjust icon sizes accordingly.
49.10.1 Understanding the Grid System¶
The grid is defined in two files:
res/xml/device_profiles.xml-- declares grid options with row/column countsInvariantDeviceProfile.java-- parses and selects the appropriate grid
The XML defines grid options like this:
<!-- res/xml/device_profiles.xml -->
<grid-option
launcher:name="4_by_4"
launcher:numRows="4"
launcher:numColumns="4"
launcher:numFolderRows="3"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="4"
launcher:dbFile="launcher_4_by_4.db"
launcher:defaultLayoutId="@xml/default_workspace_4x4"
launcher:deviceCategory="phone" >
<display-option
launcher:name="Super Short Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="300"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:allAppsBorderSpace="16"
launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
49.10.2 Step 1: Add a New Grid Option¶
Add a new grid-option entry in res/xml/device_profiles.xml:
<grid-option
launcher:name="6_by_5_custom"
launcher:numRows="5"
launcher:numColumns="6"
launcher:numFolderRows="3"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="6"
launcher:dbFile="launcher_6_by_5_custom.db"
launcher:defaultLayoutId="@xml/default_workspace_6x5"
launcher:deviceCategory="phone" >
<display-option
launcher:name="Custom Dense Grid"
launcher:minWidthDps="300"
launcher:minHeightDps="500"
launcher:iconImageSize="40"
launcher:iconTextSize="11.0"
launcher:allAppsBorderSpace="12"
launcher:allAppsCellHeight="88"
launcher:canBeDefault="true" />
</grid-option>
Key parameters:
numRows="5"andnumColumns="6"-- defines the 6x5 gridiconImageSize="40"-- smaller icons (48dp is the default)iconTextSize="11.0"-- smaller text to fit more columnsnumHotseatIcons="6"-- matches the column countallAppsCellHeight="88"-- compact cells for the all-apps drawer
49.10.3 Step 2: Create a Default Layout¶
Create res/xml/default_workspace_6x5.xml with the initial home screen content:
<?xml version="1.0" encoding="utf-8"?>
<favorites
xmlns:launcher="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- First row: favorite apps -->
<favorite
launcher:packageName="com.android.dialer"
launcher:className="com.android.dialer.main.impl.MainActivity"
launcher:container="-101"
launcher:screen="0"
launcher:x="0"
launcher:y="0" />
<favorite
launcher:packageName="com.android.contacts"
launcher:className="com.android.contacts.activities.PeopleActivity"
launcher:container="-101"
launcher:screen="0"
launcher:x="1"
launcher:y="0" />
<!-- Hotseat items -->
<favorite
launcher:packageName="com.android.messaging"
launcher:className="com.android.messaging.ui.conversationlist.ConversationListActivity"
launcher:container="-101"
launcher:screen="0"
launcher:x="2"
launcher:y="0" />
</favorites>
49.10.4 Step 3: Update Grid Selection Logic¶
In InvariantDeviceProfile.java, the grid selection uses the GRID_NAME preference.
To force your custom grid during development, temporarily modify the initialization:
The relevant file is:
The IDP reads the grid preference with:
// InvariantDeviceProfile initialization
LauncherPrefs prefs = ...;
String gridName = prefs.get(GRID_NAME);
You can set the grid name to "6_by_5_custom" via the launcher settings UI
or by writing the preference directly in a debug build:
// In a test or debug setup:
LauncherPrefs.getPrefs(context)
.edit()
.putString("idp_grid_name", "6_by_5_custom")
.apply();
49.10.5 Step 4: Adjust Responsive Specs¶
For the denser grid, create or modify responsive spec XML files. The workspace cell spec controls how much space each cell gets:
Create res/xml/spec_workspace_6_by_5_custom.xml:
<?xml version="1.0" encoding="utf-8"?>
<responsive-specs>
<workspace-spec>
<cell-size
launcher:maxAvailableSize="600"
launcher:iconSize="40dp"
launcher:iconTextSize="11sp"
launcher:iconDrawablePadding="4dp" />
<cell-size
launcher:maxAvailableSize="1200"
launcher:iconSize="44dp"
launcher:iconTextSize="12sp"
launcher:iconDrawablePadding="5dp" />
</workspace-spec>
</responsive-specs>
49.10.6 Step 5: Build and Test¶
Build the modified launcher:
To test on an emulator, push the APK:
adb install -r out/target/product/<device>/system/priv-app/Launcher3/Launcher3.apk
adb shell am force-stop com.android.launcher3
49.10.7 Step 6: Verify Grid Metrics¶
Launch the Settings app on the device, navigate to the Launcher settings, and select the custom grid. Alternatively, use the customization surface:
- Long-press on the home screen to enter Edit Mode
- The workspace should show the 6-column grid
- Verify that icons are smaller but still readable
- Check that the hotseat shows 6 slots
- Open a folder and verify the 4x3 folder grid
49.10.8 Understanding the Grid Calculation¶
When a grid option is selected, InvariantDeviceProfile computes the device
profile through interpolation between defined display options:
// InvariantDeviceProfile.java
private static final float KNEARESTNEIGHBOR = 3;
private static final float WEIGHT_POWER = 5;
private static final float WEIGHT_EFFICIENT = 100000f;
The algorithm:
- Find the
Knearest display options (by screen dimension distance) - Weight each option inversely proportional to distance raised to
WEIGHT_POWER - Interpolate icon size, text size, and spacing between the options
This ensures smooth scaling across different screen sizes within a grid option.
49.10.9 Advanced: Adding a Two-Panel Grid¶
For foldable devices, you can define a two-panel grid option with separate
portrait and landscape configurations. There are four size indices, defined in
DeviceTypedMap (InvariantDeviceProfile.java only imports them):
// src/com/android/launcher3/deviceprofile/parser/DeviceTypedMap.kt
const val INDEX_DEFAULT: Int = 0 // Portrait
const val INDEX_LANDSCAPE: Int = 1 // Landscape
const val INDEX_TWO_PANEL_PORTRAIT: Int = 2 // Two-panel portrait
const val INDEX_TWO_PANEL_LANDSCAPE: Int = 3 // Two-panel landscape
Border spaces, cell heights, and other dimensions can be specified independently for each index, allowing fine-grained control over the layout in each configuration.
49.10.10 Key Files Reference¶
For the grid customization exercise, these are the essential files:
| File | Purpose |
|---|---|
res/xml/device_profiles.xml |
Grid option definitions |
src/.../InvariantDeviceProfile.java |
Grid selection and interpolation |
src/.../DeviceProfile.java |
Runtime layout computation |
src/.../CellLayout.java |
Grid cell rendering |
src/.../Workspace.java |
Page-level grid management |
src/.../Hotseat.java |
Bottom row grid |
res/xml/default_workspace_*.xml |
Default workspace layouts |
src/.../responsive/*.kt |
Responsive spec system |
Summary¶
This chapter has explored the Launcher3 codebase in AOSP, covering:
-
Architecture (Section 49.1): The model-view separation between
LauncherModel(data loading onMODEL_EXECUTOR) and the view hierarchy rooted atLauncher. TheStateManagerdrives animated transitions between states like NORMAL, ALL_APPS, SPRING_LOADED, and OVERVIEW. Dagger dependency injection manages the singleton graph. -
App Icons and Grid (Section 49.2): The
ItemInfohierarchy represents all launcher items.CellLayoutprovides the grid container,BubbleTextViewrenders icons, and theDeviceProfile/InvariantDeviceProfilesystem adapts the layout to different screen sizes via XML-defined grid options and responsive specifications. -
Widget System (Section 49.3):
LauncherWidgetHolderwrapsAppWidgetHostfor lifecycle-aware widget management. The widget picker is now a standalone Jetpack Compose activity (WidgetPickerActivity+ themodules/widgetpicker/catalogs), backed byWidgetPickerDataProvider/WidgetPickerData, having replaced the oldWidgetsFullSheet/WidgetsListAdapter/WidgetCellviews. The pinning flow still runs the picked widget throughPendingItemDragHelper, binding, configuration, and resize. -
Drag and Drop (Section 49.4):
DragControllermanages the drag lifecycle withDragViewas the visual feedback andDragLayeras the intercept layer.SpringLoadedDragControllerhandles delayed page switching. Drop targets includeWorkspace,Folder,Hotseat, andDeleteDropTarget. -
Recents Integration (Section 49.5):
QuickstepLauncherextendsLauncherto serve as the recents provider.OverviewCommandHelperprocesses commands,RecentsViewdisplays task cards, andTaskViewrenders individual tasks. Gesture navigation flows throughTouchInteractionService. -
Taskbar (Section 49.6):
TaskbarActivityContextmanages a separate window for the taskbar on large screens. Multiple controllers handle stashing, drag-and-drop, desktop mode, and appearance.StashedHandleViewControllershows the handle when the taskbar is hidden. -
Search Integration (Section 49.7):
AllAppsSearchBarControllerdispatches queries toSearchAlgorithmimplementations.DefaultAppSearchAlgorithmperforms title matching on the model thread. External providers can replace the search implementation. -
Folder System (Section 49.8):
FolderIconrepresents folders on the workspace with a 4-item preview.Folderis the expanded view containingFolderPagedViewfor paged content.FolderNameProvidersuggests names based on app categories. Spring animations provide natural folder open/close transitions. -
Theming (Section 49.9):
ThemeManagercentralizes icon shape and theme management. Material You integration extracts wallpaper colors for dynamic theming. Themed icons use monochrome layers tinted with the palette.LocalColorExtractoradapts widget backgrounds to the wallpaper. -
Grid Customization (Section 49.10): A hands-on exercise for adding a custom 6x5 grid by modifying
device_profiles.xml, creating default layouts, and adjusting responsive specs.
Key Source Paths¶
All paths relative to packages/apps/Launcher3/:
| Component | Path |
|---|---|
| Launcher activity | src/com/android/launcher3/Launcher.java |
| Workspace | src/com/android/launcher3/Workspace.java |
| CellLayout | src/com/android/launcher3/CellLayout.java |
| BubbleTextView | src/com/android/launcher3/BubbleTextView.java |
| Hotseat | src/com/android/launcher3/Hotseat.java |
| LauncherModel | src/com/android/launcher3/LauncherModel.kt |
| LauncherAppState | src/com/android/launcher3/LauncherAppState.kt |
| InvariantDeviceProfile | src/com/android/launcher3/InvariantDeviceProfile.java |
| DeviceProfile | src/com/android/launcher3/DeviceProfile.java |
| LauncherState | src/com/android/launcher3/LauncherState.java |
| StateManager | src/com/android/launcher3/statemanager/StateManager.java |
| DragController | src/com/android/launcher3/dragndrop/DragController.java |
| DragLayer | src/com/android/launcher3/dragndrop/DragLayer.java |
| DragView | src/com/android/launcher3/dragndrop/DragView.java |
| SpringLoadedDragController | src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt |
| FolderIcon | src/com/android/launcher3/folder/FolderIcon.java |
| Folder | src/com/android/launcher3/folder/Folder.java |
| FolderPagedView | src/com/android/launcher3/folder/FolderPagedView.java |
| FolderGridOrganizer | src/com/android/launcher3/folder/FolderGridOrganizer.java |
| FolderNameProvider | src/com/android/launcher3/folder/FolderNameProvider.java |
| LauncherWidgetHolder | src/com/android/launcher3/widget/LauncherWidgetHolder.java |
| LauncherAppWidgetHost | src/com/android/launcher3/widget/LauncherAppWidgetHost.java |
| WidgetPickerActivity | src/com/android/launcher3/widgetpicker/WidgetPickerActivity.kt |
| WidgetPickerComposeWrapper | src/com/android/launcher3/widgetpicker/WidgetPickerComposeWrapper.kt |
| QuickstepWidgetPickerActivity | quickstep/src/com/android/launcher3/QuickstepWidgetPickerActivity.kt |
| Widget catalogs (Compose) | modules/widgetpicker/src/com/android/launcher3/widgetpicker/ui/ |
| WidgetPickerDataProvider | src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt |
| ThemeManager | src/com/android/launcher3/graphics/ThemeManager.kt |
| AllAppsContainer | src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java |
| AlphabeticalAppsList | src/com/android/launcher3/allapps/AlphabeticalAppsList.java |
| SearchBarController | src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java |
| DefaultSearch | src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java |
| ItemInfo | src/com/android/launcher3/model/data/ItemInfo.java |
| WorkspaceItemInfo | src/com/android/launcher3/model/data/WorkspaceItemInfo.java |
| Grid profiles | res/xml/device_profiles.xml |
| QuickstepLauncher | quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java |
| RecentsView | quickstep/src/com/android/quickstep/views/RecentsView.java |
| TaskView | quickstep/src/com/android/quickstep/views/TaskView.kt |
| OverviewCommandHelper | quickstep/src/com/android/quickstep/OverviewCommandHelper.kt |
| TaskbarActivityContext | quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java |
| StashedHandleVC | quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java |
| TaskbarManager (interface) | quickstep/src/com/android/launcher3/taskbar/TaskbarManager.kt |
| TaskbarManagerImpl | quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java |
| PerDisplayTaskbarResource | quickstep/src/com/android/launcher3/taskbar/PerDisplayTaskbarResource.kt |
| TaskbarDesktopModeController | quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt |
| DisplayModel | quickstep/src/com/android/quickstep/DisplayModel.kt |
| RecentsWindowManager | quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt |
| RecentsWindowFlags | quickstep/src/com/android/quickstep/window/RecentsWindowFlags.kt |
| DesktopAppLaunchTransitionManager | quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt |
| DesktopRecentsTransitionController | quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt |