Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 77a5abb1 authored by Dave Mankoff's avatar Dave Mankoff Committed by Automerger Merge Worker
Browse files

Merge "Update README.md and dagger.md." into tm-qpr-dev am: 212f86c9

parents f804faaf 212f86c9
Loading
Loading
Loading
Loading
+55 −35
Original line number Diff line number Diff line
@@ -5,46 +5,72 @@
SystemUI is a persistent process that provides UI for the system but outside
of the system_server process.

The starting point for most of sysui code is a list of services that extend
SystemUI that are started up by SystemUIApplication. These services then depend
on some custom dependency injection provided by Dependency.

Inputs directed at sysui (as opposed to general listeners) generally come in
through IStatusBar. Outputs from sysui are through a variety of private APIs to
the android platform all over.

## SystemUIApplication

When SystemUIApplication starts up, it will start up the services listed in
config_systemUIServiceComponents or config_systemUIServiceComponentsPerUser.
When SystemUIApplication starts up, it instantiates a Dagger graph from which
various pieces of the application are built.

Each of these services extend SystemUI. SystemUI provides them with a Context
and gives them callbacks for onConfigurationChanged (this historically was
the main path for onConfigurationChanged, now also happens through
ConfigurationController). They also receive a callback for onBootCompleted
since these objects may be started before the device has finished booting.
To support customization, SystemUIApplication relies on the AndroidManifest.xml
having an `android.app.AppComponentFactory` specified. Specifically, it relies
on an `AppComponentFactory` that subclases `SystemUIAppComponentFactoryBase`.
Implementations of this abstract base class must override
`#createSystemUIInitializer(Context)` which returns a `SystemUIInitializer`.
`SystemUIInitializer` primary job in turn is to intialize and return the Dagger
root component back to the `SystemUIApplication`.

Each SystemUI service is expected to be a major part of system ui and the
goal is to minimize communication between them. So in general they should be
relatively silo'd.
Writing a custom `SystemUIAppComponentFactoryBase` and `SystemUIInitializer`,
should be enough for most implementations to stand up a customized Dagger
graph, and launch a custom version of SystemUI.

## Dependencies
## Dagger / Dependency Injection

The first SystemUI service that is started should always be Dependency.
Dependency provides a static method for getting a hold of dependencies that
have a lifecycle that spans sysui. Dependency has code for how to create all
dependencies manually added. SystemUIFactory is also capable of
adding/replacing these dependencies.
See [dagger.md](docs/dagger.md) and https://dagger.dev/.

Dependencies are lazily initialized, so if a Dependency is never referenced at
runtime, it will never be created.
## CoreStartable

If an instantiated dependency implements Dumpable it will be included in dumps
of sysui (and bug reports), allowing it to include current state information.
This is how \*Controllers dump state to bug reports.
The starting point for most of SystemUI code is a list of classes that
implement `CoreStartable` that are started up by SystemUIApplication.
CoreStartables are like miniature services. They have their `#start` method
called after being instantiated, and a reference to them is stored inside
SystemUIApplication. They are in charge of their own behavior beyond this,
registering and unregistering with the rest of the system as needed.

`CoreStartable` also receives a callback for `#onBootCompleted`
since these objects may be started before the device has finished booting.

If an instantiated dependency implements ConfigurationChangeReceiver it will
receive onConfigurationChange callbacks when the configuration changes.
`CoreStartable` is an ideal place to add new features and functionality
that does not belong directly under the umbrella of an existing feature.
It is better to define a new `CoreStartable` than to stick unrelated
initialization code together in catch-all methods.

CoreStartables are tied to application startup via Dagger:

```kotlin
class FeatureStartable
@Inject
constructor(
    /* ... */
) : CoreStartable {
    override fun start() {
        // ...
    }
}

@Module
abstract class FeatureModule {
    @Binds
    @IntoMap
    @ClassKey(FeatureStartable::class)
    abstract fun bind(impl: FeatureStartable): CoreStartable
}
```

Including `FeatureModule` in the Dagger graph such as this will ensure that
`FeatureStartable` gets constructed and that its `#start` method is called.

## IStatusBar

@@ -64,12 +90,6 @@ across sysui. Such as when StatusBar calls CommandQueue#recomputeDisableFlags.
This is generally used a shortcut to directly trigger CommandQueue rather than
calling StatusManager and waiting for the call to come back to IStatusBar.

## Default SystemUI services list

### [com.android.systemui.Dependency](/packages/SystemUI/src/com/android/systemui/Dependency.java)

Provides custom dependency injection.

### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)

Creates/initializes the channels sysui uses when posting notifications.
@@ -88,11 +108,11 @@ activity. It provides this cached data to RecentsActivity when it is started.
Registers all the callbacks/listeners required to show the Volume dialog when
it should be shown.

### [com.android.systemui.status.phone.StatusBar](/packages/SystemUI/src/com/android/systemui/status/phone/StatusBar.java)
### [com.android.systemui.status.phone.CentralSurfaces](/packages/SystemUI/src/com/android/systemui/status/phone/CentralSurfaces.java)

This shows the UI for the status bar and the notification shade it contains.
It also contains a significant amount of other UI that interacts with these
surfaces (keyguard, AOD, etc.). StatusBar also contains a notification listener
surfaces (keyguard, AOD, etc.). CentralSurfaces also contains a notification listener
to receive notification callbacks.

### [com.android.systemui.usb.StorageNotification](/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java)
+77 −109
Original line number Diff line number Diff line
@@ -8,105 +8,110 @@ Go read about Dagger 2.

 - [User's guide](https://google.github.io/dagger/users-guide)

TODO: Add some links.

## State of the world

Dagger 2 has been turned on for SystemUI and a early first pass has been taken
for converting everything in [Dependency.java](packages/systemui/src/com/android/systemui/Dependency.java)
to use Dagger. Since a lot of SystemUI depends on Dependency, stubs have been added to Dependency 
to proxy any gets through to the instances provided by dagger, this will allow migration of SystemUI 
through a number of CLs.
Dagger 2 has been turned on for SystemUI and much of
[Dependency.java](../src/com/android/systemui/Dependency.java)
has been converted to use Dagger. Since a lot of SystemUI depends on Dependency,
stubs have been added to Dependency to proxy any gets through to the instances
provided by dagger, this will allow migration of SystemUI through a number of CLs.

### How it works in SystemUI

There are three high level "scopes" of concern in SystemUI. They all represent
singleton scopes, but serve different purposes.

* `@Singleton` - Instances that are shared everywhere. There isn't a  lot of
   code in this scope. Things like the main thread, and Android Framework
   provided instances mostly.
* `@WMShell` - WindowManager related code in the SystemUI process. We don't
   want this code relying on the rest of SystemUI, and we don't want the rest
   of SystemUI peeking into its internals, so it runs in its own Subcomponent.
* `@SysUISingleton` - Most of what would be considered "SystemUI". Most feature
   work by SystemUI developers goes into this scope. Useful interfaces from
   WindowManager are made available inside this Subcomponent.

The root dagger graph is created by an instance of `SystemUIInitializer`.
See [README.md](../README.md) for more details.
For the classes that we're using in Dependency and are switching to dagger, the
equivalent dagger version is using `@Singleton` and therefore only has one instance.
To have the single instance span all of SystemUI and be easily accessible for
other components, there is a single root `@Component` that exists that generates
these. The component lives in [SystemUIFactory](packages/systemui/src/com/android/systemui/SystemUIFactory.java)
and is called `SystemUIRootComponent`.

```java

@Singleton
@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
        ContextHolder.class})
public interface SystemUIRootComponent {
    @Singleton
    Dependency.DependencyInjector createDependency();
}
```

The root component is composed of root modules, which in turn provide the global singleton 
dependencies across all of SystemUI.

- `SystemUIFactory` `@Provides` dependencies that need to be overridden by SystemUI
variants (like other form factors e.g. Car). 

- `DependencyBinder` creates the mapping from interfaces to implementation classes. 

- `DependencyProvider` provides or binds any remaining depedencies required.

### Adding injection to a new SystemUI object

SystemUI object are made injectable by adding an entry in `SystemUIBinder`. SystemUIApplication uses
information in that file to locate and construct an instance of the requested SystemUI class.
these. The component lives in
[ReferenceGlobalRootComponent.java](../src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java).

### Adding a new injectable object

First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
instance should be created.
First annotate the constructor with `@Inject`. Also annotate it with
`@SysUISingleton` if only one instance should be created.

```java
@Singleton
public class SomethingController {
```kotlin
@SysUISingleton
class FeatureStartable
@Inject
  public SomethingController(Context context,
    @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
      // context and mainHandler will be automatically populated.
  }
constructor(
/* ... */
) {
    // ...
}
```

If you have an interface class and an implementation class, dagger needs to know
how to map it. The simplest way to do this is to add an `@Provides` method to
DependencyProvider. The type of the return value tells dagger which dependency it's providing.
If you have an interface class and an implementation class, Dagger needs to
know how to map it. The simplest way to do this is to add an `@Binds` method
in a module. The type of the return value tells dagger which dependency it's
providing:

```java
public class DependencyProvider {
  //...
  @Singleton
  @Provides
  public SomethingController provideSomethingController(Context context,
      @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
    return new SomethingControllerImpl(context, mainHandler);
  }
```kotlin
@Module
abstract class FeatureModule {
    @Binds
    abstract fun bindsFeature(impl: FeatureImpl): Feature
}
```

If you need to access this from Dependency#get, then add an adapter to Dependency
that maps to the instance provided by Dagger. The changes should be similar
to the following diff.
If you have a class that you want to make injectable that has can not
be easily constructed by Dagger, write a `@Provides` method for it:

```java
public class Dependency {
  //...
  @Inject Lazy<SomethingController> mSomethingController;
  //...
  public void start() {
    //...
    mProviders.put(SomethingController.class, mSomethingController::get);
```kotlin
@Module
abstract class FeatureModule {
    @Module
    companion object {
        @Provides
        fun providesFeature(ctx: Context): Feature {
            return FeatureImpl.constructFromContext(ctx)
        }
    }
}
```

### Module Organization

Please define your modules on _at least_ per-package level. If the scope of a
package grows to encompass a great number of features, create per-feature
modules.

**Do not create catch-all modules.** Those quickly grow unwieldy and
unmaintainable. Any that exist today should be refactored into obsolescence.

You can then include your module in one of three places:

1) Within another module that depends on it. Ideally, this creates a clean
   dependency graph between features and utilities.
2) For features that should exist in all versions of SystemUI (AOSP and
   any variants), include the module in
   [SystemUIModule.java](../src/com/android/systemui/dagger/SystemUIModule.java).
3) For features that should exist only in AOSP, include the module in
   [ReferenceSystemUIModule.java](../src/com/android/systemui/dagger/ReferenceSystemUIModule.java).
   Similarly, if you are working on a custom version of SystemUI and have code
   specific to your version, include it in a module specific to your version.

### Using injection with Fragments

Fragments are created as part of the FragmentManager, so they need to be
setup so the manager knows how to create them. To do that, add a method
to com.android.systemui.fragments.FragmentService$FragmentCreator that
returns your fragment class. Thats all thats required, once the method
returns your fragment class. That is all that is required, once the method
exists, FragmentService will automatically pick it up and use injection
whenever your fragment needs to be created.

@@ -123,48 +128,11 @@ then the FragmentHostManager can do this for you.
FragmentHostManager.get(view).create(NavigationBarFragment.class);
```

### Using injection with Views

DO NOT ADD NEW VIEW INJECTION. VIEW INJECTION IS BEING ACTIVELY DEPRECATED.

Needing to inject objects into your View's constructor generally implies you
are doing more work in your presentation layer than is advisable.
Instead, create an injected controller for you view, inject into the
controller, and then attach the view to the controller after inflation.

View injection generally causes headaches while testing, as inflating a view
(which may in turn inflate other views) implicitly causes a Dagger graph to 
be stood up, which may or may not contain the appropriately 
faked/mocked/stubbed objects. It is a hard to control process.

## Updating Dagger2

We depend on the Dagger source found in external/dagger2. We should automatically pick up on updates
when that repository is updated.
 
*Deprecated:*

Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
into
[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)

The following commands should work, substituting in the version that you are looking for:

````
cd prebuilts/tools/common/m2/repository/com/google/dagger/

wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger/2.28.1/

wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.28.1/

wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-spi/2.28.1/

wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.28.1/
````

Then update `prebuilts/tools/common/m2/Android.bp` to point at your new jars.
 
## TODO List

 - Eliminate usages of Dependency#get
 - Add links in above TODO
 - Eliminate usages of Dependency#get: http://b/hotlists/3940788