diff options
author | Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> | 2023-07-06 10:36:07 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2023-07-06 10:36:07 +0000 |
commit | 55dfd408b232c9d46cfff5372df4014191d5708f (patch) | |
tree | 20d79915c5dd258b2b9ea038d3ec1a1ed1adbe8c | |
parent | fd0ff4b94116ce16bfb19ecbf3ff50ba0b6f6e90 (diff) | |
parent | fce69f27571cc0f9ad2a5ce994ac0712a5fa4533 (diff) | |
download | support-55dfd408b232c9d46cfff5372df4014191d5708f.tar.gz |
Merge "Add support for resource only watch face runtimes" into androidx-main
30 files changed, 871 insertions, 69 deletions
diff --git a/wear/watchface/watchface-client-guava/api/current.txt b/wear/watchface/watchface-client-guava/api/current.txt index 471cc7f1a69..9c9689dc854 100644 --- a/wear/watchface/watchface-client-guava/api/current.txt +++ b/wear/watchface/watchface-client-guava/api/current.txt @@ -7,6 +7,7 @@ package androidx.wear.watchface.client { method @Deprecated public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight); method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(String id, android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight); method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName); + method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceRuntimeControlClientAsync(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName); method @Deprecated public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.DefaultComplicationDataSourcePolicyAndType> getDefaultComplicationDataSourcePoliciesAndType(android.content.ComponentName watchFaceName); method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient(); method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId); @@ -19,15 +20,18 @@ package androidx.wear.watchface.client { public static final class ListenableWatchFaceControlClient.Companion { method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName); + method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceRuntimeControlClientAsync(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName); } public final class ListenableWatchFaceMetadataClient { method public static com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> create(android.content.Context context, android.content.ComponentName watchFaceName); + method public static com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> createForRuntime(android.content.Context context, android.content.ComponentName watchFaceName, String resourceOnlyWatchFacePackageName); field public static final androidx.wear.watchface.client.ListenableWatchFaceMetadataClient.Companion Companion; } public static final class ListenableWatchFaceMetadataClient.Companion { method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> create(android.content.Context context, android.content.ComponentName watchFaceName); + method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> createForRuntime(android.content.Context context, android.content.ComponentName watchFaceName, String resourceOnlyWatchFacePackageName); } } diff --git a/wear/watchface/watchface-client-guava/api/restricted_current.txt b/wear/watchface/watchface-client-guava/api/restricted_current.txt index 471cc7f1a69..9c9689dc854 100644 --- a/wear/watchface/watchface-client-guava/api/restricted_current.txt +++ b/wear/watchface/watchface-client-guava/api/restricted_current.txt @@ -7,6 +7,7 @@ package androidx.wear.watchface.client { method @Deprecated public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight); method public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(String id, android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, int surfaceWidth, int surfaceHeight); method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName); + method public static final com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceRuntimeControlClientAsync(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName); method @Deprecated public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.DefaultComplicationDataSourcePolicyAndType> getDefaultComplicationDataSourcePoliciesAndType(android.content.ComponentName watchFaceName); method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient(); method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId); @@ -19,15 +20,18 @@ package androidx.wear.watchface.client { public static final class ListenableWatchFaceControlClient.Companion { method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceControlClient(android.content.Context context, String watchFacePackageName); + method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.ListenableWatchFaceControlClient> createWatchFaceRuntimeControlClientAsync(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName); } public final class ListenableWatchFaceMetadataClient { method public static com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> create(android.content.Context context, android.content.ComponentName watchFaceName); + method public static com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> createForRuntime(android.content.Context context, android.content.ComponentName watchFaceName, String resourceOnlyWatchFacePackageName); field public static final androidx.wear.watchface.client.ListenableWatchFaceMetadataClient.Companion Companion; } public static final class ListenableWatchFaceMetadataClient.Companion { method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> create(android.content.Context context, android.content.ComponentName watchFaceName); + method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.WatchFaceMetadataClient> createForRuntime(android.content.Context context, android.content.ComponentName watchFaceName, String resourceOnlyWatchFacePackageName); } } diff --git a/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt b/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt index e3de1528d32..11fa95bb7c9 100644 --- a/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt +++ b/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt @@ -21,6 +21,8 @@ import android.content.Context import androidx.concurrent.futures.ResolvableFuture import androidx.core.util.Consumer import androidx.wear.watchface.Renderer +import androidx.wear.watchface.WatchFaceRuntimeService +import androidx.wear.watchface.client.WatchFaceControlClient.Companion.createWatchFaceControlClient import androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.style.UserStyleData @@ -119,6 +121,51 @@ public open class ListenableWatchFaceControlClient( ) ) } + + /** + * Similar [createWatchFaceControlClient] this constructs a [WatchFaceControlClient] which + * attempts to connect to the watch face runtime in the android package + * [runtimePackageName]. + * + * A watch face runtime is a special type of watch face, which renders a watch face + * described by resources in another package [resourceOnlyWatchFacePackageName]. + * + * Note only one watch face definition per resource only watch face package is supported. + * + * Currently Wear OS only supports the runtime for the Android Watch Face Format (see + * https://developer.android.com/training/wearables/wff for more details). + * + * @param context Calling application's [Context]. + * @param runtimePackageName The name of the package containing the watch face runtime's + * control service to bind to. + * @param resourceOnlyWatchFacePackageName The name of the package from which to load the + * resource only watch face. This is exposed to the runtime via the + * `resourceOnlyWatchFacePackageName` parameter passed to + * [WatchFaceRuntimeService.createUserStyleSchema], + * [WatchFaceRuntimeService.createComplicationSlotsManager], + * [WatchFaceRuntimeService.createUserStyleFlavors] and + * [WatchFaceRuntimeService.createWatchFace]). + * @return [ListenableFuture]<[ListenableWatchFaceControlClient]> which on success resolves + * to a [ListenableWatchFaceControlClient] or throws a [ServiceNotBoundException] if the + * watch face control service can not be bound. + */ + @JvmStatic + public fun createWatchFaceRuntimeControlClientAsync( + context: Context, + runtimePackageName: String, + resourceOnlyWatchFacePackageName: String + ): ListenableFuture<ListenableWatchFaceControlClient> = + launchFutureCoroutine( + "ListenableWatchFaceControlClient.createWatchFaceRuntimeControlClient", + ) { + ListenableWatchFaceControlClient( + WatchFaceControlClient.createWatchFaceRuntimeControlClient( + context, + runtimePackageName, + resourceOnlyWatchFacePackageName + ) + ) + } } @Suppress("DEPRECATION") diff --git a/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceMetadataClient.kt b/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceMetadataClient.kt index 6d25efd16e9..5a49cbb44fb 100644 --- a/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceMetadataClient.kt +++ b/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceMetadataClient.kt @@ -53,6 +53,38 @@ public class ListenableWatchFaceMetadataClient private constructor() { WatchFaceMetadataClient.Companion.ParserProvider() ) + /** + * Constructs a [WatchFaceMetadataClient] for fetching metadata for the specified resource + * only watch face runtime. A resource only watch face runtime, is a special kind of watch + * face that is the runtime for a watch face defined by another package that contains only + * resources and no executable code. + * + * @param context Calling application's [Context]. + * @param watchFaceName The [ComponentName] of the watch face to fetch meta data from. + * @param resourceOnlyWatchFacePackageName The package the runtime should load the resources + * from. + * @return A [ListenableFuture] which resolves with [WatchFaceMetadataClient] if there is + * one, otherwise it throws a [ServiceNotBoundException] if the underlying watch face + * control service can not be bound or a [ServiceStartFailureException] if the watch face + * dies during startup. + */ + @Suppress("AsyncSuffixFuture") + @JvmStatic + public fun createForRuntime( + context: Context, + watchFaceName: ComponentName, + resourceOnlyWatchFacePackageName: String + ) = + ListenableWatchFaceControlClient.launchFutureCoroutine( + "ListenableWatchFaceMetadataClient.create" + ) { + WatchFaceMetadataClient.createForRuntime( + context, + watchFaceName, + resourceOnlyWatchFacePackageName + ) + } + internal fun createImpl( context: Context, intent: Intent, diff --git a/wear/watchface/watchface-client/api/current.txt b/wear/watchface/watchface-client/api/current.txt index 41cfe987217..9e3a9b69065 100644 --- a/wear/watchface/watchface-client/api/current.txt +++ b/wear/watchface/watchface-client/api/current.txt @@ -206,6 +206,7 @@ package androidx.wear.watchface.client { method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, @Px int surfaceWidth, @Px int surfaceHeight) throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(String id, android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, @Px int surfaceWidth, @Px int surfaceHeight) throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public static suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; + method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public static suspend Object? createWatchFaceRuntimeControlClient(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.DefaultComplicationDataSourcePolicyAndType> getDefaultComplicationDataSourcePoliciesAndType(android.content.ComponentName watchFaceName) throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient() throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId) throws android.os.RemoteException; @@ -217,6 +218,7 @@ package androidx.wear.watchface.client { public static final class WatchFaceControlClient.Companion { method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; + method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceRuntimeControlClient(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; } public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception { @@ -244,6 +246,7 @@ package androidx.wear.watchface.client { public static final class WatchFaceMetadataClient.Companion { method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class, PackageManager.NameNotFoundException::class}) public suspend Object? create(android.content.Context context, android.content.ComponentName watchFaceName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceMetadataClient>) throws android.content.pm.PackageManager.NameNotFoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceStartFailureException; + method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class, PackageManager.NameNotFoundException::class}) public suspend Object? createForRuntime(android.content.Context context, android.content.ComponentName watchFaceName, String runtimePackage, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceMetadataClient>) throws android.content.pm.PackageManager.NameNotFoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceStartFailureException; } public static final class WatchFaceMetadataClient.ServiceNotBoundException extends java.lang.Exception { diff --git a/wear/watchface/watchface-client/api/restricted_current.txt b/wear/watchface/watchface-client/api/restricted_current.txt index 41cfe987217..9e3a9b69065 100644 --- a/wear/watchface/watchface-client/api/restricted_current.txt +++ b/wear/watchface/watchface-client/api/restricted_current.txt @@ -206,6 +206,7 @@ package androidx.wear.watchface.client { method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, @Px int surfaceWidth, @Px int surfaceHeight) throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.HeadlessWatchFaceClient? createHeadlessWatchFaceClient(String id, android.content.ComponentName watchFaceName, androidx.wear.watchface.client.DeviceConfig deviceConfig, @Px int surfaceWidth, @Px int surfaceHeight) throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public static suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; + method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public static suspend Object? createWatchFaceRuntimeControlClient(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.DefaultComplicationDataSourcePolicyAndType> getDefaultComplicationDataSourcePoliciesAndType(android.content.ComponentName watchFaceName) throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient() throws android.os.RemoteException; method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId) throws android.os.RemoteException; @@ -217,6 +218,7 @@ package androidx.wear.watchface.client { public static final class WatchFaceControlClient.Companion { method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; + method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceRuntimeControlClient(android.content.Context context, String runtimePackageName, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException; } public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception { @@ -244,6 +246,7 @@ package androidx.wear.watchface.client { public static final class WatchFaceMetadataClient.Companion { method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class, PackageManager.NameNotFoundException::class}) public suspend Object? create(android.content.Context context, android.content.ComponentName watchFaceName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceMetadataClient>) throws android.content.pm.PackageManager.NameNotFoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceStartFailureException; + method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class, PackageManager.NameNotFoundException::class}) public suspend Object? createForRuntime(android.content.Context context, android.content.ComponentName watchFaceName, String runtimePackage, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceMetadataClient>) throws android.content.pm.PackageManager.NameNotFoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceMetadataClient.ServiceStartFailureException; } public static final class WatchFaceMetadataClient.ServiceNotBoundException extends java.lang.Exception { diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt index d6c8fb57650..bf838f8c95e 100644 --- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt +++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/HeadlessWatchFaceClientTest.kt @@ -61,7 +61,8 @@ abstract class HeadlessWatchFaceClientTestBase { context, Intent(context, WatchFaceControlTestService::class.java).apply { action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE - } + }, + resourceOnlyWatchFacePackageName = null ) } diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt index ff8ba4b1449..26eb17a8026 100644 --- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt +++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/SerializationTest.kt @@ -22,6 +22,7 @@ import android.graphics.Color import android.graphics.Rect import android.os.Build import android.os.Parcel +import android.support.wearable.watchface.SharedMemoryImage import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest @@ -29,8 +30,8 @@ import androidx.wear.watchface.ComplicationSlotBoundsType import androidx.wear.watchface.DrawMode import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.client.ComplicationSlotState +import androidx.wear.watchface.client.EditorState import androidx.wear.watchface.client.WatchFaceId -import androidx.wear.watchface.client.asApiEditorState import androidx.wear.watchface.complications.SystemDataSources import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.LongTextComplicationData @@ -52,6 +53,26 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +// FIXME: We shouldn't have to define this here, we should be able to import it. +internal fun EditorStateWireFormat.asApiEditorState(): EditorState { + return EditorState( + WatchFaceId(watchFaceInstanceId ?: ""), + UserStyleData(userStyle.mUserStyle), + previewComplicationData.associateBy( + { it.id }, + { it.complicationData.toApiComplicationData() } + ), + commitChanges, + previewImageBundle?.let { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + SharedMemoryImage.ashmemReadImageBundle(it) + } else { + null + } + } + ) +} + /** Tests that we can deserialize golden resources correctly to ensure backwards compatibility. */ @RunWith(AndroidJUnit4::class) @MediumTest diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt index d758df72b21..52e8c101c68 100644 --- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt +++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestWatchFaceServices.kt @@ -34,6 +34,7 @@ import androidx.wear.watchface.ComplicationTapFilter import androidx.wear.watchface.RenderParameters import androidx.wear.watchface.Renderer import androidx.wear.watchface.WatchFace +import androidx.wear.watchface.WatchFaceRuntimeService import androidx.wear.watchface.WatchFaceService import androidx.wear.watchface.WatchFaceType import androidx.wear.watchface.WatchState @@ -54,6 +55,7 @@ import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Compa import androidx.wear.watchface.samples.ExampleOpenGLBackgroundInitWatchFaceService import androidx.wear.watchface.samples.R import androidx.wear.watchface.style.CurrentUserStyleRepository +import androidx.wear.watchface.style.UserStyleFlavors import androidx.wear.watchface.style.UserStyleSchema import androidx.wear.watchface.style.UserStyleSetting import androidx.wear.watchface.style.WatchFaceLayer @@ -261,6 +263,74 @@ internal class TestWatchfaceOverlayStyleWatchFaceService( .setOverlayStyle(watchFaceOverlayStyle) } +@Suppress("Deprecation") +internal class TestWatchFaceRuntimeService( + testContext: Context, + private var surfaceHolderOverride: SurfaceHolder +) : WatchFaceRuntimeService() { + + lateinit var lastResourceOnlyWatchFacePackageName: String + + init { + attachBaseContext(testContext) + } + + override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride + + override fun createUserStyleSchema(resourceOnlyWatchFacePackageName: String) = + UserStyleSchema(emptyList()) + + override fun createComplicationSlotsManager( + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ) = ComplicationSlotsManager(emptyList(), currentUserStyleRepository) + + override fun createUserStyleFlavors( + currentUserStyleRepository: CurrentUserStyleRepository, + complicationSlotsManager: ComplicationSlotsManager, + resourceOnlyWatchFacePackageName: String + ) = UserStyleFlavors() + + override suspend fun createWatchFace( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ): WatchFace { + lastResourceOnlyWatchFacePackageName = resourceOnlyWatchFacePackageName + + return WatchFace( + WatchFaceType.DIGITAL, + @Suppress("deprecation") + object : + Renderer.CanvasRenderer( + surfaceHolder, + currentUserStyleRepository, + watchState, + CanvasType.HARDWARE, + 16 + ) { + override fun render( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime + ) { + // Actually rendering something isn't required. + } + + override fun renderHighlightLayer( + canvas: Canvas, + bounds: Rect, + zonedDateTime: ZonedDateTime + ) { + // Actually rendering something isn't required. + } + } + ) + } +} + internal class TestAsyncCanvasRenderInitWatchFaceService( testContext: Context, private var surfaceHolderOverride: SurfaceHolder, diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt index 6e145da87ea..1769ce1f735 100644 --- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt +++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt @@ -133,7 +133,8 @@ abstract class WatchFaceControlClientTestBase { context, Intent(context, WatchFaceControlTestService::class.java).apply { action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE - } + }, + resourceOnlyWatchFacePackageName = null ) } @@ -503,6 +504,44 @@ class WatchFaceControlClientTest : WatchFaceControlClientTestBase() { } @Test + @Suppress("deprecation") // getOrCreateInteractiveWatchFaceClient + fun resourceOnlyWatchFacePackageName() { + val watchFaceService = TestWatchFaceRuntimeService(context, surfaceHolder) + val service = runBlocking { + WatchFaceControlClient.createWatchFaceControlClientImpl( + context, + Intent(context, WatchFaceControlTestService::class.java).apply { + action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE + }, + resourceOnlyWatchFacePackageName = "com.example.watchface" + ) + } + + val deferredInteractiveInstance = handlerCoroutineScope.async { + service.getOrCreateInteractiveWatchFaceClient( + "testId", + deviceConfig, + systemState, + userStyle = null, + complications + ) + } + + // Create the engine which triggers construction of the interactive instance. + handler.post { + engine = watchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper + } + + // Wait for the instance to be created. + val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance) + + assertThat(watchFaceService.lastResourceOnlyWatchFacePackageName) + .isEqualTo("com.example.watchface") + + interactiveInstance.close() + } + + @Test @Suppress("Deprecation") fun getInteractiveWatchFaceInstance() { val testId = "testId" diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt index 4414c284de5..833269ba559 100644 --- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt +++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt @@ -27,6 +27,7 @@ import androidx.annotation.Px import androidx.annotation.RestrictTo import androidx.core.util.Consumer import androidx.wear.watchface.Renderer +import androidx.wear.watchface.WatchFaceService import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationType @@ -79,13 +80,52 @@ public interface WatchFaceControlClient : AutoCloseable { context, Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE).apply { setPackage(watchFacePackageName) - } + }, + null + ) + + /** + * Similar [createWatchFaceControlClient] this constructs a [WatchFaceControlClient] which + * attempts to connect to the watch face runtime in the android package + * [runtimePackageName]. + * + * A watch face runtime is a special type of watch face, which renders a watch face + * described by resources in another package [resourceOnlyWatchFacePackageName]. + * + * Currently Wear OS only supports the runtime for the Android Watch Face Format (see + * https://developer.android.com/training/wearables/wff for more details). + * + * @param context Calling application's [Context]. + * @param runtimePackageName The name of the package containing the watch face runtime's + * control service to bind to. + * @param resourceOnlyWatchFacePackageName The name of the package from which to load the + * resource only watch face. This is exposed to the runtime via + * [WatchFaceService.resourceOnlyWatchFacePackageName]. Note only one watch face + * definition per resource only watch face package is supported. + * @return The [WatchFaceControlClient] if there is one. + * @throws [ServiceNotBoundException] if the watch face control service can not be bound or + * a [ServiceStartFailureException] if the watch face dies during startup. + */ + @JvmStatic + @Throws(ServiceNotBoundException::class, ServiceStartFailureException::class) + public suspend fun createWatchFaceRuntimeControlClient( + context: Context, + runtimePackageName: String, + resourceOnlyWatchFacePackageName: String + ): WatchFaceControlClient = + createWatchFaceControlClientImpl( + context, + Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE).apply { + setPackage(runtimePackageName) + }, + resourceOnlyWatchFacePackageName ) @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public suspend fun createWatchFaceControlClientImpl( context: Context, - intent: Intent + intent: Intent, + resourceOnlyWatchFacePackageName: String? ): WatchFaceControlClient { val deferredService = CompletableDeferred<IWatchFaceControlService>() val traceEvent = AsyncTraceEvent("WatchFaceControlClientImpl.bindService") @@ -107,7 +147,12 @@ public interface WatchFaceControlClient : AutoCloseable { traceEvent.close() throw ServiceNotBoundException() } - return WatchFaceControlClientImpl(context, deferredService.await(), serviceConnection) + return WatchFaceControlClientImpl( + context, + deferredService.await(), + serviceConnection, + resourceOnlyWatchFacePackageName + ) } } @@ -342,7 +387,8 @@ internal class WatchFaceControlClientImpl internal constructor( private val context: Context, private val service: IWatchFaceControlService, - private val serviceConnection: ServiceConnection + private val serviceConnection: ServiceConnection, + private val resourceOnlyWatchFacePackageName: String? ) : WatchFaceControlClient { private var closed = false @@ -499,8 +545,8 @@ internal constructor( it.value.asWireComplicationData() ) }, - null, - null + /* auxiliaryComponentPackageName = */ resourceOnlyWatchFacePackageName, + /* auxiliaryComponentClassName = */ null ), object : IPendingInteractiveWatchFace.Stub() { override fun getApiVersion() = diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt index f1569cb54e7..a0dfe0024b8 100644 --- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt +++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceMetadataClient.kt @@ -93,6 +93,43 @@ public interface WatchFaceMetadataClient : AutoCloseable { ) } + /** + * Constructs a [WatchFaceMetadataClient] for fetching metadata for the specified resource + * only watch face from its runtime. A resource only watch face runtime is a special watch + * face that knows how to load watch faces from resources in another package that contains + * only resources and no executable code. + * + * @param context Calling application's [Context]. + * @param watchFaceName The [ComponentName] of the watch face to fetch meta data from. + * @param runtimePackage The package that contains the Resource only Watch Face runtime. + * @return The [WatchFaceMetadataClient] if there is one. + * @throws [ServiceNotBoundException] if the underlying watch face control service can not + * be bound or a [ServiceStartFailureException] if the watch face dies during startup. If + * the service's manifest contains an + * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data node then + * [PackageManager.NameNotFoundException] is thrown if [watchFaceName] is invalid. + */ + @Throws( + ServiceNotBoundException::class, + ServiceStartFailureException::class, + PackageManager.NameNotFoundException::class + ) + @SuppressWarnings("MissingJvmstatic") // Can't really call a suspend fun from java. + public suspend fun createForRuntime( + context: Context, + watchFaceName: ComponentName, + runtimePackage: String + ): WatchFaceMetadataClient { + return createImpl( + context, + Intent(WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE).apply { + setPackage(runtimePackage) + }, + watchFaceName, + parserProvider = null + ) + } + @Suppress("DEPRECATION") // getServiceInfo internal fun isXmlVersionCompatible( context: Context, @@ -156,10 +193,10 @@ public interface WatchFaceMetadataClient : AutoCloseable { context: Context, intent: Intent, watchFaceName: ComponentName, - parserProvider: ParserProvider + parserProvider: ParserProvider? ): WatchFaceMetadataClient { // Check if there's static metadata we can read (fast). - parserProvider.getParser(context, watchFaceName)?.let { + parserProvider?.getParser(context, watchFaceName)?.let { return XmlWatchFaceMetadataClientImpl( XmlSchemaAndComplicationSlotsDefinition.inflate( context.packageManager.getResourcesForApplication( diff --git a/wear/watchface/watchface-data/src/main/java/androidx/wear/watchface/control/data/WallpaperInteractiveWatchFaceInstanceParams.java b/wear/watchface/watchface-data/src/main/java/androidx/wear/watchface/control/data/WallpaperInteractiveWatchFaceInstanceParams.java index 5e20ee57fa0..f9ea844685b 100644 --- a/wear/watchface/watchface-data/src/main/java/androidx/wear/watchface/control/data/WallpaperInteractiveWatchFaceInstanceParams.java +++ b/wear/watchface/watchface-data/src/main/java/androidx/wear/watchface/control/data/WallpaperInteractiveWatchFaceInstanceParams.java @@ -145,6 +145,16 @@ public class WallpaperInteractiveWatchFaceInstanceParams mIdAndComplicationDataWireFormats = idAndComplicationDataWireFormats; } + @Nullable + public String getAuxiliaryComponentClassName() { + return mAuxiliaryComponentClassName; + } + + @Nullable + public String getAuxiliaryComponentPackageName() { + return mAuxiliaryComponentPackageName; + } + /** * Serializes this WallpaperInteractiveWatchFaceInstanceParams to the specified {@link Parcel}. */ diff --git a/wear/watchface/watchface-guava/api/current.txt b/wear/watchface/watchface-guava/api/current.txt index 2460e8b2b2d..115e276a5ec 100644 --- a/wear/watchface/watchface-guava/api/current.txt +++ b/wear/watchface/watchface-guava/api/current.txt @@ -43,6 +43,12 @@ package androidx.wear.watchface { method @UiThread public final void runUiThreadGlCommands(Runnable runnable); } + public abstract class ListenableWatchFaceRuntimeService extends androidx.wear.watchface.WatchFaceRuntimeService { + ctor public ListenableWatchFaceRuntimeService(); + method protected final suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); + method protected abstract com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.WatchFace> createWatchFaceFutureAsync(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName); + } + public abstract class ListenableWatchFaceService extends androidx.wear.watchface.WatchFaceService { ctor public ListenableWatchFaceService(); method protected suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); diff --git a/wear/watchface/watchface-guava/api/restricted_current.txt b/wear/watchface/watchface-guava/api/restricted_current.txt index 2460e8b2b2d..115e276a5ec 100644 --- a/wear/watchface/watchface-guava/api/restricted_current.txt +++ b/wear/watchface/watchface-guava/api/restricted_current.txt @@ -43,6 +43,12 @@ package androidx.wear.watchface { method @UiThread public final void runUiThreadGlCommands(Runnable runnable); } + public abstract class ListenableWatchFaceRuntimeService extends androidx.wear.watchface.WatchFaceRuntimeService { + ctor public ListenableWatchFaceRuntimeService(); + method protected final suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); + method protected abstract com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.WatchFace> createWatchFaceFutureAsync(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName); + } + public abstract class ListenableWatchFaceService extends androidx.wear.watchface.WatchFaceService { ctor public ListenableWatchFaceService(); method protected suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt index c831044466e..540297d5f5f 100644 --- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt +++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt @@ -137,11 +137,12 @@ public class AsyncListenableCanvasRenderer2Test : WatchFaceControlClientServiceT initFuture, sharedAssetsFuture ) + val controlClient = createWatchFaceControlClientService() val deferredClient = handlerCoroutineScope.async { @Suppress("deprecation") - watchFaceControlClientService.getOrCreateInteractiveWatchFaceClient( + controlClient.getOrCreateInteractiveWatchFaceClient( "testId", DeviceConfig(false, false, 0, 0), WatchUiState(false, 0), @@ -150,7 +151,7 @@ public class AsyncListenableCanvasRenderer2Test : WatchFaceControlClientServiceT ) } - handler.post { watchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper } + handler.post { watchFaceService.onCreateEngine() } val client = awaitWithTimeout(deferredClient) diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt index 66ce0b78326..491c44373d2 100644 --- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt +++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt @@ -109,11 +109,12 @@ public class AsyncListenableCanvasRendererTest : WatchFaceControlClientServiceTe val initFuture = SettableFuture.create<Unit>() val watchFaceService = TestAsyncCanvasRenderInitWatchFaceService(context, surfaceHolder, initFuture) + val controlClient = createWatchFaceControlClientService() val deferredClient = handlerCoroutineScope.async { @Suppress("deprecation") - watchFaceControlClientService.getOrCreateInteractiveWatchFaceClient( + controlClient.getOrCreateInteractiveWatchFaceClient( "testId", DeviceConfig(false, false, 0, 0), WatchUiState(false, 0), @@ -122,7 +123,7 @@ public class AsyncListenableCanvasRendererTest : WatchFaceControlClientServiceTe ) } - handler.post { watchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper } + handler.post { watchFaceService.onCreateEngine() } val client = awaitWithTimeout(deferredClient) diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRenderer2Test.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRenderer2Test.kt index b7edca4ceee..e64777b1817 100644 --- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRenderer2Test.kt +++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRenderer2Test.kt @@ -127,11 +127,12 @@ public class AsyncListenableGlesRenderer2Test : WatchFaceControlClientServiceTes onBackgroundThreadGlContextFuture, sharedAssetsFuture ) + val controlClient = createWatchFaceControlClientService() val deferredClient = handlerCoroutineScope.async { @Suppress("deprecation") - watchFaceControlClientService.getOrCreateInteractiveWatchFaceClient( + controlClient.getOrCreateInteractiveWatchFaceClient( "testId", DeviceConfig(false, false, 0, 0), WatchUiState(false, 0), @@ -140,7 +141,7 @@ public class AsyncListenableGlesRenderer2Test : WatchFaceControlClientServiceTes ) } - handler.post { watchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper } + handler.post { watchFaceService.onCreateEngine() } val client = awaitWithTimeout(deferredClient) try { diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRendererTest.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRendererTest.kt index 21de4366fea..0c292bc3b8e 100644 --- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRendererTest.kt +++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableGlesRendererTest.kt @@ -106,11 +106,12 @@ public class AsyncListenableGlesRendererTest : WatchFaceControlClientServiceTest onUiThreadGlSurfaceCreatedFuture, onBackgroundThreadGlContextFuture ) + val controlClient = createWatchFaceControlClientService() val deferredClient = handlerCoroutineScope.async { @Suppress("deprecation") - watchFaceControlClientService.getOrCreateInteractiveWatchFaceClient( + controlClient.getOrCreateInteractiveWatchFaceClient( "testId", DeviceConfig(false, false, 0, 0), WatchUiState(false, 0), @@ -119,7 +120,7 @@ public class AsyncListenableGlesRendererTest : WatchFaceControlClientServiceTest ) } - handler.post { watchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper } + handler.post { watchFaceService.onCreateEngine() } val client = awaitWithTimeout(deferredClient) try { diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt index c4a385b9ce1..b082a313ff4 100644 --- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt +++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt @@ -16,12 +16,18 @@ package androidx.wear.watchface +import android.content.Context import android.graphics.Canvas import android.graphics.Rect +import android.os.Build import android.view.SurfaceHolder +import androidx.annotation.RequiresApi import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest +import androidx.wear.watchface.client.DeviceConfig +import androidx.wear.watchface.client.WatchUiState import androidx.wear.watchface.style.CurrentUserStyleRepository +import androidx.wear.watchface.style.UserStyleFlavors import androidx.wear.watchface.style.UserStyleSchema import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.ListenableFuture @@ -30,6 +36,7 @@ import java.time.Instant import java.time.ZonedDateTime import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import kotlinx.coroutines.async import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith @@ -90,6 +97,56 @@ private class TestAsyncListenableWatchFaceService : ListenableWatchFaceService() ) } +private class TestAsyncListenableWatchFaceRuntimeService( + testContext: Context, + private var surfaceHolderOverride: SurfaceHolder, +) : ListenableWatchFaceRuntimeService() { + lateinit var lastResourceOnlyWatchFacePackageName: String + + init { + attachBaseContext(testContext) + } + + override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride + + override fun createWatchFaceFutureAsync( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ): ListenableFuture<WatchFace> { + lastResourceOnlyWatchFacePackageName = resourceOnlyWatchFacePackageName + + val future = SettableFuture.create<WatchFace>() + // Post a task to resolve the future. + getUiThreadHandler().post { + future.set( + WatchFace( + WatchFaceType.DIGITAL, + FakeRenderer(surfaceHolder, watchState, currentUserStyleRepository) + ) + .apply { setOverridePreviewReferenceInstant(REFERENCE_PREVIEW_TIME) } + ) + } + return future + } + + override fun createUserStyleSchema(resourceOnlyWatchFacePackageName: String) = + UserStyleSchema(emptyList()) + + override fun createComplicationSlotsManager( + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ) = ComplicationSlotsManager(emptyList(), currentUserStyleRepository) + + override fun createUserStyleFlavors( + currentUserStyleRepository: CurrentUserStyleRepository, + complicationSlotsManager: ComplicationSlotsManager, + resourceOnlyWatchFacePackageName: String + ) = UserStyleFlavors() +} + /** * Illustrates that createWatchFaceFuture can be resolved in a different task posted to the main * looper. @@ -126,3 +183,39 @@ public class AsyncListenableWatchFaceServiceTest { assertThat(watchFace.overridePreviewReferenceInstant).isEqualTo(REFERENCE_PREVIEW_TIME) } } + +@RunWith(AndroidJUnit4::class) +@RequiresApi(Build.VERSION_CODES.O_MR1) +@MediumTest +public class AsyncListenableWatchFaceRuntimeServiceTest : WatchFaceControlClientServiceTest() { + + @Test + public fun asyncTest() { + val service = TestAsyncListenableWatchFaceRuntimeService(context, surfaceHolder) + val controlClient = createWatchFaceRuntimeControlClientService("com.example.wf") + + val deferredClient = + handlerCoroutineScope.async { + @Suppress("deprecation") + controlClient.getOrCreateInteractiveWatchFaceClient( + "testId", + DeviceConfig(false, false, 0, 0), + WatchUiState(false, 0), + null, + emptyMap() + ) + } + + handler.post { service.onCreateEngine() } + + val client = awaitWithTimeout(deferredClient) + + // To avoid a race condition, we need to wait for the watchface to be fully created, which + // this does. + client.complicationSlotsState + + assertThat(service.lastResourceOnlyWatchFacePackageName).isEqualTo("com.example.wf") + + client.close() + } +}
\ No newline at end of file diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/WatchFaceControlTestService.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/WatchFaceControlTestService.kt index 9ebc13c1150..029435fcaa9 100644 --- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/WatchFaceControlTestService.kt +++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/WatchFaceControlTestService.kt @@ -91,14 +91,27 @@ public open class WatchFaceControlClientServiceTest { val glSurface = Surface(surfaceTexture) val glSurfaceHolder = Mockito.mock(SurfaceHolder::class.java) - val watchFaceControlClientService = runBlocking { - WatchFaceControlClient.createWatchFaceControlClientImpl( - context, - Intent(context, WatchFaceControlTestService::class.java).apply { - action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE - } - ) - } + fun createWatchFaceControlClientService() = + runBlocking { + WatchFaceControlClient.createWatchFaceControlClientImpl( + context, + Intent(context, WatchFaceControlTestService::class.java).apply { + action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE + }, + resourceOnlyWatchFacePackageName = null + ) + } + + fun createWatchFaceRuntimeControlClientService(resourceOnlyWatchFacePackageName: String) = + runBlocking { + WatchFaceControlClient.createWatchFaceControlClientImpl( + context, + Intent(context, WatchFaceControlTestService::class.java).apply { + action = WatchFaceControlService.ACTION_WATCHFACE_CONTROL_SERVICE + }, + resourceOnlyWatchFacePackageName + ) + } @Before fun setUp() { diff --git a/wear/watchface/watchface-guava/src/main/java/androidx/wear/watchface/ListenableWatchFaceService.kt b/wear/watchface/watchface-guava/src/main/java/androidx/wear/watchface/ListenableWatchFaceService.kt index a1e25de8382..28542f469db 100644 --- a/wear/watchface/watchface-guava/src/main/java/androidx/wear/watchface/ListenableWatchFaceService.kt +++ b/wear/watchface/watchface-guava/src/main/java/androidx/wear/watchface/ListenableWatchFaceService.kt @@ -73,3 +73,58 @@ public abstract class ListenableWatchFaceService : WatchFaceService() { future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() }) } } + +/** + * [ListenableFuture]-based compatibility wrapper around [WatchFaceRuntimeService]'s suspending + * [WatchFaceService.createWatchFace]. + */ +public abstract class ListenableWatchFaceRuntimeService : WatchFaceRuntimeService() { + /** + * Override this factory method to create your WatchFaceImpl. This method will be called by the + * library on a background thread, if possible any expensive initialization should be done + * asynchronously. The [WatchFace] and its [Renderer] should be accessed exclusively from the + * UiThread afterwards. There is a memory barrier between construction and rendering so no + * special threading primitives are required. + * + * Warning the system will likely time out waiting for watch face initialization if it takes + * longer than [MAX_CREATE_WATCHFACE_TIME_MILLIS] milliseconds. + * + * Note cancellation of the returned future is not supported. + * + * @param surfaceHolder The [SurfaceHolder] to pass to the [Renderer]'s constructor. + * @param watchState The [WatchState] for the watch face. + * @param complicationSlotsManager The [ComplicationSlotsManager] returned by + * [createComplicationSlotsManager]. + * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the + * [UserStyleSchema] returned by [createUserStyleSchema]. + * @param resourceOnlyWatchFacePackageName The android package from which the watch face + * definition should be loaded. + * @return A [ListenableFuture] for a [WatchFace] whose [Renderer] uses the provided + * [surfaceHolder]. + */ + protected abstract fun createWatchFaceFutureAsync( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ): ListenableFuture<WatchFace> + + final override suspend fun createWatchFace( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ): WatchFace = suspendCancellableCoroutine { + val future = + createWatchFaceFutureAsync( + surfaceHolder, + watchState, + complicationSlotsManager, + currentUserStyleRepository, + resourceOnlyWatchFacePackageName + ) + future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() }) + } +}
\ No newline at end of file diff --git a/wear/watchface/watchface/api/current.txt b/wear/watchface/watchface/api/current.txt index cc6e5d8a926..ba6196763db 100644 --- a/wear/watchface/watchface/api/current.txt +++ b/wear/watchface/watchface/api/current.txt @@ -394,6 +394,18 @@ package androidx.wear.watchface { @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface WatchFaceExperimental { } + public abstract class WatchFaceRuntimeService extends androidx.wear.watchface.WatchFaceService { + ctor public WatchFaceRuntimeService(); + method protected final androidx.wear.watchface.ComplicationSlotsManager createComplicationSlotsManager(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository); + method @WorkerThread protected abstract androidx.wear.watchface.ComplicationSlotsManager createComplicationSlotsManager(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName); + method protected final androidx.wear.watchface.style.UserStyleFlavors createUserStyleFlavors(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager); + method @WorkerThread protected abstract androidx.wear.watchface.style.UserStyleFlavors createUserStyleFlavors(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, String resourceOnlyWatchFacePackageName); + method protected final androidx.wear.watchface.style.UserStyleSchema createUserStyleSchema(); + method @WorkerThread protected abstract androidx.wear.watchface.style.UserStyleSchema createUserStyleSchema(String resourceOnlyWatchFacePackageName); + method @WorkerThread protected abstract suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); + method protected final suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); + } + public abstract class WatchFaceService extends android.service.wallpaper.WallpaperService { ctor public WatchFaceService(); method @WorkerThread protected androidx.wear.watchface.ComplicationSlotsManager createComplicationSlotsManager(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository); diff --git a/wear/watchface/watchface/api/restricted_current.txt b/wear/watchface/watchface/api/restricted_current.txt index cc6e5d8a926..ba6196763db 100644 --- a/wear/watchface/watchface/api/restricted_current.txt +++ b/wear/watchface/watchface/api/restricted_current.txt @@ -394,6 +394,18 @@ package androidx.wear.watchface { @SuppressCompatibility @kotlin.RequiresOptIn(message="This is an experimental API that may change or be removed without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface WatchFaceExperimental { } + public abstract class WatchFaceRuntimeService extends androidx.wear.watchface.WatchFaceService { + ctor public WatchFaceRuntimeService(); + method protected final androidx.wear.watchface.ComplicationSlotsManager createComplicationSlotsManager(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository); + method @WorkerThread protected abstract androidx.wear.watchface.ComplicationSlotsManager createComplicationSlotsManager(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName); + method protected final androidx.wear.watchface.style.UserStyleFlavors createUserStyleFlavors(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager); + method @WorkerThread protected abstract androidx.wear.watchface.style.UserStyleFlavors createUserStyleFlavors(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, String resourceOnlyWatchFacePackageName); + method protected final androidx.wear.watchface.style.UserStyleSchema createUserStyleSchema(); + method @WorkerThread protected abstract androidx.wear.watchface.style.UserStyleSchema createUserStyleSchema(String resourceOnlyWatchFacePackageName); + method @WorkerThread protected abstract suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, String resourceOnlyWatchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); + method protected final suspend Object? createWatchFace(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.WatchState watchState, androidx.wear.watchface.ComplicationSlotsManager complicationSlotsManager, androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository, kotlin.coroutines.Continuation<? super androidx.wear.watchface.WatchFace>); + } + public abstract class WatchFaceService extends android.service.wallpaper.WallpaperService { ctor public WatchFaceService(); method @WorkerThread protected androidx.wear.watchface.ComplicationSlotsManager createComplicationSlotsManager(androidx.wear.watchface.style.CurrentUserStyleRepository currentUserStyleRepository); diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt index 20f94603963..9d57c46fa01 100644 --- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt +++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt @@ -249,7 +249,8 @@ public class WatchFace( } .apply { setContext(context) } - val engine = watchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + val engine = watchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper val headlessWatchFaceImpl = engine.createHeadlessInstance(params) return engine.deferredWatchFaceImpl.await().WFEditorDelegate(headlessWatchFaceImpl) } @@ -665,11 +666,7 @@ constructor( internal var lastDrawTimeMillis: Long = 0 internal var nextDrawTimeMillis: Long = 0 - internal val componentName = - ComponentName( - watchFaceHostApi.getContext().packageName, - watchFaceHostApi.getContext().javaClass.name - ) + internal val componentName = watchFaceHostApi.getComponentName() internal fun getWatchFaceStyle() = WatchFaceStyle( diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt index 3f160d078b1..4d6b2dce56c 100644 --- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt +++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt @@ -149,4 +149,7 @@ public interface WatchFaceHostApi { /** Requests the system to capture an updated preview image. */ public fun sendPreviewImageNeedsUpdateRequest() {} + + /** Returns ComponentName of the watch face. */ + public fun getComponentName(): ComponentName } diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt index d9c04a97b2d..9f2b9f2c003 100644 --- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt +++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt @@ -299,6 +299,12 @@ public abstract class WatchFaceService : WallpaperService() { public companion object { private const val TAG = "WatchFaceService" + /** + * [ComponentName] is not allowed to have null for the class name (and it can be null for + * resource only watch faces), hence this placeholder. + */ + private const val RESOURCE_ONLY_CLASS_NAME_PLACEHOLDER = "null" + /** Whether to log every frame. */ private const val LOG_VERBOSE = false @@ -446,7 +452,7 @@ public abstract class WatchFaceService : WallpaperService() { } } - private val xmlSchemaAndComplicationSlotsDefinition: + internal val xmlSchemaAndComplicationSlotsDefinition: XmlSchemaAndComplicationSlotsDefinition by lazy { val resourceId = getXmlWatchFaceResourceId() if (resourceId == 0) { @@ -477,6 +483,10 @@ public abstract class WatchFaceService : WallpaperService() { xmlSchemaAndComplicationSlotsDefinition.schema?.userStyleSettings ?: emptyList() ) + internal open fun createUserStyleSchemaInternal( + resourceOnlyWatchFacePackageName: String? + ): UserStyleSchema = createUserStyleSchema() + /** * If the WatchFaceService's manifest doesn't define a * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override @@ -501,6 +511,13 @@ public abstract class WatchFaceService : WallpaperService() { getComplicationSlotInflationFactory(currentUserStyleRepository) ) + internal open fun createComplicationSlotsManagerInternal( + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String? + ): ComplicationSlotsManager = createComplicationSlotsManager( + currentUserStyleRepository + ) + /** * Used when inflating [ComplicationSlot]s from XML to provide a * [ComplicationSlotInflationFactory] which provides the [CanvasComplicationFactory] and where @@ -566,6 +583,15 @@ public abstract class WatchFaceService : WallpaperService() { complicationSlotsManager: ComplicationSlotsManager ): UserStyleFlavors = xmlSchemaAndComplicationSlotsDefinition.flavors ?: UserStyleFlavors() + internal open fun createUserStyleFlavorsInternal( + currentUserStyleRepository: CurrentUserStyleRepository, + complicationSlotsManager: ComplicationSlotsManager, + resourceOnlyWatchFacePackageName: String? + ): UserStyleFlavors = createUserStyleFlavors( + currentUserStyleRepository, + complicationSlotsManager + ) + /** * Override this factory method to create your WatchFaceImpl. This method will be called by the * library on a background thread, if possible any expensive initialization should be done @@ -592,13 +618,26 @@ public abstract class WatchFaceService : WallpaperService() { currentUserStyleRepository: CurrentUserStyleRepository ): WatchFace + internal open suspend fun createWatchFaceInternal( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String? + ): WatchFace = createWatchFace( + surfaceHolder, + watchState, + complicationSlotsManager, + currentUserStyleRepository, + ) + /** Creates an interactive engine for WallpaperService. */ final override fun onCreateEngine(): Engine = - EngineWrapper(getUiThreadHandler(), getBackgroundThreadHandler(), false) + EngineWrapper(getUiThreadHandler(), getBackgroundThreadHandler(), null) /** Creates a headless engine. */ - internal fun createHeadlessEngine(): Engine = - EngineWrapper(getUiThreadHandler(), getBackgroundThreadHandler(), true) + internal fun createHeadlessEngine(watchFaceName: ComponentName): Engine = + EngineWrapper(getUiThreadHandler(), getBackgroundThreadHandler(), watchFaceName) /** Returns the ui thread [Handler]. */ public fun getUiThreadHandler(): Handler = getUiThreadHandlerImpl() @@ -1149,7 +1188,7 @@ public abstract class WatchFaceService : WallpaperService() { public inner class EngineWrapper( private val uiThreadHandler: Handler, private val backgroundThreadHandler: Handler, - headless: Boolean + headlessComponentName: ComponentName? ) : WallpaperService.Engine(), WatchFaceHostApi, @@ -1197,7 +1236,7 @@ public abstract class WatchFaceService : WallpaperService() { // 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 + isHeadless = (headlessComponentName != null) isLocked.value = (getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager).isDeviceLocked } @@ -1277,6 +1316,7 @@ public abstract class WatchFaceService : WallpaperService() { internal var immutableSystemStateDone = false internal var immutableChinHeightDone = false internal var systemHasSentWatchUiState = false + internal var resourceOnlyWatchFacePackageName: String? = headlessComponentName?.packageName private var asyncWatchFaceConstructionPending = false @@ -1885,23 +1925,34 @@ public abstract class WatchFaceService : WallpaperService() { /** This will be called from a binder thread. */ @WorkerThread internal fun getDefaultProviderPolicies(): Array<IdTypeAndDefaultProviderPolicyWireFormat> { - return createComplicationSlotsManager( - CurrentUserStyleRepository(createUserStyleSchema()) + return createComplicationSlotsManagerInternal( + CurrentUserStyleRepository( + createUserStyleSchemaInternal(resourceOnlyWatchFacePackageName) + ), + resourceOnlyWatchFacePackageName ) .getDefaultProviderPolicies() } /** This will be called from a binder thread. */ @WorkerThread - internal fun getUserStyleSchemaWireFormat() = createUserStyleSchema().toWireFormat() + internal fun getUserStyleSchemaWireFormat() = + createUserStyleSchemaInternal(resourceOnlyWatchFacePackageName).toWireFormat() /** This will be called from a binder thread. */ @WorkerThread internal fun getUserStyleFlavorsWireFormat(): UserStyleFlavorsWireFormat { - val currentUserStyleRepository = CurrentUserStyleRepository(createUserStyleSchema()) - return createUserStyleFlavors( + val currentUserStyleRepository = + CurrentUserStyleRepository( + createUserStyleSchemaInternal(resourceOnlyWatchFacePackageName) + ) + return createUserStyleFlavorsInternal( currentUserStyleRepository, - createComplicationSlotsManager(currentUserStyleRepository) + createComplicationSlotsManagerInternal( + currentUserStyleRepository, + resourceOnlyWatchFacePackageName + ), + resourceOnlyWatchFacePackageName ) .toWireFormat() } @@ -1910,7 +1961,11 @@ public abstract class WatchFaceService : WallpaperService() { @OptIn(ComplicationExperimental::class) @WorkerThread internal fun getComplicationSlotMetadataWireFormats() = - createComplicationSlotsManager(CurrentUserStyleRepository(createUserStyleSchema())) + createComplicationSlotsManagerInternal( + CurrentUserStyleRepository( + createUserStyleSchemaInternal(resourceOnlyWatchFacePackageName)), + resourceOnlyWatchFacePackageName + ) .complicationSlots .map { val systemDataSourceFallbackDefaultType = @@ -2043,6 +2098,8 @@ public abstract class WatchFaceService : WallpaperService() { setWatchUiState(params.watchUiState, fromSysUi = false) initialUserStyle = params.userStyle + resourceOnlyWatchFacePackageName = params.auxiliaryComponentPackageName + mutableWatchState.watchFaceInstanceId.value = sanitizeWatchFaceId(params.instanceId) val watchState = mutableWatchState.asWatchState() @@ -2108,13 +2165,18 @@ public abstract class WatchFaceService : WallpaperService() { val timeBefore = System.currentTimeMillis() val currentUserStyleRepository = TraceEvent("WatchFaceService.createUserStyleSchema").use { - CurrentUserStyleRepository(createUserStyleSchema()) + CurrentUserStyleRepository( + createUserStyleSchemaInternal(resourceOnlyWatchFacePackageName) + ) } initStyle(currentUserStyleRepository) val complicationSlotsManager = TraceEvent("WatchFaceService.createComplicationsManager").use { - createComplicationSlotsManager(currentUserStyleRepository) + createComplicationSlotsManagerInternal( + currentUserStyleRepository, + resourceOnlyWatchFacePackageName + ) } complicationSlotsManager.watchFaceHostApi = this@EngineWrapper complicationSlotsManager.watchState = watchState @@ -2130,7 +2192,11 @@ public abstract class WatchFaceService : WallpaperService() { val userStyleFlavors = TraceEvent("WatchFaceService.createUserStyleFlavors").use { - createUserStyleFlavors(currentUserStyleRepository, complicationSlotsManager) + createUserStyleFlavorsInternal( + currentUserStyleRepository, + complicationSlotsManager, + resourceOnlyWatchFacePackageName + ) } deferredEarlyInitDetails.complete( @@ -2164,13 +2230,13 @@ public abstract class WatchFaceService : WallpaperService() { TraceEvent("WatchFaceService.createWatchFace").use { // Note by awaiting deferredSurfaceHolder we ensure onSurfaceChanged has // been called and we're passing the correct updated surface holder. - // This is - // important for GL rendering. - createWatchFace( + // This is important for GL rendering. + createWatchFaceInternal( surfaceHolder, watchState, complicationSlotsManager, - currentUserStyleRepository + currentUserStyleRepository, + resourceOnlyWatchFacePackageName ) } this@EngineWrapper.deferredWatchFace.complete(watchFace) @@ -2497,6 +2563,14 @@ public abstract class WatchFaceService : WallpaperService() { } } + override fun getComponentName(): ComponentName { + resourceOnlyWatchFacePackageName?.let { + return ComponentName(it, RESOURCE_ONLY_CLASS_NAME_PLACEHOLDER) + } + + return ComponentName(getContext().packageName, getContext().javaClass.name) + } + override fun onWatchFaceColorsChanged(watchFaceColors: WatchFaceColors?) { synchronized(lock) { lastWatchFaceColors = watchFaceColors @@ -2777,6 +2851,7 @@ public abstract class WatchFaceService : WallpaperService() { writer.println("surfaceDestroyed=$surfaceDestroyed") writer.println("lastComplications=${complicationsFlow.value.joinToString()}") writer.println("pendingUpdateTime=${pendingUpdateTime.isPending()}") + writer.println("Resource only package name $resourceOnlyWatchFacePackageName") synchronized(lock) { forEachListener("dump") { writer.println("listener = ${it.asBinder()}") } @@ -2824,6 +2899,183 @@ public abstract class WatchFaceService : WallpaperService() { } /** + * WatchFaceRuntimeService is a special kind of [WatchFaceService], which loads the watch face + * definition from another resource only watch face package (see the + * `resourceOnlyWatchFacePackageName` parameter passed to [createUserStyleSchema], + * [createComplicationSlotsManager], [createUserStyleFlavors] and + * [createWatchFace]). + * + * Note because a WatchFaceRuntimeService loads it's resources from another package, it will need + * the following permission: + * + * ``` + * <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" + * tools:ignore="QueryAllPackagesPermission" /> + * ``` + * + * Currently Wear OS only supports the runtime for the Android Watch Face Format (see + * https://developer.android.com/training/wearables/wff for more details). + * + * Note only one watch face definition per resource only watch face package is supported. + */ +abstract class WatchFaceRuntimeService : WatchFaceService() { + /** + * If the WatchFaceService's manifest doesn't define a + * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override + * this factory method to create a non-empty [UserStyleSchema]. A [CurrentUserStyleRepository] + * constructed with this schema will be passed to [createComplicationSlotsManager], + * [createUserStyleFlavors] and [createWatchFace]. This is called on a background thread. + * + * @param resourceOnlyWatchFacePackageName The android package from which the watch face + * definition should be loaded. + * @return The [UserStyleSchema] to create a [CurrentUserStyleRepository] with, which is passed + * to [createComplicationSlotsManager] and [createWatchFace]. + */ + @WorkerThread + protected abstract fun createUserStyleSchema( + resourceOnlyWatchFacePackageName: String + ): UserStyleSchema + + override fun createUserStyleSchemaInternal( + resourceOnlyWatchFacePackageName: String? + ): UserStyleSchema = createUserStyleSchema(resourceOnlyWatchFacePackageName!!) + + @Suppress("DocumentExceptions") // NB this method isn't expected to be called from user code. + final override fun createUserStyleSchema(): UserStyleSchema { + throw UnsupportedOperationException("resourceOnlyWatchFacePackageName must be specified") + } + + /** + * If the WatchFaceService's manifest doesn't define a + * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override + * this factory method to create a non-empty [ComplicationSlotsManager]. This manager will be + * passed to [createUserStyleFlavors] and [createWatchFace]. This will be called from a + * background thread but the ComplicationSlotsManager should be accessed exclusively from the + * UiThread afterwards. + * + * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the + * [UserStyleSchema] returned by [createUserStyleSchema]. + * @param resourceOnlyWatchFacePackageName The android package from which the watch face + * definition should be loaded. + * @return The [ComplicationSlotsManager] to pass into [createWatchFace]. + */ + @WorkerThread + protected abstract fun createComplicationSlotsManager( + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ): ComplicationSlotsManager + + internal override fun createComplicationSlotsManagerInternal( + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String? + ): ComplicationSlotsManager = createComplicationSlotsManager( + currentUserStyleRepository, + resourceOnlyWatchFacePackageName!! + ) + + @Suppress("DocumentExceptions") // NB this method isn't expected to be called from user code. + final override fun createComplicationSlotsManager( + currentUserStyleRepository: CurrentUserStyleRepository, + ): ComplicationSlotsManager { + throw UnsupportedOperationException("resourceOnlyWatchFacePackageName must be specified") + } + + /** + * If the WatchFaceService's manifest doesn't define a + * androidx.wear.watchface.XmlSchemaAndComplicationSlotsDefinition meta data tag then override + * this factory method to create non-empty [UserStyleFlavors]. This is called on a background + * thread. The system reads the flavors once and changes may be ignored until the APK is + * updated. Metadata tag "androidx.wear.watchface.FLAVORS_SUPPORTED" should be added to let the + * system know the service supports flavors. + * + * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the + * [UserStyleSchema] returned by [createUserStyleSchema]. + * @param complicationSlotsManager The [ComplicationSlotsManager] returned by + * [createComplicationSlotsManager] + * @param resourceOnlyWatchFacePackageName The android package from which the watch face + * definition should be loaded. + * @return The [UserStyleFlavors], which is exposed to the system. + */ + @WorkerThread + protected abstract fun createUserStyleFlavors( + currentUserStyleRepository: CurrentUserStyleRepository, + complicationSlotsManager: ComplicationSlotsManager, + resourceOnlyWatchFacePackageName: String + ): UserStyleFlavors + + internal override fun createUserStyleFlavorsInternal( + currentUserStyleRepository: CurrentUserStyleRepository, + complicationSlotsManager: ComplicationSlotsManager, + resourceOnlyWatchFacePackageName: String? + ): UserStyleFlavors = createUserStyleFlavors( + currentUserStyleRepository, + complicationSlotsManager, + resourceOnlyWatchFacePackageName!! + ) + + @Suppress("DocumentExceptions") // NB this method isn't expected to be called from user code. + final override fun createUserStyleFlavors( + currentUserStyleRepository: CurrentUserStyleRepository, + complicationSlotsManager: ComplicationSlotsManager + ): UserStyleFlavors { + throw UnsupportedOperationException("resourceOnlyWatchFacePackageName must be specified") + } + + /** + * Override this factory method to create your WatchFaceImpl. This method will be called by the + * library on a background thread, if possible any expensive initialization should be done + * asynchronously. The [WatchFace] and its [Renderer] should be accessed exclusively from the + * UiThread afterwards. There is a memory barrier between construction and rendering so no + * special threading primitives are required. + * + * Warning the system will likely time out waiting for watch face initialization if it takes + * longer than [WatchFaceService.MAX_CREATE_WATCHFACE_TIME_MILLIS] milliseconds. + * + * @param surfaceHolder The [SurfaceHolder] to pass to the [Renderer]'s constructor. + * @param watchState The [WatchState] for the watch face. + * @param complicationSlotsManager The [ComplicationSlotsManager] returned by + * [createComplicationSlotsManager]. + * @param currentUserStyleRepository The [CurrentUserStyleRepository] constructed using the + * [UserStyleSchema] returned by [createUserStyleSchema]. + * @param resourceOnlyWatchFacePackageName The android package from which the watch face + * definition should be loaded. + * @return A [WatchFace] whose [Renderer] uses the provided [surfaceHolder]. + */ + @WorkerThread + protected abstract suspend fun createWatchFace( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String + ): WatchFace + + internal override suspend fun createWatchFaceInternal( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository, + resourceOnlyWatchFacePackageName: String? + ): WatchFace = createWatchFace( + surfaceHolder, + watchState, + complicationSlotsManager, + currentUserStyleRepository, + resourceOnlyWatchFacePackageName!! + ) + + @Suppress("DocumentExceptions") // NB this method isn't expected to be called from user code. + final override suspend fun createWatchFace( + surfaceHolder: SurfaceHolder, + watchState: WatchState, + complicationSlotsManager: ComplicationSlotsManager, + currentUserStyleRepository: CurrentUserStyleRepository + ): WatchFace { + throw UnsupportedOperationException("resourceOnlyWatchFacePackageName must be specified") + } +} + +/** * Runs the supplied task on the handler thread. If we're not on the handler thread a task is * posted. * diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt index 07232b010dd..d4ef01346f8 100644 --- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt +++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt @@ -17,6 +17,7 @@ package androidx.wear.watchface.control import android.annotation.SuppressLint +import android.util.Log import androidx.annotation.UiThread import androidx.annotation.VisibleForTesting import androidx.wear.watchface.IndentingPrintWriter @@ -49,6 +50,8 @@ internal class InteractiveInstanceManager { ) companion object { + private const val TAG = "InteractiveInstanceManager" + private val instances = HashMap<String, RefCountedInteractiveWatchFaceInstance>() private val pendingWallpaperInteractiveWatchFaceInstanceLock = Any() private var pendingWallpaperInteractiveWatchFaceInstance: @@ -204,6 +207,18 @@ internal class InteractiveInstanceManager { // system thinks we should have. Note runBlocking is safe here because we never await. val engine = impl.engine!! engine.setUserStyle(value.params.userStyle) + + if (engine.resourceOnlyWatchFacePackageName != + value.params.auxiliaryComponentPackageName + ) { + val message = + "Existing instance has the resourceOnlyWatchFacePackageName of " + + "${engine.resourceOnlyWatchFacePackageName}, which is different from the " + + "argument watchFaceId of ${value.params.auxiliaryComponentPackageName}." + Log.e(TAG, message) + throw IllegalStateException(message) + } + return impl } diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/WatchFaceControlService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/WatchFaceControlService.kt index 3ca3862d436..98582e5b4e8 100644 --- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/WatchFaceControlService.kt +++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/WatchFaceControlService.kt @@ -193,7 +193,8 @@ public open class IWatchFaceInstanceServiceStub( } watchFaceService.onCreate() val engine = - watchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + watchFaceService.createHeadlessEngine(watchFaceName) + as WatchFaceService.EngineWrapper ServiceAndEngine(watchFaceService, engine) } else { null diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt index b953af0736a..d55140dac7b 100644 --- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt +++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt @@ -3319,12 +3319,14 @@ public class WatchFaceServiceTest { choreographer ) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") engineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper engineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, @@ -3886,7 +3888,9 @@ public class WatchFaceServiceTest { choreographer ) - testWatchFaceService.createHeadlessEngine() + testWatchFaceService.createHeadlessEngine( + ComponentName("test.watchface.app", "test.watchface.class") + ) runPostedTasksFor(0) @@ -4550,12 +4554,14 @@ public class WatchFaceServiceTest { engineWrapper.onCreate(surfaceHolder) engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") val headlessEngineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper headlessEngineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, @@ -4691,12 +4697,14 @@ public class WatchFaceServiceTest { engineWrapper.onCreate(surfaceHolder) engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") val headlessEngineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper headlessEngineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, @@ -4823,12 +4831,14 @@ public class WatchFaceServiceTest { engineWrapper.onCreate(surfaceHolder) engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") val headlessEngineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper headlessEngineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, @@ -5119,12 +5129,14 @@ public class WatchFaceServiceTest { choreographer ) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") engineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper engineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, @@ -5187,12 +5199,14 @@ public class WatchFaceServiceTest { choreographer ) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") engineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper engineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, @@ -6381,12 +6395,14 @@ public class WatchFaceServiceTest { forceIsVisible = true ) + val componentName = ComponentName("test.watchface.app", "test.watchface.class") engineWrapper = - testWatchFaceService.createHeadlessEngine() as WatchFaceService.EngineWrapper + testWatchFaceService.createHeadlessEngine(componentName) + as WatchFaceService.EngineWrapper engineWrapper.createHeadlessInstance( HeadlessWatchFaceInstanceParams( - ComponentName("test.watchface.app", "test.watchface.class"), + componentName, DeviceConfig(false, false, 100, 200), 100, 100, |