| /* |
| * 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.utility.AsyncTraceEvent |
| import androidx.wear.watchface.utility.TraceEvent |
| 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 java.io.FileDescriptor |
| import java.io.PrintWriter |
| import kotlinx.coroutines.CoroutineScope |
| import kotlinx.coroutines.MainScope |
| |
| /** |
| * A service for creating and controlling watch face instances. |
| * |
| * @hide |
| */ |
| @RequiresApi(27) |
| @VisibleForTesting |
| @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) |
| public open class WatchFaceControlService : Service() { |
| private var watchFaceInstanceServiceStub: IWatchFaceInstanceServiceStub? = null |
| |
| /** @hide */ |
| public companion object { |
| public const val ACTION_WATCHFACE_CONTROL_SERVICE: String = |
| "com.google.android.wearable.action.WATCH_FACE_CONTROL" |
| } |
| |
| 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 |
| } |
| } |
| |
| @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() |
| } |
| } |
| |
| /** @hide */ |
| @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: Service?, |
| private val uiThreadCoroutineScope: CoroutineScope |
| ) : IWatchFaceControlService.Stub() { |
| override fun getApiVersion(): Int = IWatchFaceControlService.API_VERSION |
| |
| internal companion object { |
| const val TAG = "IWatchFaceInstanceServiceStub" |
| } |
| |
| override fun getInteractiveWatchFaceInstance(instanceId: String): IInteractiveWatchFace? = |
| 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? = 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() { |
| try { |
| engine.onDestroy() |
| service.onDestroy() |
| } catch (e: Exception) { |
| Log.e( |
| TAG, |
| "ServiceAndEngine.destroy failed due to exception", |
| e |
| ) |
| throw e |
| } |
| } |
| } |
| |
| @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]. |
| try { |
| val watchFaceServiceClass = Class.forName(watchFaceName.className) ?: return null |
| if (!WatchFaceService::class.java.isAssignableFrom(WatchFaceService::class.java)) { |
| null |
| } else { |
| val watchFaceService = |
| watchFaceServiceClass.getConstructor().newInstance() as WatchFaceService |
| |
| // 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() as WatchFaceService.EngineWrapper |
| ServiceAndEngine(watchFaceService, engine) |
| } |
| } catch (e: ClassNotFoundException) { |
| null |
| } |
| } |
| |
| override fun getOrCreateInteractiveWatchFace( |
| params: WallpaperInteractiveWatchFaceInstanceParams, |
| callback: IPendingInteractiveWatchFace |
| ): IInteractiveWatchFace? { |
| val asyncTraceEvent = |
| AsyncTraceEvent("IWatchFaceInstanceServiceStub.getOrCreateInteractiveWatchFaceWCS") |
| return try { |
| InteractiveInstanceManager |
| .getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance( |
| InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance( |
| params, |
| // Wrapped IPendingInteractiveWatchFace to support tracing. |
| object : IPendingInteractiveWatchFace.Stub() { |
| override fun getApiVersion() = callback.apiVersion |
| |
| override fun onInteractiveWatchFaceCreated( |
| iInteractiveWatchFaceWcs: IInteractiveWatchFace? |
| ) { |
| asyncTraceEvent.close() |
| callback.onInteractiveWatchFaceCreated(iInteractiveWatchFaceWcs) |
| } |
| |
| override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel) { |
| asyncTraceEvent.close() |
| callback.onInteractiveWatchFaceCrashed(exception) |
| } |
| } |
| ) |
| ) |
| } catch (e: Exception) { |
| Log.e(TAG, "getOrCreateInteractiveWatchFace failed ", e) |
| throw e |
| } |
| } |
| |
| override fun getEditorService(): EditorService = EditorService.globalEditorService |
| |
| override fun getDefaultProviderPolicies( |
| params: DefaultProviderPoliciesParams |
| ): Array<IdTypeAndDefaultProviderPolicyWireFormat>? = createServiceAndHeadlessEngineAndEvaluate( |
| params.watchFaceName, |
| "IWatchFaceInstanceServiceStub.getDefaultProviderPolicies" |
| ) { it.engine.getDefaultProviderPolicies() } |
| |
| override fun getUserStyleSchema( |
| params: GetUserStyleSchemaParams |
| ): UserStyleSchemaWireFormat? = createServiceAndHeadlessEngineAndEvaluate( |
| params.watchFaceName, |
| "IWatchFaceInstanceServiceStub.getUserStyleSchema" |
| ) { it.engine.getUserStyleSchemaWireFormat() } |
| |
| override fun getComplicationSlotMetadata( |
| params: GetComplicationSlotMetadataParams |
| ): Array<ComplicationSlotMetadataWireFormat>? = createServiceAndHeadlessEngineAndEvaluate( |
| params.watchFaceName, |
| "IWatchFaceInstanceServiceStub.getComplicationSlotMetadata" |
| ) { it.engine.getComplicationSlotMetadataWireFormats() } |
| |
| override fun hasComplicationCache() = true |
| |
| override fun getUserStyleFlavors( |
| params: GetUserStyleFlavorsParams |
| ): UserStyleFlavorsWireFormat? = createServiceAndHeadlessEngineAndEvaluate( |
| params.watchFaceName, |
| "IWatchFaceInstanceServiceStub.getUserStyleFlavors" |
| ) { it.engine.getUserStyleFlavorsWireFormat() } |
| |
| private fun <T> createServiceAndHeadlessEngineAndEvaluate( |
| watchFaceName: ComponentName, |
| functionName: String, |
| function: (serviceAndEngine: ServiceAndEngine) -> T |
| ): T? = TraceEvent(functionName).use { |
| return try { |
| createServiceAndHeadlessEngine(watchFaceName)?.let { serviceAndEngine -> |
| try { |
| function(serviceAndEngine) |
| } finally { |
| serviceAndEngine.destroy() |
| } |
| } |
| } catch (e: Exception) { |
| Log.e(TAG, "$functionName failed due to exception", e) |
| throw e |
| } |
| } |
| |
| fun onDestroy() { |
| service = null |
| } |
| } |