xref: /aosp_15_r20/frameworks/base/packages/SystemUI/docs/plugins.md (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker
2*d57664e9SAndroid Build Coastguard Worker# SystemUI Plugins
3*d57664e9SAndroid Build Coastguard Worker
4*d57664e9SAndroid Build Coastguard WorkerPlugins provide an easy way to rapidly prototype SystemUI features.  Plugins are APKs that will be installable only on Build.IS_DEBUGGABLE (dogfood) builds, that can change the behavior of SystemUI at runtime.  This is done by creating a basic set of interfaces that the plugins can expect to be in SysUI, then the portion of code controlled by the interface can be iterated on faster than currently.
5*d57664e9SAndroid Build Coastguard Worker
6*d57664e9SAndroid Build Coastguard WorkerPlugins keep the experimental and turbulent code outside of master and only on the devices which need to use the prototype.  You can distribute early prototype directly to those that need to see it either through drive or email, and only show it to dogfooders when ready.
7*d57664e9SAndroid Build Coastguard Worker
8*d57664e9SAndroid Build Coastguard Worker## Adding Plugin Hooks
9*d57664e9SAndroid Build Coastguard Worker
10*d57664e9SAndroid Build Coastguard WorkerExisting plugin hooks can be found [here](/packages/SystemUI/docs/plugin_hooks.md).
11*d57664e9SAndroid Build Coastguard Worker
12*d57664e9SAndroid Build Coastguard Worker### Writing the Interface(s)
13*d57664e9SAndroid Build Coastguard Worker
14*d57664e9SAndroid Build Coastguard WorkerThe first step of adding a plugin hook to SysUI is to define the interface layer between the plugin and SysUI.  This interface should be relatively stable so that many different plugins will work across multiple different builds.
15*d57664e9SAndroid Build Coastguard Worker
16*d57664e9SAndroid Build Coastguard WorkerAll interfaces need to be independent and not reference classes from SysUI.  They should be placed in the plugin library, under com.android.systemui.plugin or sub-packages.  The main interface (entry point) for the plugin should extend the interface Plugin so that you can listen for it.
17*d57664e9SAndroid Build Coastguard Worker
18*d57664e9SAndroid Build Coastguard Worker
19*d57664e9SAndroid Build Coastguard WorkerThe most important part of interfaces is the version included in them.  Every time the interface changes in an incompatible way, the version should be incremented.  Incompatible changes are changes to the signature of any of the interface methods, or the addition of a new method that doesn’t have a default implementation.  All classes that are in the plugin library should be tagged with a version, they should also be tagged with an action if they are the root interface for the Plugin. If a plugin makes use of the other versioned interface, they can use DependsOn to indicate their dependence. They are tagged using annotations like the following.
20*d57664e9SAndroid Build Coastguard Worker
21*d57664e9SAndroid Build Coastguard Worker
22*d57664e9SAndroid Build Coastguard Worker```java
23*d57664e9SAndroid Build Coastguard Worker@ProvidesInterface(action = MyPlugin.ACTION, version = MyPlugin.VERSION)
24*d57664e9SAndroid Build Coastguard Worker@DependsOn(target = OtherInterface.class)
25*d57664e9SAndroid Build Coastguard Workerpublic interface MyPlugin extends Plugin {
26*d57664e9SAndroid Build Coastguard Worker    String ACTION = "com.android.systemui.action.PLUGIN_MY_PLUGIN";
27*d57664e9SAndroid Build Coastguard Worker    int VERSION = 1;
28*d57664e9SAndroid Build Coastguard Worker    ...
29*d57664e9SAndroid Build Coastguard Worker}
30*d57664e9SAndroid Build Coastguard Worker```
31*d57664e9SAndroid Build Coastguard Worker
32*d57664e9SAndroid Build Coastguard Worker### Plugin Listener
33*d57664e9SAndroid Build Coastguard Worker
34*d57664e9SAndroid Build Coastguard WorkerTo actually listen for plugins, you implement a plugin listener that has the following interface.
35*d57664e9SAndroid Build Coastguard Worker
36*d57664e9SAndroid Build Coastguard Worker```java
37*d57664e9SAndroid Build Coastguard Workerpublic interface PluginListener<T extends Plugin> {
38*d57664e9SAndroid Build Coastguard Worker    /**
39*d57664e9SAndroid Build Coastguard Worker     * Called when the plugin has been loaded and is ready to be used.
40*d57664e9SAndroid Build Coastguard Worker     * This may be called multiple times if multiple plugins are allowed.
41*d57664e9SAndroid Build Coastguard Worker     * It may also be called in the future if the plugin package changes
42*d57664e9SAndroid Build Coastguard Worker     * and needs to be reloaded.
43*d57664e9SAndroid Build Coastguard Worker     */
44*d57664e9SAndroid Build Coastguard Worker    void onPluginConnected(T plugin);
45*d57664e9SAndroid Build Coastguard Worker
46*d57664e9SAndroid Build Coastguard Worker    /**
47*d57664e9SAndroid Build Coastguard Worker     * Called when a plugin has been uninstalled/updated and should be removed
48*d57664e9SAndroid Build Coastguard Worker     * from use.
49*d57664e9SAndroid Build Coastguard Worker     */
50*d57664e9SAndroid Build Coastguard Worker    default void onPluginDisconnected(T plugin) {
51*d57664e9SAndroid Build Coastguard Worker        // Optional.
52*d57664e9SAndroid Build Coastguard Worker    }
53*d57664e9SAndroid Build Coastguard Worker}
54*d57664e9SAndroid Build Coastguard Worker```
55*d57664e9SAndroid Build Coastguard Worker
56*d57664e9SAndroid Build Coastguard WorkerThen you register the PluginListener with the PluginManager.  The constants for action and version should be defined on class T.  If allowMultiple is false, the plugin listener will only be connected to one plugin at a time.
57*d57664e9SAndroid Build Coastguard Worker
58*d57664e9SAndroid Build Coastguard Worker```java
59*d57664e9SAndroid Build Coastguard Workervoid addPluginListener(String action, PluginListener<T> listener,
60*d57664e9SAndroid Build Coastguard Worker            int version, boolean allowMultiple);
61*d57664e9SAndroid Build Coastguard Worker```
62*d57664e9SAndroid Build Coastguard Worker
63*d57664e9SAndroid Build Coastguard Worker### Examples
64*d57664e9SAndroid Build Coastguard Worker[Allow quick settings panel to be replaced with another view](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java)
65*d57664e9SAndroid Build Coastguard Worker
66*d57664e9SAndroid Build Coastguard Worker[Allow plugins to create new nav bar buttons](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java)
67*d57664e9SAndroid Build Coastguard Worker
68*d57664e9SAndroid Build Coastguard Worker[Allow lockscreen camera/phone/assistant buttons to be replaced](/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java)
69*d57664e9SAndroid Build Coastguard Worker
70*d57664e9SAndroid Build Coastguard Worker## Writing Plugins
71*d57664e9SAndroid Build Coastguard Worker### Make Files and Manifests
72*d57664e9SAndroid Build Coastguard Worker
73*d57664e9SAndroid Build Coastguard WorkerWhen compiling plugins there are a couple vital pieces required.
74*d57664e9SAndroid Build Coastguard Worker1. They must be signed with the platform cert
75*d57664e9SAndroid Build Coastguard Worker2. They must include SystemUIPluginLib in LOCAL_JAVA_LIBRARIES (NOT LOCAL_STATIC_JAVA_LIBRARIES)
76*d57664e9SAndroid Build Coastguard Worker
77*d57664e9SAndroid Build Coastguard WorkerBasically just copy the [example blueprint file](/packages/SystemUI/plugin/ExamplePlugin/Android.bp).
78*d57664e9SAndroid Build Coastguard Worker
79*d57664e9SAndroid Build Coastguard WorkerTo declare a plugin, you add a service to your manifest.  Add an intent filter to match the action for the plugin, and set the name to point at the class that implements the plugin interface.
80*d57664e9SAndroid Build Coastguard Worker
81*d57664e9SAndroid Build Coastguard Worker```xml
82*d57664e9SAndroid Build Coastguard Worker       <service android:name=".SampleOverlayPlugin"
83*d57664e9SAndroid Build Coastguard Worker            android:label="@string/plugin_label">
84*d57664e9SAndroid Build Coastguard Worker            <intent-filter>
85*d57664e9SAndroid Build Coastguard Worker                <action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
86*d57664e9SAndroid Build Coastguard Worker            </intent-filter>
87*d57664e9SAndroid Build Coastguard Worker        </service>
88*d57664e9SAndroid Build Coastguard Worker```
89*d57664e9SAndroid Build Coastguard Worker
90*d57664e9SAndroid Build Coastguard WorkerPlugins must also hold the plugin permission.
91*d57664e9SAndroid Build Coastguard Worker
92*d57664e9SAndroid Build Coastguard Worker```xml
93*d57664e9SAndroid Build Coastguard Worker   <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
94*d57664e9SAndroid Build Coastguard Worker ```
95*d57664e9SAndroid Build Coastguard Worker
96*d57664e9SAndroid Build Coastguard Worker
97*d57664e9SAndroid Build Coastguard Worker### Implementing the interface
98*d57664e9SAndroid Build Coastguard Worker
99*d57664e9SAndroid Build Coastguard WorkerImplementing the interface is generally pretty straightforward.  The version of the plugin should tagged with an annotation to declare its dependency on each of the plugin classes it depends on.  This ensures that the latest version will be included in the plugin APK when it is compiled.
100*d57664e9SAndroid Build Coastguard Worker
101*d57664e9SAndroid Build Coastguard Worker```java
102*d57664e9SAndroid Build Coastguard Worker@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
103*d57664e9SAndroid Build Coastguard Workerpublic class SampleOverlayPlugin implements OverlayPlugin {
104*d57664e9SAndroid Build Coastguard Worker    ...
105*d57664e9SAndroid Build Coastguard Worker}
106*d57664e9SAndroid Build Coastguard Worker```
107*d57664e9SAndroid Build Coastguard Worker
108*d57664e9SAndroid Build Coastguard WorkerAfter the plugin is created and passes all permission/security checks, then the plugin will receive the onCreate callback.  The pluginContext is pregenerated for the plugin and can be used to inflate or get any resources included in the plugin APK.
109*d57664e9SAndroid Build Coastguard Worker
110*d57664e9SAndroid Build Coastguard Worker```java
111*d57664e9SAndroid Build Coastguard Workerpublic void onCreate(Context sysuiContext, Context pluginContext);
112*d57664e9SAndroid Build Coastguard Worker```
113*d57664e9SAndroid Build Coastguard Worker
114*d57664e9SAndroid Build Coastguard WorkerWhen the plugin is being removed, the plugin will receive the onDestroy callback.  At this point the plugin should ensure that all its resources and static references are cleaned up.
115*d57664e9SAndroid Build Coastguard Worker
116*d57664e9SAndroid Build Coastguard Worker```java
117*d57664e9SAndroid Build Coastguard Workerpublic void onDestroy();
118*d57664e9SAndroid Build Coastguard Worker```
119*d57664e9SAndroid Build Coastguard Worker
120*d57664e9SAndroid Build Coastguard Worker### Adding Settings
121*d57664e9SAndroid Build Coastguard Worker
122*d57664e9SAndroid Build Coastguard WorkerA plugin can provide plugin-specific settings that will be surfaced as a gear button on the plugin tuner screen where plugins can be enabled or disabled.  To add settings just add an activity to receive the PLUGIN_SETTINGS action.
123*d57664e9SAndroid Build Coastguard Worker
124*d57664e9SAndroid Build Coastguard Worker```xml
125*d57664e9SAndroid Build Coastguard Worker        <activity android:name=".PluginSettings"
126*d57664e9SAndroid Build Coastguard Worker            android:label="@string/plugin_label">
127*d57664e9SAndroid Build Coastguard Worker            <intent-filter>
128*d57664e9SAndroid Build Coastguard Worker                <action android:name="com.android.systemui.action.PLUGIN_SETTINGS" />
129*d57664e9SAndroid Build Coastguard Worker            </intent-filter>
130*d57664e9SAndroid Build Coastguard Worker        </activity>
131*d57664e9SAndroid Build Coastguard Worker ```
132*d57664e9SAndroid Build Coastguard Worker
133*d57664e9SAndroid Build Coastguard WorkerThe plugin settings activity does not run in SysUI like the rest of the plugin, so it cannot reference any of the classes from SystemUIPluginLib.
134*d57664e9SAndroid Build Coastguard Worker
135*d57664e9SAndroid Build Coastguard Worker## Examples
136*d57664e9SAndroid Build Coastguard Worker[The definitive ExamplePlugin](/packages/SystemUI/plugin/ExamplePlugin)
137*d57664e9SAndroid Build Coastguard Worker
138*d57664e9SAndroid Build Coastguard Worker[Replace lock screen camera button with a settings trigger](todo)
139*d57664e9SAndroid Build Coastguard Worker
140*d57664e9SAndroid Build Coastguard Worker[A nav button that launches an action](todo)
141*d57664e9SAndroid Build Coastguard Worker
142*d57664e9SAndroid Build Coastguard Worker
143*d57664e9SAndroid Build Coastguard Worker## Writing plugins in Android Studio
144*d57664e9SAndroid Build Coastguard Worker
145*d57664e9SAndroid Build Coastguard WorkerAs long as the plugin doesn’t depend on any hidden APIs (which plugins should avoid anyway) and only uses Plugin APIs, you can be setup to build in android studio with only a couple steps.
146*d57664e9SAndroid Build Coastguard Worker
147*d57664e9SAndroid Build Coastguard Worker### Signing
148*d57664e9SAndroid Build Coastguard Worker
149*d57664e9SAndroid Build Coastguard WorkerPlugins need to be signed with the platform cert, so you’ll need a copy of the keystore that contains the same cert.  You might find one at http://go/plugin-keystore, you can copy it to the root directory of your project.  Then you can tell your module to be signed with it by adding the following to the android section of your module’s build.gradle.
150*d57664e9SAndroid Build Coastguard Worker
151*d57664e9SAndroid Build Coastguard Worker```groovy
152*d57664e9SAndroid Build Coastguard Workerandroid {
153*d57664e9SAndroid Build Coastguard Worker    ...
154*d57664e9SAndroid Build Coastguard Worker    buildTypes {
155*d57664e9SAndroid Build Coastguard Worker        release {
156*d57664e9SAndroid Build Coastguard Worker            minifyEnabled false
157*d57664e9SAndroid Build Coastguard Worker            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
158*d57664e9SAndroid Build Coastguard Worker        }
159*d57664e9SAndroid Build Coastguard Worker        debug {
160*d57664e9SAndroid Build Coastguard Worker            signingConfig signingConfigs.debug
161*d57664e9SAndroid Build Coastguard Worker        }
162*d57664e9SAndroid Build Coastguard Worker     }
163*d57664e9SAndroid Build Coastguard Worker     signingConfigs {
164*d57664e9SAndroid Build Coastguard Worker        debug {
165*d57664e9SAndroid Build Coastguard Worker            keyAlias 'platform'
166*d57664e9SAndroid Build Coastguard Worker            keyPassword 'android'
167*d57664e9SAndroid Build Coastguard Worker            storeFile file('../platform.keystore')
168*d57664e9SAndroid Build Coastguard Worker            storePassword 'android'
169*d57664e9SAndroid Build Coastguard Worker        }
170*d57664e9SAndroid Build Coastguard Worker     }
171*d57664e9SAndroid Build Coastguard Worker   ...
172*d57664e9SAndroid Build Coastguard Worker}
173*d57664e9SAndroid Build Coastguard Worker```
174*d57664e9SAndroid Build Coastguard Worker
175*d57664e9SAndroid Build Coastguard Worker
176*d57664e9SAndroid Build Coastguard Worker### Compiling against Plugin APIs
177*d57664e9SAndroid Build Coastguard Worker
178*d57664e9SAndroid Build Coastguard WorkerTo be able to implement a plugin, you’ll need a jar file that contains the plugin classes for compilation.  Generally you can grab a recent plugin lib from jmonk’s experimental directory.  However if you recently changed one of the plugin interfaces, you might want to build an updated version, you can use the following script to do so.
179*d57664e9SAndroid Build Coastguard Worker
180*d57664e9SAndroid Build Coastguard Worker```
181*d57664e9SAndroid Build Coastguard Worker$ frameworks/base/packages/SystemUI/plugin/update_plugin_lib.sh
182*d57664e9SAndroid Build Coastguard Worker```
183*d57664e9SAndroid Build Coastguard Worker
184*d57664e9SAndroid Build Coastguard WorkerOnce you have the jar you are going to compile against, you need to include it in your android studio project as a file dependency.  Once it is included change its scope from Compile to Provided in the project structure (you may need to build once before changing to provided).  This is required to ensure you don’t actually include the plugin library in your plugin APK.
185*d57664e9SAndroid Build Coastguard Worker
186*d57664e9SAndroid Build Coastguard Worker## Implementation Details
187*d57664e9SAndroid Build Coastguard Worker
188*d57664e9SAndroid Build Coastguard WorkerPlugins are APKs that contain code and resources that can be dynamically loaded into SystemUI.  The plugins are compiled against a set of relatively stable (and version tagged) interfaces, that the implementations are provided by SysUI.  This figure shows an overview of how the plugin compiling/loading flow works.
189*d57664e9SAndroid Build Coastguard Worker
190*d57664e9SAndroid Build Coastguard Worker![How plugins work](sysui-plugins.png)
191*d57664e9SAndroid Build Coastguard Worker
192*d57664e9SAndroid Build Coastguard Worker### Security
193*d57664e9SAndroid Build Coastguard Worker
194*d57664e9SAndroid Build Coastguard WorkerWhenever loading a code from another APK into a privileged process like SysUI, there are serious security concerns to be addressed.  To handle this, plugins have a couple lines of defense to ensure these don’t create any security holes.
195*d57664e9SAndroid Build Coastguard Worker
196*d57664e9SAndroid Build Coastguard WorkerThe first line of defense is Build.IS_DEBUGGABLE checks.  In 2 different places, SysUI checks to ensure that the build is debuggable before even scanning or loading any plugins on the device.  There are even tests in place to help ensure these checks are not lost.
197*d57664e9SAndroid Build Coastguard Worker
198*d57664e9SAndroid Build Coastguard WorkerThe second line of defense is a signature permission.  This ensures that plugins are always provided by the source of the android build.  All plugins must hold this permission for any of their code to be loaded, otherwise the infraction will be logged, and the plugin ignored.
199*d57664e9SAndroid Build Coastguard Worker
200*d57664e9SAndroid Build Coastguard Worker```xml
201*d57664e9SAndroid Build Coastguard Worker   <permission android:name="com.android.systemui.permission.PLUGIN"
202*d57664e9SAndroid Build Coastguard Worker            android:protectionLevel="signature" />
203*d57664e9SAndroid Build Coastguard Worker ```
204*d57664e9SAndroid Build Coastguard Worker
205*d57664e9SAndroid Build Coastguard Worker### Plugin Management
206*d57664e9SAndroid Build Coastguard Worker
207*d57664e9SAndroid Build Coastguard WorkerPlugins are scanned for by intent filters of services.  A plugin is not actually a service, but the benefits of declaring it as a service makes it worth it.  Each plugin listener in SysUI simply specifies an action to look for, and the PluginManager scans for services declaring that action and uses that to know the class to instantiate.
208*d57664e9SAndroid Build Coastguard Worker
209*d57664e9SAndroid Build Coastguard Worker
210*d57664e9SAndroid Build Coastguard WorkerThe other major advantage to declaring plugins through components in a manifest is management of enabled state.  Whether a plugin is enabled or disabled is managed by the package manager component enabled state.  When a device has had a plugin installed on it, an extra section is added to the SystemUI Tuner, it lists all of the plugins on the device and allows the components to be easily enabled and disabled.
211*d57664e9SAndroid Build Coastguard Worker
212*d57664e9SAndroid Build Coastguard Worker### Versioning
213*d57664e9SAndroid Build Coastguard Worker
214*d57664e9SAndroid Build Coastguard WorkerWhen a plugin listener is registered in SysUI, the interface version is specified.  Whenever a plugin is detected, the first thing that is done after instantiation is the version is checked.  If the version of the interface the plugin was compiled with does not match the version SysUI contains, then the plugin will be ignored.
215*d57664e9SAndroid Build Coastguard Worker
216*d57664e9SAndroid Build Coastguard Worker### Class loading
217*d57664e9SAndroid Build Coastguard Worker
218*d57664e9SAndroid Build Coastguard WorkerWhen plugins are loaded, they are done so by creating a PathClassLoader that points at the plugin APK.  The parent of the classloader is a special classloader based on SysUI’s that only includes the classes within the package com.android.systemui.plugin and its sub-packages.
219*d57664e9SAndroid Build Coastguard Worker
220*d57664e9SAndroid Build Coastguard WorkerHaving SysUI provide the implementations of the interfaces allows them to be more stable.  Some version changes can be avoided by adding defaults to the interfaces, and not requiring older plugins to implement new functionality.  The plugin library can also have static utility methods that plugins compile against, but the implementations are in sync with the platform builds.
221*d57664e9SAndroid Build Coastguard Worker
222*d57664e9SAndroid Build Coastguard WorkerThe class filtering in the parent classloader allows plugins to include any classes they want without worrying about collisions with SysUI.  Plugins can include SettingsLib, or copy classes directly out of SysUI to facilitate faster prototyping.
223*d57664e9SAndroid Build Coastguard Worker
224*d57664e9SAndroid Build Coastguard Worker### Crashing
225*d57664e9SAndroid Build Coastguard Worker
226*d57664e9SAndroid Build Coastguard WorkerWhether it be from accidental reference of hidden APIs, unstable prototypes, or other unexpected reasons, plugins will inevitably cause SysUI to crash.  When this happens it needs to ensure a bad acting plugin do not stop the phone from being usable.
227*d57664e9SAndroid Build Coastguard Worker
228*d57664e9SAndroid Build Coastguard WorkerWhen a plugin crashes, the PluginManager catches it and tries to determine the plugin that caused the crash.  If any of the classes in the stack trace are from the package of the plugin APK, then the plugin is disabled.  If no plugins can be identified as the source of the crash, then all plugins are disabled, just to be sure they aren’t causing future crashes.
229