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.utility.AsyncTraceEvent
import androidx.wear.utility.TraceEvent
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.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.editor.EditorService
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.launch
import java.io.FileDescriptor
import java.io.FileNotFoundException
import java.io.PrintWriter
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 suspend fun createWatchFace(
        surfaceHolder: SurfaceHolder,
        watchState: WatchState
    ): WatchFace

    // Creates an interactive engine for WallpaperService.
    final override fun onCreateEngine(): Engine = EngineWrapper(getHandler(), false)

    // Creates a headless engine.
    internal fun createHeadlessEngine(): Engine = EngineWrapper(getHandler(), true)

    // 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 open fun readDirectBootPrefs(
        context: Context,
        fileName: String
    ): WallpaperInteractiveWatchFaceInstanceParams? = TraceEvent(
        "WatchFaceService.readDirectBootPrefs"
    ).use {
        try {
            val reader = context.openFileInput(fileName)
            val result =
                ParcelUtils.fromInputStream<WallpaperInteractiveWatchFaceInstanceParams>(reader)
            reader.close()
            result
        } catch (e: FileNotFoundException) {
            null
        }
    }

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

    internal inner class EngineWrapper(
        private val uiThreadHandler: Handler,
        headless: Boolean
    ) : WallpaperService.Engine(), WatchFaceHostApi {
        internal val coroutineScope = CoroutineScope(getHandler().asCoroutineDispatcher().immediate)
        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
            // Watch faces with the old [onSetBinder] init flow don't know whether the system
            // is ambient until they have received a background action wallpaper command.
            // That's supposed to get sent very quickly, but in case it doesn't we initially
            // assume we're not in ambient mode which should be correct most of the time.
            isAmbient.value = false
            isHeadless = headless
        }

        /**
         * 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()

        internal 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) {
                    "Choreographer doFrame called but allowWatchfaceToAnimate is false"
                }
                frameCallbackPending = false
                draw()
            }
        }

        private val invalidateRunnable = Runnable(this::invalidate)

        // If non-null then changes to the style must be persisted.
        private var directBootParams: WallpaperInteractiveWatchFaceInstanceParams? = null

        // State to support the old WSL style initialzation flow.
        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 asyncWatchFaceConstructionPending = false

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

        private var createdBy = "?"

        init {
            TraceEvent("EngineWrapper.init").use {
                // If this is a headless instance then we don't want to create a WCS instance.
                if (!mutableWatchState.isHeadless) {
                    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()) {
                directBootParams = readDirectBootPrefs(_context, DIRECT_BOOT_PREFS)
                if (directBootParams != null) {
                    val asyncTraceEvent = AsyncTraceEvent("DirectBoot")
                    coroutineScope.launch {
                        // In tests a watchface may already have been created.
                        if (!watchFaceCreatedOrPending()) {
                            createInteractiveInstance(
                                directBootParams!!,
                                "DirectBoot"
                            ).createWCSApi()
                            asyncTraceEvent.close()
                        }
                    }
                }
            }

            // If there's a pending WallpaperInteractiveWatchFaceInstance then create it.
            if (pendingWallpaperInstance != null) {
                val asyncTraceEvent =
                    AsyncTraceEvent("Create PendingWallpaperInteractiveWatchFaceInstance")
                coroutineScope.launch {
                    pendingWallpaperInstance.callback.onInteractiveWatchFaceWcsCreated(
                        createInteractiveInstance(
                            pendingWallpaperInstance.params,
                            "Boot with pendingWallpaperInstance"
                        ).createWCSApi()
                    )
                    asyncTraceEvent.close()
                    val params = pendingWallpaperInstance.params
                    directBootParams = params
                    // 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...
                    params.idAndComplicationDataWireFormats = emptyList()

                    // Writing even small amounts of data to storage is quite slow and if we did
                    // that immediately, we'd delay the first frame which is rendered via
                    // onSurfaceRedrawNeeded. By posting this task we expedite first frame
                    // rendering. There is a small window where the direct boot could be stale if
                    // the watchface crashed but this seems unlikely in practice.
                    uiThreadHandler.post {
                        writeDirectBootPrefs(_context, DIRECT_BOOT_PREFS, params)
                    }
                }
            }
        }

        override fun onUserStyleChanged() {
            val params = directBootParams
            if (!this::watchFaceImpl.isInitialized || params == null) {
                return
            }

            val currentStyle = watchFaceImpl.userStyleRepository.userStyle.toWireFormat()
            if (params.userStyle.equals(currentStyle)) {
                return
            }
            params.userStyle = currentStyle
            // 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...
            params.idAndComplicationDataWireFormats = emptyList()
            writeDirectBootPrefs(_context, DIRECT_BOOT_PREFS, params)
        }

        @UiThread
        fun ambientTickUpdate(): Unit = TraceEvent("EngineWrapper.ambientTickUpdate").use {
            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
        ): Unit = TraceEvent("EngineWrapper.setUserStyle").use {
            watchFaceImpl.onSetStyleInternal(
                UserStyle(userStyle, watchFaceImpl.userStyleRepository.schema)
            )
            onUserStyleChanged()
        }

        @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
        ): Unit = TraceEvent("EngineWrapper.setComplicationData").use {
            if (watchFaceCreated()) {
                watchFaceImpl.onComplicationDataUpdate(complicationId, data)
            } else {
                pendingComplicationDataUpdates.add(
                    PendingComplicationData(complicationId, data)
                )
            }
        }

        fun clearComplicationData() {
            require(watchFaceCreated()) {
                "WatchFace must have been created first"
            }
            watchFaceImpl.clearComplicationData()
        }

        @UiThread
        fun getComplicationState(): List<IdAndComplicationStateWireFormat> =
            uiThreadHandler.runOnHandlerWithTracing("EngineWrapper.getComplicationState") {
                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.initiallyEnabled,
                            it.value.renderer.getIdAndData()?.complicationData?.type
                                ?.asWireComplicationType()
                                ?: ComplicationType.NO_DATA.asWireComplicationType(),
                            it.value.fixedComplicationProvider
                        )
                    )
                }
            }

        @UiThread
        fun setComplicationDataList(
            complicationDatumWireFormats: MutableList<IdAndComplicationDataWireFormat>
        ): Unit = TraceEvent("EngineWrapper.setComplicationDataList").use {
            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 = TraceEvent("EngineWrapper.takeWatchFaceScreenshot").use {
            val oldStyle = HashMap(watchFaceImpl.userStyleRepository.userStyle.selectedOptions)
            params.userStyle?.let {
                watchFaceImpl.onSetStyleInternal(
                    UserStyle(it, watchFaceImpl.userStyleRepository.schema)
                )
            }

            val oldComplicationData =
                watchFaceImpl.complicationsManager.complications.values.map {
                    it.renderer.getIdAndData() ?: IdAndComplicationData(
                        it.id,
                        NoDataComplicationData()
                    )
                }
            params.idAndComplicationDatumWireFormats?.let {
                for (idAndData in it) {
                    watchFaceImpl.complicationsManager[idAndData.id]!!.renderer
                        .setIdAndData(
                            IdAndComplicationData(
                                idAndData.id, idAndData.complicationData.asApiComplicationData()
                            ),
                            false
                        )
                }
            }

            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 (idAndData in oldComplicationData) {
                    watchFaceImpl.complicationsManager[idAndData.complicationId]!!.renderer
                        .setIdAndData(idAndData, false)
                }
            }

            return SharedMemoryImage.ashmemWriteImageBundle(bitmap)
        }

        @UiThread
        @RequiresApi(27)
        fun takeComplicationScreenshot(
            params: ComplicationScreenshotParams
        ): Bundle? = TraceEvent("EngineWrapper.takeComplicationScreenshot").use {
            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
                val screenshotComplicationData = params.complicationData
                if (screenshotComplicationData != null) {
                    prevIdAndComplicationData = it.renderer.getIdAndData()
                    it.renderer.setIdAndData(
                        IdAndComplicationData(
                            params.complicationId,
                            screenshotComplicationData
                        ),
                        false
                    )
                }

                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.setIdAndData(prevIdAndComplicationData, false)
                }

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

                SharedMemoryImage.ashmemWriteImageBundle(complicationBitmap)
            }
        }

        @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
        ): Unit = TraceEvent("EngineWrapper.onCreate").use {
            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 invalidate() 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(): Unit = TraceEvent("EngineWrapper.onDestroy").use {
            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? {
            // From android R onwards the integration changes and no wallpaper commands are allowed
            // or expected and can/should be ignored.
            if (!expectPreRInitFlow()) {
                return null
            }
            when (action) {
                Constants.COMMAND_AMBIENT_UPDATE ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_AMBIENT_UPDATE") {
                        ambientTickUpdate()
                    }
                Constants.COMMAND_BACKGROUND_ACTION ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_BACKGROUND_ACTION") {
                        onBackgroundAction(extras!!)
                    }
                Constants.COMMAND_COMPLICATION_DATA ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_COMPLICATION_DATA") {
                        onComplicationDataUpdate(extras!!)
                    }
                Constants.COMMAND_REQUEST_STYLE ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_REQUEST_STYLE") {
                        onRequestStyle()
                    }
                Constants.COMMAND_SET_BINDER ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_SET_BINDER") {
                        onSetBinder(extras!!)
                    }
                Constants.COMMAND_SET_PROPERTIES ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_SET_PROPERTIES") {
                        onPropertiesChanged(extras!!)
                    }
                Constants.COMMAND_TAP ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_TAP") {
                        sendTouchEvent(x, y, TapType.TAP)
                    }
                Constants.COMMAND_TOUCH ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_TOUCH") {
                        sendTouchEvent(x, y, TapType.TOUCH)
                    }
                Constants.COMMAND_TOUCH_CANCEL ->
                    uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_TOUCH_CANCEL") {
                        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)
            }

            coroutineScope.launch { maybeCreateWatchFace() }
        }

        override fun getInitialUserStyle(): UserStyleWireFormat? = initialUserStyle

        @RequiresApi(27)
        suspend fun createHeadlessInstance(
            params: HeadlessWatchFaceInstanceParams
        ): HeadlessWatchFaceImpl = TraceEvent("EngineWrapper.createHeadlessInstance").use {
            require(!watchFaceCreatedOrPending()) {
                "WatchFace already exists! Created by $createdBy"
            }
            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()
                }
            }

            allowWatchfaceToAnimate = false
            require(mutableWatchState.isHeadless)
            val watchState = mutableWatchState.asWatchState()

            createWatchFaceInternal(
                watchState,
                fakeSurfaceHolder,
                "createHeadlessInstance"
            )

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

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

        @UiThread
        @RequiresApi(27)
        suspend fun createInteractiveInstance(
            params: WallpaperInteractiveWatchFaceInstanceParams,
            _createdBy: String
        ): InteractiveWatchFaceImpl = TraceEvent(
            "EngineWrapper.createInteractiveInstance"
        ).use {
            require(!watchFaceCreatedOrPending()) {
                "WatchFace already exists! Created by $createdBy"
            }
            require(!mutableWatchState.isHeadless)

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

            val watchState = mutableWatchState.asWatchState()

            createWatchFaceInternal(
                watchState,
                getWallpaperSurfaceHolderOverride() ?: surfaceHolder,
                _createdBy
            )

            asyncWatchFaceConstructionPending = false

            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)
            interactiveInstanceId = params.instanceId

            // WatchFace init is async so its possible we have a pending
            // WallpaperInteractiveWatchFaceInstance request.
            InteractiveInstanceManager.takePendingWallpaperInteractiveWatchFaceInstance()?.let {
                require(it.params.instanceId == params.instanceId) {
                    "Miss match between pendingWallpaperInstance id $it.params.instanceId and " +
                        "constructed instance id $params.instanceId"
                }
                it.callback.onInteractiveWatchFaceWcsCreated(instance.createWCSApi())
            }

            return instance
        }

        override fun onSurfaceRedrawNeeded(holder: SurfaceHolder) {
            if (TRACE_DRAW) {
                Trace.beginSection("onSurfaceRedrawNeeded")
            }
            if (watchFaceCreated()) {
                watchFaceImpl.onSurfaceRedrawNeeded()
            }
            if (TRACE_DRAW) {
                Trace.endSection()
            }
        }

        private suspend fun createWatchFaceInternal(
            watchState: WatchState,
            surfaceHolder: SurfaceHolder,
            _createdBy: String
        ) {
            asyncWatchFaceConstructionPending = true
            createdBy = _createdBy
            val watchface = TraceEvent("WatchFaceService.createWatchFace").use {
                createWatchFace(surfaceHolder, watchState)
            }
            watchFaceImpl = TraceEvent("WatchFaceImpl.init").use {
                WatchFaceImpl(watchface, this, watchState)
            }
            asyncWatchFaceConstructionPending = false
        }

        private suspend fun maybeCreateWatchFace(): Unit = TraceEvent(
            "EngineWrapper.maybeCreateWatchFace"
        ).use {
            // 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 &&
                !watchFaceCreatedOrPending()
            ) {
                watchFaceInitStarted = true

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

                val watchState = mutableWatchState.asWatchState()
                createWatchFaceInternal(watchState, surfaceHolder, "maybeCreateWatchFace")
                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): Unit = TraceEvent(
            "onVisibilityChanged"
        ).use {
            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
                coroutineScope.launch { 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

        internal fun watchFaceCreatedOrPending() =
            watchFaceCreated() || asyncWatchFaceConstructionPending

        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)
            }
        }

        @UiThread
        fun dump(writer: IndentingPrintWriter) {
            require(uiThreadHandler.looper.isCurrentThread) {
                "dump must be called from the UIThread"
            }
            writer.println("WatchFaceEngine:")
            writer.increaseIndent()
            when {
                this::iWatchFaceService.isInitialized -> writer.println("WSL style init flow")
                this::watchFaceImpl.isInitialized -> writer.println("Androidx style init flow")
                expectPreRInitFlow() -> writer.println("Expecting WSL style init")
                else -> writer.println("Expecting androidx style style init")
            }

            if (this::iWatchFaceService.isInitialized) {
                writer.println(
                    "iWatchFaceService.asBinder().isBinderAlive=" +
                        "${iWatchFaceService.asBinder().isBinderAlive}"
                )
                if (iWatchFaceService.asBinder().isBinderAlive) {
                    writer.println("iWatchFaceService.apiVersion=${iWatchFaceService.apiVersion}")
                }
            }
            writer.println("createdBy=$createdBy")
            writer.println("watchFaceInitStarted=$watchFaceInitStarted")
            writer.println("asyncWatchFaceConstructionPending=$asyncWatchFaceConstructionPending")

            if (this::interactiveInstanceId.isInitialized) {
                writer.println("interactiveInstanceId=$interactiveInstanceId")
            }

            writer.println("frameCallbackPending=$frameCallbackPending")
            writer.println("destroyed=$destroyed")

            if (!destroyed && this::watchFaceImpl.isInitialized) {
                watchFaceImpl.dump(writer)
            }
            writer.decreaseIndent()
        }
    }

    @UiThread
    override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<String>) {
        super.dump(fd, writer, args)
        val indentingPrintWriter = IndentingPrintWriter(writer)
        indentingPrintWriter.println("AndroidX WatchFaceService $packageName")
        InteractiveInstanceManager.dump(indentingPrintWriter)
        EditorService.globalEditorService.dump(indentingPrintWriter)
        HeadlessWatchFaceImpl.dump(indentingPrintWriter)
        indentingPrintWriter.flush()
    }
}

/**
 * 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.runOnHandlerWithTracing(
    traceEventName: String,
    task: () -> R
): R = TraceEvent(traceEventName).use {
    if (looper == Looper.myLooper()) {
        task.invoke()
    } else {
        val latch = CountDownLatch(1)
        var returnVal: R? = null
        var exception: Exception? = null
        if (post {
            try {
                returnVal = TraceEvent("$traceEventName invokeTask").use {
                    task.invoke()
                }
            } catch (e: Exception) {
                // Will rethrow on the calling thread.
                exception = e
            }
            latch.countDown()
        }
        ) {
            latch.await()
            if (exception != null) {
                throw exception as Exception
            }
        }
        // R might be nullable so we can't assert nullability here.
        @Suppress("UNCHECKED_CAST")
        returnVal as R
    }
}