WatchFaceControlService.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.control

import android.annotation.SuppressLint
import android.app.Service
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import androidx.wear.watchface.IndentingPrintWriter
import androidx.wear.watchface.WatchFaceService
import androidx.wear.watchface.control.data.CrashInfoParcel
import androidx.wear.watchface.control.data.DefaultProviderPoliciesParams
import androidx.wear.watchface.control.data.GetComplicationSlotMetadataParams
import androidx.wear.watchface.control.data.GetUserStyleFlavorsParams
import androidx.wear.watchface.control.data.GetUserStyleSchemaParams
import androidx.wear.watchface.control.data.HeadlessWatchFaceInstanceParams
import androidx.wear.watchface.control.data.IdTypeAndDefaultProviderPolicyWireFormat
import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
import androidx.wear.watchface.data.ComplicationSlotMetadataWireFormat
import androidx.wear.watchface.editor.EditorService
import androidx.wear.watchface.runBlockingWithTracing
import androidx.wear.watchface.style.data.UserStyleFlavorsWireFormat
import androidx.wear.watchface.style.data.UserStyleSchemaWireFormat
import androidx.wear.watchface.utility.AsyncTraceEvent
import androidx.wear.watchface.utility.TraceEvent
import androidx.wear.watchface.utility.aidlMethod
import java.io.FileDescriptor
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope

/** A service for creating and controlling watch face instances. */
@RequiresApi(27)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open class WatchFaceControlService : Service() {
    private var watchFaceInstanceServiceStub: IWatchFaceInstanceServiceStub? = null

    public companion object {
        public const val ACTION_WATCHFACE_CONTROL_SERVICE: String =
            "com.google.android.wearable.action.WATCH_FACE_CONTROL"
        internal const val TAG = "IWatchFaceInstanceServiceStub"
    }

    override fun onBind(intent: Intent?): IBinder? =
        TraceEvent("WatchFaceControlService.onBind").use {
            if (ACTION_WATCHFACE_CONTROL_SERVICE == intent?.action) {
                if (watchFaceInstanceServiceStub == null) {
                    watchFaceInstanceServiceStub = createServiceStub()
                }
                watchFaceInstanceServiceStub
            } else {
                null
            }
        }

    open fun createWatchFaceService(watchFaceName: ComponentName): WatchFaceService? {
        return try {
            val watchFaceServiceClass = Class.forName(watchFaceName.className) ?: return null
            if (!WatchFaceService::class.java.isAssignableFrom(watchFaceServiceClass)) {
                return null
            }
            watchFaceServiceClass.getConstructor().newInstance() as WatchFaceService
        } catch (e: ClassNotFoundException) {
            Log.w(TAG, "createWatchFaceService failed for $watchFaceName", e)
            null
        }
    }

    @VisibleForTesting
    public open fun createServiceStub(): IWatchFaceInstanceServiceStub =
        TraceEvent("WatchFaceControlService.createServiceStub").use {
            IWatchFaceInstanceServiceStub(this, MainScope())
        }

    @VisibleForTesting
    public fun setContext(context: Context) {
        attachBaseContext(context)
    }

    @UiThread
    override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<String>) {
        val indentingPrintWriter = IndentingPrintWriter(writer)
        indentingPrintWriter.println("WatchFaceControlService:")
        InteractiveInstanceManager.dump(indentingPrintWriter)
        HeadlessWatchFaceImpl.dump(indentingPrintWriter)
        indentingPrintWriter.flush()
    }

    override fun onDestroy() {
        super.onDestroy()
        watchFaceInstanceServiceStub?.onDestroy()
    }
}

@RequiresApi(27)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open class IWatchFaceInstanceServiceStub(
    // We need to explicitly null this object in onDestroy to avoid a memory leak.
    private var service: WatchFaceControlService?,
    private val uiThreadCoroutineScope: CoroutineScope
) : IWatchFaceControlService.Stub() {
    override fun getApiVersion(): Int =
        aidlMethod(TAG, "getApiVersion") { IWatchFaceControlService.API_VERSION }

    internal companion object {
        const val TAG = "IWatchFaceInstanceServiceStub"
    }

    override fun getInteractiveWatchFaceInstance(instanceId: String): IInteractiveWatchFace? =
        aidlMethod(TAG, "getInteractiveWatchFaceInstance") {
            TraceEvent("IWatchFaceInstanceServiceStub.getInteractiveWatchFaceInstance").use {
                // This call is thread safe so we don't need to trampoline via the UI thread.
                InteractiveInstanceManager.getAndRetainInstance(instanceId)
            }
        }

    override fun createHeadlessWatchFaceInstance(
        params: HeadlessWatchFaceInstanceParams
    ): IHeadlessWatchFace? =
        aidlMethod(TAG, "createHeadlessWatchFaceInstance") {
            TraceEvent("IWatchFaceInstanceServiceStub.createHeadlessWatchFaceInstance").use {
                createServiceAndHeadlessEngine(params.watchFaceName)?.let { serviceAndEngine ->
                    // This is serviced on a background thread so it should be fine to block.
                    uiThreadCoroutineScope.runBlockingWithTracing("createHeadlessInstance") {
                        // However the WatchFaceService.createWatchFace method needs to be run on
                        // the UI thread.
                        serviceAndEngine.engine.createHeadlessInstance(params)
                    }
                }
            }
        }

    private class ServiceAndEngine(
        val service: WatchFaceService,
        val engine: WatchFaceService.EngineWrapper
    ) {
        fun destroy() {
            engine.onDestroy()
            service.onDestroy()
        }
    }

    @SuppressLint("BanUncheckedReflection")
    private fun createServiceAndHeadlessEngine(watchFaceName: ComponentName) =
        TraceEvent("IWatchFaceInstanceServiceStub.createEngine").use {
            // Attempt to construct the class for the specified watchFaceName, failing if it either
            // doesn't exist or isn't a [WatchFaceService].
            val watchFaceService = service?.createWatchFaceService(watchFaceName)

            if (watchFaceService != null) {
                // Set the context and if possible the application for watchFaceService.
                try {
                    val method = Service::class.java.declaredMethods.find { it.name == "attach" }
                    method!!.isAccessible = true
                    method.invoke(
                        watchFaceService,
                        service as Context,
                        null,
                        watchFaceService::class.qualifiedName,
                        null,
                        service!!.application,
                        null
                    )
                } catch (e: Exception) {
                    Log.w(
                        TAG,
                        "createServiceAndHeadlessEngine can't call attach by reflection, " +
                            "falling back to setContext",
                        e
                    )
                    watchFaceService.setContext(watchFaceService)
                }
                watchFaceService.onCreate()
                val engine =
                    watchFaceService.createHeadlessEngine(watchFaceName)
                        as WatchFaceService.EngineWrapper
                ServiceAndEngine(watchFaceService, engine)
            } else {
                null
            }
        }

    override fun getOrCreateInteractiveWatchFace(
        params: WallpaperInteractiveWatchFaceInstanceParams,
        callback: IPendingInteractiveWatchFace
    ): IInteractiveWatchFace? =
        aidlMethod(TAG, "getOrCreateInteractiveWatchFace") {
            val asyncTraceEvent =
                AsyncTraceEvent("IWatchFaceInstanceServiceStub.getOrCreateInteractiveWatchFaceWCS")
            return@aidlMethod InteractiveInstanceManager
                .getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
                    InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance(
                        params,
                        // Wrapped IPendingInteractiveWatchFace to support tracing.
                        object : IPendingInteractiveWatchFace.Stub() {
                            override fun getApiVersion() =
                                aidlMethod(TAG, "getApiVersion") { callback.apiVersion }

                            override fun onInteractiveWatchFaceCreated(
                                iInteractiveWatchFaceWcs: IInteractiveWatchFace?
                            ) =
                                aidlMethod(TAG, "onInteractiveWatchFaceCreated") {
                                    asyncTraceEvent.close()
                                    callback.onInteractiveWatchFaceCreated(iInteractiveWatchFaceWcs)
                                }

                            override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel) =
                                aidlMethod(TAG, "onInteractiveWatchFaceCrashed") {
                                    asyncTraceEvent.close()
                                    callback.onInteractiveWatchFaceCrashed(exception)
                                }
                        }
                    )
                )
        }

    override fun getEditorService(): EditorService =
        aidlMethod(TAG, "getEditorService") { EditorService.globalEditorService }

    override fun getDefaultProviderPolicies(
        params: DefaultProviderPoliciesParams
    ): Array<IdTypeAndDefaultProviderPolicyWireFormat>? =
        aidlMethod(TAG, "getDefaultProviderPolicies") {
            createServiceAndHeadlessEngineAndEvaluate(
                params.watchFaceName,
                "IWatchFaceInstanceServiceStub.getDefaultProviderPolicies"
            ) {
                it.engine.getDefaultProviderPolicies()
            }
        }

    override fun getUserStyleSchema(params: GetUserStyleSchemaParams): UserStyleSchemaWireFormat? =
        aidlMethod(TAG, "getUserStyleSchema") {
            createServiceAndHeadlessEngineAndEvaluate(
                params.watchFaceName,
                "IWatchFaceInstanceServiceStub.getUserStyleSchema"
            ) {
                it.engine.getUserStyleSchemaWireFormat()
            }
        }

    override fun getComplicationSlotMetadata(
        params: GetComplicationSlotMetadataParams
    ): Array<ComplicationSlotMetadataWireFormat>? =
        aidlMethod(TAG, "getComplicationSlotMetadata") {
            createServiceAndHeadlessEngineAndEvaluate(
                params.watchFaceName,
                "IWatchFaceInstanceServiceStub.getComplicationSlotMetadata"
            ) {
                it.engine.getComplicationSlotMetadataWireFormats()
            }
        }

    override fun hasComplicationCache() = aidlMethod(TAG, "hasComplicationCache") { true }

    override fun getUserStyleFlavors(
        params: GetUserStyleFlavorsParams
    ): UserStyleFlavorsWireFormat? =
        aidlMethod(TAG, "getUserStyleFlavors") {
            createServiceAndHeadlessEngineAndEvaluate(
                params.watchFaceName,
                "IWatchFaceInstanceServiceStub.getUserStyleFlavors"
            ) {
                it.engine.getUserStyleFlavorsWireFormat()
            }
        }

    private fun <T> createServiceAndHeadlessEngineAndEvaluate(
        watchFaceName: ComponentName,
        functionName: String,
        function: (serviceAndEngine: ServiceAndEngine) -> T
    ): T? =
        TraceEvent(functionName).use {
            createServiceAndHeadlessEngine(watchFaceName)?.let { serviceAndEngine ->
                try {
                    function(serviceAndEngine)
                } finally {
                    serviceAndEngine.destroy()
                }
            }
        }

    fun onDestroy() {
        service = null
    }
}