Overview

LocaleĀ® contains an advanced plug-in architecture, allowing new conditions and settings to be created by third party developers. In fact, there are over 500 plug-ins currently available for Locale on Google Play! This page contains documentation, code samples, and tips for implementing a Locale plug-in.

Downloads

Locale Display Condition: Source code for a sample plug-in condition that detects when the display is on or off.

Locale Toast Setting: Source code for a sample plug-in setting that posts a toast message.

The samples are fully backwards compatible with Android API Level 8, but they must be compiled with Android API Level 17 or later. (A compiler error saying that com.twofortyfouram.locale.api.R cannot be found usually means the wrong API Level is being used). The samples depend on an Android library project to share common constants and resources.

Technical Documentation

The Locale Developer Platform exposes APIs for plug-in conditions and settings. Interaction with Locale occurs via Intents which are documented in the following sections, along with the Developer Platform JavaDocs.

AndroidManifest Entries

A plug-in must implement several things in its AndroidManifest:

  1. "Edit" Activity for the ACTION_EDIT_CONDITION or ACTION_EDIT_SETTING Intent action. The Activity icon will be shown in the Locale UI. (The ldpi version of the icon should be 27x27 pixels, the mdpi version should be 36x36 pixels, the hdpi version of the icon should be 48x48 pixels, and the xhdpi version of the icon should be 72x72). In addition, the Label is the name that will be displayed in the Locale UI.
  2. BroadcastReceiver: for ACTION_QUERY_CONDITION or ACTION_FIRE_SETTING Intent action.
  3. Stable Package and Class: The package and class names for the edit Activity are a plug-in's public API to Locale. These names are very important and must remain consistent across different versions of the plug-in. If they do not remain consistent, then saved instances of the plug-in created previously will be orphaned. For more information, see Dianne Hackborn's blog post Things That Cannot Change.

A plug-in condition's AndroidManifest may look something like this:

  <?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.yourcompany.yourcondition" >
      <application
          android:label="@string/app_name" >
          <!-- Listen for the "edit" action -->
          <activity
                android:name=".EditActivity"
                android:label="@string/setting_name"
                android:icon="@drawable/icon_plugin"
                <intent-filter>
                    <action android:name="com.twofortyfouram.locale.intent.action.EDIT_CONDITION" />
                </intent-filter>
          </activity>
          <!-- Listen for the "query" action-->
          <receiver android:name=".QueryReceiver" >
              <intent-filter>
                  <action android:name="com.twofortyfouram.locale.intent.action.QUERY_CONDITION" />
              </intent-filter>
          </receiver>
      </application>
  </manifest>
      

A plug-in setting's AndroidManifest may look something like this:

  <?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.yourcompany.yoursetting" >
      <application
          android:label="@string/app_name" >
          <!-- Listen for the "edit" action -->
          <activity
                android:name=".EditActivity"
                android:label="@string/setting_name"
                android:icon="@drawable/icon_plugin"
                <intent-filter>
                    <action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
                </intent-filter>
          </activity>
          <!-- Listen for the "fire" action-->
          <receiver android:name=".FireReceiver" >
              <intent-filter>
                  <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
              </intent-filter>
          </receiver>
      </application>
  </manifest>
      

UI Activity for Editing

When the user taps on the plug-in in Locale, the "edit" action (either ACTION_EDIT_CONDITION or ACTION_EDIT_SETTING) is broadcast 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 Locale must contain the following:

  1. Result Code: Tells Locale 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 Locale and forwarded back to the plug-in whenever the plug-in is edited again or eventually queried/fired. Locale guarantees that the Bundle extras will not be modified in any way. Practically anything can be stored in the Bundle, with the following limitations: 1. The Bundle cannot contain Parcelable objects, because these cannot be stored long-term. Use Serializable instead. 2. The Bundle can only contain objects that are part of the standard Android platform, because Locale's class loader will need to serialize and deserialize these objects. 3. The Bundle MUST be less than 100-kilobytes (base-10) in size, and should be significantly smaller than that.

BroadcastReceiver for Querying a Condition

When Locale queries a plug-in condition, Locale will send an explicit ordered Intent with the action ACTION_QUERY_CONDITION to the BroadcastReceiver exported by the plug-in's AndroidManifest. This Intent will contain the EXTRA_BUNDLE extra that was previously saved by the edit Activity. There will be one broadcast for each plug-in instance. Using the Display Condition example, let's say the user has created two situations in Locale, each with a single Display condition. Locale will broadcast two ACTION_QUERY_CONDITION Intents, each with a different EXTRA_BUNDLE. Plug-ins should not keep state about what instances have been created, as the user could delete these from within Locale at any time. A plug-in should simply process each ordered broadcast as it comes.

Locale makes no guarantees as to how often or if the plug-in will be queried. Typically, a plug-in will be queried once when Locale first starts up, and additional queries may happen at any time. If a situation in Locale contains multiple conditions, the order in which conditions are queried is unspecified. Locale may choose to stop evaluating conditions in a situation if one condition is not satisfied, at which point a plug-in may never be queried. Similarly, a plug-in will no longer be queried if Locale is uninstalled or its service is stopped.

In addition to waiting for Locale to query the plug-in, a plug-in can signal to Locale that it is ready to be queried via the ACTION_REQUEST_QUERY Intent. If a plug-in does not receive a response to a requery request within one minute, the plug-in should stop requesting requeries as no response indicates that Locale is not currently interested in that plug-in. (All instances of that plug-in could have been deleted within Locale, Locale's service could have been stopped, Locale could have been uninstalled, etc.). When Locale becomes interested in the plug-in again, the plug-in will receive ACTION_QUERY_CONDITION.

It may not be possible to implement all conceivable plug-in conditions with this interface. Plug-ins that operate asynchronously (e.g. they receive actions like ACTION_POWER_CONNECTED and then broadcast ACTION_REQUEST_QUERY to Locale) or plug-ins that can respond to a query Intent immediately, such as a Calendar, will work best. Plug-ins that require a background service to operate are not recommended, as each running service consumes additional RAM which is limited resource on an Android device.

Below is a code sample showing how that BroadcastReceiver might try to handle the "query" Intent.

    public final class QueryReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(final Context context, final Intent intent)
        {
            if (com.twofortyfouram.locale.Intent.ACTION_QUERY_CONDITION.equals(intent.getAction()))
            {
                [your implementation here...]
            }
        }
    }
      

BroadcastReceiver for Firing a Setting

When it comes time to apply a plug-in setting, Locale will send an explicit Intent with the action ACTION_FIRE_SETTING to the BroadcastReceiver exported by the plug-in's AndroidManifest. Below is a code sample showing how that BroadcastReceiver might try to handle the "fire" Intent and apply the setting.

    public final class FireReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(final Context context, final Intent intent)
        {
            if (com.twofortyfouram.locale.Intent.ACTION_FIRE_SETTING.equals(intent.getAction()))
            {
                [your implementation here...]
            }
        }
    }
      

Tips

  • Debugging

    Locale will print a number of logcat messages to help plug-in developers. Specifically, Locale prints log messages during the registration 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 background service also prints log messages when querying plug-in conditions or firing plug-in settings.

  • Security

    Although Locale keeps 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 Locale to a plug-in Activity through ActivityManager.getRecentTasks(int, int).

    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 process and will have its own private storage space. To improve security of private data, such as usernames and passwords, only minimal information needs to be passed to Locale 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 Locale 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 Locale. 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 Locale 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 via the Android Backup Manager. 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. For example, an app with a targetSdkVersion="3" won't be allowed to use high-resolution icons and graphics. 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, disk IO) and WakeLocks

    A BroadcastReceiver is supposed to do its work quickly. In fact, a BroadcastReceiver has 10 seconds to complete before Android kills it. Longer-running tasks, such as making a request over the Internet, will require an Android Service. When launching a Service, plug-ins also also need to acquire a WakeLock in the BroadcastReceiver to ensure that the device doesn't fall asleep until the task is completed because Locale may send broadcasts to a plug-in's BroadcastReceiver when Android is asleep. The Display plug-in condition exemplifies how to do this.

  • 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 Shortcut setting can only choose a single app to launch. In order to launch multiple apps, the user simply performs a long-press on the Add Setting button to add 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.

  • 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. Without such an entrypoint, Google Play will give a "launch error" when users try to open the plug-in. 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 end-user. For such plug-ins, a Launcher Activity should be provided so that the user can undo such changes even if Locale isn't installed. For example, consider APNDroid: APNDroid provides a Launcher Activity and a Locale plug-in. Even if the user were unable to open Locale, the user could still restore his 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 from within Locale 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.