WatchFaceService.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.wear.watchface

import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.os.RemoteException
import android.os.Trace
import android.service.wallpaper.WallpaperService
import android.support.wearable.watchface.Constants
import android.support.wearable.watchface.IWatchFaceService
import android.support.wearable.watchface.SharedMemoryImage
import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
import android.util.Log
import android.view.Choreographer
import android.view.Surface
import android.view.SurfaceHolder
import androidx.annotation.IntDef
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
import androidx.versionedparcelable.ParcelUtils
import androidx.wear.complications.SystemProviders.ProviderId
import androidx.wear.complications.data.ComplicationData
import androidx.wear.complications.data.ComplicationType
import androidx.wear.complications.data.IdAndComplicationData
import androidx.wear.complications.data.NoDataComplicationData
import androidx.wear.complications.data.asApiComplicationData
import androidx.wear.watchface.control.HeadlessWatchFaceImpl
import androidx.wear.watchface.control.IInteractiveWatchFaceSysUI
import androidx.wear.watchface.control.InteractiveInstanceManager
import androidx.wear.watchface.control.InteractiveWatchFaceImpl
import androidx.wear.watchface.control.data.ComplicationScreenshotParams
import androidx.wear.watchface.control.data.HeadlessWatchFaceInstanceParams
import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
import androidx.wear.watchface.control.data.WatchfaceScreenshotParams
import androidx.wear.watchface.data.ComplicationBoundsType
import androidx.wear.watchface.data.ComplicationStateWireFormat
import androidx.wear.watchface.data.DeviceConfig
import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
import androidx.wear.watchface.data.IdAndComplicationStateWireFormat
import androidx.wear.watchface.data.SystemState
import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.UserStyleRepository
import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.data.UserStyleWireFormat
import java.io.FileNotFoundException
import java.util.concurrent.CountDownLatch

/** The wire format for [ComplicationData]. */
internal typealias WireComplicationData = android.support.wearable.complications.ComplicationData

/**
 * After user code finishes, we need up to 100ms of wake lock holding for the drawing to occur. This
 * isn't the ideal approach, but the framework doesn't expose a callback that would tell us when our
 * Canvas was drawn. 100 ms should give us time for a few frames to be drawn, in case there is a
 * backlog. If we encounter issues with this approach, we should consider asking framework team to
 * expose a callback.
 */
internal const val SURFACE_DRAW_TIMEOUT_MS = 100L

/** @hide */
@IntDef(
    value = [
        TapType.TOUCH,
        TapType.TOUCH_CANCEL,
        TapType.TAP
    ]
)
public annotation class TapType {
    public companion object {
        /** Used in [WatchFaceImpl#onTapCommand] to indicate a "down" touch event on the watch face. */
        public const val TOUCH: Int = IInteractiveWatchFaceSysUI.TAP_TYPE_TOUCH

        /**
         * Used in [WatchFaceImpl#onTapCommand] to indicate that a previous TapType.TOUCH touch event
         * has been canceled. This generally happens when the watch face is touched but then a
         * move or long press occurs.
         */
        public const val TOUCH_CANCEL: Int = IInteractiveWatchFaceSysUI.TAP_TYPE_TOUCH_CANCEL

        /**
         * Used in [WatchFaceImpl#onTapCommand] to indicate that an "up" event on the watch face has
         * occurred that has not been consumed by another activity. A TapType.TOUCH will always
         * occur first. This event will not occur if a TapType.TOUCH_CANCEL is sent.
         */
        public const val TAP: Int = IInteractiveWatchFaceSysUI.TAP_TYPE_TAP
    }
}

private class PendingComplicationData(val complicationId: Int, val data: ComplicationData)

/**
 * WatchFaceService and [WatchFace] are a pair of classes intended to handle much of
 * the boilerplate needed to implement a watch face without being too opinionated. The suggested
 * structure of a WatchFaceService based watch face is:
 *
 * @sample androidx.wear.watchface.samples.kDocCreateExampleWatchFaceService
 *
 * Sub classes of WatchFaceService are expected to implement [createWatchFace] which is the
 * factory for making [WatchFace]s. All [Complication]s are assumed to be enumerated up upfront and
 * passed as a collection into [ComplicationsManager]'s constructor which is in turn passed to
 * [WatchFace]'s constructor. Complications can be enabled and disabled via [UserStyleSetting
 * .ComplicationsUserStyleSetting].
 *
 * Watch face styling (color and visual look of watch face elements such as numeric fonts, watch
 * hands and ticks, etc...) is directly supported via [UserStyleSetting] and
 * [UserStyleRepository].
 *
 * To aid debugging watch face animations, WatchFaceService allows you to speed up or slow down
 * time, and to loop between two instants.  This is controlled by MOCK_TIME_INTENT intents
 * with a float extra called "androidx.wear.watchface.extra.MOCK_TIME_SPEED_MULTIPLIE" and to long
 * extras called "androidx.wear.watchface.extra.MOCK_TIME_WRAPPING_MIN_TIME" and
 * "androidx.wear.watchface.extra.MOCK_TIME_WRAPPING_MAX_TIME" (which are UTC time in milliseconds).
 * If minTime is omitted or set to -1 then the current time is sampled as minTime.
 *
 * E.g, to make time go twice as fast:
 *  adb shell am broadcast -a androidx.wear.watchface.MockTime \
 *            --ef androidx.wear.watchface.extra.MOCK_TIME_SPEED_MULTIPLIER 2.0
 *
 *
 * To use the default watch face configuration UI add the following into your watch face's
 * AndroidManifest.xml:
 *
 * ```
 * <activity
 *   android:name="androidx.wear.watchface.ui.WatchFaceConfigActivity"
 *   android:exported="true"
 *   android:directBootAware="true"
 *   android:label="Config"
 *   android:theme="@android:style/Theme.Translucent.NoTitleBar">
 *   <intent-filter>
 *     <action android:name="com.google.android.clockwork.watchfaces.complication.CONFIG_DIGITAL" />
 *       <category android:name=
 *            "com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" />
 *       <category android:name="android.intent.category.DEFAULT" />
 *    </intent-filter>
 * </activity>
 * ```
 *
 * To register a WatchFaceService with the system add a <service> tag to the <application> in your
 * watch face's AndroidManifest.xml:
 *
 * ```
 *  <service
 *    android:name=".MyWatchFaceServiceClass"
 *    android:exported="true"
 *    android:label="@string/watch_face_name"
 *    android:permission="android.permission.BIND_WALLPAPER">
 *    <intent-filter>
 *      <action android:name="android.service.wallpaper.WallpaperService" />
 *      <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
 *    </intent-filter>
 *    <meta-data
 *       android:name="com.google.android.wearable.watchface.preview"
 *       android:resource="@drawable/my_watch_preview" />
 *    <meta-data
 *      android:name="com.google.android.wearable.watchface.preview_circular"
 *      android:resource="@drawable/my_watch_circular_preview" />
 *    <meta-data
 *      android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
 *      android:value="com.google.android.clockwork.watchfaces.complication.CONFIG_DIGITAL"/>
 *    <meta-data
 *      android:name="android.service.wallpaper"
 *      android:resource="@xml/watch_face" />
 *  </service>
 * ```
 *
 * Multiple watch faces can be defined in the same package, requiring multiple <service> tags.
 */
public abstract class WatchFaceService : WallpaperService() {

    /** @hide */
    private companion object {
        private const val TAG = "WatchFaceService"

        /** Whether to log every frame. */
        private const val LOG_VERBOSE = false

        /** Whether to enable tracing for each call to [Engine.onDraw]. */
        private const val TRACE_DRAW = false

        // Reference time for editor screenshots for analog watch faces.
        // 2020/10/10 at 09:30 Note the date doesn't matter, only the hour.
        private const val ANALOG_WATCHFACE_REFERENCE_TIME_MS = 1602318600000L

        // Reference time for editor screenshots for digital watch faces.
        // 2020/10/10 at 10:10 Note the date doesn't matter, only the hour.
        private const val DIGITAL_WATCHFACE_REFERENCE_TIME_MS = 1602321000000L

        // Filename for persisted preferences to be used in a direct boot scenario.
        private const val DIRECT_BOOT_PREFS = "directboot.prefs"
    }

    /** Override this factory method to create your WatchFaceImpl. */
    protected abstract fun createWatchFace(
        surfaceHolder: SurfaceHolder,
        watchState: WatchState
    ): WatchFace

    final override fun onCreateEngine(): Engine = EngineWrapper(getHandler())

    // This is open to allow mocking.
    internal open fun getHandler() = Handler(Looper.getMainLooper())

    // This is open to allow mocking.
    internal open fun getMutableWatchState() = MutableWatchState()

    // This is open for use by tests.
    internal open fun allowWatchFaceToAnimate() = true

    // Whether or not the pre R style init flow (SET_BINDER wallpaper command) is expected.
    // This is open for use by tests.
    internal open fun expectPreRInitFlow() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R

    /**
     * This is open for use by tests, it allows them to inject a custom [SurfaceHolder].
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public open fun getWallpaperSurfaceHolderOverride(): SurfaceHolder? = null

    internal fun setContext(context: Context) {
        attachBaseContext(context)
    }

    internal inner class EngineWrapper(
        private val uiThreadHandler: Handler
    ) : WallpaperService.Engine(), WatchFaceHostApi {
        private val _context = this@WatchFaceService as Context

        internal lateinit var iWatchFaceService: IWatchFaceService
        internal lateinit var watchFaceImpl: WatchFaceImpl

        internal val mutableWatchState = getMutableWatchState().apply {
            isVisible.value = this@EngineWrapper.isVisible
        }

        /**
         * Whether or not we allow watchfaces to animate. In some tests or for headless
         * rendering (for remote config) we don't want this.
         */
        internal var allowWatchfaceToAnimate = allowWatchFaceToAnimate()

        private var destroyed = false

        internal lateinit var ambientUpdateWakelock: PowerManager.WakeLock

        private lateinit var choreographer: Choreographer

        /**
         * Whether we already have a [frameCallback] posted and waiting in the [Choreographer]
         * queue. This protects us from drawing multiple times in a single frame.
         */
        private var frameCallbackPending = false

        private val frameCallback = object : Choreographer.FrameCallback {
            @SuppressWarnings("SyntheticAccessor")
            override fun doFrame(frameTimeNs: Long) {
                if (destroyed) {
                    return
                }
                require(allowWatchfaceToAnimate)
                frameCallbackPending = false
                draw()
            }
        }

        private val invalidateRunnable = Runnable(this::invalidate)

        // TODO(alexclarke): Figure out if we can remove this.
        private var pendingBackgroundAction: Bundle? = null
        private var pendingProperties: Bundle? = null
        private var pendingSetWatchFaceStyle = false
        private var pendingVisibilityChanged: Boolean? = null
        private var pendingComplicationDataUpdates = ArrayList<PendingComplicationData>()
        private var complicationsActivated = false

        // Only valid after onSetBinder has been called.
        private var systemApiVersion = -1

        internal var firstSetSystemState = true
        internal var immutableSystemStateDone = false

        internal var lastActiveComplications: IntArray? = null
        internal var lastA11yLabels: Array<ContentDescriptionLabel>? = null

        private var watchFaceInitStarted = false

        private var initialUserStyle: UserStyleWireFormat? = null
        private lateinit var interactiveInstanceId: String

        init {
            maybeCreateWCSApi()
        }

        @SuppressWarnings("NewApi")
        private fun maybeCreateWCSApi() {
            val pendingWallpaperInstance =
                InteractiveInstanceManager.takePendingWallpaperInteractiveWatchFaceInstance()

            // In a direct boot scenario attempt to load the previously serialized parameters.
            if (pendingWallpaperInstance == null && !expectPreRInitFlow()) {
                val params = readDirectBootPrefs(_context, DIRECT_BOOT_PREFS)
                if (params != null) {
                    createInteractiveInstance(params).createWCSApi()
                    keepSerializedDirectBootParamsUpdated(params)
                }
            }

            // If there's a pending WallpaperInteractiveWatchFaceInstance then create it.
            if (pendingWallpaperInstance != null) {
                pendingWallpaperInstance.callback.onInteractiveWatchFaceWcsCreated(
                    createInteractiveInstance(pendingWallpaperInstance.params).createWCSApi()
                )

                interactiveInstanceId = pendingWallpaperInstance.params.instanceId
                keepSerializedDirectBootParamsUpdated(pendingWallpaperInstance.params)
            }
        }

        private fun keepSerializedDirectBootParamsUpdated(
            directBootParams: WallpaperInteractiveWatchFaceInstanceParams
        ) {
            // We don't want to display complications in direct boot mode so replace with an
            // empty list. NB we can't actually serialise complications anyway so that's just as
            // well...
            directBootParams.idAndComplicationDataWireFormats = emptyList()

            watchFaceImpl.userStyleRepository.addUserStyleListener(
                object : UserStyleRepository.UserStyleListener {
                    @SuppressLint("SyntheticAccessor")
                    override fun onUserStyleChanged(userStyle: UserStyle) {
                        directBootParams.userStyle = userStyle.toWireFormat()
                        writeDirectBootPrefs(_context, DIRECT_BOOT_PREFS, directBootParams)
                    }
                })

            writeDirectBootPrefs(_context, DIRECT_BOOT_PREFS, directBootParams)
        }

        @UiThread
        fun ambientTickUpdate() {
            if (mutableWatchState.isAmbient.value) {
                ambientUpdateWakelock.acquire()
                watchFaceImpl.renderer.invalidate()
                ambientUpdateWakelock.acquire(SURFACE_DRAW_TIMEOUT_MS)
            }
        }

        @UiThread
        fun setSystemState(systemState: SystemState) {
            if (firstSetSystemState ||
                systemState.inAmbientMode != mutableWatchState.isAmbient.value
            ) {
                mutableWatchState.isAmbient.value = systemState.inAmbientMode
            }

            if (firstSetSystemState ||
                systemState.interruptionFilter != mutableWatchState.interruptionFilter.value
            ) {
                mutableWatchState.interruptionFilter.value = systemState.interruptionFilter
            }

            firstSetSystemState = false
        }

        @UiThread
        fun setUserStyle(userStyle: UserStyleWireFormat) {
            watchFaceImpl.onSetStyleInternal(
                UserStyle(userStyle, watchFaceImpl.userStyleRepository.schema)
            )
        }

        @UiThread
        fun setImmutableSystemState(deviceConfig: DeviceConfig) {
            // These properties never change so set them once only.
            if (!immutableSystemStateDone) {
                mutableWatchState.hasLowBitAmbient = deviceConfig.hasLowBitAmbient
                mutableWatchState.hasBurnInProtection =
                    deviceConfig.hasBurnInProtection
                mutableWatchState.analogPreviewReferenceTimeMillis =
                    deviceConfig.analogPreviewReferenceTimeMillis
                mutableWatchState.digitalPreviewReferenceTimeMillis =
                    deviceConfig.digitalPreviewReferenceTimeMillis

                immutableSystemStateDone = true
            }
        }

        @SuppressLint("SyntheticAccessor")
        fun setComplicationData(complicationId: Int, data: ComplicationData) {
            if (watchFaceCreated()) {
                watchFaceImpl.onComplicationDataUpdate(complicationId, data)
            } else {
                pendingComplicationDataUpdates.add(
                    PendingComplicationData(complicationId, data)
                )
            }
        }

        @UiThread
        fun getComplicationState(): List<IdAndComplicationStateWireFormat> =
            uiThreadHandler.runOnHandler {
                watchFaceImpl.complicationsManager.complications.map {
                    IdAndComplicationStateWireFormat(
                        it.key,
                        ComplicationStateWireFormat(
                            it.value.computeBounds(watchFaceImpl.renderer.screenBounds),
                            it.value.boundsType,
                            ComplicationType.toWireTypes(it.value.supportedTypes),
                            it.value.defaultProviderPolicy.providersAsList(),
                            it.value.defaultProviderPolicy.systemProviderFallback,
                            it.value.defaultProviderType.asWireComplicationType(),
                            it.value.enabled,
                            it.value.renderer.idAndData?.complicationData?.type
                                ?.asWireComplicationType()
                                ?: ComplicationType.NO_DATA.asWireComplicationType()
                        )
                    )
                }
            }

        @UiThread
        fun setComplicationDataList(
            complicationDatumWireFormats: MutableList<IdAndComplicationDataWireFormat>
        ) {
            if (watchFaceCreated()) {
                for (idAndComplicationData in complicationDatumWireFormats) {
                    watchFaceImpl.onComplicationDataUpdate(
                        idAndComplicationData.id,
                        idAndComplicationData.complicationData.asApiComplicationData()
                    )
                }
            } else {
                for (idAndComplicationData in complicationDatumWireFormats) {
                    pendingComplicationDataUpdates.add(
                        PendingComplicationData(
                            idAndComplicationData.id,
                            idAndComplicationData.complicationData.asApiComplicationData()
                        )
                    )
                }
            }
        }

        private fun requestWatchFaceStyle() {
            try {
                iWatchFaceService.setStyle(watchFaceImpl.getWatchFaceStyle())
            } catch (e: RemoteException) {
                Log.e(TAG, "Failed to set WatchFaceStyle: ", e)
            }

            val activeComplications = lastActiveComplications
            if (activeComplications != null) {
                setActiveComplications(activeComplications)
            }

            val a11yLabels = lastA11yLabels
            if (a11yLabels != null) {
                setContentDescriptionLabels(a11yLabels)
            }
        }

        @UiThread
        @RequiresApi(27)
        fun takeWatchFaceScreenshot(params: WatchfaceScreenshotParams): Bundle {
            val oldStyle = HashMap(watchFaceImpl.userStyleRepository.userStyle.selectedOptions)
            params.userStyle?.let {
                watchFaceImpl.onSetStyleInternal(
                    UserStyle(it, watchFaceImpl.userStyleRepository.schema)
                )
            }

            val oldComplicationData =
                watchFaceImpl.complicationsManager.complications.mapValues {
                    it.value.renderer.idAndData?.complicationData ?: NoDataComplicationData()
                }
            params.idAndComplicationDatumWireFormats?.let {
                for (idAndData in it) {
                    watchFaceImpl.onComplicationDataUpdate(
                        idAndData.id, idAndData.complicationData.asApiComplicationData()
                    )
                }
            }

            val bitmap = watchFaceImpl.renderer.takeScreenshot(
                Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
                    timeInMillis = params.calendarTimeMillis
                },
                RenderParameters(params.renderParametersWireFormat)
            )

            // Restore previous style & complications if required.
            if (params.userStyle != null) {
                watchFaceImpl.onSetStyleInternal(UserStyle(oldStyle))
            }

            if (params.idAndComplicationDatumWireFormats != null) {
                for ((id, data) in oldComplicationData) {
                    watchFaceImpl.onComplicationDataUpdate(id, data)
                }
            }

            return SharedMemoryImage.ashmemCompressedImageBundle(
                bitmap,
                params.compressionQuality
            )
        }

        @UiThread
        @RequiresApi(27)
        fun takeComplicationScreenshot(params: ComplicationScreenshotParams): Bundle? {
            val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
                timeInMillis = params.calendarTimeMillis
            }
            return watchFaceImpl.complicationsManager[params.complicationId]?.let {
                val oldStyle = HashMap(watchFaceImpl.userStyleRepository.userStyle.selectedOptions)
                val newStyle = params.userStyle
                if (newStyle != null) {
                    watchFaceImpl.onSetStyleInternal(
                        UserStyle(newStyle, watchFaceImpl.userStyleRepository.schema)
                    )
                }

                val bounds = it.computeBounds(watchFaceImpl.renderer.screenBounds)
                val complicationBitmap =
                    Bitmap.createBitmap(
                        bounds.width(), bounds.height(),
                        Bitmap.Config.ARGB_8888
                    )

                var prevIdAndComplicationData: IdAndComplicationData? = null
                var screenshotComplicationData = params.complicationData
                if (screenshotComplicationData != null) {
                    prevIdAndComplicationData = it.renderer.idAndData
                    it.renderer.idAndData =
                        IdAndComplicationData(
                            params.complicationId,
                            screenshotComplicationData
                        )
                }

                it.renderer.render(
                    Canvas(complicationBitmap),
                    Rect(0, 0, bounds.width(), bounds.height()),
                    calendar,
                    RenderParameters(params.renderParametersWireFormat)
                )

                // Restore previous ComplicationData & style if required.
                if (params.complicationData != null) {
                    it.renderer.idAndData = prevIdAndComplicationData
                }

                if (newStyle != null) {
                    watchFaceImpl.onSetStyleInternal(UserStyle(oldStyle))
                }

                SharedMemoryImage.ashmemCompressedImageBundle(
                    complicationBitmap,
                    params.compressionQuality
                )
            }
        }

        @UiThread
        fun sendTouchEvent(xPos: Int, yPos: Int, tapType: Int) {
            if (watchFaceCreated()) {
                watchFaceImpl.onTapCommand(tapType, xPos, yPos)
            }
        }

        override fun getContext() = _context

        override fun getHandler() = uiThreadHandler

        override fun onCreate(holder: SurfaceHolder) {
            super.onCreate(holder)

            ambientUpdateWakelock =
                (getSystemService(Context.POWER_SERVICE) as PowerManager)
                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "$TAG:[AmbientUpdate]")
            // Disable reference counting for our wake lock so that we can use the same wake lock
            // for user code in invaliate() and after that for having canvas drawn.
            ambientUpdateWakelock.setReferenceCounted(false)

            // Rerender watch face if the surface changes.
            holder.addCallback(
                object : SurfaceHolder.Callback {
                    override fun surfaceChanged(
                        holder: SurfaceHolder,
                        format: Int,
                        width: Int,
                        height: Int
                    ) {
                        // We can sometimes get this callback before the watchface has been created
                        // in which case it's safe to drop it.
                        if (this@EngineWrapper::watchFaceImpl.isInitialized) {
                            invalidate()
                        }
                    }

                    override fun surfaceDestroyed(holder: SurfaceHolder) {
                    }

                    override fun surfaceCreated(holder: SurfaceHolder) {
                    }
                }
            )
        }

        override fun onDestroy() {
            destroyed = true
            uiThreadHandler.removeCallbacks(invalidateRunnable)
            if (this::choreographer.isInitialized) {
                choreographer.removeFrameCallback(frameCallback)
            }

            if (this::watchFaceImpl.isInitialized) {
                watchFaceImpl.onDestroy()
            }

            if (this::interactiveInstanceId.isInitialized) {
                InteractiveInstanceManager.deleteInstance(interactiveInstanceId)
            }

            super.onDestroy()
        }

        override fun onCommand(
            action: String?,
            x: Int,
            y: Int,
            z: Int,
            extras: Bundle?,
            resultRequested: Boolean
        ): Bundle? {
            when (action) {
                Constants.COMMAND_AMBIENT_UPDATE ->
                    uiThreadHandler.runOnHandler { ambientTickUpdate() }
                Constants.COMMAND_BACKGROUND_ACTION ->
                    uiThreadHandler.runOnHandler { onBackgroundAction(extras!!) }
                Constants.COMMAND_COMPLICATION_DATA ->
                    uiThreadHandler.runOnHandler { onComplicationDataUpdate(extras!!) }
                Constants.COMMAND_REQUEST_STYLE ->
                    uiThreadHandler.runOnHandler { onRequestStyle() }
                Constants.COMMAND_SET_BINDER ->
                    uiThreadHandler.runOnHandler { onSetBinder(extras!!) }
                Constants.COMMAND_SET_PROPERTIES ->
                    uiThreadHandler.runOnHandler { onPropertiesChanged(extras!!) }
                Constants.COMMAND_TAP ->
                    uiThreadHandler.runOnHandler { sendTouchEvent(x, y, TapType.TAP) }
                Constants.COMMAND_TOUCH ->
                    uiThreadHandler.runOnHandler { sendTouchEvent(x, y, TapType.TOUCH) }
                Constants.COMMAND_TOUCH_CANCEL ->
                    uiThreadHandler.runOnHandler { sendTouchEvent(x, y, TapType.TOUCH_CANCEL) }
                else -> {
                }
            }
            return null
        }

        @UiThread
        fun onBackgroundAction(extras: Bundle) {
            // We can't guarantee the binder has been set and onSurfaceChanged called before this
            // command.
            if (!watchFaceCreated()) {
                pendingBackgroundAction = extras
                return
            }

            setSystemState(
                SystemState(
                    extras.getBoolean(
                        Constants.EXTRA_AMBIENT_MODE,
                        mutableWatchState.isAmbient.getValueOr(false)
                    ),
                    extras.getInt(
                        Constants.EXTRA_INTERRUPTION_FILTER,
                        mutableWatchState.interruptionFilter.getValueOr(0)
                    )
                )
            )

            pendingBackgroundAction = null
        }

        private fun onSetBinder(extras: Bundle) {
            val binder = extras.getBinder(Constants.EXTRA_BINDER)
            if (binder == null) {
                Log.w(TAG, "Binder is null.")
                return
            }

            iWatchFaceService = IWatchFaceService.Stub.asInterface(binder)

            try {
                // Note if the implementation doesn't support getVersion this will return zero
                // rather than throwing an exception.
                systemApiVersion = iWatchFaceService.apiVersion
            } catch (e: RemoteException) {
                Log.w(TAG, "Failed to getVersion: ", e)
            }

            maybeCreateWatchFace()
        }

        override fun getInitialUserStyle(): UserStyleWireFormat? = initialUserStyle

        @RequiresApi(27)
        fun createHeadlessInstance(
            params: HeadlessWatchFaceInstanceParams
        ): HeadlessWatchFaceImpl {
            require(!watchFaceCreated())
            setImmutableSystemState(params.deviceConfig)

            // Fake SurfaceHolder with just enough methods implemented for headless rendering.
            val fakeSurfaceHolder = object : SurfaceHolder {
                val callbacks = HashSet<SurfaceHolder.Callback>()

                override fun setType(type: Int) {
                    throw NotImplementedError()
                }

                override fun getSurface(): Surface {
                    throw NotImplementedError()
                }

                override fun setSizeFromLayout() {
                    throw NotImplementedError()
                }

                override fun lockCanvas(): Canvas {
                    throw NotImplementedError()
                }

                override fun lockCanvas(dirty: Rect?): Canvas {
                    throw NotImplementedError()
                }

                override fun getSurfaceFrame() = Rect(0, 0, params.width, params.height)

                override fun setFixedSize(width: Int, height: Int) {
                    throw NotImplementedError()
                }

                override fun removeCallback(callback: SurfaceHolder.Callback) {
                    callbacks.remove(callback)
                }

                override fun isCreating(): Boolean {
                    throw NotImplementedError()
                }

                override fun addCallback(callback: SurfaceHolder.Callback) {
                    callbacks.add(callback)
                }

                override fun setFormat(format: Int) {
                    throw NotImplementedError()
                }

                override fun setKeepScreenOn(screenOn: Boolean) {
                    throw NotImplementedError()
                }

                override fun unlockCanvasAndPost(canvas: Canvas?) {
                    throw NotImplementedError()
                }
            }

            val watchState = mutableWatchState.asWatchState()
            watchFaceImpl = WatchFaceImpl(
                createWatchFace(fakeSurfaceHolder, watchState),
                this,
                watchState
            )

            allowWatchfaceToAnimate = false
            mutableWatchState.isVisible.value = true
            mutableWatchState.isAmbient.value = false

            watchFaceImpl.renderer.onPostCreate()
            return HeadlessWatchFaceImpl(this, uiThreadHandler)
        }

        @UiThread
        @RequiresApi(27)
        fun createInteractiveInstance(
            params: WallpaperInteractiveWatchFaceInstanceParams
        ): InteractiveWatchFaceImpl {
            require(!watchFaceCreated())

            setImmutableSystemState(params.deviceConfig)
            setSystemState(params.systemState)
            initialUserStyle = params.userStyle

            val watchState = mutableWatchState.asWatchState()
            watchFaceImpl = WatchFaceImpl(
                createWatchFace(getWallpaperSurfaceHolderOverride() ?: surfaceHolder, watchState),
                this,
                watchState
            )

            params.idAndComplicationDataWireFormats?.let { setComplicationDataList(it) }

            watchFaceImpl.renderer.onPostCreate()
            val visibility = pendingVisibilityChanged
            if (visibility != null) {
                onVisibilityChanged(visibility)
                pendingVisibilityChanged = null
            }

            val instance = InteractiveWatchFaceImpl(this, params.instanceId, uiThreadHandler)
            InteractiveInstanceManager.addInstance(instance)
            return instance
        }

        override fun onSurfaceRedrawNeeded(holder: SurfaceHolder) {
            if (watchFaceCreated()) {
                watchFaceImpl.onSurfaceRedrawNeeded()
            }
        }

        private fun maybeCreateWatchFace() {
            // To simplify handling of watch face state, we only construct the [WatchFaceImpl]
            // once iWatchFaceService have been initialized and pending properties sent.
            if (this::iWatchFaceService.isInitialized && pendingProperties != null &&
                !watchFaceCreated()
            ) {
                watchFaceInitStarted = true

                // Apply immutable properties to mutableWatchState before creating the watch face.
                onPropertiesChanged(pendingProperties!!)
                pendingProperties = null

                val watchState = mutableWatchState.asWatchState()
                watchFaceImpl = WatchFaceImpl(
                    createWatchFace(surfaceHolder, watchState),
                    this,
                    watchState
                )
                watchFaceImpl.renderer.onPostCreate()

                val backgroundAction = pendingBackgroundAction
                if (backgroundAction != null) {
                    onBackgroundAction(backgroundAction)
                    pendingBackgroundAction = null
                }
                if (pendingSetWatchFaceStyle) {
                    onRequestStyle()
                }
                val visibility = pendingVisibilityChanged
                if (visibility != null) {
                    onVisibilityChanged(visibility)
                    pendingVisibilityChanged = null
                }
                for (complicationDataUpdate in pendingComplicationDataUpdates) {
                    setComplicationData(
                        complicationDataUpdate.complicationId,
                        complicationDataUpdate.data
                    )
                }
            }
        }

        private fun onRequestStyle() {
            // We can't guarantee the binder has been set and onSurfaceChanged called before this
            // command.
            if (!watchFaceCreated()) {
                pendingSetWatchFaceStyle = true
                return
            }
            requestWatchFaceStyle()
            pendingSetWatchFaceStyle = false
        }

        override fun onVisibilityChanged(visible: Boolean) {
            super.onVisibilityChanged(visible)

            // We are requesting state every time the watch face changes its visibility because
            // wallpaper commands have a tendency to be dropped. By requesting it on every
            // visibility change, we ensure that we don't become a victim of some race condition.
            sendBroadcast(
                Intent(Constants.ACTION_REQUEST_STATE).apply {
                    putExtra(Constants.EXTRA_WATCH_FACE_VISIBLE, visible)
                }
            )

            // We can't guarantee the binder has been set and onSurfaceChanged called before this
            // command.
            if (!watchFaceCreated()) {
                pendingVisibilityChanged = visible
                return
            }

            mutableWatchState.isVisible.value = visible
            pendingVisibilityChanged = null
        }

        override fun invalidate() {
            if (!allowWatchfaceToAnimate) {
                return
            }
            if (!frameCallbackPending) {
                if (LOG_VERBOSE) {
                    Log.v(TAG, "invalidate: requesting draw")
                }
                frameCallbackPending = true
                if (!this::choreographer.isInitialized) {
                    choreographer = Choreographer.getInstance()
                }
                choreographer.postFrameCallback(frameCallback)
            } else {
                if (LOG_VERBOSE) {
                    Log.v(TAG, "invalidate: draw already requested")
                }
            }
        }

        internal fun draw() {
            try {
                if (TRACE_DRAW) {
                    Trace.beginSection("onDraw")
                }
                if (LOG_VERBOSE) {
                    Log.v(WatchFaceService.TAG, "drawing frame")
                }
                watchFaceImpl.onDraw()
            } finally {
                if (TRACE_DRAW) {
                    Trace.endSection()
                }
            }
        }

        private fun onComplicationDataUpdate(extras: Bundle) {
            extras.classLoader = WireComplicationData::class.java.classLoader
            val complicationData: WireComplicationData =
                extras.getParcelable(Constants.EXTRA_COMPLICATION_DATA)!!
            setComplicationData(
                extras.getInt(Constants.EXTRA_COMPLICATION_ID),
                complicationData.asApiComplicationData()
            )
        }

        @UiThread
        internal fun onPropertiesChanged(properties: Bundle) {
            if (!watchFaceInitStarted) {
                pendingProperties = properties
                maybeCreateWatchFace()
                return
            }

            setImmutableSystemState(
                DeviceConfig(
                    properties.getBoolean(Constants.PROPERTY_LOW_BIT_AMBIENT),
                    properties.getBoolean(Constants.PROPERTY_BURN_IN_PROTECTION),
                    ANALOG_WATCHFACE_REFERENCE_TIME_MS,
                    DIGITAL_WATCHFACE_REFERENCE_TIME_MS
                )
            )
        }

        internal fun watchFaceCreated() = this::watchFaceImpl.isInitialized

        override fun setDefaultComplicationProviderWithFallbacks(
            watchFaceComplicationId: Int,
            providers: List<ComponentName>?,
            @ProviderId fallbackSystemProvider: Int,
            type: Int
        ) {
            // For wear 3.0 watchfaces iWatchFaceService won't have been set.
            if (!this::iWatchFaceService.isInitialized) {
                return
            }

            if (systemApiVersion >= 2) {
                iWatchFaceService.setDefaultComplicationProviderWithFallbacks(
                    watchFaceComplicationId,
                    providers,
                    fallbackSystemProvider,
                    type
                )
            } else {
                // If the implementation doesn't support the new API we emulate its behavior by
                // setting complication providers in the reverse order. This works because if
                // setDefaultComplicationProvider attempts to set a non-existent or incompatible
                // provider it does nothing, which allows us to emulate the same semantics as
                // setDefaultComplicationProviderWithFallbacks albeit with more calls.
                if (fallbackSystemProvider != WatchFaceImpl.NO_DEFAULT_PROVIDER) {
                    iWatchFaceService.setDefaultSystemComplicationProvider(
                        watchFaceComplicationId, fallbackSystemProvider, type
                    )
                }

                if (providers != null) {
                    // Iterate in reverse order. This could be O(n^2) but n is expected to be small
                    // and the list is probably an ArrayList so it's probably O(n) in practice.
                    for (i in providers.size - 1 downTo 0) {
                        iWatchFaceService.setDefaultComplicationProvider(
                            watchFaceComplicationId, providers[i], type
                        )
                    }
                }
            }
        }

        override fun setActiveComplications(watchFaceComplicationIds: IntArray) {
            // For wear 3.0 watchfaces iWatchFaceService won't have been set.
            if (!this::iWatchFaceService.isInitialized) {
                return
            }

            lastActiveComplications = watchFaceComplicationIds

            try {
                iWatchFaceService.setActiveComplications(
                    watchFaceComplicationIds, /* updateAll= */ !complicationsActivated
                )
                complicationsActivated = true
            } catch (e: RemoteException) {
                Log.e(TAG, "Failed to set active complications: ", e)
            }
        }

        override fun setContentDescriptionLabels(labels: Array<ContentDescriptionLabel>) {
            // For wear 3.0 watchfaces iWatchFaceService won't have been set.
            if (!this::iWatchFaceService.isInitialized) {
                return
            }

            lastA11yLabels = labels
            try {
                iWatchFaceService.setContentDescriptionLabels(labels)
            } catch (e: RemoteException) {
                Log.e(TAG, "Failed to set accessibility labels: ", e)
            }
        }

        override fun setCurrentUserStyle(userStyle: UserStyleWireFormat) {
            // TODO(alexclarke): Report programmatic style changes to WCS.
        }

        override fun setComplicationDetails(
            complicationId: Int,
            bounds: Rect,
            @ComplicationBoundsType boundsType: Int,
            types: IntArray
        ) {
            // TODO(alexclarke): Report programmatic complication details changes to WCS.
        }
    }
}

/**
 * Runs the supplied task on the handler thread. If we're not on the handler thread a task is posted
 * and we block until it's been processed.
 *
 * AIDL calls are dispatched from a thread pool, but for simplicity WatchFaceImpl code is
 * largely single threaded so we need to post tasks to the UI thread and wait for them to
 * execute.
 */
internal fun <R> Handler.runOnHandler(task: () -> R) =
    if (looper == Looper.myLooper()) {
        task.invoke()
    } else {
        val latch = CountDownLatch(1)
        var returnVal: R? = null
        var exception: Exception? = null
        if (post {
            try {
                returnVal = task.invoke()
            } catch (e: Exception) {
                // Will rethrow on the calling thread.
                exception = e
            }
            latch.countDown()
        }
        ) {
            latch.await()
            if (exception != null) {
                throw exception as Exception
            }
        }
        returnVal!!
    }

internal fun readDirectBootPrefs(
    context: Context,
    fileName: String
): WallpaperInteractiveWatchFaceInstanceParams? =
    try {
        val reader = context.openFileInput(fileName)
        val result =
            ParcelUtils.fromInputStream<WallpaperInteractiveWatchFaceInstanceParams>(reader)
        reader.close()
        result
    } catch (e: FileNotFoundException) {
        null
    }

internal fun writeDirectBootPrefs(
    context: Context,
    fileName: String,
    prefs: WallpaperInteractiveWatchFaceInstanceParams
) {
    val writer = context.openFileOutput(fileName, Context.MODE_PRIVATE)
    ParcelUtils.toOutputStream(prefs, writer)
    writer.close()
}