Technical Documentation: LawnchairLauncher/lawnchair
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (README.md)
Lawnchair 16
Lawnchair is a free, open-source home app for Android. Taking Launcher3βAndroidβs default home appβas a starting point, it ports Pixel Launcher features and introduces rich customization options.
This branch houses the codebase of Lawnchair 16, which is currently in development and is based on Launcher3 from Android 16. For Lawnchair 9 to 15, see the branches with the 9- to 15- prefixes, respectively.
Features
- Material 3 Expressive theming that follows your wallpaper and system colors.
- At a Glance widget support, with integration for Smartspacer.
- QuickSwitch support for Android Recents integration on Android 15-16 (root required).
- Global search for apps, contacts, and web results from the home screen.
- Customization options for icon packs, fonts, and color settings.
Download
Lawnchair on Play Store will install as a different app compared to other sources. Features may be restricted to comply with Google Playβs publishing rules.
You can also verify your installation to check if you have installed an official build.
Development builds
Interested in keeping yourself up-to-date with every Lawnchair development? Try our development builds!
These builds offer the latest features and bug fixes at a cost of performance and additional issues. Make backups before installing.
Download: Obtainium β’ GitHub β’ nightly.link
Sponsors
CodeRabbit is an AI-powered code review platform that integrates directly into pull-request workflows and IDEs, examining code changes in context and suggesting improvements.
Support Lawnchair
If you love what we do, consider supporting us on Open Collective! Your contributions help keep Lawnchair independent and enable us to develop faster.
A huge thank you to our Core Backers ($5+):
(These backers directly fund our Project Velocity Fund)
Become a supporter to help us cover our operational costs, or become a Core Backer to be featured here!
Contribute
Visit the Lawnchair contributing guidelines for information and tips on contributing to Lawnchair.
Quick links
Wmshell Src Com Android Wm Shell Docs README (`wmshell/src/com/android/wm/shell/docs/README.md`)
Window Manager Shell Readme
go/wmshell
The following docs present more detail about the implementation of the WMShell library (in no
particular order):
- What is the Shell
- Integration with SystemUI & Launcher
- Usage of Dagger
- Threading model in the Shell
- Making changes in the Shell
- Extending the Shell for Products/OEMs
- Shell transitions
- Debugging in the Shell
- Testing in the Shell
Todo
- Per-feature docs
- Feature flagging
- Best practices & patterns
Wmshell Src Com Android Wm Shell Docs Overview (`wmshell/src/com/android/wm/shell/docs/overview.md`)
What is the WindowManager Shell
Motivation
The primary motivation for the WindowManager Shell (WMShell) library is to effectively scale
WindowManager by making it easy™ and safe to create windowing features to fit the needs of
various Android products and form factors.
To achieve this, WindowManager separates the policy of managing windows (WMCore) from the
presentation of surfaces (WMShell) and provides a minimal interface boundary for the two to
communicate.
Who is using the library?
Currently, the WMShell library is used to drive the windowing experience on handheld
(phones & tablets), TV, Auto, Arc++, and Wear to varying degrees.
Where does the code live
The core WMShell library code is currently located in the frameworks/base/libs/WindowManager/Shell
directory and is included as a part dependency of the host SystemUI apk.
How do I build the Shell library
The library can be built directly by running:
m WindowManager-ShellBut this is mainly useful for inspecting the contents of the library or verifying it builds. The
various targets can be found in the Shell library's Android.bp
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
# Phone SystemUI variant
m SystemUI
adevice updateOr preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
building via go/sysui-studio allows for very quick iteration (one click
build and push of SysUI in < 30s).
If you are making framework changes and are using aidegen to set up your platform IDE, make sure
to include the appropriate directories to build, for example:
# frameworks/base will include base/libs/WindowManager/Shell and base/packages/SystemUI
aidegen frameworks/base \
vendor/<oem>/packages/SystemUI \
...Other useful links
- go/o-o-summit-20 (Video presentations from the WM team)
- go/o-o-summit-21
Wmshell Src Com Android Wm Shell Docs Changes (`wmshell/src/com/android/wm/shell/docs/changes.md`)
Making changes in the Shell
Code reviews
In addition to the individual reviewers who are most familiar with the changes you are making,
please also add [email protected] to keep other WM folks
in the loop.
Adding new code
Internal Shell utility classes
If the new component is used only within the WMShell library, then there are no special
considerations, go ahead and add it (in the com.android.wm.shell.common package for example)
and make sure the appropriate unit tests are added.
Internal Shell components
If the new component is to be used by other components/features within the Shell library, then
you can create an appropriate package for this component to add your new code. The current
pattern is to have a single <Component name>Controller that handles the initialization of the
component.
As mentioned in the Dagger usage docs, you need to determine whether it should go into:
WMShellBaseModulefor components that other base & product components will depend on- or
WMShellModule,TvWmShellModule, etc. for product specific components that no base
components depend on
SysUI accessible components
In addition to doing the above, you will also need to provide an interface for calling to SysUI
from the Shell and vice versa. The current pattern is to have a parallel Optional<Component name>
interface that the <Component name>Controller implements and handles on the main Shell thread
(see SysUI/Shell threading).
In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
add an appropriate method in WMComponent to get the interface and update the Builder inSysUIComponent to take the interface so it can be injected in SysUI code. The binding between
the two is done in SystemUIFactory#init() which will need to be updated as well.
Specifically, to support calling into a controller from an external process (like Launcher):
- Create an implementation of the external interface within the controller
- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
controller if needed) - Note that callbacks into SysUI should take an associated executor to call back on
Launcher accessible components
Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
Launcher requires a new AIDL interface to be created and implemented by the controller. The
implementation of the stub interface in the controller otherwise behaves similar to the interface
to SysUI where it posts the work to the main Shell thread.
Specifically, to support calling into a controller from an external process (like Launcher):
- Create an implementation of the interface binder's
Stubclass within the controller, have it
extendExternalInterfaceBinderand implementinvalidate()to ensure it doesn't hold long
references to the outer controller - Make the controller implement
RemoteCallable<T>, and have all incoming calls use one of
theexecuteRemoteCallWithTaskPermission()calls to verify the caller's identity and ensure the
call happens on the main shell thread and not the binder thread - Inject
ShellControllerand add the instance of the implementation as external interface - In Launcher, update
TouchInteractionServiceto pass the interface toSystemUIProxy, and then
call the SystemUIProxy method as needed in that code
Component initialization
To initialize the component:
- On the Shell side, you potentially need to do two things to initialize the component:
- Inject
ShellInitinto your component and add an init callback - Ensure that your component is a part of the dagger dependency graph, either by:
- Making this component a dependency of an existing component already exposed to SystemUI
- Explicitly add this component to the WMShellBaseModule @ShellCreateTrigger provider or
the @ShellCreateTriggerOverride provider for your product module to expose it explicitly
if it is a completely independent component
- Inject
- On the SysUI side, update
WMShellto setup any bindings for the component that depend on
SysUI code
To verify that your component is being initialized at startup, you can enable the WM_SHELL_INIT
protolog group and restart the SysUI process:
adb shell wm logging enable-text WM_SHELL_INIT
adb shell kill `pid com.android.systemui`
adb logcat *:S WindowManagerShellGeneral Do's & Dont's
Do:
- Add unit tests for all new components
- Keep controllers simple and break them down as needed
- Any SysUI callbacks should also take an associated executor to run the callback on
Don't:
- Don't do initialization in the constructor, only do initialization in the init callbacks.
Otherwise it complicates the building of the dependency graph. - Don't create dependencies from base-module components on specific features (the base module
is intended for use with all products)- Try adding a mechanism to register and listen for changes from the base module component instead
- Don't add blocking synchronous calls in the SysUI interface between Shell & SysUI
- Try adding a push-mechanism to share data, or an async callback to request data
Exposing shared code for use in Launcher
Launcher doesn't currently build against the Shell library, but needs to have access to some shared
AIDL interfaces and constants. Currently, all AIDL files, and classes under thecom.android.wm.shell.shared package are automatically built into the SystemUISharedLib that
Launcher uses.
If the new code doesn't fall into those categories, they should be moved to the Shell shared
package (com.android.wm.shell.shared) under the WindowManager-Shell-shared library.
Wmshell Src Com Android Wm Shell Docs Dagger (`wmshell/src/com/android/wm/shell/docs/dagger.md`)
Usage of Dagger in the Shell library
Dependencies
Dagger is not required to use the Shell library, but it has a lot of obvious benefits:
- Not having to worry about how to instantiate all the dependencies of a class, especially as
dependencies evolve (ie. product controller depends on base controller) - Can create boundaries within the same app to encourage better code modularity
As such, the Shell also tries to provide some reasonable out-of-the-box modules for use with Dagger.
Modules
All the Dagger related code in the Shell can be found in the com.android.wm.shell.dagger package,
this is intentional as it keeps the "magic" in a single location. The explicit nature of how
components in the shell are provided is as a result a bit more verbose, but it makes it easy for
developers to jump into a few select files and understand how different components are provided
(especially as products override components).
The module dependency tree looks a bit like:
- WMShellConcurrencyModule
(provides threading-related components)- WMShellBaseModule
(provides components that are common to many products, ie. DisplayController, Transactions,
etc.)- WMShellModule
(phone/tablet specific components only) - TvPipModule
(PIP specific components for TV)- TvWMShellModule
(TV specific components only)
- TvWMShellModule
- WMShellModule
- etc.
- WMShellBaseModule
Ideally features could be abstracted out into their own modules and included as needed by each
product.
Changing WMShellBaseModule
Because all products will include WMShellBaseModule, we don't want it to provide instances for
features that aren't used across multiple products (ie. Handheld, TV, Auto, Wear). This module
should generally only provide:
- Concrete implementations that are needed for startup
(seeprovideIndependentShellComponentsToCreate()) - Things used directly/indirectly by interfaces
exposed to SysUI
in WMComponent.java.
For the latter, not every feature will be enabled on the SysUI form factor including the base
module, so the recommendation is to have an @BindsOptionalOf for the interface, and have the
actual implementation provided in the form-factor specific module (ie. WMShellModule).
Overriding base components
In some rare cases, there are base components that can change behavior depending on which
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
The alternative is to use
the @DynamicOverride
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
details can be found in the class's javadoc.
Starting up Shell components aren't dependencies for other components
With Dagger, objects are created in dependency order and individual components can register withShellInit (see Component initialization) to initialize in
dependency order as well. However, if there is code that needs to run on startup but has nothing
dependent on it (imagine a background error detector for example), thenprovideIndependentShellComponentsToCreate() can serve as the artificial dependent object (itself
a dependency for ShellInterface) to trigger creation of such a component.
This can be declared within each module, so if a product includes WMShellModule, all the
components in provideIndependentShellComponentsToCreate() for both it and WMShellBaseModule will
be created.
Note that long term we are looking to move to a CoreStartable like infrastructure.
Wmshell Src Com Android Wm Shell Docs Debugging (`wmshell/src/com/android/wm/shell/docs/debugging.md`)
Debugging in the Shell
Logging & ProtoLogs
The interactions in the Shell can be pretty complicated, so having good logging is crucial to
debugging problems that arise (especially in dogfood). The Shell uses the same efficient Protolog
mechanism as WM Core, which can be enabled at runtime on debug devices.
TLDR Donβt use Logs or Slogs except for error cases, Protologs are much more flexible,
easy to add and easy to use
Adding a new ProtoLog
Update ShellProtoLogGroup to include a new log group (ie. NEW_FEATURE) for the content you want to
log. ProtoLog log calls mirror Log.v/d/e(), and take a format message and arguments:
ProtoLog.v(NEW_FEATURE, "Test log w/ params: %d %s", 1, βaβ)This code itself will not compile by itself, but the protologtool will preprocess the file when
building to check the log state (is enabled) before printing the print format style log.
Notes
- ProtoLogs are only fully supported from soong builds (ie. via make/mp). In SysUI-studio it falls
back to log via Logcat - Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with
traces in Winscope)
Kotlin
Kotlin protologging is supported, but with some differences compared to Java. The ProtoLog tool
currently does not process Kotlin code. This means that while ProtoLogs in Kotlin will still be
traced to Perfetto, they are not pre-processed to extract static strings like in Java. Consequently,
using ProtoLogging in Kotlin does not provide the same memory gains as in Java, and log calls may be
slightly less performant due to additional string interning at runtime.
Enabling ProtoLog command line logging
Run these commands to enable protologs (in logcat) for WM Core (list of all core groups) or WM Shell (list of all shell groups):
# Note: prior to 25Q2, you may need to use:
# adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
adb shell cmd protolog_configuration logcat enable <group>
adb shell cmd protolog_configuration logcat disable <group>Use these commands to print protolog groups and their status:
adb shell cmd protolog_configuration groups list
adb shell cmd protolog_configuration groups status <group>R8 optimizations & ProtoLog
If the APK that the Shell library is included into has R8 optimizations enabled, then you may need
to update the proguard flags to keep the generated protolog classes (ie. AOSP SystemUI's proguard.flags).
Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow go/winscope to learn how to
use the tool. This trace will contain all the information about the windows/activities/surfaces on
screen.
WindowManager/SurfaceFlinger/InputDispatcher information
A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
adb shell dumpsys activity containers
# The output lists the containers in the hierarchy from top -> bottom in z-orderTo get more information about windows on the screen:
# All windows in WM
adb shell dumpsys window -a
# The windows are listed from top -> bottom in z-order
# Visible windows only
adb shell dumpsys window -a visibleLikewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
adb shell dumpsys SurfaceFlinger
# Search output for "Layer Hierarchy", the surfaces in the table are listed bottom -> top in z-orderAnd the visible input windows can be dumped via:
adb shell dumpsys input
# Search output for "Windows:", they are ordered top -> bottom in z-orderTracing global SurfaceControl transaction updates
While Winscope traces are very useful, it sometimes doesn't give you enough information about which
part of the code is initiating the transaction updates. In such cases, it can be helpful to get
stack traces when specific surface transaction calls are made (regardless of process), which is
possible by enabling the following system properties for example:
# Enabling
adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,setPosition # matches the name of the SurfaceControlTransaction methods
adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
adb reboot
adb logcat -s "SurfaceControlRegistry"
# Disabling logging
adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"A reboot is required to enable the logging. Once enabled, reboot is not needed to update the
properties.
It is not necessary to set both log_match_call and log_match_name, but note logs can be quite
noisy if unfiltered.
Tracing transaction merge & apply
Tracing the method calls on SurfaceControl.Transaction tells you where a change is requested, but
the changes are not actually committed until the transaction itself is applied. And because
transactions can be passed across processes, or prepared in advance for later application (ie.
when restoring state after a Transition), the ordering of the change logs is not always clear
by itself.
In such cases, you can also enable the "merge" and "apply" calls to get additional information
about how/when transactions are respectively merged/applied:
# Enabling
adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,merge,apply # apply will dump logs of each setAlpha or merge call on that tx
adb reboot
adb logcat -s "SurfaceControlRegistry"Using those logs, you can first look at where the desired change is called, note the transaction
id, and then search the logs for where that transaction id is used. If it is merged into another
transaction, you can continue the search using the merged transaction until you find the final
transaction which is applied.
Tracing activity starts & finishes in the app process
It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
or via a WindowContainerTransaction (ie. if you are repro'ing a bug related to activity starts).
You can enable this system property to get this trace:
# Enabling
adb shell setprop persist.wm.debug.start_activity true
adb reboot
adb logcat -s "Instrumentation"
# Disabling
adb shell setprop persist.wm.debug.start_activity \"\"
adb rebootLikewise, to trace where a finish() call may be made in the app process, you can enable this system
property:
# Enabling
adb shell setprop persist.wm.debug.finish_activity true
adb reboot
adb logcat -s "Instrumentation"
# Disabling
adb shell setprop persist.wm.debug.finish_activity \"\"
adb rebootTracing transition starts/finishes in the Shell
To trace where a new WM transition is started and finished in the Shell, you can enable these system
properties respectively:
# Enabling
adb shell setprop persist.wm.debug.start_shell_transition true
adb shell setprop persist.wm.debug.finish_shell_transition true
adb reboot
adb logcat -s "ShellTransitions"
# Disabling
adb shell setprop persist.wm.debug.start_shell_transition \"\"
adb shell setprop persist.wm.debug.finish_shell_transition \"\"
adb rebootDumps
Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
part of dumping the SystemUI service. Dumping the Shell specific data can be done by specifying the
WMShell SysUI service:
# Note: prior to 25Q2, you may need to use:
# adb shell dumpsys activity service SystemUIService WMShell dump
adb shell wm shell dumpIf information should be added to the dump, either:
- Update
WMShellif you are dumping SysUI state - Inject
ShellCommandHandlerinto your Shell class, and add a dump callback
Shell commands
It can be useful to add additional shell commands to drive and test specific interactions.
To add a new command for your feature, inject a ShellCommandHandler into your class and add a
shell command handler in your controller.
# List all available commands
# Note: prior to 25Q2, you may need to use:
# adb shell dumpsys activity service SystemUIService WMShell help
adb shell wm shell help
# Run a specific command
# Note: prior to 25Q2, you may need to use:
# adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
adb shell wm shell <cmd> <args> ...Debugging in Android Studio
If you are using the go/sysui-studio project, then you can debug Shell
code directly from Android Studio like any other app.
Wmshell Src Com Android Wm Shell Docs Extending (`wmshell/src/com/android/wm/shell/docs/extending.md`)
Extending the Shell for Products/OEMs
General Do's & Dont's
Do:
Don't
- Don't override classes provided by WMShellBaseModule, it makes it difficult to make
simple changes to the Shell library base modules which are shared by all products- If possible add mechanisms to modify the base class behavior
Wmshell Src Com Android Wm Shell Docs Sysui (`wmshell/src/com/android/wm/shell/docs/sysui.md`)
Shell & SystemUI
Setup
The SystemUI of various products depend on and build against the WM Shell library. To ensure
that we don't inadvertently build dependencies between the Shell library and one particular
product (ie. handheld SysUI), we deliberately separate the initialization of the WM Shell
component from the SysUI component when set up through Dagger.
TLDR Initialize everything as needed in the WM component scope and export only well
defined interfaces to SysUI.
Initialization
There are more details in the Dagger docs, but the general overview of the SysUI/Shell
initialization flow is such:
- SysUI Global scope is initialize (see
GlobalModuleand its included modules) - WM Shell scope is initialized, for example
- On phones:
WMComponentincludesWMShellModulewhich includesWMShellBaseModule
(common to all SysUI) - On TVs:
TvWMComponentincludesTvWMShellModulewhich includesWMShellBaseModule - etc.
- On phones:
- SysUI explicitly passes interfaces provided from the
WMComponenttoSysUIComponentvia
theSysUIComponent#Builder, then builds the SysUI scoped components WMShellis the SystemUI βserviceβ (in the SysUI scope) that initializes with the app after the
SystemUI part of the dependency graph has been created. It contains the binding code between the
interfaces provided by the Shell and the rest of SystemUI.- SysUI can inject the interfaces into its own components
More detail can be found in go/wm-sysui-dagger.
Interfaces from SysUI to Shell components
Within the same process, the WM Shell components can be running on a different thread than the main
SysUI thread (disabled on certain products). This introduces challenges where we have to be
careful about how SysUI calls into the Shell and vice versa.
As a result, we enforce explicit interfaces between SysUI and Shell components, and the
implementations of the interfaces on each side need to post to the right thread before it calls
into other code.
For example, you might have:
- (Shell) ShellFeature interface to be used from SysUI
- (Shell) ShellFeatureController handles logic, implements ShellFeature interface and posts to
main Shell thread - SysUI application init injects Optional
as an interface to SysUI to call - (SysUI) SysUIFeature depends on ShellFeature interface
- (SysUI) SysUIFeature injects Optional
, and sets up a callback for the Shell to
call, and the callback posts to the main SysUI thread
Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently
necessary to maintain proper threading and logic isolation.
Listening for Configuration changes & other SysUI events
Aside from direct calls into Shell controllers for exposed features, the Shell also receives
common event callbacks from SysUI via the ShellController. This includes things like:
- Configuration changes
- Keyguard events
- Shell init
- Shell dumps & commands
For other events which are specific to the Shell feature, then you can add callback methods on
the Shell feature interface. Any such calls should never be synchronous calls as
they will need to post to the Shell main thread to run.
Shell commands & Dumps
Since the Shell library is a part of the SysUI process, it relies on SysUI to trigger commands
on individual Shell components.
More detail can be found in Debugging in the Shell section.
Wmshell Src Com Android Wm Shell Docs Testing (`wmshell/src/com/android/wm/shell/docs/testing.md`)
Testing
Unit tests
New WM Shell unit tests can be added to the
Shell/tests/unittest directory, and can
be run via command line using atest:
atest WMShellUnitTestsIf you use the SysUI Studio project, you can run and debug tests directly in the source files
(click on the little arrows next to the test class or test method).
These unit tests are run as a part of WindowManager presubmit, and the dashboards for these unit
tests tests can be found at go/wm-tests.
This GCL file configures the tests being run on the server.
Flicker tests
Flicker tests are tests that perform actions and make assertions on the state in Window Manager
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
Shell/tests/flicker directory, and can be run via command line using atest:
# Bubbles
atest WMShellFlickerTestsBubbles
# PIP
atest WMShellFlickerTestsPip1
atest WMShellFlickerTestsPip2
atest WMShellFlickerTestsPip3
atest WMShellFlickerTestsPipApps
atest WMShellFlickerTestsPipAppsCSuite
# Splitscreen
atest WMShellFlickerTestsSplitScreenGroup1
atest WMShellFlickerTestsSplitScreenGroup2
# Other
atest WMShellFlickerTestsOtherNote: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
A subset of the flicker tests tests are run as a part of WindowManager presubmit, and the
dashboards for these tests tests can be found at go/wm-tests-flicker.
CTS tests
Some windowing features also have CTS tests to ensure consistent behavior across OEMs. For example:
- Picture-in-Picture:
PinnedStackTests - etc.
These can also be run via commandline only using atest, for example:
atest PinnedStackTestsWmshell Src Com Android Wm Shell Docs Threading (`wmshell/src/com/android/wm/shell/docs/threading.md`)
Threading
Boundaries
Thread boundary
|
WM Shell | SystemUI
|
|
FeatureController <-> FeatureInterface <--|--> WMShell <-> SysUI
| (^post to shell thread) | (^post to main thread)
... |
| |
OtherControllers |Threads
We currently have multiple threads in use in the Shell library depending on the configuration by
the product.
- SysUI main thread (standard main thread)
ShellMainThread(only used if the resourceconfig_enableShellMainThreadis set true
(ie. phones))- This falls back to the SysUI main thread otherwise
- Note:
- This thread runs with
THREAD_PRIORITY_DISPLAYpriority since so many windowing-critical
components depend on it - This is also the UI thread for almost all UI created by the Shell
- The Shell main thread Handler (and the Executor that wraps it) is async, so
messages/runnables used via this Handler are handled immediately if there is no sync
messages prior to it in the queue.
- This thread runs with
ShellBackgroundThread(for longer running tasks where we don't want to block the shell main
thread)- This is always another thread even if config_enableShellMainThread is not set true
- Note:
- This thread runs with
THREAD_PRIORITY_BACKGROUNDpriority but can be requested to be boosted
toTHREAD_PRIORITY_FOREGROUND
- This thread runs with
ShellAnimationThread(currently only used for Transitions and Splitscreen, but potentially all
animations could be offloaded here)ShellSplashScreenThread(only for use with splashscreens)
Dagger setup
The threading-related components are provided by the WMShellConcurrencyModule,
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.@ShellMainThread Executor) when injecting into your Shell component.
To get the SysUI main thread, you can use the @Main annotation.
Best practices
Components
- Don't do initialization in the Shell component constructors
- If the host SysUI is not careful, it may construct the WMComponent dependencies on the main
thread, and this reduces the likelihood that components will intiailize on the wrong thread
in such cases
- If the host SysUI is not careful, it may construct the WMComponent dependencies on the main
- Be careful of using CountDownLatch and other blocking synchronization mechanisms in Shell code
- If the Shell main thread is not a separate thread, this will cause a deadlock
- Callbacks, Observers, Listeners to any non-shell component should post onto main Shell thread
- This includes Binder calls, SysUI calls, BroadcastReceivers, etc. Basically any API that
takes a runnable should either be registered with the right Executor/Handler or posted to
the main Shell thread manually
- This includes Binder calls, SysUI calls, BroadcastReceivers, etc. Basically any API that
- Since everything in the Shell runs on the main Shell thread, you do not need to explicitly
synchronizeyour code (unless you are trying to prevent reentrantcy, but that can also be
done in other ways)
Handlers/Executors
- You generally never need to create Handlers explicitly, instead inject
@ShellMainThread ShellExecutorinstead- This is a common pattern to defer logic in UI code, but the Handler created wraps the Looper
that is currently running, which can be wrong (see above for initialization vs construction)
- This is a common pattern to defer logic in UI code, but the Handler created wraps the Looper
- That said, sometimes Handlers are necessary because Framework API only takes Handlers or you
want to dedupe multiple messages- In such cases inject
@ShellMainThread Handleror use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
- In such cases inject
- Never use Looper.getMainLooper()
- It's likely going to be wrong, you can inject
@Main ShellExecutorto get the SysUI main thread
- It's likely going to be wrong, you can inject
Testing
- You can use a
TestShellExecutorto control the processing of messages
Wmshell Src Com Android Wm Shell Docs Transitions (`wmshell/src/com/android/wm/shell/docs/transitions.md`)
Shell transitions
General
General guides for using Shell Transitions can be found here:
Transient-launch transitions
Use with care!
Transient-launch transitions are a way to handle non-atomic (ie. gestural) transitions by allowing
WM Core to put participating activities into a transiently visible or hidden state for the duration
of the animation and adding the ability to cancel the transition.
For example, if you are launching an activity normally, WM Core will be updated
at the start of the animation which includes pausing the previous activity and resuming the next
activity (and subsequently the transition will reconcile that state via an animation).
If you are transiently launching an activity though, WM Core will ensure that both the leaving
activity and the incoming activity will be RESUMED for the duration of the transition duration. In
addition, WM Core will track the position of the transient-launch activity in the window hierarchy
prior to the launch, and allow Shell to restore it to that position if the transitions needs to be
canceled.
Starting a transient-launch transition can be done via the activity options (since the activity may
not have been started yet):
val opts = ActivityOptions.makeBasic().setTransientLaunch()
val wct = WindowContainerTransaction()
wct.sendPendingIntent(pendingIntent, new Intent(), opts.toBundle())
transitions.startTransition(TRANSIT_OPEN, wct, ...)And restoring the transient order via a WCT:
val wct = WindowContainerTransaction()
wct.restoreTransientOrder(transientLaunchContainerToken)
transitions.startTransition(TRANSIT_RESTORE, wct, ...)Considerations
Usage of transient-launch transitions should be done with consideration, there are a few gotchas
that might result in subtle and hard-to-reproduce issues.
Understanding the flow
When starting a transient-launch transition, there are several possible outcomes:
The transition finishes as normal: The user is committing the transition to the state requested
at the start of the transition. In such cases, you can simply finish the transition and the
states of the transiently shown/hidden activities will be updated to match the original state
that a non-transient transition would have (ie. closing activities will be stopped).The transition is interrupted: A change in the system results in the window hierarchy changing
in a way which may or may not affect the transient-launch activity. eg. We transiently-launch
home from app A, but then app B launches. In this case, WM attempts to create a new transition
reflecting the window hierarchy changes (ie. if B occludes Home in the above example, then the
transition will have Home TO_BACK, and B TO_FRONT).At this point, the transition handler can choose to merge the incoming transition or not (to
queue it after this current transition). Take note of the next section for concerns re. bookend
transitions.The transition is canceled: The user is canceling the transition to the previous state. In such
cases, you need to store theWindowContainerTokenfor the task associated with the
transient-launch activity, and restore the transient order via theWindowContainerTransaction
API above. In some cases, if anything has been reordered since (ie. due to other merged
transitions), then you may also want to useWindowContainerTransaction#reorder()to place all
the relevant containers to their original order (provided via the change-order in the initial
launch transition).
Finishing the transient-launch transition
When restoring the transient order in the 3rd flow above, it is recommended to do it in a new
transition and not via the WindowContainerTransaction inTransitionFinishCallback#onTransitionFinished() provided when starting the transition.
Changes to the window hierarchy via the finish transaction are not applied in sync with other
transitions that are collecting and applying, and are also not observable in Shell in any way.
Starting a new transition instead ensures both. (The finish transaction can still be used if there
are non-transition affecting properties (ie. container properties) that need to be updated as a part
of finishing the transient-launch transition).
So the general idea is when restoring is:
- Start transient-launch transition START_T
- ...
- All done, start bookend transition END_T
- Handler receives END_T, merges it and then finishes START_T
In practice it's not quite that simple, due to the ordering of transitions and extra care must be
taken when using a new transition to prevent deadlocking when merging transitions.
When a new transition arrives while a transient-launch transition is playing, the handler can
choose to handle/merge the transition into the ongoing one, or skip merging to queue it up to be
played after. In the above flow, we can see how this might result in a deadlock:
Queueing END during merge:
- Start transient-launch transition START_T
- ...
- Incoming transition OTHER_T, choose to cancel START_T -> start bookend transition END_T, but don't merge OTHER_T
- Waiting for END_T... Deadlock!
Interrupt while pending END:
- Start transient-launch transition START_T
- ...
- All done, start bookend transition END_T
- Incoming transition OTHER_T occurs before END_T, but don't merge OTHER_T
- Waiting for END_T... Deadlock!
This means that when using transient-launch transitions with a bookend transition
requires you to handle any incoming transitions if the bookend is
ever queued (or already posted) after it. You can do so by preempting the bookend transition
(finishing the transient-launch transition), or handling the merge of the new transition (so it
doesn't queue).
Finish-transactions with bookend transitions
Another thing to keep in mind when using bookend transitions is the ordering of the finish
transactions. In particular, if cleanup of tasks is necessary, then you need to ensure that the
cleanup happens on the bookend's finish transaction and not the finish transaction provided when
starting the animation, otherwise properties on the tasks in the transition could be overwritten by
the default finish transaction created by Core (see Transition#buildFinishTransaction()).
ie.
- Start START (Core builds finishT1 to reset state)
- Start BOOKEND (Core builds finishT2 to reset state)
- Merge BOOKEND into START
- <<< Finish transaction properties should be updated here and not before 2)
- Finish BOOKEND (Shell transitions applies finishT1 then finishT2 in order)
Wmshell Src Com Android Wm Shell Docs Patterns TEMPLATE (`wmshell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md`)
Pattern (one line description)
What this pattern solves
Give detailed information about the problem that this pattern addresses, include when it
should/should not be used.
How it works
Give a high level overview of how this pattern works technically with sufficient links for the
relevant dependencies for folks to read more.
Code examples
Explain how this pattern is used in code.
File.kt:
fun someFunction() {
// Use this code
}Relevant links
Add relevant links to other files that implement this pattern, design docs, or other important
info.
Link 1: More info
Link 2: More info
--- METRICS ---
- Files Extracted: 12
- Estimated Token Budget: ~11973 tokens
- Canonical Reference: https://codewiki.google/github.com/LawnchairLauncher/lawnchair