Developer API

Locale® contains an advanced plug-in architecture, allowing developers to create new 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.

Sample Code

Interaction between Locale (host) and plug-ins (client) occurs via an Intent-based API. The plug-in architecture is implemented in multiple different layers (Specification, API, SDK, and Example), with each subsequent layer becoming more abstract and easier to work with. These layers are open sourced on GitHub.

  1. Specification: Plug-in API Specification defines the deep details of the plug-in protocol. We do not recommend developers start at this layer.

  2. API: pluginApiLib defines the Intent-based API constants. We do not recommend developers start at this layer.

  3. SDK: pluginClientSdkLib contains a set of classes that abstract the Intent-based API, allowing plug-in developers to focus on the novel behavior of their own plug-ins. If you’re adding a plug-in to an existing app, use pluginClientSdkLib. (For completeness, the pluginHostSdkLib is also available to demonstrate Locale's internal implementation for interacting with plug-ins).

  4. Example: Real working plug-ins that demonstrate how to use the SDK layer. If you’re creating a new app from scratch, simply fork the Github project and modify the behavior for your plug-in.

    1. android-toast-setting-plugin-for-locale An example plug-in setting that posts a Toast message on the screen.

Plug-in API Change Log

  • 1.0 December 2009

    • Initial release

  • 1.5 July 2021

    • EXTRA_BUNDLE has updated to a new convention of containing a single String value, representing plug-in data in JSON format. This is backwards compatible, because the host still serializes EXTRA_BUNDLE in the same way.

  • 2.0 (Upcoming)

    • Simplified integration through a new Kotlin SDK and example plug-in.

    • Improved reliability on newer versions of Android through a new ContentProvider based mechanism to improve reliability with newer versions of Android.

    • Backwards compatible with older hosts still using the BroadcastReceiver approach.

    • Support for plug-ins to signal warnings to the host, such as missing permissions.

    • Plug-in Conditions are deprecated because Android has restricted background apps, causing plug-in conditions that need to run all the time to be unreliable. Plug-in settings are not affected by this change, because they run quickly and then shut down.

Documentation

Plug-in Fundamentals

A plug-in implementation consists of two things:

  1. Activity: for the ACTION_EDIT_SETTING Intent action. The Activity icon and label will be shown in the host UI, identifying the plug-in to the user.

  2. BroadcastReceiver: for ACTION_FIRE_SETTING Intent action.

At runtime the host launches the plug-in's Activity, the plug-in's Activity returns a Bundle to the host, and the host will send that Bundle back to the plug-in's BroadcastReceiver when it is time to fire the plug-in. The host may also pass the Bundle back to the Activity in the future, if the user wishes to edit the plug-in's configuration.

Activity for Editing

When the user taps on the plug-in in the host, the "edit" action (ACTION_EDIT_SETTING) is sent to launch the Activity exported by the plug-in's AndroidManifest.

Once the user has completed configuring the plug-in, the Activity result Intent back to the host must contain the following:

  1. Result Code: Tells the host whether the plug-in should be saved or not. RESULT_OK indicates that changes should be saved and blurb and Bundle are required. RESULT_CANCELED indicates that changes should not be saved and no blurb or Bundle are required.

  2. Blurb Extra: Constant key value EXTRA_STRING_BLURB maps to a String extra that represents a concise, human-readable status of the plug-in instance. For example, a volume setting might have a blurb that says, "50%."

  3. Bundle Extra: Constant key value EXTRA_BUNDLE maps to a Bundle that represents the entire state of the plug-in. This Bundle is stored by the host and forwarded back to the plug-in whenever the plug-in is edited again or eventually queried/fired. The host guarantees that the Bundle will not be modified in any way. By convention, the Bundle contains a single String under EXTRA_STRING_JSON that is a valid JSON string. Note that the final Bundle MUST be less than 25-kilobytes (base-10) in size, and SHOULD be significantly smaller than that.

BroadcastReceiver for Firing a Setting

When it comes time to apply a plug-in setting, the host will send an explicit Intent with the action ACTION_FIRE_SETTING to the BroadcastReceiver exported by the plug-in's AndroidManifest.

Tips

Debugging

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.

Security

Although the host should keep its configuration private, plug-ins MUST NOT pass sensitive information such as login credentials via EXTRA_BUNDLE. 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 EXTRA_BUNDLE Bundle 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.

Target SDK

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.

Hardware Acceleration

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 BroadcastReceiver (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 BroadcastReceiver is supposed to do its work quickly. In fact, a plug-in BroadcastReceiver has only five seconds to complete before Android kills it. Longer-running tasks, such as making a request over the Internet, may require an Android Service.

Selecting multiple items

In general, a plug-in 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.

Duplication

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.

Default entrypoint

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 example plug-ins provide InfoActivity for this purpose.

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, such as Seal. When creating such plug-ins, be sure to test what happens if the user tries launching the plug-in before launching the standalone app.

Concise blurbs

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.

Localization

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.