Merge "Document ambiguous camera state errors" into androidx-main
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
index 79ad758..2b2530f 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ExistingActivityLifecycleTest.kt
@@ -33,7 +33,6 @@
import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice
import androidx.testutils.withActivity
-import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.AfterClass
@@ -158,11 +157,6 @@
Espresso.onView(ViewMatchers.withId(R.id.direction_toggle))
.perform(ViewActions.click())
- // TODO(b/159257773): Currently have no reliable way of checking that camera has
- // switched. Delay to ensure previous camera has stopped streaming and the
- // idling resource actually is becoming idle due to frames from front camera.
- delay(500)
-
// Check front camera is now idle
withActivity { resetViewIdlingResource() }
waitForViewfinderIdle()
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index 0b6db82..6c2f515 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -81,6 +81,7 @@
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
import androidx.core.math.MathUtils;
+import androidx.core.util.Consumer;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.test.espresso.IdlingResource;
@@ -214,6 +215,20 @@
private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener =
(compoundButton, isChecked) -> tryBindUseCases();
+ private Consumer<Long> mFrameUpdateListener = timestamp -> {
+ if (mPreviewFrameCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY) {
+ try {
+ if (!this.mViewIdlingResource.isIdleNow()) {
+ Log.d(TAG, FRAMES_UNTIL_VIEW_IS_READY + " or more counted on preview."
+ + " Make IdlingResource idle.");
+ this.mViewIdlingResource.decrement();
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Unexpected decrement. Continuing");
+ }
+ }
+ };
+
// Espresso testing variables
private final CountingIdlingResource mViewIdlingResource = new CountingIdlingResource("view");
private static final int FRAMES_UNTIL_VIEW_IS_READY = 5;
@@ -605,21 +620,6 @@
Objects.requireNonNull((DisplayManager) getSystemService(Context.DISPLAY_SERVICE));
dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
- previewRenderer.setFrameUpdateListener(ContextCompat.getMainExecutor(this), timestamp -> {
- // Wait until surface texture receives enough updates. This is for testing.
- if (mPreviewFrameCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY) {
- try {
- if (!mViewIdlingResource.isIdleNow()) {
- Log.d(TAG, FRAMES_UNTIL_VIEW_IS_READY + " or more counted on preview."
- + " Make IdlingResource idle.");
- mViewIdlingResource.decrement();
- }
- } catch (IllegalStateException e) {
- Log.e(TAG, "Unexpected decrement. Continuing");
- }
- }
- });
-
StrictMode.VmPolicy vmPolicy =
new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build();
StrictMode.setVmPolicy(vmPolicy);
@@ -691,6 +691,9 @@
// next thing being ready.
return;
}
+ // Clear listening frame update before unbind all.
+ mPreviewRenderer.clearFrameUpdateListener();
+
mCameraProvider.unbindAll();
try {
List<UseCase> useCases = buildUseCases();
@@ -725,7 +728,14 @@
.setTargetName("Preview")
.build();
resetViewIdlingResource();
- mPreviewRenderer.attachInputPreview(preview);
+ // Use the listener of the future to make sure the Preview setup the new surface.
+ mPreviewRenderer.attachInputPreview(preview).addListener(() -> {
+ Log.d(TAG, "OpenGLRenderer get the new surface for the Preview");
+ mPreviewRenderer.setFrameUpdateListener(
+ ContextCompat.getMainExecutor(this), mFrameUpdateListener
+ );
+ }, ContextCompat.getMainExecutor(this));
+
useCases.add(preview);
}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java
index 6e5feda..fe77d0b 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLActivity.java
@@ -236,8 +236,9 @@
// with ConstraintLayout).
Preview preview = new Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_4_3).build();
- mRenderer.attachInputPreview(preview);
-
+ mRenderer.attachInputPreview(preview).addListener(() -> {
+ Log.d(TAG, "OpenGLRenderer get the new surface for the Preview");
+ }, ContextCompat.getMainExecutor(this));
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
mCameraProvider.bindToLifecycle(this, cameraSelector, preview);
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
index 50646a9..6793c95 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/OpenGLRenderer.java
@@ -101,47 +101,63 @@
mExecutor.execute(() -> mNativeContext = initContext());
}
+ /**
+ * Attach the Preview to the renderer.
+ *
+ * @param preview Preview use-case used in the renderer.
+ * @return A {@link ListenableFuture} that signals the new surface is ready to be used in the
+ * renderer for the input Preview use-case.
+ */
@OptIn(markerClass = ExperimentalUseCaseGroup.class)
@MainThread
- void attachInputPreview(@NonNull Preview preview) {
- preview.setSurfaceProvider(
- mExecutor,
- surfaceRequest -> {
- if (mIsShutdown) {
- surfaceRequest.willNotProvideSurface();
- return;
- }
- SurfaceTexture surfaceTexture = resetPreviewTexture(
- surfaceRequest.getResolution());
- Surface inputSurface = new Surface(surfaceTexture);
- mNumOutstandingSurfaces++;
- surfaceRequest.setTransformationInfoListener(
- mExecutor,
- transformationInfo -> {
- mMvpDirty = true;
- // TODO(b/159127941): add the rotation to MVP transformation.
- if (!isCropRectFullTexture(transformationInfo.getCropRect())) {
- // Crop rect is pre-calculated. Use it directly.
- mPreviewCropRect = new RectF(
- transformationInfo.getCropRect());
- } else {
- // Crop rect needs to be calculated before drawing.
- mPreviewCropRect = null;
- }
- });
- surfaceRequest.provideSurface(
- inputSurface,
- mExecutor,
- result -> {
- inputSurface.release();
- surfaceTexture.release();
- if (surfaceTexture == mPreviewTexture) {
- mPreviewTexture = null;
- }
- mNumOutstandingSurfaces--;
- doShutdownExecutorIfNeeded();
- });
- });
+ @SuppressWarnings("ObjectToString")
+ @NonNull
+ ListenableFuture<Void> attachInputPreview(@NonNull Preview preview) {
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ preview.setSurfaceProvider(
+ mExecutor,
+ surfaceRequest -> {
+ if (mIsShutdown) {
+ surfaceRequest.willNotProvideSurface();
+ return;
+ }
+ SurfaceTexture surfaceTexture = resetPreviewTexture(
+ surfaceRequest.getResolution());
+ Surface inputSurface = new Surface(surfaceTexture);
+ mNumOutstandingSurfaces++;
+
+ surfaceRequest.setTransformationInfoListener(
+ mExecutor,
+ transformationInfo -> {
+ mMvpDirty = true;
+ if (!isCropRectFullTexture(transformationInfo.getCropRect())) {
+ // Crop rect is pre-calculated. Use it directly.
+ mPreviewCropRect = new RectF(
+ transformationInfo.getCropRect());
+ } else {
+ // Crop rect needs to be calculated before drawing.
+ mPreviewCropRect = null;
+ }
+ });
+
+ surfaceRequest.provideSurface(
+ inputSurface,
+ mExecutor,
+ result -> {
+ inputSurface.release();
+ surfaceTexture.release();
+ if (surfaceTexture == mPreviewTexture) {
+ mPreviewTexture = null;
+ }
+ mNumOutstandingSurfaces--;
+ doShutdownExecutorIfNeeded();
+ });
+ // Make sure the renderer use the new surface for the input Preview.
+ completer.set(null);
+
+ });
+ return "attachInputPreview [" + this + "]";
+ });
}
void attachOutputSurface(
@@ -187,6 +203,14 @@
}
}
+ void clearFrameUpdateListener() {
+ try {
+ mExecutor.execute(() -> mFrameUpdateListener = null);
+ } catch (RejectedExecutionException e) {
+ // Renderer is shutting down. Ignore.
+ }
+ }
+
void invalidateSurface(int surfaceRotationDegrees) {
try {
mExecutor.execute(
diff --git a/car/app/app-samples/navigation/automotive/github_build.gradle b/car/app/app-samples/navigation/automotive/github_build.gradle
index c4a6f6a..7f7f843 100644
--- a/car/app/app-samples/navigation/automotive/github_build.gradle
+++ b/car/app/app-samples/navigation/automotive/github_build.gradle
@@ -21,7 +21,7 @@
defaultConfig {
applicationId "androidx.car.app.sample.navigation"
- minSdkVersion 23
+ minSdkVersion 29
targetSdkVersion 29
versionCode 1
versionName "1.0"
diff --git a/car/app/app-samples/navigation/common/github_build.gradle b/car/app/app-samples/navigation/common/github_build.gradle
index 24cd50d..97e6f97 100644
--- a/car/app/app-samples/navigation/common/github_build.gradle
+++ b/car/app/app-samples/navigation/common/github_build.gradle
@@ -25,6 +25,10 @@
versionCode 1
versionName "1.0"
}
+ compileOptions {
+ targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ }
}
dependencies {
@@ -32,4 +36,5 @@
implementation "androidx.core:core:1.5.0-alpha01"
implementation "androidx.car.app:app:1.0.0-rc01"
+ implementation "androidx.annotation:annotation-experimental:1.0.0"
}
diff --git a/car/app/app-samples/places/common/github_build.gradle b/car/app/app-samples/places/common/github_build.gradle
index 093e8ef..8fc79fb 100644
--- a/car/app/app-samples/places/common/github_build.gradle
+++ b/car/app/app-samples/places/common/github_build.gradle
@@ -24,6 +24,10 @@
versionCode 1
versionName "1.0"
}
+ compileOptions {
+ targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ }
}
dependencies {
diff --git a/car/app/app-samples/showcase/common/github_build.gradle b/car/app/app-samples/showcase/common/github_build.gradle
index 6ba37a2..e9be321 100644
--- a/car/app/app-samples/showcase/common/github_build.gradle
+++ b/car/app/app-samples/showcase/common/github_build.gradle
@@ -17,19 +17,24 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 30
+ compileSdkVersion 29
defaultConfig {
- minSdkVersion 29
- targetSdkVersion 30
+ minSdkVersion 23
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
}
+
+ compileOptions {
+ targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ }
}
dependencies {
implementation "androidx.core:core:1.6.0-alpha01"
implementation "androidx.activity:activity:1.2.2"
implementation "androidx.car.app:app:1.0.0-rc01"
- implementation(project(":annotation:annotation-experimental"))
+ implementation "androidx.annotation:annotation-experimental:1.0.0"
}
diff --git a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
index df5857e..8b58ce6 100644
--- a/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/mobile/src/main/AndroidManifest.xml
@@ -58,20 +58,6 @@
android:value="androidx.car.app.samples.showcase.Showcase" />
</service>
- <activity-alias
- android:enabled="true"
- android:exported="true"
- android:label="Showcase"
- android:name=".Showcase"
- android:targetActivity="androidx.car.app.activity.CarAppActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- <meta-data android:name="androidx.car.app.CAR_APP_SERVICE"
- android:value="androidx.car.app.samples.showcase.ShowcaseService" />
- </activity-alias>
-
<service
android:name=".common.navigation.NavigationNotificationService"
android:exported="true">
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
index 2f4a022..dfed0118 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
@@ -115,14 +115,6 @@
// with 0 parameters, but one that has a receiver scope (SomeScope.() -> Unit).
if (functionType != argumentType) return
- // Unfortunately if the types come from a separate module, we don't have access to
- // the type information in the function / argument, so instead we just get an error
- // type. If both compare equally, and they are reporting an error type, we cannot do
- // anything about this so just skip warning. This will only happen if there _are_
- // types, i.e a scoped / parameterized function type, so it's rare enough that it
- // shouldn't matter that much in practice.
- if (functionType.reference.canonicalText.contains(NonExistentClass)) return
-
val expectedComposable = node.isComposable
// Hack to get the psi of the lambda declaration / source. The !!s here probably
@@ -148,9 +140,7 @@
}
companion object {
- private const val NonExistentClass = "error.NonExistentClass"
-
- private const val explanation =
+ private const val Explanation =
"Creating this extra lambda instead of just passing the already captured lambda means" +
" that during code generation the Compose compiler will insert code around " +
"this lambda to track invalidations. This adds some extra runtime cost so you" +
@@ -159,7 +149,7 @@
val ISSUE = Issue.create(
"UnnecessaryLambdaCreation",
"Creating an unnecessary lambda to emit a captured lambda",
- explanation,
+ Explanation,
Category.PERFORMANCE, 5, Severity.ERROR,
Implementation(
UnnecessaryLambdaCreationDetector::class.java,
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
index 27efac9..d76a86f 100644
--- a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
@@ -30,11 +30,16 @@
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.UastLintUtils
import com.intellij.psi.PsiParameter
+import com.intellij.psi.PsiVariable
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.tryResolve
import org.jetbrains.uast.util.isConstructorCall
import java.util.EnumSet
@@ -56,10 +61,10 @@
if (node.isConstructorCall()) {
if (method.containingClass?.name != Colors.shortName) return
} else {
- // Functions with inline class parameters have their names mangled, so we use
- // startsWith instead of comparing the full name.
- if (!method.name.startsWith(LightColors.shortName) &&
- !method.name.startsWith(DarkColors.shortName)
+ if (
+ node.methodName != null &&
+ node.methodName != LightColors.shortName &&
+ node.methodName != DarkColors.shortName
) return
}
@@ -179,21 +184,43 @@
* file - so we can't resolve what it is.
*/
val sourceText: String? by lazy {
- val argumentText = argument?.sourcePsi?.text
- when {
+ val sourceExpression: UElement? = when {
// An argument was provided
- argumentText != null -> argumentText
+ argument != null -> argument
// A default value exists (so !! is safe), and we are browsing Kotlin source
// Note: this should be is KtLightParameter, but this was changed from an interface
// to a class, so we get an IncompatibleClassChangeError.
// TODO: change to KtParameter when we upgrade the min lint version we compile against
// b/182832722
parameter is KtLightElement<*, *> -> {
- (parameter.kotlinOrigin!! as KtParameter).defaultValue!!.text
+ (parameter.kotlinOrigin!! as KtParameter).defaultValue.toUElement()
}
// A default value exists, but it is in a class file so we can't access it anymore
else -> null
}
+
+ sourceExpression?.resolveToDeclarationText()
+ }
+
+ /**
+ * Returns a string that matches the original declaration for this UElement. If this is a
+ * literal or a reference to something out of scope (such as parameter), this will just be
+ * that text. If this is a reference to a variable, this will try and find the text of the
+ * variable, the last time it was assigned.
+ */
+ private fun UElement.resolveToDeclarationText(): String? {
+ // Get the source psi and go back to a UElement since if the declaration is a property, it
+ // will actually be represented as a method (since in Kotlin properties are just getter
+ // methods by default). Going to the source then back again gives us the actual UField.
+ // This might be fixed in later versions of UAST, but not on the current version we run
+ // tests against
+ val resolved = tryResolve()?.toUElement()?.sourcePsi.toUElement()
+ return if (resolved is PsiVariable) {
+ val declaration = UastLintUtils.findLastAssignment(resolved, this)
+ declaration?.resolveToDeclarationText() ?: sourcePsi?.text
+ } else {
+ sourcePsi?.text
+ }
}
}
diff --git a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt
index 27e545d..6e5dada 100644
--- a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt
+++ b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt
@@ -334,6 +334,54 @@
}
@Test
+ fun trackVariableAssignment() {
+ lint().files(
+ kotlin(
+ """
+ package androidx.compose.material.foo
+
+ import androidx.compose.material.*
+ import androidx.compose.ui.graphics.*
+
+ val testColor1 = Color.Black
+
+ fun test() {
+ val colors = lightColors(
+ primary = Color.Green,
+ background = Color.Green,
+ onPrimary = testColor1,
+ onBackground = Color.Black,
+ )
+
+ val testColor2 = Color.Black
+
+ val colors2 = lightColors(
+ primary = Color.Green,
+ background = Color.Green,
+ onPrimary = testColor2,
+ onBackground = Color.Black,
+ )
+
+ var testColor3 = Color.Green
+ testColor3 = Color.Black
+
+ val colors2 = lightColors(
+ primary = Color.Green,
+ background = Color.Green,
+ onPrimary = testColor3,
+ onBackground = Color.Black,
+ )
+ }
+ """
+ ),
+ ColorStub,
+ ColorsStub
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun noErrors() {
lint().files(
kotlin(
diff --git a/wear/wear-complications-data/api/current.txt b/wear/wear-complications-data/api/current.txt
index 81e1229..2031367 100644
--- a/wear/wear-complications-data/api/current.txt
+++ b/wear/wear-complications-data/api/current.txt
@@ -164,7 +164,7 @@
public final class ProviderInfoRetriever implements java.lang.AutoCloseable {
ctor public ProviderInfoRetriever(android.content.Context context);
method public void close();
- method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? requestPreviewComplicationData(android.content.ComponentName providerComponent, androidx.wear.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.complications.data.ComplicationData> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName providerComponent, androidx.wear.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.complications.data.ComplicationData> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveProviderInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.complications.ProviderInfoRetriever.ProviderInfo[]> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
}
diff --git a/wear/wear-complications-data/api/public_plus_experimental_current.txt b/wear/wear-complications-data/api/public_plus_experimental_current.txt
index 8829abf..8f1e849 100644
--- a/wear/wear-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/wear-complications-data/api/public_plus_experimental_current.txt
@@ -164,7 +164,7 @@
public final class ProviderInfoRetriever implements java.lang.AutoCloseable {
ctor public ProviderInfoRetriever(android.content.Context context);
method public void close();
- method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? requestPreviewComplicationData(android.content.ComponentName providerComponent, androidx.wear.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.complications.data.ComplicationData> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName providerComponent, androidx.wear.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.complications.data.ComplicationData> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveProviderInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.complications.ProviderInfoRetriever.ProviderInfo[]> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
}
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index 2c16c7d..da29a6f 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -205,7 +205,7 @@
public final class ProviderInfoRetriever implements java.lang.AutoCloseable {
ctor public ProviderInfoRetriever(android.content.Context context);
method public void close();
- method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? requestPreviewComplicationData(android.content.ComponentName providerComponent, androidx.wear.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.complications.data.ComplicationData> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.R) @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrievePreviewComplicationData(android.content.ComponentName providerComponent, androidx.wear.complications.data.ComplicationType complicationType, kotlin.coroutines.Continuation<? super androidx.wear.complications.data.ComplicationData> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
method @kotlin.jvm.Throws(exceptionClasses=ServiceDisconnectedException::class) public suspend Object? retrieveProviderInfo(android.content.ComponentName watchFaceComponent, int[] watchFaceComplicationIds, kotlin.coroutines.Continuation<? super androidx.wear.complications.ProviderInfoRetriever.ProviderInfo[]> p) throws androidx.wear.complications.ProviderInfoRetriever.ServiceDisconnectedException;
}
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderInfoRetriever.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderInfoRetriever.kt
index 70dea0a..c714b42 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderInfoRetriever.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderInfoRetriever.kt
@@ -135,7 +135,7 @@
*/
@Throws(ServiceDisconnectedException::class)
@RequiresApi(Build.VERSION_CODES.R)
- public suspend fun requestPreviewComplicationData(
+ public suspend fun retrievePreviewComplicationData(
providerComponent: ComponentName,
complicationType: ComplicationType
): ComplicationData? = TraceEvent(
diff --git a/wear/wear-complications-data/src/test/java/androidx/wear/complications/ProviderInfoRetrieverTest.kt b/wear/wear-complications-data/src/test/java/androidx/wear/complications/ProviderInfoRetrieverTest.kt
index 6c7fac6..94803bb 100644
--- a/wear/wear-complications-data/src/test/java/androidx/wear/complications/ProviderInfoRetrieverTest.kt
+++ b/wear/wear-complications-data/src/test/java/androidx/wear/complications/ProviderInfoRetrieverTest.kt
@@ -80,7 +80,7 @@
)
val previewData =
- providerInfoRetriever.requestPreviewComplicationData(component, type)!!
+ providerInfoRetriever.retrievePreviewComplicationData(component, type)!!
assertThat(previewData.type).isEqualTo(type)
assertThat(
(previewData as LongTextComplicationData).text.getTextAt(
@@ -108,7 +108,7 @@
any()
)
- assertThat(providerInfoRetriever.requestPreviewComplicationData(component, type))
+ assertThat(providerInfoRetriever.retrievePreviewComplicationData(component, type))
.isNull()
}
}
@@ -121,7 +121,7 @@
Mockito.`when`(mockService.apiVersion).thenReturn(0)
Mockito.`when`(mockService.asBinder()).thenReturn(mockBinder)
- assertThat(providerInfoRetriever.requestPreviewComplicationData(component, type))
+ assertThat(providerInfoRetriever.retrievePreviewComplicationData(component, type))
.isNull()
}
}
@@ -141,7 +141,7 @@
any()
)
- assertThat(providerInfoRetriever.requestPreviewComplicationData(component, type))
+ assertThat(providerInfoRetriever.retrievePreviewComplicationData(component, type))
.isNull()
}
}
diff --git a/wear/wear-watchface-client/build.gradle b/wear/wear-watchface-client/build.gradle
index dc89440..3c29c46 100644
--- a/wear/wear-watchface-client/build.gradle
+++ b/wear/wear-watchface-client/build.gradle
@@ -46,6 +46,12 @@
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(TRUTH)
+ testImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ testImplementation(ANDROIDX_TEST_CORE)
+ testImplementation(ANDROIDX_TEST_RULES)
+ testImplementation(ROBOLECTRIC)
+ testImplementation(TRUTH)
+
implementation("androidx.core:core:1.1.0")
}
diff --git a/wear/wear-watchface-client/src/test/java/androidx/wear/watchface/client/WatchFaceIdTest.kt b/wear/wear-watchface-client/src/test/java/androidx/wear/watchface/client/WatchFaceIdTest.kt
new file mode 100644
index 0000000..25cd2ec
--- /dev/null
+++ b/wear/wear-watchface-client/src/test/java/androidx/wear/watchface/client/WatchFaceIdTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.watchface.client
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.internal.bytecode.InstrumentationConfiguration
+import org.junit.runner.RunWith
+import org.junit.runners.model.FrameworkMethod
+
+// Without this we get test failures with an error:
+// "failed to access class kotlin.jvm.internal.DefaultConstructorMarker".
+public class EditorTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+ override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration =
+ InstrumentationConfiguration.Builder(
+ super.createClassLoaderConfig(method)
+ )
+ .doNotInstrumentPackage("androidx.wear.watchface.client")
+ .build()
+}
+
+@RunWith(EditorTestRunner::class)
+public class WatchFaceIdTest {
+ @Test
+ public fun watchFaceId_equals() {
+ val a1 = WatchFaceId("A")
+ val a2 = WatchFaceId("A")
+ val b1 = WatchFaceId("B")
+
+ assertThat(a1).isEqualTo(a1)
+ assertThat(a1).isEqualTo(a2)
+ assertThat(a1).isNotEqualTo(b1)
+ assertThat(a1).isNotEqualTo(false)
+ }
+
+ @Test
+ public fun watchFaceId_hashCode() {
+ val a1 = WatchFaceId("A")
+ val a2 = WatchFaceId("A")
+ val b1 = WatchFaceId("B")
+
+ assertThat(a1.hashCode()).isEqualTo(a2.hashCode())
+ assertThat(a1.hashCode()).isNotEqualTo(b1.hashCode())
+ }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface-client/src/test/resources/robolectric.properties b/wear/wear-watchface-client/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..ce87047
--- /dev/null
+++ b/wear/wear-watchface-client/src/test/resources/robolectric.properties
@@ -0,0 +1,3 @@
+# Robolectric currently doesn't support API 30, so we have to explicitly specify 29 as the target
+# sdk for now. Remove when no longer necessary.
+sdk=29
diff --git a/wear/wear-watchface-complications-rendering/api/current.txt b/wear/wear-watchface-complications-rendering/api/current.txt
index 1e136b6..bd8e23b 100644
--- a/wear/wear-watchface-complications-rendering/api/current.txt
+++ b/wear/wear-watchface-complications-rendering/api/current.txt
@@ -13,6 +13,7 @@
method public long getCurrentTimeMillis();
method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
method public long getHighlightDuration();
+ method public CharSequence? getNoDataText();
method @Deprecated public int getOpacity();
method public boolean isBurnInProtectionOn();
method public boolean isHighlighted();
diff --git a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
index 1e136b6..bd8e23b 100644
--- a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
@@ -13,6 +13,7 @@
method public long getCurrentTimeMillis();
method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
method public long getHighlightDuration();
+ method public CharSequence? getNoDataText();
method @Deprecated public int getOpacity();
method public boolean isBurnInProtectionOn();
method public boolean isHighlighted();
diff --git a/wear/wear-watchface-complications-rendering/api/restricted_current.txt b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
index 3fc7fc4..25d5b4f 100644
--- a/wear/wear-watchface-complications-rendering/api/restricted_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
@@ -13,6 +13,7 @@
method public long getCurrentTimeMillis();
method public static androidx.wear.watchface.complications.rendering.ComplicationDrawable? getDrawable(android.content.Context, int);
method public long getHighlightDuration();
+ method public CharSequence? getNoDataText();
method @Deprecated public int getOpacity();
method public boolean isBurnInProtectionOn();
method public boolean isHighlighted();
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
index f9b8b7e..449cad4 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationDrawable.java
@@ -848,8 +848,11 @@
return mComplicationRenderer;
}
+ /**
+ * Returns the text to be rendered when {@link ComplicationData} is of type {@link
+ * ComplicationData#TYPE_NO_DATA}.
+ */
@Nullable
- @VisibleForTesting(otherwise = VisibleForTesting.NONE)
public CharSequence getNoDataText() {
return mNoDataText;
}
diff --git a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/StyleConfigFragment.kt b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/StyleConfigFragment.kt
index e3b783f..c6140e0 100644
--- a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/StyleConfigFragment.kt
+++ b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/StyleConfigFragment.kt
@@ -34,9 +34,13 @@
import androidx.wear.watchface.style.UserStyleSchema
import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption
import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting
import androidx.wear.watchface.style.UserStyleData
import androidx.wear.watchface.style.data.UserStyleSchemaWireFormat
import androidx.wear.watchface.style.data.UserStyleWireFormat
@@ -86,28 +90,17 @@
inflater.inflate(R.layout.style_options_layout, container, false)
as SwipeDismissFrameLayout
- val styleOptions = styleSetting.options
- val booleanUserStyleSetting =
- styleOptions.filterIsInstance<BooleanUserStyleSetting.BooleanOption>()
- val listUserStyleSetting =
- styleOptions.filterIsInstance<ListUserStyleSetting.ListOption>()
- val complicationsUserStyleSetting =
- styleOptions.filterIsInstance<ComplicationsUserStyleSetting.ComplicationsOption>()
- val rangeUserStyleSetting =
- styleOptions.filterIsInstance<DoubleRangeUserStyleSetting.DoubleRangeOption>()
-
val booleanStyle = view.findViewById<ToggleButton>(R.id.styleToggle)
val styleOptionsList = view.findViewById<WearableRecyclerView>(R.id.styleOptionsList)
val rangedStyle = view.findViewById<SeekBar>(R.id.styleRange)
- when {
- booleanUserStyleSetting.isNotEmpty() -> {
- booleanStyle.isChecked = userStyle[styleSetting]?.toBooleanOption()!!.value
+ val userStyleOption = userStyle[styleSetting]!!
+ when (styleSetting) {
+ is BooleanUserStyleSetting -> {
+ booleanStyle.isChecked = (userStyleOption as BooleanOption).value
booleanStyle.setOnCheckedChangeListener { _, isChecked ->
setUserStyleOption(
- styleSetting.getOptionForId(
- BooleanUserStyleSetting.BooleanOption(isChecked).id.value
- )
+ styleSetting.getOptionForId(BooleanOption(isChecked).id.value)
)
}
styleOptionsList.visibility = View.GONE
@@ -116,13 +109,13 @@
rangedStyle.isEnabled = false
}
- listUserStyleSetting.isNotEmpty() -> {
+ is ListUserStyleSetting -> {
booleanStyle.isEnabled = false
booleanStyle.visibility = View.GONE
styleOptionsList.adapter =
ListStyleSettingViewAdapter(
requireContext(),
- listUserStyleSetting,
+ styleSetting.options.filterIsInstance<ListUserStyleSetting.ListOption>(),
this@StyleConfigFragment
)
styleOptionsList.layoutManager = WearableLinearLayoutManager(context)
@@ -130,13 +123,14 @@
rangedStyle.visibility = View.GONE
}
- complicationsUserStyleSetting.isNotEmpty() -> {
+ is ComplicationsUserStyleSetting -> {
booleanStyle.isEnabled = false
booleanStyle.visibility = View.GONE
styleOptionsList.adapter =
ComplicationsStyleSettingViewAdapter(
requireContext(),
- complicationsUserStyleSetting,
+ styleSetting.options
+ .filterIsInstance<ComplicationsUserStyleSetting.ComplicationsOption>(),
this@StyleConfigFragment
)
styleOptionsList.layoutManager = WearableLinearLayoutManager(context)
@@ -144,20 +138,18 @@
rangedStyle.visibility = View.GONE
}
- rangeUserStyleSetting.isNotEmpty() -> {
+ is CustomValueUserStyleSetting -> {
+ // TODO(alexclarke): Implement.
+ }
+
+ is DoubleRangeUserStyleSetting -> {
val rangedStyleSetting = styleSetting as DoubleRangeUserStyleSetting
val minValue =
- (
- rangedStyleSetting.options.first() as
- DoubleRangeUserStyleSetting.DoubleRangeOption
- ).value
+ (rangedStyleSetting.options.first() as DoubleRangeOption).value
val maxValue =
- (
- rangedStyleSetting.options.last() as
- DoubleRangeUserStyleSetting.DoubleRangeOption
- ).value
+ (rangedStyleSetting.options.last() as DoubleRangeOption).value
val delta = (maxValue - minValue) / 100.0f
- val value = userStyle[styleSetting]!!.toDoubleRangeOption()!!.value.toFloat()
+ val value = (userStyleOption as DoubleRangeOption).value.toFloat()
rangedStyle.progress = ((value - minValue) / delta).toInt()
rangedStyle.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
@@ -168,9 +160,8 @@
) {
setUserStyleOption(
rangedStyleSetting.getOptionForId(
- DoubleRangeUserStyleSetting.DoubleRangeOption(
- minValue + delta * progress.toFloat()
- ).id.value
+ DoubleRangeOption(minValue + delta * progress.toFloat())
+ .id.value
)
)
}
@@ -185,6 +176,10 @@
styleOptionsList.isEnabled = false
styleOptionsList.visibility = View.GONE
}
+
+ is LongRangeUserStyleSetting -> {
+ // TODO(alexclarke): Implement.
+ }
}
view.addCallback(object : SwipeDismissFrameLayout.Callback() {
@@ -196,7 +191,7 @@
return view
}
- internal fun readOptionsFromArguments() {
+ private fun readOptionsFromArguments() {
settingId = requireArguments().getCharSequence(SETTING_ID).toString()
styleSchema = UserStyleSchema(
diff --git a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index 65863bd..7e2dffe4 100644
--- a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -374,7 +374,7 @@
// Fetch preview ComplicationData if possible.
return providerInfo.providerComponentName?.let {
try {
- providerInfoRetriever.requestPreviewComplicationData(
+ providerInfoRetriever.retrievePreviewComplicationData(
it,
ComplicationType.fromWireType(providerInfo.complicationType)
)
diff --git a/wear/wear-watchface-style/api/current.txt b/wear/wear-watchface-style/api/current.txt
index c63feab..ff75d4a 100644
--- a/wear/wear-watchface-style/api/current.txt
+++ b/wear/wear-watchface-style/api/current.txt
@@ -55,6 +55,7 @@
method public androidx.wear.watchface.style.UserStyleSetting.Option getOptionForId(byte[] optionId);
method public final java.util.List<androidx.wear.watchface.style.UserStyleSetting.Option> getOptions();
property public final java.util.Collection<androidx.wear.watchface.style.Layer> affectedLayers;
+ property public final androidx.wear.watchface.style.UserStyleSetting.Option defaultOption;
property public final int defaultOptionIndex;
property public final CharSequence description;
property public final CharSequence displayName;
@@ -125,6 +126,9 @@
method public double getDefaultValue();
method public double getMaximumValue();
method public double getMinimumValue();
+ property public final double defaultValue;
+ property public final double maximumValue;
+ property public final double minimumValue;
}
public static final class UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption extends androidx.wear.watchface.style.UserStyleSetting.Option {
@@ -162,6 +166,9 @@
method public long getDefaultValue();
method public long getMaximumValue();
method public long getMinimumValue();
+ property public final long defaultValue;
+ property public final long maximumValue;
+ property public final long minimumValue;
}
public static final class UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption extends androidx.wear.watchface.style.UserStyleSetting.Option {
@@ -173,12 +180,6 @@
public abstract static class UserStyleSetting.Option {
ctor public UserStyleSetting.Option(androidx.wear.watchface.style.UserStyleSetting.Option.Id id);
method public final androidx.wear.watchface.style.UserStyleSetting.Option.Id getId();
- method public final androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption? toBooleanOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationsOption? toComplicationsOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting.CustomValueOption? toCustomValueOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption? toDoubleRangeOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting.ListOption? toListOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption? toLongRangeOption();
property public final androidx.wear.watchface.style.UserStyleSetting.Option.Id id;
field public static final androidx.wear.watchface.style.UserStyleSetting.Option.Companion Companion;
}
diff --git a/wear/wear-watchface-style/api/public_plus_experimental_current.txt b/wear/wear-watchface-style/api/public_plus_experimental_current.txt
index bd690a3..7bc6a09 100644
--- a/wear/wear-watchface-style/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-style/api/public_plus_experimental_current.txt
@@ -55,6 +55,7 @@
method public androidx.wear.watchface.style.UserStyleSetting.Option getOptionForId(byte[] optionId);
method public final java.util.List<androidx.wear.watchface.style.UserStyleSetting.Option> getOptions();
property public final java.util.Collection<androidx.wear.watchface.style.Layer> affectedLayers;
+ property public final androidx.wear.watchface.style.UserStyleSetting.Option defaultOption;
property public final int defaultOptionIndex;
property public final CharSequence description;
property public final CharSequence displayName;
@@ -132,6 +133,9 @@
method public double getMaximumValue();
method public double getMinimumValue();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.wear.watchface.style.data.DoubleRangeUserStyleSettingWireFormat toWireFormat();
+ property public final double defaultValue;
+ property public final double maximumValue;
+ property public final double minimumValue;
}
public static final class UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption extends androidx.wear.watchface.style.UserStyleSetting.Option {
@@ -173,6 +177,9 @@
method public long getMaximumValue();
method public long getMinimumValue();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.wear.watchface.style.data.LongRangeUserStyleSettingWireFormat toWireFormat();
+ property public final long defaultValue;
+ property public final long maximumValue;
+ property public final long minimumValue;
}
public static final class UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption extends androidx.wear.watchface.style.UserStyleSetting.Option {
@@ -185,12 +192,6 @@
public abstract static class UserStyleSetting.Option {
ctor public UserStyleSetting.Option(androidx.wear.watchface.style.UserStyleSetting.Option.Id id);
method public final androidx.wear.watchface.style.UserStyleSetting.Option.Id getId();
- method public final androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption? toBooleanOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationsOption? toComplicationsOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting.CustomValueOption? toCustomValueOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption? toDoubleRangeOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting.ListOption? toListOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption? toLongRangeOption();
property public final androidx.wear.watchface.style.UserStyleSetting.Option.Id id;
field public static final androidx.wear.watchface.style.UserStyleSetting.Option.Companion Companion;
}
diff --git a/wear/wear-watchface-style/api/restricted_current.txt b/wear/wear-watchface-style/api/restricted_current.txt
index 2b3bab3..707ed6a 100644
--- a/wear/wear-watchface-style/api/restricted_current.txt
+++ b/wear/wear-watchface-style/api/restricted_current.txt
@@ -62,6 +62,7 @@
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final java.util.List<androidx.wear.watchface.style.data.OptionWireFormat> getWireFormatOptionsList();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract androidx.wear.watchface.style.data.UserStyleSettingWireFormat toWireFormat();
property public final java.util.Collection<androidx.wear.watchface.style.Layer> affectedLayers;
+ property public final androidx.wear.watchface.style.UserStyleSetting.Option defaultOption;
property public final int defaultOptionIndex;
property public final CharSequence description;
property public final CharSequence displayName;
@@ -139,6 +140,9 @@
method public double getMaximumValue();
method public double getMinimumValue();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.wear.watchface.style.data.DoubleRangeUserStyleSettingWireFormat toWireFormat();
+ property public final double defaultValue;
+ property public final double maximumValue;
+ property public final double minimumValue;
}
public static final class UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption extends androidx.wear.watchface.style.UserStyleSetting.Option {
@@ -180,6 +184,9 @@
method public long getMaximumValue();
method public long getMinimumValue();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.wear.watchface.style.data.LongRangeUserStyleSettingWireFormat toWireFormat();
+ property public final long defaultValue;
+ property public final long maximumValue;
+ property public final long minimumValue;
}
public static final class UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption extends androidx.wear.watchface.style.UserStyleSetting.Option {
@@ -192,12 +199,6 @@
public abstract static class UserStyleSetting.Option {
ctor public UserStyleSetting.Option(androidx.wear.watchface.style.UserStyleSetting.Option.Id id);
method public final androidx.wear.watchface.style.UserStyleSetting.Option.Id getId();
- method public final androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption? toBooleanOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationsOption? toComplicationsOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting.CustomValueOption? toCustomValueOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption? toDoubleRangeOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting.ListOption? toListOption();
- method public final androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption? toLongRangeOption();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract androidx.wear.watchface.style.data.OptionWireFormat toWireFormat();
property public final androidx.wear.watchface.style.UserStyleSetting.Option.Id id;
field public static final androidx.wear.watchface.style.UserStyleSetting.Option.Companion Companion;
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
index 4e8622f..8f78541 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
@@ -59,7 +59,7 @@
if (option != null) {
this[styleSetting] = styleSetting.getSettingOptionForId(option)
} else {
- this[styleSetting] = styleSetting.getDefaultOption()
+ this[styleSetting] = styleSetting.defaultOption
}
}
}
@@ -207,7 +207,7 @@
public var userStyle: UserStyle = UserStyle(
HashMap<UserStyleSetting, UserStyleSetting.Option>().apply {
for (setting in schema.userStyleSettings) {
- this[setting] = setting.getDefaultOption()
+ this[setting] = setting.defaultOption
}
}
)
diff --git a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 9fb5fac..edaa98f 100644
--- a/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/wear-watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -151,7 +151,8 @@
options.map { it.toWireFormat() }
/** Returns the default for when the user hasn't selected an option. */
- public fun getDefaultOption(): Option = options[defaultOptionIndex]
+ public val defaultOption: Option
+ get() = options[defaultOptionIndex]
override fun toString(): String = "{${id.value} : " +
options.joinToString(transform = { it.toString() }) + "}"
@@ -233,48 +234,6 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public abstract fun toWireFormat(): OptionWireFormat
- public fun toBooleanOption(): BooleanUserStyleSetting.BooleanOption? =
- if (this is BooleanUserStyleSetting.BooleanOption) {
- this
- } else {
- null
- }
-
- public fun toComplicationsOption(): ComplicationsUserStyleSetting.ComplicationsOption? =
- if (this is ComplicationsUserStyleSetting.ComplicationsOption) {
- this
- } else {
- null
- }
-
- public fun toCustomValueOption(): CustomValueUserStyleSetting.CustomValueOption? =
- if (this is CustomValueUserStyleSetting.CustomValueOption) {
- this
- } else {
- null
- }
-
- public fun toDoubleRangeOption(): DoubleRangeUserStyleSetting.DoubleRangeOption? =
- if (this is DoubleRangeUserStyleSetting.DoubleRangeOption) {
- this
- } else {
- null
- }
-
- public fun toListOption(): ListUserStyleSetting.ListOption? =
- if (this is ListUserStyleSetting.ListOption) {
- this
- } else {
- null
- }
-
- public fun toLongRangeOption(): LongRangeUserStyleSetting.LongRangeOption? =
- if (this is LongRangeUserStyleSetting.LongRangeOption) {
- this
- } else {
- null
- }
-
override fun toString(): String =
try {
id.value.decodeToString()
@@ -676,14 +635,16 @@
}
/** Returns the minimum value. */
- public fun getMinimumValue(): Double = (options.first() as DoubleRangeOption).value
+ public val minimumValue: Double
+ get() = (options.first() as DoubleRangeOption).value
/** Returns the maximum value. */
- public fun getMaximumValue(): Double = (options.last() as DoubleRangeOption).value
+ public val maximumValue: Double
+ get() = (options.last() as DoubleRangeOption).value
/** Returns the default value. */
- public fun getDefaultValue(): Double =
- (options[defaultOptionIndex] as DoubleRangeOption).value
+ public val defaultValue: Double
+ get() = (options[defaultOptionIndex] as DoubleRangeOption).value
/** We support all values in the range [min ... max] not just min & max. */
override fun getOptionForId(optionId: ByteArray): Option =
@@ -692,7 +653,7 @@
private fun checkedOptionForId(optionId: ByteArray): DoubleRangeOption {
return try {
val value = ByteBuffer.wrap(optionId).double
- if (value < getMinimumValue() || value > getMaximumValue()) {
+ if (value < minimumValue || value > maximumValue) {
options[defaultOptionIndex] as DoubleRangeOption
} else {
DoubleRangeOption(value)
@@ -910,20 +871,17 @@
override fun toString(): String = value.toString()
}
- /**
- * Returns the minimum value.
- */
- public fun getMinimumValue(): Long = (options.first() as LongRangeOption).value
+ /** The minimum value. */
+ public val minimumValue: Long
+ get() = (options.first() as LongRangeOption).value
- /**
- * Returns the maximum value.
- */
- public fun getMaximumValue(): Long = (options.last() as LongRangeOption).value
+ /** The maximum value. */
+ public val maximumValue: Long
+ get() = (options.last() as LongRangeOption).value
- /**
- * Returns the default value.
- */
- public fun getDefaultValue(): Long = (options[defaultOptionIndex] as LongRangeOption).value
+ /** The default value. */
+ public val defaultValue: Long
+ get() = (options[defaultOptionIndex] as LongRangeOption).value
/**
* We support all values in the range [min ... max] not just min & max.
@@ -934,7 +892,7 @@
private fun checkedOptionForId(optionId: ByteArray): LongRangeOption {
return try {
val value = ByteBuffer.wrap(optionId).long
- if (value < getMinimumValue() || value > getMaximumValue()) {
+ if (value < minimumValue || value > maximumValue) {
options[defaultOptionIndex] as LongRangeOption
} else {
LongRangeOption(value)
diff --git a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
index f178383..cba47d2 100644
--- a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
+++ b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/CurrentUserStyleRepositoryTest.kt
@@ -18,6 +18,7 @@
import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting.CustomValueOption
import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting
@@ -313,9 +314,8 @@
)
assertThat(
- userStyleRepository.userStyle[
- customStyleSetting
- ]?.toCustomValueOption()!!.customValue.decodeToString()
+ (userStyleRepository.userStyle[customStyleSetting]!! as CustomValueOption)
+ .customValue.decodeToString()
).isEqualTo("test")
}
@@ -349,6 +349,19 @@
}
@Test
+ public fun userStyleData_toString() {
+ val userStyleData = UserStyleData(
+ mapOf(
+ "A" to "a".encodeToByteArray(),
+ "B" to "b".encodeToByteArray()
+ )
+ )
+
+ assertThat(userStyleData.toString()).contains("A=a")
+ assertThat(userStyleData.toString()).contains("B=b")
+ }
+
+ @Test
public fun optionIdToStringTest() {
assertThat(BooleanUserStyleSetting.BooleanOption(true).toString()).isEqualTo("true")
assertThat(BooleanUserStyleSetting.BooleanOption(false).toString()).isEqualTo("false")
diff --git a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
index 322f0f0..3afade1 100644
--- a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
+++ b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
@@ -24,6 +24,7 @@
import androidx.wear.watchface.style.UserStyleSetting.CustomValueUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting.ListOption
import androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.Option
import androidx.wear.watchface.style.data.UserStyleSchemaWireFormat
@@ -43,10 +44,10 @@
private val icon2 = Icon.createWithContentUri("icon2")
private val icon3 = Icon.createWithContentUri("icon3")
private val icon4 = Icon.createWithContentUri("icon4")
- private val option1 = ListUserStyleSetting.ListOption(Option.Id("1"), "one", icon1)
- private val option2 = ListUserStyleSetting.ListOption(Option.Id("2"), "two", icon2)
- private val option3 = ListUserStyleSetting.ListOption(Option.Id("3"), "three", icon3)
- private val option4 = ListUserStyleSetting.ListOption(Option.Id("4"), "four", icon4)
+ private val option1 = ListOption(Option.Id("1"), "one", icon1)
+ private val option2 = ListOption(Option.Id("2"), "two", icon2)
+ private val option3 = ListOption(Option.Id("3"), "three", icon3)
+ private val option4 = ListOption(Option.Id("4"), "four", icon4)
@Test
public fun parcelAndUnparcelStyleSettingAndOption() {
@@ -79,9 +80,7 @@
assertThat(unparceled.icon!!.uri.toString()).isEqualTo("settingIcon")
assertThat(unparceled.affectedLayers.size).isEqualTo(1)
assertThat(unparceled.affectedLayers.first()).isEqualTo(Layer.BASE)
- val optionArray =
- unparceled.options.filterIsInstance<ListUserStyleSetting.ListOption>()
- .toTypedArray()
+ val optionArray = unparceled.options.filterIsInstance<ListOption>().toTypedArray()
assertThat(optionArray.size).isEqualTo(3)
assertThat(optionArray[0].id.value.decodeToString()).isEqualTo("1")
assertThat(optionArray[0].displayName).isEqualTo("one")
@@ -100,12 +99,9 @@
val wireFormat2 = option2.toWireFormat()
val wireFormat3 = option3.toWireFormat()
- val unmarshalled1 =
- UserStyleSetting.Option.createFromWireFormat(wireFormat1).toListOption()!!
- val unmarshalled2 =
- UserStyleSetting.Option.createFromWireFormat(wireFormat2).toListOption()!!
- val unmarshalled3 =
- UserStyleSetting.Option.createFromWireFormat(wireFormat3).toListOption()!!
+ val unmarshalled1 = Option.createFromWireFormat(wireFormat1) as ListOption
+ val unmarshalled2 = Option.createFromWireFormat(wireFormat2) as ListOption
+ val unmarshalled3 = Option.createFromWireFormat(wireFormat3) as ListOption
assertThat(unmarshalled1.id.value.decodeToString()).isEqualTo("1")
assertThat(unmarshalled1.displayName).isEqualTo("one")
@@ -177,8 +173,7 @@
assertThat(schema.userStyleSettings[0].affectedLayers.size).isEqualTo(1)
assertThat(schema.userStyleSettings[0].affectedLayers.first()).isEqualTo(Layer.BASE)
val optionArray1 =
- schema.userStyleSettings[0].options.filterIsInstance<ListUserStyleSetting.ListOption>()
- .toTypedArray()
+ schema.userStyleSettings[0].options.filterIsInstance<ListOption>().toTypedArray()
assertThat(optionArray1.size).isEqualTo(2)
assertThat(optionArray1[0].id.value.decodeToString()).isEqualTo("1")
assertThat(optionArray1[0].displayName).isEqualTo("one")
@@ -197,8 +192,7 @@
Layer.COMPLICATIONS_OVERLAY
)
val optionArray2 =
- schema.userStyleSettings[1].options.filterIsInstance<ListUserStyleSetting.ListOption>()
- .toTypedArray()
+ schema.userStyleSettings[1].options.filterIsInstance<ListOption>().toTypedArray()
assertThat(optionArray2.size).isEqualTo(2)
assertThat(optionArray2[0].id.value.decodeToString()).isEqualTo("3")
assertThat(optionArray2[0].displayName).isEqualTo("three")
@@ -216,7 +210,7 @@
assertThat(schema.userStyleSettings[2].affectedLayers.first()).isEqualTo(Layer.BASE)
assert(schema.userStyleSettings[3] is CustomValueUserStyleSetting)
- assertThat(schema.userStyleSettings[3].getDefaultOption().id.value.decodeToString())
+ assertThat(schema.userStyleSettings[3].defaultOption.id.value.decodeToString())
.isEqualTo("default")
assertThat(schema.userStyleSettings[3].affectedLayers.size).isEqualTo(1)
assertThat(schema.userStyleSettings[3].affectedLayers.first()).isEqualTo(Layer.BASE)
@@ -301,7 +295,7 @@
listOf(Layer.BASE),
-1.0
)
- assertThat(doubleRangeUserStyleSettingDefaultMin.getDefaultValue()).isEqualTo(-1.0)
+ assertThat(doubleRangeUserStyleSettingDefaultMin.defaultValue).isEqualTo(-1.0)
val doubleRangeUserStyleSettingDefaultMid = DoubleRangeUserStyleSetting(
UserStyleSetting.Id("id2"),
@@ -313,7 +307,7 @@
listOf(Layer.BASE),
0.5
)
- assertThat(doubleRangeUserStyleSettingDefaultMid.getDefaultValue()).isEqualTo(0.5)
+ assertThat(doubleRangeUserStyleSettingDefaultMid.defaultValue).isEqualTo(0.5)
val doubleRangeUserStyleSettingDefaultMax = DoubleRangeUserStyleSetting(
UserStyleSetting.Id("id2"),
@@ -325,7 +319,7 @@
listOf(Layer.BASE),
1.0
)
- assertThat(doubleRangeUserStyleSettingDefaultMax.getDefaultValue()).isEqualTo(1.0)
+ assertThat(doubleRangeUserStyleSettingDefaultMax.defaultValue).isEqualTo(1.0)
}
@Test
@@ -340,7 +334,7 @@
listOf(Layer.BASE),
-1,
)
- assertThat(longRangeUserStyleSettingDefaultMin.getDefaultValue()).isEqualTo(-1)
+ assertThat(longRangeUserStyleSettingDefaultMin.defaultValue).isEqualTo(-1)
val longRangeUserStyleSettingDefaultMid = LongRangeUserStyleSetting(
UserStyleSetting.Id("id2"),
@@ -352,7 +346,7 @@
listOf(Layer.BASE),
5
)
- assertThat(longRangeUserStyleSettingDefaultMid.getDefaultValue()).isEqualTo(5)
+ assertThat(longRangeUserStyleSettingDefaultMid.defaultValue).isEqualTo(5)
val longRangeUserStyleSettingDefaultMax = LongRangeUserStyleSetting(
UserStyleSetting.Id("id2"),
@@ -364,7 +358,7 @@
listOf(Layer.BASE),
10
)
- assertThat(longRangeUserStyleSettingDefaultMax.getDefaultValue()).isEqualTo(10)
+ assertThat(longRangeUserStyleSettingDefaultMax.defaultValue).isEqualTo(10)
}
@Test
diff --git a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleSettingTest.kt b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleSettingTest.kt
index 742850d..dbd8114 100644
--- a/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleSettingTest.kt
+++ b/wear/wear-watchface-style/src/test/java/androidx/wear/watchface/style/UserStyleSettingTest.kt
@@ -16,9 +16,10 @@
package androidx.wear.watchface.style
-import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption
import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting
-import androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption
+import androidx.wear.watchface.style.UserStyleSetting.LongRangeUserStyleSetting.LongRangeOption
import androidx.wear.watchface.style.UserStyleSetting.Option
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
@@ -50,27 +51,29 @@
)
assertThat(
- rangedUserStyleSetting.getOptionForId("not a number".encodeToByteArray())
- .toDoubleRangeOption()!!.value
+ (
+ rangedUserStyleSetting.getOptionForId("not a number".encodeToByteArray()) as
+ DoubleRangeOption
+ ).value
).isEqualTo(defaultValue)
assertThat(
- rangedUserStyleSetting.getOptionForId("-1".encodeToByteArray())
- .toDoubleRangeOption()!!.value
+ (rangedUserStyleSetting.getOptionForId("-1".encodeToByteArray()) as DoubleRangeOption)
+ .value
).isEqualTo(defaultValue)
assertThat(
- rangedUserStyleSetting.getOptionForId("10".encodeToByteArray())
- .toDoubleRangeOption()!!.value
+ (rangedUserStyleSetting.getOptionForId("10".encodeToByteArray()) as DoubleRangeOption)
+ .value
).isEqualTo(defaultValue)
}
@Test
public fun byteArrayConversion() {
- assertThat(BooleanUserStyleSetting.BooleanOption(true).value).isEqualTo(true)
- assertThat(BooleanUserStyleSetting.BooleanOption(false).value).isEqualTo(false)
- assertThat(DoubleRangeUserStyleSetting.DoubleRangeOption(123.4).value).isEqualTo(123.4)
- assertThat(LongRangeUserStyleSetting.LongRangeOption(1234).value).isEqualTo(1234)
+ assertThat(BooleanOption(true).value).isEqualTo(true)
+ assertThat(BooleanOption(false).value).isEqualTo(false)
+ assertThat(DoubleRangeOption(123.4).value).isEqualTo(123.4)
+ assertThat(LongRangeOption(1234).value).isEqualTo(1234)
}
@Test
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index a489434..68ca68b 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -252,8 +252,9 @@
}
public final class WatchState {
- ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis, boolean isHeadless);
+ ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis, @Px int chinHeight, boolean isHeadless);
method public long getAnalogPreviewReferenceTimeMillis();
+ method @Px public int getChinHeight();
method public long getDigitalPreviewReferenceTimeMillis();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
method public boolean hasBurnInProtection();
@@ -262,6 +263,7 @@
method public boolean isHeadless();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
property public final long analogPreviewReferenceTimeMillis;
+ property @Px public final int chinHeight;
property public final long digitalPreviewReferenceTimeMillis;
property public final boolean hasBurnInProtection;
property public final boolean hasLowBitAmbient;
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index a489434..68ca68b 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -252,8 +252,9 @@
}
public final class WatchState {
- ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis, boolean isHeadless);
+ ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis, @Px int chinHeight, boolean isHeadless);
method public long getAnalogPreviewReferenceTimeMillis();
+ method @Px public int getChinHeight();
method public long getDigitalPreviewReferenceTimeMillis();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
method public boolean hasBurnInProtection();
@@ -262,6 +263,7 @@
method public boolean isHeadless();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
property public final long analogPreviewReferenceTimeMillis;
+ property @Px public final int chinHeight;
property public final long digitalPreviewReferenceTimeMillis;
property public final boolean hasBurnInProtection;
property public final boolean hasLowBitAmbient;
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 33f3560..7a3ec19 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -123,6 +123,7 @@
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class MutableWatchState {
method public androidx.wear.watchface.WatchState asWatchState();
method public long getAnalogPreviewReferenceTimeMillis();
+ method @Px public int getChinHeight();
method public long getDigitalPreviewReferenceTimeMillis();
method public boolean getHasBurnInProtection();
method public boolean getHasLowBitAmbient();
@@ -132,12 +133,14 @@
method public boolean isHeadless();
method public androidx.wear.watchface.MutableObservableWatchData<java.lang.Boolean> isVisible();
method public void setAnalogPreviewReferenceTimeMillis(long p);
+ method public void setChinHeight(@Px int value);
method public void setDigitalPreviewReferenceTimeMillis(long p);
method public void setHasBurnInProtection(boolean p);
method public void setHasLowBitAmbient(boolean p);
method public void setHeadless(boolean p);
method public void setInterruptionFilter(androidx.wear.watchface.MutableObservableWatchData<java.lang.Integer> p);
property public final long analogPreviewReferenceTimeMillis;
+ property @Px public final int chinHeight;
property public final long digitalPreviewReferenceTimeMillis;
property public final boolean hasBurnInProtection;
property public final boolean hasLowBitAmbient;
@@ -319,8 +322,9 @@
}
public final class WatchState {
- ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis, boolean isHeadless);
+ ctor public WatchState(androidx.wear.watchface.ObservableWatchData<java.lang.Integer> interruptionFilter, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isAmbient, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isBatteryLowAndNotCharging, androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible, boolean hasLowBitAmbient, boolean hasBurnInProtection, long analogPreviewReferenceTimeMillis, long digitalPreviewReferenceTimeMillis, @Px int chinHeight, boolean isHeadless);
method public long getAnalogPreviewReferenceTimeMillis();
+ method @Px public int getChinHeight();
method public long getDigitalPreviewReferenceTimeMillis();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Integer> getInterruptionFilter();
method public boolean hasBurnInProtection();
@@ -329,6 +333,7 @@
method public boolean isHeadless();
method public androidx.wear.watchface.ObservableWatchData<java.lang.Boolean> isVisible();
property public final long analogPreviewReferenceTimeMillis;
+ property @Px public final int chinHeight;
property public final long digitalPreviewReferenceTimeMillis;
property public final boolean hasBurnInProtection;
property public final boolean hasLowBitAmbient;
diff --git a/wear/wear-watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java b/wear/wear-watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java
index 2becac8..eb93d21a 100644
--- a/wear/wear-watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java
+++ b/wear/wear-watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java
@@ -42,6 +42,7 @@
private static final long UPDATE_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1);
private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+ private final WatchState mWatchState;
private final Paint mPaint;
private final char[] mTimeText = new char[5];
@@ -51,6 +52,7 @@
@NotNull WatchState watchState) {
super(surfaceHolder, currentUserStyleRepository, watchState, CanvasType.HARDWARE,
UPDATE_DELAY_MILLIS);
+ mWatchState = watchState;
mPaint = new Paint();
mPaint.setTextAlign(Align.CENTER);
mPaint.setTextSize(64f);
@@ -69,6 +71,11 @@
mTimeText[2] = second % 2 == 0 ? ':' : ' ';
mTimeText[3] = DIGITS[minute / 10];
mTimeText[4] = DIGITS[minute % 10];
- canvas.drawText(mTimeText, 0, 5, rect.centerX(), rect.centerY(), mPaint);
+ canvas.drawText(mTimeText,
+ 0,
+ 5,
+ rect.centerX(),
+ rect.centerY() - mWatchState.getChinHeight(),
+ mPaint);
}
}
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt
index 7f0943b..ad5079f 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt
@@ -41,15 +41,17 @@
import androidx.wear.watchface.WatchFaceService
import androidx.wear.watchface.WatchFaceType
import androidx.wear.watchface.WatchState
+import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.Layer
import androidx.wear.watchface.style.UserStyle
-import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyleSchema
import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.BooleanUserStyleSetting.BooleanOption
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay
import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.DoubleRangeUserStyleSetting.DoubleRangeOption
import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.Option
import kotlin.math.cos
@@ -334,12 +336,10 @@
complication.renderer.drawable = watchFaceColorStyle.getDrawable(context)!!
}
- val drawPipsOption = userStyle[drawPipsStyleSetting]?.toBooleanOption()!!
- val watchHandLengthOption =
- userStyle[watchHandLengthStyleSettingDouble]?.toDoubleRangeOption()!!
-
- drawHourPips = drawPipsOption.value
- watchHandScale = watchHandLengthOption.value.toFloat()
+ drawHourPips = (userStyle[drawPipsStyleSetting]!! as BooleanOption).value
+ watchHandScale =
+ (userStyle[watchHandLengthStyleSettingDouble]!! as DoubleRangeOption)
+ .value.toFloat()
}
}
)
diff --git a/wear/wear-watchface/samples/src/main/res/values/dimens.xml b/wear/wear-watchface/samples/src/main/res/values/dimens.xml
index 2662e6e..5080c63c 100644
--- a/wear/wear-watchface/samples/src/main/res/values/dimens.xml
+++ b/wear/wear-watchface/samples/src/main/res/values/dimens.xml
@@ -17,6 +17,6 @@
<resources>
<!-- constants for watch face element renderer. -->
- <dimen name="hour_mark_size">10sp</dimen>
+ <dimen name="hour_mark_size">10dp</dimen>
<dimen name="clock_hand_stroke_width">2dp</dimen>
</resources>
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
index f39f8e4..121eae73 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
@@ -35,6 +35,7 @@
import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationsOption
import java.lang.ref.WeakReference
private fun getComponentName(context: Context) = ComponentName(
@@ -135,7 +136,7 @@
object : CurrentUserStyleRepository.UserStyleChangeListener {
override fun onUserStyleChanged(userStyle: UserStyle) {
val newlySelectedOption =
- userStyle[complicationsStyleCategory]?.toComplicationsOption()!!
+ userStyle[complicationsStyleCategory]!! as ComplicationsOption
if (previousOption != newlySelectedOption) {
previousOption = newlySelectedOption
applyComplicationsStyleCategoryOption(newlySelectedOption)
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 61b0832..a2d4a1a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -41,7 +41,9 @@
import android.view.Choreographer
import android.view.Surface
import android.view.SurfaceHolder
+import android.view.WindowInsets
import androidx.annotation.IntDef
+import androidx.annotation.Px
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
@@ -66,10 +68,10 @@
import androidx.wear.watchface.data.IdAndComplicationStateWireFormat
import androidx.wear.watchface.data.WatchUiState
import androidx.wear.watchface.editor.EditorService
-import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.CurrentUserStyleRepository
-import androidx.wear.watchface.style.UserStyleSetting
+import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.UserStyleData
+import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.data.UserStyleWireFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.android.asCoroutineDispatcher
@@ -375,6 +377,7 @@
internal var firstSetWatchUiState = true
internal var immutableSystemStateDone = false
+ internal var immutableChinHeightDone = false
private var ignoreNextOnVisibilityChanged = false
internal var lastActiveComplications: IntArray? = null
@@ -776,6 +779,29 @@
}
}
+ override fun onApplyWindowInsets(insets: WindowInsets?) {
+ super.onApplyWindowInsets(insets)
+ @Px val chinHeight =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ ChinHeightApi30.extractFromWindowInsets(insets)
+ } else {
+ ChinHeightApi25.extractFromWindowInsets(insets)
+ }
+ if (immutableChinHeightDone) {
+ // The chin size cannot change so this should be called only once.
+ if (mutableWatchState.chinHeight != chinHeight) {
+ Log.w(
+ TAG,
+ "unexpected chin size change ignored: " +
+ "${mutableWatchState.chinHeight} != $chinHeight"
+ )
+ }
+ return
+ }
+ mutableWatchState.chinHeight = chinHeight
+ immutableChinHeightDone = true
+ }
+
override fun onDestroy(): Unit = TraceEvent("EngineWrapper.onDestroy").use {
destroyed = true
coroutineScope.cancel()
@@ -1340,6 +1366,20 @@
HeadlessWatchFaceImpl.dump(indentingPrintWriter)
indentingPrintWriter.flush()
}
+
+ private object ChinHeightApi25 {
+ @Suppress("DEPRECATION")
+ @Px
+ fun extractFromWindowInsets(insets: WindowInsets?) =
+ insets?.systemWindowInsetBottom ?: 0
+ }
+
+ @RequiresApi(30)
+ private object ChinHeightApi30 {
+ @Px
+ fun extractFromWindowInsets(insets: WindowInsets?) =
+ insets?.getInsets(WindowInsets.Type.systemBars())?.bottom ?: 0
+ }
}
/**
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
index 7caeaed..d1ee966 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
@@ -17,6 +17,7 @@
package androidx.wear.watchface
import android.app.NotificationManager
+import androidx.annotation.Px
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
@@ -46,6 +47,9 @@
* milliseconds since the epoch.
* @param digitalPreviewReferenceTimeMillis UTC reference time for previews of digital watch faces
* in milliseconds since the epoch.
+ * @param chinHeight the size, in pixels, of the chin or zero if the device does not have a
+ * chin. A chin is a section at the bottom of a circular display that is visible due to
+ * hardware limitations.
* @param isHeadless Whether or not this is a headless watchface.
*/
public class WatchState(
@@ -60,6 +64,7 @@
public val hasBurnInProtection: Boolean,
public val analogPreviewReferenceTimeMillis: Long,
public val digitalPreviewReferenceTimeMillis: Long,
+ @Px @get:Px public val chinHeight: Int,
public val isHeadless: Boolean
) {
@UiThread
@@ -74,6 +79,7 @@
writer.println("hasBurnInProtection=$hasBurnInProtection")
writer.println("analogPreviewReferenceTimeMillis=$analogPreviewReferenceTimeMillis")
writer.println("digitalPreviewReferenceTimeMillis=$digitalPreviewReferenceTimeMillis")
+ writer.println("chinHeight=$chinHeight")
writer.println("isHeadless=$isHeadless")
writer.decreaseIndent()
}
@@ -91,6 +97,12 @@
public var hasBurnInProtection: Boolean = false
public var analogPreviewReferenceTimeMillis: Long = 0
public var digitalPreviewReferenceTimeMillis: Long = 0
+ @Px
+ public var chinHeight: Int = 0
+ @Px get
+ set(@Px value) {
+ field = value
+ }
public var isHeadless: Boolean = false
public fun asWatchState(): WatchState = WatchState(
@@ -102,6 +114,7 @@
hasBurnInProtection = hasBurnInProtection,
analogPreviewReferenceTimeMillis = analogPreviewReferenceTimeMillis,
digitalPreviewReferenceTimeMillis = digitalPreviewReferenceTimeMillis,
+ chinHeight = chinHeight,
isHeadless = isHeadless
)
}
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index db00764..8352418 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.content.Intent
import android.graphics.Color
+import android.graphics.Insets
import android.graphics.Rect
import android.graphics.RectF
import android.os.BatteryManager
@@ -35,7 +36,10 @@
import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
import android.view.SurfaceHolder
import android.view.ViewConfiguration
+import android.view.WindowInsets
+import androidx.annotation.Px
import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SdkSuppress
import androidx.wear.complications.ComplicationBounds
import androidx.wear.complications.DefaultComplicationProviderPolicy
import androidx.wear.complications.SystemProviders
@@ -50,9 +54,9 @@
import androidx.wear.watchface.data.DeviceConfig
import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
import androidx.wear.watchface.data.WatchUiState
+import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.Layer
import androidx.wear.watchface.style.UserStyle
-import androidx.wear.watchface.style.CurrentUserStyleRepository
import androidx.wear.watchface.style.UserStyleSchema
import androidx.wear.watchface.style.UserStyleSetting
import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting
@@ -1113,6 +1117,61 @@
)
}
+ @SdkSuppress(maxSdkVersion = 29)
+ @Test
+ public fun onApplyWindowInsetsBeforeR_setsChinHeight() {
+ initEngine(
+ WatchFaceType.ANALOG,
+ emptyList(),
+ UserStyleSchema(emptyList())
+ )
+ // Initially the chin size is set to zero.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(0)
+ // When window insets are delivered to the watch face.
+ engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 12))
+ // Then the chin size is updated.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
+ }
+
+ @SdkSuppress(minSdkVersion = 30)
+ @Test
+ public fun onApplyWindowInsetsRAndAbove_setsChinHeight() {
+ initEngine(
+ WatchFaceType.ANALOG,
+ emptyList(),
+ UserStyleSchema(emptyList())
+ )
+ // Initially the chin size is set to zero.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(0)
+ // When window insets are delivered to the watch face.
+ engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi30(chinHeight = 12))
+ // Then the chin size is updated.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
+ }
+
+ @Test
+ public fun onApplyWindowInsetsBeforeR_multipleCallsIgnored() {
+ initEngine(
+ WatchFaceType.ANALOG,
+ emptyList(),
+ UserStyleSchema(emptyList())
+ )
+ // Initially the chin size is set to zero.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(0)
+ // When window insets are delivered to the watch face.
+ engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 12))
+ // Then the chin size is updated.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
+ // When the same window insets are delivered to the watch face again.
+ engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 12))
+ // Nothing happens.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
+ // When different window insets are delivered to the watch face again.
+ engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 24))
+ // Nothing happens and the size is unchanged.
+ assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
+ }
+
@Test
public fun initWallpaperInteractiveWatchFaceInstanceWithUserStyle() {
initWallpaperInteractiveWatchFaceInstance(
@@ -2380,4 +2439,16 @@
engineWrapper.onVisibilityChanged(false)
verify(observer).onChanged(true)
}
+
+ @Suppress("DEPRECATION")
+ private fun getChinWindowInsetsApi25(@Px chinHeight: Int): WindowInsets =
+ WindowInsets.Builder().setSystemWindowInsets(
+ Insets.of(0, 0, 0, chinHeight)
+ ).build()
+
+ private fun getChinWindowInsetsApi30(@Px chinHeight: Int): WindowInsets =
+ WindowInsets.Builder().setInsets(
+ WindowInsets.Type.systemBars(),
+ Insets.of(Rect().apply { bottom = chinHeight })
+ ).build()
}
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchStateTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchStateTest.kt
index 221b3ad..bae221e 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchStateTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchStateTest.kt
@@ -125,6 +125,18 @@
}
@Test
+ public fun asWatchFace_chinHeight_isNotPropagated() {
+ val mutableWatchState = MutableWatchState()
+ var watchState = mutableWatchState.asWatchState()
+ // Defaults to 0.
+ assertThat(watchState.chinHeight).isEqualTo(0)
+ // Value updated is not propagated unless a new instance is created.
+ mutableWatchState.chinHeight = 48
+ assertThat(watchState.chinHeight).isEqualTo(0)
+ assertThat(mutableWatchState.asWatchState().chinHeight).isEqualTo(48)
+ }
+
+ @Test
public fun asWatchFace_isHeadless_isNotPropagated() {
val mutableWatchState = MutableWatchState()
var watchState = mutableWatchState.asWatchState()