-
-
Notifications
You must be signed in to change notification settings - Fork 56
4. SDK: Creating a Complication
Smartspacer's Complications are small snippets of information, consisting of an icon and a short amount of content. Complications are displayed alongside Targets, with any remaining Complications displayed in additional pages after the Targets.
Firstly, create a new project or open an existing one. It does not matter if the project uses Compose or Views, since Complications use neither.
Add the Plugin SDK:
implementation 'com.kieronquinn.smartspacer:sdk-plugin:version'
You can find the latest version here
Complications at their core are Content Providers, but the Smartspacer SDK handles most of the logic required for a Provider to function.
The basic Complication class is as follows:
class ExampleComplication: SmartspacerComplicationProvider() {
override fun getSmartspaceActions(smartspacerId: String): List<SmartspaceAction> {
//Return a list of SmartspaceActions
}
override fun getConfig(smartspacerId: String?): Config {
//Return your configuration
}
override fun onProviderRemoved(smartspacerId: String) {
//Handle removal of the Complication (optional)
}
}
Note: Since
SmartspaceAction
is also used in the core ofSmartspaceTarget
, the name has been kept to match the system. In the case of Complications,SmartspaceAction
represents a single Complication.
Since a Complication is based on ContentProvider, it must be specified as one, with a specific action and permission:
<provider
android:name=".ExampleComplication"
android:authorities="${applicationId}.complication.example"
android:permission="com.kieronquinn.app.smartspacer.permission.ACCESS_SMARTSPACER_COMPLICATIONS"
android:exported="true">
<intent-filter>
<action android:name="com.kieronquinn.app.smartspacer.COMPLICATION" />
</intent-filter>
</provider>
Your Complication must be exported, and must have the specific action and permission shown above. Without this, they will not work in Smartspacer.
The getConfig
method needs to return some basic information for Smartspacer about your Complication. The available options are:
Config(
label = // Your Complication's label to be shown in Smartspacer's settings UI
description = // A short description for your Complication to be shown in the same UI
icon = // An Icon (android.graphics.drawable.Icon) to show in the same UI
allowAddingMoreThanOnce = // Optional: Whether the Complication should be able to be added multiple times at once (defaults to false)
configActivity = // Optional: An Intent given as an option to the user in the settings UI after adding
setupActivity = // Optional: An Intent of an Activity presented to the user during setup (see below)
refreshPeriodMinutes = // Optional: How often the refresh broadcast for this Complication should be called, in minutes (defaults to 0, never)
refreshIfNotVisible = // Optional: Whether to call the refresh broadcast if the Complication did not last return any Complications (defaults to false)
compatibilityState = // Optional: A CompatibilityState object representing whether this Complication is compatible with the device or not (defaults to always compatible)
widgetProvider = // Optional: The authority of a Widget Provider attached to this Complication (see Widget Providers page)
notificationProvider = // Optional: The authority of a Notification Provider attached to this Complication (see Notification Providers page)
broadcastProvider = // Optional: The authority of a Broadcast Provider attached to this Complication (see Broadcast Providers page)
)
As a bare minimum, you must specify a label, description and icon. It's also recommended you consider specifying a Compatibility State, checking whether the device is able to add your Complication or not. For example, you may require another app to load data from, in which case you would return CompatibilityState.Incompatible("App X is not installed")
if the app is not installed, or CompatibilityState.Compatible
if it is.
The setup and configuration activities work much the same as those used for App Widgets by Android. Specify an Intent, with any extras you wish, and Smartspacer will open it during the adding of a Complication or when the user selects the settings option from the UI respectively. These Intents will also automatically be provided with the extra SmartspacerConstants.EXTRA_SMARTSPACER_ID
, a String containing the unique ID given to this Complication during setup.
The setup activity must call Activity.setResult(Activity.RESULT_OK)
before finishing to tell Smartspacer setup was successful and to continue adding the Complication. Otherwise, the Complication will not be added and the provider's onProviderRemovedMethod
will be called.
The configuration activity may call Activity.setResult(Activity.RESULT_OK)
before finishing to tell Smartspacer to send a change notification to the Complication, though this is optional.
When specifying a refresh period, please be mindful of battery use. Refreshes are also not guaranteed, the value is a limit to how often the refresh call will be made, not a guaranteed call. Refreshes also are not guaranteed to happen at the top of the minute (:00), so may not be reliable for time-sensitive refreshes such as calendar events or reminders. Enabling the refreshIfNotVisible
option means the refresh call will also be made when your Complication is not currently in Smartspace, which may be useful if you check for an update to something periodically. Enabling this option can have a severe effect on battery life if combined with refreshing often, so please be extremely careful.
The getSmartspaceActions
method expects a list of SmartspaceActions in return, this can be empty up to containing as many Complications as you wish. The ID provided to this method is the same unique ID provided during setup, so you can use it to retreive local data to display in a Complication, but please do not block the thread. Very simple data loading (eg. reading a row from a database or a JSON file) is usually fine, but making a network request at this state may result in failed loads and your Complication not updating.
Smartspacer's SDK comes with a template you can use from to show data in a Complication. Unlike Targets, only one type of Complication Template is currently supported: Basic.
ComplicationTemplate.Basic(
id = "example_$smartspacerId",
icon = Icon(android.graphics.drawable.Icon.createWithResource(provideContext(), R.drawable.ic_example)),
content = Text("Example"),
onClick = TapAction(
intent = Intent(provideContext(), MainActivity::class.java)
)
).create().apply {
//Apply additional options here (optional)
}
This example Basic Complication Template has all the required fields filled as follows:
-
id
: The unique ID for this SmartspaceAction, you don't need to consider other plugins in this ID, but it should be unique across your app. Consider using the provided Smartspacer ID to aid with this. The ID is also used throughout Smartspacer for efficiency and animations, so the same SmartspaceAction (for example a weather forecast for a location) should keep the same ID. -
icon
: TheIcon
object representing this SmartspaceAction's icon. This is a model provided by the SDK, containing anandroid.graphics.drawable.Icon
object, as well as options for whether to automatically tint the icon and an accessibility content description. Due to the duplication of class names, it's recommended to use import aliases to make this easier to understand. Note here howprovideContext()
is used - this is the same asrequireContext()
, but backwards compatible down to API 29. -
content
: TheText
object representing this SmartspaceAction's content. While just content is required, you can also specify justification here. The content is also a CharSequence and thus supports basic Parcelable formatting - colour and style - though both this and justification are not supported by OEM Smartspace. This content is limited to 12 characters. -
onClick
: TheTapAction
object representing this SmartspaceAction's action to be invoked on click. It contains:-
intent
: An Intent to be invoked on tap (must be an Activity) -
pendingIntent
: A PendingIntent to be be invoked on tap (can be any, takes priority over Intent) -
shouldShowOnLockScreen
: If set, Smartspacer will not try to unlock the device when the action is run. If you start an Activity that can display over the lock screen, or send a PendingIntent which starts a service or sends a broadcast, this can mean no interruption to the user.
-
After creating a SmartspaceAction, some fields can be set to modify Smartspacer behaviour. You can set these on a SmartspaceAction
object:
-
limitToSurfaces
: An optional set of UI Surfaces (HOMESCREEN
andLOCKSCREEN
) on which the Complication should be shown. If not set, it will be shown on all. -
weatherData
: An optionalWeatherData
object containing a weather data represented by the Complication. This allows weather to be displayed on the AoD when using the special "Weather" style AoD on Pixels on Android 14+, which has its own limited set of weather icons. It's not used anywhere else, and only the first Complication with this data is used.
When the user removes a Complication from Smartspacer, the onProviderRemoved
method will be called with your unique Smartspacer ID. You should perform cleanup actions here, such as removing any data from the database related to the Complication.
Please note that this method will not be called if Smartspacer is uninstalled.
In the settings UI for your Complication within Smartspacer, the user can be presented with a link to your app's settings. Some customisation is available, depending on how you declare the Activity your Intent is linking to in your manifest:
<activity
android:name=".ConfigurationActivity"
android:description="A short description to be shown to the user of what they can configure"
android:exported="true"
android:icon="@drawable/ic_complication_settings"
android:label="Example Settings" />
If you wish to notify Smartspacer of a change to your Complication, and thus a call to getSmartspaceActions
should be made, you can call one of the two static notifyChange
methods in SmartspacerComplicationProvider
from anywhere you have a context. These are:
-
SmartspacerComplicationProvider.notifyChange(Context, Authority, Smartspacer ID (optional))
: Updates a Complication with a given provider Authority. If provided, only that with a given Smartspacer ID will be updated (only useful if you are allowing multiple of the same Complication to be added) -
SmartspacerComplicationProvider.notifyChange(Context, Class<out SmartspacerComplicationProvider>, Smartspacer ID (optional))
: Updates a Complication with a given Complication class. If provided, only that with a given Smartspacer ID will be updated (only useful if you are allowing multiple of the same Complication to be added)
If you specified a refresh period in your config, detailed earlier, your app will receive periodic broadcasts. To receive them, set up a receiver extending from SmartspacerComplicationUpdateReceiver
:
class ExampleComplicationUpdateReceiver: SmartspacerComplicationUpdateReceiver() {
override fun onRequestSmartspaceComplicationUpdate(
context: Context,
requestComplications: List<RequestComplication>
) {
//Handle updates to the requested complications
}
}
You must also declare this in your manifest with the required action and permission, since it is a BroadcastReceiver
:
<receiver
android:name=".ExampleComplicationUpdateReceiver"
android:exported="true"
android:permission="com.kieronquinn.app.smartspacer.permission.SEND_UPDATE_BROADCAST">
<intent-filter>
<action android:name="com.kieronquinn.app.smartspacer.REQUEST_COMPLICATION_UPDATE" />
</intent-filter>
</receiver>
Note: The
RequestComplication
object contains the authority and Smartspacer ID for each Complication an update has been requested for. Updates are batched, so if you have multiple Complications requesting an update at the same minute, the broadcast will only be received once, with multiplerequestComplications
If you wish to receive a broadcast when Smartspace becomes visible, or stops being visible, you can add a receiver for this too. This could be used to pause background updates, or request an update to your data automatically when Smartspace becomes visible. Create a receiver extending from SmartspacerVisibilityChangedReceiver
:
class SmartspaceVisibilityReceiver: SmartspacerVisibilityChangedReceiver() {
override fun onSmartspaceVisibilityChanged(context: Context, visible: Boolean, timestamp: Long) {
//Run an update?
}
}
You must also declare this in your manifest with the required action and permission, since it is a BroadcastReceiver
:
<receiver android:name=".SmartspaceVisibilityReceiver"
android:exported="true"
android:permission="com.kieronquinn.app.smartspacer.permission.SEND_UPDATE_BROADCAST">
<intent-filter>
<action android:name="com.kieronquinn.app.smartspacer.SMARTSPACE_VISIBILITY_CHANGED" />
</intent-filter>
</receiver>
Important note: This option requires the user have Smartspacer v1.3 or above installed. You can use
"minimum_smartspacer_version": 130
in yourupdate.json
file to enforce this.
Smartspacer supports an integrated backup system, since it is not available on Google Play. The idea of this is all Targets, Complications and Requirements contribute to a single backup file, which when restored provides the same data back to the plugins for restoration. You should put basic information such as settings in here, but data such as images should not be backed up - and large data will be rejected due to Android's Binder limit of 1MB. Backups are entirely optional - if you do not wish to implement it, the user will simply be presented with your Complication during restoration and add it as if it were new.
During a backup, the createBackup
method will be called, passing the standard Smartspacer ID:
override fun createBackup(smartspacerId: String): Backup {
val settings = //load settings
return Backup(settings.serialiseToString(), "A short description")
}
You should use the ID to load the settings of your Complication, and serialise it to a String (consider using GSON or similar libraries to flatten to JSON easily). A short description should also be provided specifying what was in the backup, which will be shown to the user in lieu of the Complication's description so they know what is being restored.
Similarly, the restoreBackup
method is called during the restoration process:
override fun restoreBackup(smartspacerId: String, backup: Backup): Boolean {
//Handle restoring your backup
notifyChange(smartspacerId)
return true
}
Important note: The Smartspacer ID here will not be the same as your one at the time of backup. Complication do not keep a static ID between different Smartspacer install instances
The Backup
object here contains the data you sent during the backup process. You should deserialise the data, commit it however you wish, and then call notifyChange(smartspacerId)
to update the Complication with this new data. Returning true
tells Smartspacer the restoration was successful, and the setup activity (if specified) does not need to be shown for this Complication. If you cannot restore the Complication for whatever reason, return false
and the setup activity will be shown.
Note: If your Complication requires certain permissions which will not persist across a reinstall, consider returning
false
even if the restore succeeds, so you can make sure the user grants the permission before the Complication finishes being added.