Android Wear OS Tile configuration

Vairavan Srinivasan
3 min readAug 14, 2023

Wear OS complications and tiles might need one time initialization just as they are added by users. Both are considered as Service by Android framework and doesn’t have any UI window like an Activity. UI corresponding to these components are created by Wear OS and is very similar to notifications, widget’s RemoteViews etc.

Context

A weather application to provide current temperature as both watch face complication and tile. They share the same business logic, backend API, permissions etc and the only variables is the UI and Wear OS APIs.

Complications

Complications (meant for watch faces) have a straight forward API (android.support.wearable.complications.PROVIDER_CONFIG_ACTION) for any configuration.

<service
android:name=".WeatherComplicationDataSourceService"
android:exported="true"
android:label="@string/complications_data_sources_service_label"
android:enabled="true"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
</intent-filter>

<!-- Supported types -->
<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="SHORT_TEXT, LONG_TEXT" />

<!-- Update once every 5 minutes, 5 is the minimum period -->
<meta-data
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
android:value="300" />

<!-- Metadata for Config Activity to prompt users about permission -->
<meta-data
android:name="android.support.wearable.complications.PROVIDER_CONFIG_ACTION"
android:value="com.weather.PROVIDER_CONFIG_ACTION"/>

</service>

Meta data corresponding to PROVIDER_CONFIG_ACTION links complication data source service with a configuration activity. From here on, Wear OS takes care of starting the activity as and when the complication is added to active watch face.

<activity
android:name=".WeatherConfigActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="com.weather.PROVIDER_CONFIG_ACTION" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.support.wearable.complications.category.PROVIDER_CONFIG"/>
</intent-filter>
</activity>

Tiles

Tiles on the other hand (despite being a service similar to a complication) doesn’t seem to have an explicit API and needs some work around for initializations. Tile service can conditionally render a placeholder UI with a button to launch configuration activity.

class WeatherTileService : SuspendingTileService() {

override suspend fun tileRequest(requestParams: RequestBuilders.TileRequest): TileBuilders.Tile {
return if (isLocationPermissionGranted) {
// Actual Tile displaying Temerature
temperatureTileRenderer.renderTimeline(...)
} else {
// Placeholder Tile for location permission configuration
permissionTileRenderer.renderTimeline(...)
}
}
}
class PermissionTileRenderer @Inject constructor(
@ApplicationContext context: Context
) : SingleTileLayoutRenderer<Unit, Unit>(context) {

private fun Context.getLaunchChip(
deviceParameters: DeviceParametersBuilders.DeviceParameters
): LayoutElement {
return CompactChip.Builder(
this,
getString(R.string.tile_permission_launch),
launchActivityClickable("launch_button", openConfig()),
deviceParameters
).build()
}
}
Placeholder Tile

Upon selection by user, activity is started with a bundle extra parameter to indicate Tile entry point. Note, this bundle extra prameter (EXTRA_REFRESH_TILE_BOOLEAN) wouldn’t be set when it is launched for complication entry point.

internal fun openConfig() = ActionBuilders.AndroidActivity.Builder()
.setConfigActivity()
.addKeyToExtraMapping(
WeatherConfigActivity.EXTRA_REFRESH_TILE_BOOLEAN,
ActionBuilders.booleanExtra(true)
)
.build()
Configuration Activity prompting location permission access

Configuration activity can then conditionally refresh Tile (WeatherTileService) via TileService as and when the configuration activity is finished.

class WeatherConfigActivity : ComponentActivity() {

private val tileService by lazy { TileService.getUpdater(this) }

override fun onStop() {
super.onStop()

if (intent.extras?.getBoolean(EXTRA_REFRESH_TILE_BOOLEAN) == true) {
tileService.requestUpdate(WeatherTileService::class.java)
}
}
}
Actual Tile

--

--