NOTE: Several hyperlinks on this page will be broken until the code is merged to the master branch of the monorepo.
Locale® contains an advanced plug-in architecture, allowing developers to create new conditions and settings. In fact, there are hundreds of plug-ins currently available for Locale! This page contains code samples, documentation, and tips for implementing a plug-in.
Interaction between Locale (host) and plug-ins (client) occurs via an interprocess communication (IPC) API. The plug-in architecture is implemented in three different layers (API, SDK, and Example), with each subsequent layer becoming easier to work with. These layers are open sourced on GitHub.
- API: android-plugin-api-for-locale defines the IPC API using Intents and ContentProviders. We do not recommend developers start at this layer.
- Low Level SDK: android-plugin-client-sdk-for-locale contains a set of classes that abstract the API somewhat to simplify dealing with IPC, however underlying implementation details are still exposed. (For completeness, the android-plugin-host-sdk-for-locale is also available to demonstrate Locale's internal implementation for interacting with plug-ins).
- High Level SDK: pluginClientConditionSdkLib and pluginClientSettingSdkLib Significantly abstract away implementation details, allowing plug-in developers to focus on the novel behavior of their own plug-ins with a minimum of additional code
- Example: Real working plug-ins that demonstrate how to use the High Level SDK layer. For all plug-in developers, we highly recommend starting at this layer. Simply fork one of these Github projects and modify the behavior for your plug-in.
A plug-in has a lifecycle consisting of three main phases:
- Scan: The plug-in host detects the plug-in is installed. This happens when the plug-in is installed and before the plug-in runs. The host finds the plug-in's Edit Activity (for the ACTION_EDIT_CONDITION or ACTION_EDIT_SETTING Intent action) and Execute ContentProvider (for ACTION_QUERY_CONDITION to query a condition or for ACTION_FIRE_SETTING to fire the setting). These details are taken care of by the SDK.
- Edit: At runtime the host launches the plug-in's Activity, the plug-in's Activity returns JSON to the host. The host will store this JSON and may pass it back to the plug-in in the future, if the user wishes to edit the plug-in's configuration.
- Execute: The previously saved JSON from the Edit phase is sent to the plug-in to execute.
To implement those phases with the High Level SDK, plug-ins just need to implement a few specific classes:
- Application object: Create a custom application object, inherit from the
PluginConditionApplicationinterface, and add the Application object to your
AndroidManifest.xml. This allows the SDK to find your specific plug-in implementation.
- Implement the behavior objects:
PluginDataDelegate- Used during both the Edit and Execute phases. This takes care of converting the JSON that passed back and forth between the plug-in and the host to a regular Object that is easier to work with
- Add resource overlays
com_twofortyfouram_locale_sdk_client_condition_name- A string for the plug-in's name to display in the host
com_twofortyfouram_locale_sdk_client_condition_ic_plugin- A drawable for the plug-in's icon to display in the host
com_twofortyfouram_locale_sdk_client_setting_name- A string for the plug-in's name to display in the host
com_twofortyfouram_locale_sdk_client_setting_ic_plugin- A drawable for the plug-in's icon to display in the host
Upgrading from Earlier Versions of the Plug-in SDK
- Convert data from Bundle to JSON
- Provide remapping of old Edit activity class name
Locale prints logcat messages to help plug-in developers. Specifically, Locale prints log messages during the plug-in discovery process and will note any errors detected in the plug-in. In addition, Locale prints log messages when starting and receiving results from the plug-in's edit Activity. Finally, Locale's Service also prints log messages when querying plug-in conditions or firing plug-in settings.
Although the host should keep its configuration private, plug-ins MUST NOT pass sensitive information such as login credentials via JSON. Doing so would constitute a serious security flaw because Android allows any application with the GET_TASKS permission to read the Intent sent by the host to a plug-in Activity through ActivityManager.getRecentTasks(int, int). Although this security flaw is fixed in Android 5.0 and later, there are many devices still running vulnerable versions of Android.
If a plug-in needs to store login credentials, there are more secure implementations. Remember that each app on Android with a unique digital signature will run in its own sandbox. To improve security of private data, such as usernames and passwords, only minimal information needs to be passed to the host via EXTRA_BUNDLE. For example, consider a hypothetical plug-in setting that posts a Tweet to Twitter. It could store OAuth credentials in a SharedPreference file private to the plug-in and only returns the non-private Tweet message via EXTRA_BUNDLE.
Backup/restore with login credentials
Consider what happens to a plug-in if its application data is cleared but the instances within the host are not. For example, a hypothetical plug-in might post a Tweet to Twitter. This plug-in stores only the Tweet message within its EXTRA_BUNDLE to the host. The Tweet plug-in stores its OAuth login credentials in a SharedPreference file that is private to the Tweet plug-in. When the Tweet plug-in receives a "fire" Intent from the host but doesn't have any login credentials stored, it displays a notification in order to let the user login again. This ensures that the Tweet plug-in doesn't fail mysteriously or silently when it is reinstalled. For security reasons, a plug-in should not store login credentials within the JSON nor should it back them up via the Android Backup Manager.
Backup/restore with hardware changes
Locale supports backup and restore. Consider what happens to a plug-in if it depends on certain hardware or software characteristics that are different across Android devices. For example, the HTC Nexus One ringer volume can range from 0 to 7, while the Motorola Droid X ringer volume can range from 0 to 15. Locale's built-in Volume setting stores volumes as a relative value (e.g. 100%) rather than an absolute value (e.g. 15) in order to ensure portability across different devices during backup and restore.
Every Android application should contain the targetSdkVersion attribute in its Android Manifest. This should be set to the highest value that the application has been tested against. By not keeping this value up to date with the latest versions of Android, the application will be forced to run in Android's backwards compatibility mode. Locale will print a warning via logcat for all plug-ins whose targetSdkVersion is less than Locale's.
Android 3.0 (API level 11) and later provide support for hardware acceleration of an app's UI. When hardware acceleration is enabled, an app's RAM memory usage is significantly increased for the lifetime of the process as soon as any UI is displayed. For this reason, we recommend one of two solutions: 1. Disable hardware acceleration for the plug-in. 2. Put the plug-in's ContentProvider (and any Services) in a separate process from the UI so that background processing will use less memory.
Long requests (e.g. Internet requests) and WakeLocks
A plug-in's ContentProvider is supposed to do its work quickly. Longer-running tasks, such as making a request over the Internet, will require an Android Service or using WorkManager.
Selecting multiple items
In general, a condition or setting within Locale should only choose a single item because Locale can add multiple conditions or settings of the same type to a situation. For example, the built-in Shortcut setting only chooses a single app to launch. In order to launch multiple apps, the user adds another instance of the Shortcut setting to the situation.
In general, avoid creating plug-ins that duplicate functionality built-in to Locale. For example, Locale's built-in Volume setting controls ringer and notification volumes. If a hypothetical third-party volume plug-in were to also control ringer and notification volumes, it would be possible for a user to configure both the built-in and plug-in settings within Locale at the same time and the two settings would conflict with each other. In this case, it would be best if the plug-in omitted ringer and notification volumes and only controlled voice and alarm volumes, which Locale does not natively control at this time.
If a plug-in is not a standalone application and does not provide a launcher screen Activity, then it should implement an Info Activity that gives users an entrypoint to the plug-in from Google Play. The plug-in SDK provides InfoActivity for this purpose.
If you wish to remove the InfoActivity, that can be done with a manifest merger remove node.
Launcher Activity for undo of plug-in settings
Some plug-ins may change settings that are not normally exposed by Android to the user. For such plug-ins, a Launcher Activity should be provided so that the user can undo such changes even if the host isn't installed. For example, consider APNDroid: APNDroid provides a Launcher Activity and a plug-in. Even if the user were unable to open Locale, the user could still restore APN settings from the APNDroid Launcher Activity. By providing a Launcher Activity, it is possible for a user to undo anything that the plug-in has set.
Test plug-ins for standalone apps
Many plug-ins are part of a larger standalone app. When creating such plug-ins, be sure to test what happens if the user tries launching the plug-in before launching the standalone app.
If the blurb cannot represent the plug-in concisely, then consider reducing the complexity of the plug-in. For example, the Locale Volume setting does not control media, alarm, and voice volumes specifically because all those values cannot be represented in a single blurb. Instead, it would be better to break the different volumes into separate settings. With that said, this is simply a guideline and best judgment should be used when creating a blurb.
Run from internal memory
Locale is a background service. Therefore Locale will only run from internal memory in order to meet Android's guidelines for app install locations. Because plug-ins similarly run in the background, they must also be configured to run from internal memory only.
Locale is currently localized to Czech (cs), German (de), English (en), Spanish (es), French (fr), Italian (it), Japanese (jp), Korean (ko), Norwegian (nb), Dutch (nl), Brazilian Portuguese (pt-rBR), Russian (ru), Swedish (sv), and Vietnamese (vi). Plug-in developers should consider localizing plug-ins in order to provide a seamless experience for users.