PluginsDisplay any dynamic content you like in Canvas by writing a plugin. Plugins can provide text or image content for users to select when they design screens.
A plugin is an Android application which implements the Canvas plugin interface. I won't explain the basics of Android programming here...
The example Now Playing project, which also includes the PebbleCanvasPlugin.java file which you will need, can be viewed/downloaded at https://github.com/sjp4/CanvasNowPlaying
How this works
The plugin tells Canvas what content it can provide, then Canvas asks the plugin for content when it needs it. A plugin either provides text content or an image.
Text content is broken down into format masks. Have a play around with the various built-in content types in Canvas to get a feel how these work. Your text plugin will define a set of format masks (e.g. the Now Playing example defines masks for artist, title and album), and will receive requests for values of individual masks.
3 steps to a plugin:
- Firstly, you need to include the plugin interface in your Android project. This is one java file (PebbleCanvasPlugin.java) containing an abstract class (PebbleCanvasPlugin) which you must extend in your app. Don't edit this file - it forms the interface between Canvas and the plugin.
- Create your own class (your equivalent of NowPlayingPlugin in the Now Playing example plugin) which extends PebbleCanvasPlugin. There are 3 abstract methods which must be overriden in your implementation:
protected abstract ArrayList<PluginDefinition> get_plugin_definitions(Context context)
A callback for the plugin to provide metadata describing to Canvas what content it provides.
A plugin application could actually provide more than one plugin. For each one, create an instance of either TextPluginDefinition or ImagePluginDefinition and fill in all of the metadata, then add it to the return value. You should keep track of the ID that you define for each plugin (which should be statically defined) - see the Now Playing example.
The format_mask_examples field is optional - populate this with data for each text field if possible when requested.
protected abstract String get_format_mask_value(int def_id, String format_mask, Context context)
A callback for the plugin to provide a text value for the specified format mask. This will be called for each used format mask (and not for masks which are not used). You should always check the def_id if you are defining multiple plugins
protected abstract Bitmap get_bitmap_value(int def_id, Context context)
A callback for the plugin to provide a bitmap value. Always check the def_id.
- Add an entry to your AndroidManifest.xml file, within the Application element:
<receiver android:exported="true" android:name="com.example.package.MyPlugin"> <intent-filter> <action android:name="com.pennas.pebblecanvas.plugin.REQUEST_DEFINITIONS"> <action android:name="com.pennas.pebblecanvas.plugin.REQUEST_UPDATE"> </action></action></intent-filter> </receiver>
.. changing com.example.package.MyPlugin to the fully qualified name of the class in your application which extends PebbleCanvasPlugin. Without this entry, nothing will work ;)
If you implement an image plugin, then you must also request <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> so that the interface can write each image to disk for transfer to Canvas.
Plugins do not need to define an Activity (so they will not have an icon in the app drawer)... although they can if required.
If you followed the 3 steps above, returned some dummy strings/images in the callbacks, and installed the application to your phone, then your plugin would appear in Canvas! Then you just need to provide some real dynamic content...
As you can see in the Now Playing example there is a separate BroadcastReceiver which is called whenever the currently playing music changes. This performs some checks, finds the album art if possible, then sets some static variables in the plugin class (the static is important because a new instance of the class is created when each message from Canvas is received), ready to be sent to Canvas in the callback. The example also stores these values in SharedPreferences, so that they can be recalled if the process is killed.
But when will Canvas callback for new values?
- When it wants to. This could be because the currently active screen uses your plugin and this is updated on a regular basis, or because a screen in the Editor is using your plugin and updating rather more regularly
- When you ask it to. You can call PebbleCanvasPlugin.notify_canvas_updates_available(int def_id, Context context) if the plugin receives some new data which it thinks that Canvas would like to know about. Please note: Canvas is not guaranteed to callback for updated data after you call this - it might be that your plugin isn't being used at the moment, or that it is too soon after the last update.
Notice that, when the Now Playing example receives a callback to supply values, it doesn't have to go searching for them or do anything complex - just return the values that are already stored for each field.
Plugins should ideally not do anything complicated or long-running during the callback, because it is possible (and indeed very likely when the user is designing a screen using your plugin) that this will be called many times in a short space of time.
The best way to retrieve new values is to either follow the BroadcastReceiver model used in the example, or to poll on a semi-regular basis (perhaps triggered by the first callback) if your data isn't available in a Broadcast.
I have already had feedback on the plugin API, and the provision of per-item preferences, a screen selection API, and other changes are all something that I hope to add in future releases.