Home | History | Annotate | Download | only in docs
      1 # SystemUI Plugins
      2 
      3 Plugins 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.
      4 
      5 Plugins 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.
      6 
      7 ## Adding Plugin Hooks
      8 
      9 Existing plugin hooks can be found [here](/packages/SystemUI/docs/plugin_hooks.md).
     10 
     11 ### Writing the Interface(s)
     12 
     13 The 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.
     14 
     15 All 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.
     16 
     17 
     18 The 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 doesnt 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.
     19 
     20 
     21 ```java
     22 @ProvidesInterface(action = MyPlugin.ACTION, version = MyPlugin.VERSION)
     23 @DependsOn(target = OtherInterface.class)
     24 public interface MyPlugin extends Plugin {
     25  String ACTION = "com.android.systemui.action.PLUGIN_MY_PLUGIN";
     26  int VERSION = 1;
     27     ...
     28 }
     29 ```
     30 
     31 ### Plugin Listener
     32 
     33 To actually listen for plugins, you implement a plugin listener that has the following interface.
     34 
     35 ```java
     36 public interface PluginListener<T extends Plugin> {
     37  /**
     38  * Called when the plugin has been loaded and is ready to be used.
     39  * This may be called multiple times if multiple plugins are allowed.
     40  * It may also be called in the future if the plugin package changes
     41  * and needs to be reloaded.
     42  */
     43  void onPluginConnected(T plugin);
     44 
     45  /**
     46  * Called when a plugin has been uninstalled/updated and should be removed
     47  * from use.
     48  */
     49  default void onPluginDisconnected(T plugin) {
     50  // Optional.
     51  }
     52 }
     53 ```
     54 
     55 Then 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.
     56 
     57 ```java
     58 void addPluginListener(String action, PluginListener<T> listener,
     59  int version, boolean allowMultiple);
     60 ```
     61 
     62 ### Examples
     63 [Allow quick settings panel to be replaced with another view](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java)
     64 
     65 [Allow plugins to create new nav bar buttons](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java)
     66 
     67 [Allow lockscreen camera/phone/assistant buttons to be replaced](/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java)
     68 
     69 ## Writing Plugins
     70 ### Make Files and Manifests
     71 
     72 When compiling plugins there are a couple vital pieces required.
     73 1. They must be signed with the platform cert
     74 2. They must include SystemUIPluginLib in LOCAL_JAVA_LIBRARIES (NOT LOCAL_STATIC_JAVA_LIBRARIES)
     75 
     76 Basically just copy the [example blueprint file](/packages/SystemUI/plugin/ExamplePlugin/Android.bp).
     77 
     78 To 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.
     79 
     80 ```xml
     81  <service android:name=".SampleOverlayPlugin"
     82  android:label="@string/plugin_label">
     83  <intent-filter>
     84  <action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
     85  </intent-filter>
     86  </service>
     87 ```
     88 
     89 Plugins must also hold the plugin permission.
     90 
     91 ```xml
     92  <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
     93  ```
     94 
     95 
     96 ### Implementing the interface
     97 
     98 Implementing 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.
     99 
    100 ```java
    101 @Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
    102 public class SampleOverlayPlugin implements OverlayPlugin {
    103     ...
    104 }
    105 ```
    106 
    107 After 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.
    108 
    109 ```java
    110 public void onCreate(Context sysuiContext, Context pluginContext);
    111 ```
    112 
    113 When 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.
    114 
    115 ```java
    116 public void onDestroy();
    117 ```
    118 
    119 ### Adding Settings
    120 
    121 A 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.
    122 
    123 ```xml
    124  <activity android:name=".PluginSettings"
    125  android:label="@string/plugin_label">
    126  <intent-filter>
    127  <action android:name="com.android.systemui.action.PLUGIN_SETTINGS" />
    128  </intent-filter>
    129  </activity>
    130  ```
    131 
    132 The plugin settings activity does not run in SysUI like the rest of the plugin, so it cannot reference any of the classes from SystemUIPluginLib.
    133 
    134 ## Examples
    135 [The definitive ExamplePlugin](/packages/SystemUI/plugin/ExamplePlugin)
    136 
    137 [Replace lock screen camera button with a settings trigger](todo)
    138 
    139 [A nav button that launches an action](todo)
    140 
    141 
    142 ## Writing plugins in Android Studio
    143 
    144 As long as the plugin doesnt 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.
    145 
    146 ### Signing
    147 
    148 Plugins need to be signed with the platform cert, so youll 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 modules build.gradle.
    149 
    150 ```groovy
    151 android {
    152   ...
    153     buildTypes {
    154       release {
    155       minifyEnabled false
    156       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    157       }
    158       debug {
    159       signingConfig signingConfigs.debug
    160       }
    161      }
    162      signingConfigs {
    163       debug {
    164       keyAlias 'platform'
    165       keyPassword 'android'
    166       storeFile file('../platform.keystore')
    167       storePassword 'android'
    168       }
    169      }
    170  ...
    171 }
    172 ```
    173 
    174 
    175 ### Compiling against Plugin APIs
    176 
    177 To be able to implement a plugin, youll need a jar file that contains the plugin classes for compilation. Generally you can grab a recent plugin lib from jmonks 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.
    178 
    179 ```
    180 $ frameworks/base/packages/SystemUI/plugin/update_plugin_lib.sh
    181 ```
    182 
    183 Once 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 dont actually include the plugin library in your plugin APK.
    184 
    185 ## Implementation Details
    186 
    187 Plugins 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.
    188 
    189 ![How plugins work](sysui-plugins.png)
    190 
    191 ### Security
    192 
    193 Whenever 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 dont create any security holes.
    194 
    195 The 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.
    196 
    197 The 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.
    198 
    199 ```xml
    200  <permission android:name="com.android.systemui.permission.PLUGIN"
    201  android:protectionLevel="signature" />
    202  ```
    203 
    204 ### Plugin Management
    205 
    206 Plugins 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.
    207 
    208 
    209 The 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.
    210 
    211 ### Versioning
    212 
    213 When 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.
    214 
    215 ### Class loading
    216 
    217 When 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 SysUIs that only includes the classes within the package com.android.systemui.plugin and its sub-packages.
    218 
    219 Having 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.
    220 
    221 The 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.
    222 
    223 ### Crashing
    224 
    225 Whether 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.
    226 
    227 When 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 arent causing future crashes.
    228