Merge "Migrate AmbientModeSupport to use LifecycleObserver." into androidx-main
diff --git a/wear/wear-samples-ambient/build.gradle b/wear/wear-samples-ambient/build.gradle
index 1cf4e6a..b73c613 100644
--- a/wear/wear-samples-ambient/build.gradle
+++ b/wear/wear-samples-ambient/build.gradle
@@ -24,8 +24,8 @@
dependencies {
api(project(":wear:wear"))
+ implementation("androidx.core:core:1.6.0")
implementation(libs.kotlinStdlib)
-
}
androidx {
diff --git a/wear/wear-samples-ambient/src/main/java/androidx/wear/samples/ambient/MainActivity.kt b/wear/wear-samples-ambient/src/main/java/androidx/wear/samples/ambient/MainActivity.kt
index 0b9ead7..1c8e558 100644
--- a/wear/wear-samples-ambient/src/main/java/androidx/wear/samples/ambient/MainActivity.kt
+++ b/wear/wear-samples-ambient/src/main/java/androidx/wear/samples/ambient/MainActivity.kt
@@ -5,15 +5,16 @@
import android.os.Looper
import android.util.Log
import android.widget.TextView
-import androidx.fragment.app.FragmentActivity
-import androidx.wear.ambient.AmbientModeSupport
+import androidx.core.app.ComponentActivity
+import androidx.wear.ambient.AmbientLifecycleObserver
+import androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientDetails
+import androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback
import java.text.SimpleDateFormat
import java.util.Date
/** Sample activity that provides an ambient experience. */
class MainActivity :
- FragmentActivity(R.layout.activity_main),
- AmbientModeSupport.AmbientCallbackProvider {
+ ComponentActivity() {
/** Used to dispatch periodic updates when the activity is in active mode. */
private val activeUpdatesHandler = Handler(Looper.getMainLooper())
@@ -22,7 +23,7 @@
private val model = MainViewModel()
/** The controller for ambient mode, initialized when the activity is created. */
- private lateinit var ambientController: AmbientModeSupport.AmbientController
+ private lateinit var ambientObserver: AmbientLifecycleObserver
// The views that are part of the activity.
private val timerTextView by lazy { findViewById<TextView>(R.id.timer) }
@@ -30,11 +31,31 @@
private val timestampTextView by lazy { findViewById<TextView>(R.id.timestamp) }
private val updatesTextView by lazy { findViewById<TextView>(R.id.updates) }
+ private val ambientCallback = object : AmbientLifecycleCallback {
+ override fun onEnterAmbient(ambientDetails: AmbientDetails) {
+ Log.d(TAG, "onEnterAmbient()")
+ model.setStatus(Status.AMBIENT)
+ model.publishUpdate()
+ }
+
+ override fun onUpdateAmbient() {
+ Log.d(TAG, "onUpdateAmbient()")
+ model.publishUpdate()
+ }
+
+ override fun onExitAmbient() {
+ Log.d(TAG, "onExitAmbient()")
+ model.setStatus(Status.ACTIVE)
+ model.publishUpdate()
+ schedule()
+ }
+ }
+
/** Invoked on [activeUpdatesHandler], posts an update when the activity is in active mode. */
private val mActiveUpdatesRunnable: Runnable =
Runnable {
// If invoked in ambient mode, do nothing.
- if (ambientController.isAmbient) {
+ if (ambientObserver.isAmbient()) {
return@Runnable
}
model.publishUpdate()
@@ -45,8 +66,10 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate")
+ setContentView(R.layout.activity_main)
observeModel()
- ambientController = AmbientModeSupport.attach(this)
+ ambientObserver = AmbientLifecycleObserver(this, ambientCallback)
+ this.lifecycle.addObserver(ambientObserver)
}
override fun onStart() {
@@ -78,29 +101,6 @@
super.onDestroy()
}
- override fun getAmbientCallback() = object : AmbientModeSupport.AmbientCallback() {
- override fun onEnterAmbient(ambientDetails: Bundle) {
- super.onEnterAmbient(ambientDetails)
- Log.d(TAG, "onEnterAmbient()")
- model.setStatus(Status.AMBIENT)
- model.publishUpdate()
- }
-
- override fun onUpdateAmbient() {
- super.onUpdateAmbient()
- Log.d(TAG, "onUpdateAmbient()")
- model.publishUpdate()
- }
-
- override fun onExitAmbient() {
- super.onExitAmbient()
- Log.d(TAG, "onExitAmbient()")
- model.setStatus(Status.ACTIVE)
- model.publishUpdate()
- schedule()
- }
- }
-
private fun observeModel() {
model.observeStartTime(this) {
timerTextView.text = formatTimer(model.getTimer())
diff --git a/wear/wear/api/current.txt b/wear/wear/api/current.txt
index 30a2fbd..b1e72e0 100644
--- a/wear/wear/api/current.txt
+++ b/wear/wear/api/current.txt
@@ -17,6 +17,30 @@
package androidx.wear.ambient {
+ public final class AmbientLifecycleObserver implements androidx.wear.ambient.AmbientLifecycleObserverInterface {
+ ctor public AmbientLifecycleObserver(android.app.Activity activity, java.util.concurrent.Executor callbackExecutor, androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback callbacks);
+ ctor public AmbientLifecycleObserver(android.app.Activity activity, androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback callbacks);
+ method public boolean isAmbient();
+ }
+
+ public interface AmbientLifecycleObserverInterface extends androidx.lifecycle.DefaultLifecycleObserver {
+ method public boolean isAmbient();
+ }
+
+ public static final class AmbientLifecycleObserverInterface.AmbientDetails {
+ ctor public AmbientLifecycleObserverInterface.AmbientDetails(boolean burnInProtectionRequired, boolean deviceHasLowBitAmbient);
+ method public boolean getBurnInProtectionRequired();
+ method public boolean getDeviceHasLowBitAmbient();
+ property public final boolean burnInProtectionRequired;
+ property public final boolean deviceHasLowBitAmbient;
+ }
+
+ public static interface AmbientLifecycleObserverInterface.AmbientLifecycleCallback {
+ method public default void onEnterAmbient(androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientDetails ambientDetails);
+ method public default void onExitAmbient();
+ method public default void onUpdateAmbient();
+ }
+
@Deprecated public final class AmbientMode extends android.app.Fragment {
ctor @Deprecated public AmbientMode();
method @Deprecated public static <T extends android.app.Activity> androidx.wear.ambient.AmbientMode.AmbientController! attachAmbientSupport(T!);
@@ -50,30 +74,30 @@
method @Deprecated public void setAmbientOffloadEnabled(boolean);
}
- public final class AmbientModeSupport extends androidx.fragment.app.Fragment {
- ctor public AmbientModeSupport();
- method public static <T extends androidx.fragment.app.FragmentActivity> androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!);
- field public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
- field public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
- field public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
+ @Deprecated public final class AmbientModeSupport extends androidx.fragment.app.Fragment {
+ ctor @Deprecated public AmbientModeSupport();
+ method @Deprecated public static <T extends androidx.fragment.app.FragmentActivity> androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!);
+ field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
+ field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
+ field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
}
- public abstract static class AmbientModeSupport.AmbientCallback {
- ctor public AmbientModeSupport.AmbientCallback();
- method public void onAmbientOffloadInvalidated();
- method public void onEnterAmbient(android.os.Bundle!);
- method public void onExitAmbient();
- method public void onUpdateAmbient();
+ @Deprecated public abstract static class AmbientModeSupport.AmbientCallback {
+ ctor @Deprecated public AmbientModeSupport.AmbientCallback();
+ method @Deprecated public void onAmbientOffloadInvalidated();
+ method @Deprecated public void onEnterAmbient(android.os.Bundle!);
+ method @Deprecated public void onExitAmbient();
+ method @Deprecated public void onUpdateAmbient();
}
- public static interface AmbientModeSupport.AmbientCallbackProvider {
- method public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback();
+ @Deprecated public static interface AmbientModeSupport.AmbientCallbackProvider {
+ method @Deprecated public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback();
}
- public final class AmbientModeSupport.AmbientController {
- method public boolean isAmbient();
- method public void setAmbientOffloadEnabled(boolean);
- method public void setAutoResumeEnabled(boolean);
+ @Deprecated public final class AmbientModeSupport.AmbientController {
+ method @Deprecated public boolean isAmbient();
+ method @Deprecated public void setAmbientOffloadEnabled(boolean);
+ method @Deprecated public void setAutoResumeEnabled(boolean);
}
}
diff --git a/wear/wear/api/public_plus_experimental_current.txt b/wear/wear/api/public_plus_experimental_current.txt
index 30a2fbd..b1e72e0 100644
--- a/wear/wear/api/public_plus_experimental_current.txt
+++ b/wear/wear/api/public_plus_experimental_current.txt
@@ -17,6 +17,30 @@
package androidx.wear.ambient {
+ public final class AmbientLifecycleObserver implements androidx.wear.ambient.AmbientLifecycleObserverInterface {
+ ctor public AmbientLifecycleObserver(android.app.Activity activity, java.util.concurrent.Executor callbackExecutor, androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback callbacks);
+ ctor public AmbientLifecycleObserver(android.app.Activity activity, androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback callbacks);
+ method public boolean isAmbient();
+ }
+
+ public interface AmbientLifecycleObserverInterface extends androidx.lifecycle.DefaultLifecycleObserver {
+ method public boolean isAmbient();
+ }
+
+ public static final class AmbientLifecycleObserverInterface.AmbientDetails {
+ ctor public AmbientLifecycleObserverInterface.AmbientDetails(boolean burnInProtectionRequired, boolean deviceHasLowBitAmbient);
+ method public boolean getBurnInProtectionRequired();
+ method public boolean getDeviceHasLowBitAmbient();
+ property public final boolean burnInProtectionRequired;
+ property public final boolean deviceHasLowBitAmbient;
+ }
+
+ public static interface AmbientLifecycleObserverInterface.AmbientLifecycleCallback {
+ method public default void onEnterAmbient(androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientDetails ambientDetails);
+ method public default void onExitAmbient();
+ method public default void onUpdateAmbient();
+ }
+
@Deprecated public final class AmbientMode extends android.app.Fragment {
ctor @Deprecated public AmbientMode();
method @Deprecated public static <T extends android.app.Activity> androidx.wear.ambient.AmbientMode.AmbientController! attachAmbientSupport(T!);
@@ -50,30 +74,30 @@
method @Deprecated public void setAmbientOffloadEnabled(boolean);
}
- public final class AmbientModeSupport extends androidx.fragment.app.Fragment {
- ctor public AmbientModeSupport();
- method public static <T extends androidx.fragment.app.FragmentActivity> androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!);
- field public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
- field public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
- field public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
+ @Deprecated public final class AmbientModeSupport extends androidx.fragment.app.Fragment {
+ ctor @Deprecated public AmbientModeSupport();
+ method @Deprecated public static <T extends androidx.fragment.app.FragmentActivity> androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!);
+ field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
+ field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
+ field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
}
- public abstract static class AmbientModeSupport.AmbientCallback {
- ctor public AmbientModeSupport.AmbientCallback();
- method public void onAmbientOffloadInvalidated();
- method public void onEnterAmbient(android.os.Bundle!);
- method public void onExitAmbient();
- method public void onUpdateAmbient();
+ @Deprecated public abstract static class AmbientModeSupport.AmbientCallback {
+ ctor @Deprecated public AmbientModeSupport.AmbientCallback();
+ method @Deprecated public void onAmbientOffloadInvalidated();
+ method @Deprecated public void onEnterAmbient(android.os.Bundle!);
+ method @Deprecated public void onExitAmbient();
+ method @Deprecated public void onUpdateAmbient();
}
- public static interface AmbientModeSupport.AmbientCallbackProvider {
- method public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback();
+ @Deprecated public static interface AmbientModeSupport.AmbientCallbackProvider {
+ method @Deprecated public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback();
}
- public final class AmbientModeSupport.AmbientController {
- method public boolean isAmbient();
- method public void setAmbientOffloadEnabled(boolean);
- method public void setAutoResumeEnabled(boolean);
+ @Deprecated public final class AmbientModeSupport.AmbientController {
+ method @Deprecated public boolean isAmbient();
+ method @Deprecated public void setAmbientOffloadEnabled(boolean);
+ method @Deprecated public void setAutoResumeEnabled(boolean);
}
}
diff --git a/wear/wear/api/restricted_current.txt b/wear/wear/api/restricted_current.txt
index 38baf3e1..023be62 100644
--- a/wear/wear/api/restricted_current.txt
+++ b/wear/wear/api/restricted_current.txt
@@ -17,6 +17,30 @@
package androidx.wear.ambient {
+ public final class AmbientLifecycleObserver implements androidx.wear.ambient.AmbientLifecycleObserverInterface {
+ ctor public AmbientLifecycleObserver(android.app.Activity activity, java.util.concurrent.Executor callbackExecutor, androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback callbacks);
+ ctor public AmbientLifecycleObserver(android.app.Activity activity, androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientLifecycleCallback callbacks);
+ method public boolean isAmbient();
+ }
+
+ public interface AmbientLifecycleObserverInterface extends androidx.lifecycle.DefaultLifecycleObserver {
+ method public boolean isAmbient();
+ }
+
+ public static final class AmbientLifecycleObserverInterface.AmbientDetails {
+ ctor public AmbientLifecycleObserverInterface.AmbientDetails(boolean burnInProtectionRequired, boolean deviceHasLowBitAmbient);
+ method public boolean getBurnInProtectionRequired();
+ method public boolean getDeviceHasLowBitAmbient();
+ property public final boolean burnInProtectionRequired;
+ property public final boolean deviceHasLowBitAmbient;
+ }
+
+ public static interface AmbientLifecycleObserverInterface.AmbientLifecycleCallback {
+ method public default void onEnterAmbient(androidx.wear.ambient.AmbientLifecycleObserverInterface.AmbientDetails ambientDetails);
+ method public default void onExitAmbient();
+ method public default void onUpdateAmbient();
+ }
+
@Deprecated public final class AmbientMode extends android.app.Fragment {
ctor @Deprecated public AmbientMode();
method @Deprecated public static <T extends android.app.Activity> androidx.wear.ambient.AmbientMode.AmbientController! attachAmbientSupport(T!);
@@ -50,30 +74,30 @@
method @Deprecated public void setAmbientOffloadEnabled(boolean);
}
- public final class AmbientModeSupport extends androidx.fragment.app.Fragment {
- ctor public AmbientModeSupport();
- method public static <T extends androidx.fragment.app.FragmentActivity> androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!);
- field public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
- field public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
- field public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
+ @Deprecated public final class AmbientModeSupport extends androidx.fragment.app.Fragment {
+ ctor @Deprecated public AmbientModeSupport();
+ method @Deprecated public static <T extends androidx.fragment.app.FragmentActivity> androidx.wear.ambient.AmbientModeSupport.AmbientController! attach(T!);
+ field @Deprecated public static final String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
+ field @Deprecated public static final String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
+ field @Deprecated public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
}
- public abstract static class AmbientModeSupport.AmbientCallback {
- ctor public AmbientModeSupport.AmbientCallback();
- method public void onAmbientOffloadInvalidated();
- method public void onEnterAmbient(android.os.Bundle!);
- method public void onExitAmbient();
- method public void onUpdateAmbient();
+ @Deprecated public abstract static class AmbientModeSupport.AmbientCallback {
+ ctor @Deprecated public AmbientModeSupport.AmbientCallback();
+ method @Deprecated public void onAmbientOffloadInvalidated();
+ method @Deprecated public void onEnterAmbient(android.os.Bundle!);
+ method @Deprecated public void onExitAmbient();
+ method @Deprecated public void onUpdateAmbient();
}
- public static interface AmbientModeSupport.AmbientCallbackProvider {
- method public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback();
+ @Deprecated public static interface AmbientModeSupport.AmbientCallbackProvider {
+ method @Deprecated public androidx.wear.ambient.AmbientModeSupport.AmbientCallback! getAmbientCallback();
}
- public final class AmbientModeSupport.AmbientController {
- method public boolean isAmbient();
- method public void setAmbientOffloadEnabled(boolean);
- method public void setAutoResumeEnabled(boolean);
+ @Deprecated public final class AmbientModeSupport.AmbientController {
+ method @Deprecated public boolean isAmbient();
+ method @Deprecated public void setAmbientOffloadEnabled(boolean);
+ method @Deprecated public void setAutoResumeEnabled(boolean);
}
}
diff --git a/wear/wear/build.gradle b/wear/wear/build.gradle
index a85136e..83bcb50 100644
--- a/wear/wear/build.gradle
+++ b/wear/wear/build.gradle
@@ -14,6 +14,7 @@
api("androidx.core:core:1.6.0")
api("androidx.versionedparcelable:versionedparcelable:1.1.1")
api('androidx.dynamicanimation:dynamicanimation:1.0.0')
+ api('androidx.lifecycle:lifecycle-runtime:2.5.1')
androidTestImplementation(project(":test:screenshot:screenshot"))
androidTestImplementation(libs.kotlinStdlib)
diff --git a/wear/wear/src/main/java/androidx/wear/ambient/AmbientLifecycleObserver.kt b/wear/wear/src/main/java/androidx/wear/ambient/AmbientLifecycleObserver.kt
new file mode 100644
index 0000000..7bafa39
--- /dev/null
+++ b/wear/wear/src/main/java/androidx/wear/ambient/AmbientLifecycleObserver.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2022 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.ambient
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.lifecycle.LifecycleOwner
+import com.google.android.wearable.compat.WearableActivityController
+import java.util.concurrent.Executor
+
+/**
+ * Lifecycle Observer which can be used to add ambient support to an activity on Wearable devices.
+ *
+ * Applications which wish to show layouts in ambient mode should attach this observer to their
+ * activities or fragments, passing in a set of callback to be notified about ambient state. In
+ * addition, the app needs to declare that it uses the [android.Manifest.permission.WAKE_LOCK]
+ * permission in its manifest.
+ *
+ * The created [AmbientLifecycleObserver] can also be used to query whether the device is in
+ * ambient mode.
+ *
+ * As an example of how to use this class, see the following example:
+ *
+ * ```
+ * class MyActivity : ComponentActivity() {
+ * private val callbacks = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
+ * // ...
+ * }
+ *
+ * private val ambientObserver = DefaultAmbientLifecycleObserver(this, callbacks)
+ *
+ * override fun onCreate(savedInstanceState: Bundle) {
+ * lifecycle.addObserver(ambientObserver)
+ * }
+ * }
+ * ```
+ *
+ * @param activity The activity that this observer is being attached to.
+ * @param callbackExecutor The executor to run the provided callbacks on.
+ * @param callbacks An instance of [AmbientLifecycleObserverInterface.AmbientLifecycleCallback], used to
+ * notify the observer about changes to the ambient state.
+ */
+@Suppress("CallbackName")
+class AmbientLifecycleObserver(
+ activity: Activity,
+ callbackExecutor: Executor,
+ callbacks: AmbientLifecycleObserverInterface.AmbientLifecycleCallback,
+) : AmbientLifecycleObserverInterface {
+ private val delegate: AmbientDelegate
+ private val callbackTranslator = object : AmbientDelegate.AmbientCallback {
+ override fun onEnterAmbient(ambientDetails: Bundle?) {
+ val burnInProtection = ambientDetails?.getBoolean(
+ WearableActivityController.EXTRA_BURN_IN_PROTECTION) ?: false
+ val lowBitAmbient = ambientDetails?.getBoolean(
+ WearableActivityController.EXTRA_LOWBIT_AMBIENT) ?: false
+ callbackExecutor.run {
+ callbacks.onEnterAmbient(AmbientLifecycleObserverInterface.AmbientDetails(
+ burnInProtectionRequired = burnInProtection,
+ deviceHasLowBitAmbient = lowBitAmbient
+ ))
+ }
+ }
+
+ override fun onUpdateAmbient() {
+ callbackExecutor.run { callbacks.onUpdateAmbient() }
+ }
+
+ override fun onExitAmbient() {
+ callbackExecutor.run { callbacks.onExitAmbient() }
+ }
+
+ override fun onAmbientOffloadInvalidated() {
+ }
+ }
+
+ /**
+ * Construct a [AmbientLifecycleObserver], using the UI thread to dispatch ambient
+ * callbacks.
+ *
+ * @param activity The activity that this observer is being attached to.
+ * @param callbacks An instance of [AmbientLifecycleObserverInterface.AmbientLifecycleCallback], used to
+ * notify the observer about changes to the ambient state.
+ */
+ constructor(
+ activity: Activity,
+ callbacks: AmbientLifecycleObserverInterface.AmbientLifecycleCallback
+ ) : this(activity, { r -> r.run() }, callbacks)
+
+ init {
+ delegate = AmbientDelegate(activity, WearableControllerProvider(), callbackTranslator)
+ }
+
+ override fun isAmbient(): Boolean = delegate.isAmbient
+
+ override fun onCreate(owner: LifecycleOwner) {
+ super.onCreate(owner)
+ delegate.onCreate()
+ delegate.setAmbientEnabled()
+ }
+
+ override fun onResume(owner: LifecycleOwner) {
+ super.onResume(owner)
+ delegate.onResume()
+ }
+
+ override fun onPause(owner: LifecycleOwner) {
+ super.onPause(owner)
+ delegate.onPause()
+ }
+
+ override fun onStop(owner: LifecycleOwner) {
+ super.onStop(owner)
+ delegate.onStop()
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ super.onDestroy(owner)
+ delegate.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/wear/wear/src/main/java/androidx/wear/ambient/AmbientLifecycleObserverInterface.kt b/wear/wear/src/main/java/androidx/wear/ambient/AmbientLifecycleObserverInterface.kt
new file mode 100644
index 0000000..353a695
--- /dev/null
+++ b/wear/wear/src/main/java/androidx/wear/ambient/AmbientLifecycleObserverInterface.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2022 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.ambient
+
+import androidx.lifecycle.DefaultLifecycleObserver
+
+/**
+ * Interface for LifecycleObservers which are used to add ambient mode support to activities on
+ * Wearable devices.
+ *
+ * This interface can be implemented, or faked out, to allow for testing activities which use
+ * ambient support. Applications should use [AmbientLifecycleObserver] to implement ambient support
+ * on real devices.
+ */
+@Suppress("CallbackName")
+interface AmbientLifecycleObserverInterface : DefaultLifecycleObserver {
+ /**
+ * Details about ambient mode support on the current device, passed to
+ * [AmbientLifecycleCallback.onEnterAmbient].
+ *
+ * @param burnInProtectionRequired whether the ambient layout must implement burn-in protection.
+ * When this property is set to true, views must be shifted around periodically in ambient
+ * mode. To ensure that content isn't shifted off the screen, avoid placing content within
+ * 10 pixels of the edge of the screen. Activities should also avoid solid white areas to
+ * prevent pixel burn-in. Both of these requirements only apply in ambient mode, and only
+ * when this property is set to true.
+ * @param deviceHasLowBitAmbient whether this device has low-bit ambient mode. When this
+ * property is set to true, the screen supports fewer bits for each color in ambient mode.
+ * In this case, activities should disable anti-aliasing in ambient mode.
+ */
+ class AmbientDetails(
+ val burnInProtectionRequired: Boolean,
+ val deviceHasLowBitAmbient: Boolean
+ ) {
+ override fun toString(): String =
+ "AmbientDetails - burnInProtectionRequired: $burnInProtectionRequired, " +
+ "deviceHasLowBitAmbient: $deviceHasLowBitAmbient"
+ }
+
+ /** Callback to receive ambient mode state changes. */
+ interface AmbientLifecycleCallback {
+ /**
+ * Called when an activity is entering ambient mode. This event is sent while an activity is
+ * running (after onResume, before onPause). All drawing should complete by the conclusion
+ * of this method. Note that {@code invalidate()} calls will be executed before resuming
+ * lower-power mode.
+ *
+ * @param ambientDetails instance of [AmbientDetails] containing information about the
+ * display being used.
+ */
+ fun onEnterAmbient(ambientDetails: AmbientDetails) {}
+
+ /**
+ * Called when the system is updating the display for ambient mode. Activities may use this
+ * opportunity to update or invalidate views.
+ */
+ fun onUpdateAmbient() {}
+
+ /**
+ * Called when an activity should exit ambient mode. This event is sent while an activity is
+ * running (after onResume, before onPause).
+ */
+ fun onExitAmbient() {}
+ }
+
+ /**
+ * @return {@code true} if the activity is currently in ambient.
+ */
+ fun isAmbient(): Boolean
+}
diff --git a/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java b/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java
index 0b4b539..80dcd2f 100644
--- a/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java
+++ b/wear/wear/src/main/java/androidx/wear/ambient/AmbientModeSupport.java
@@ -77,7 +77,12 @@
* }
* }
* }</pre>
+ *
+ * @deprecated Use {@link AmbientLifecycleObserverInterface} and {@link AmbientLifecycleObserver}
+ * instead. These classes use lifecycle components instead, preventing the need to hook these
+ * events using fragments.
*/
+@Deprecated
public final class AmbientModeSupport extends Fragment {
private static final String TAG = "AmbientModeSupport";
diff --git a/wear/wear/src/test/java/androidx/wear/ambient/AmbientLifecycleObserverTest.kt b/wear/wear/src/test/java/androidx/wear/ambient/AmbientLifecycleObserverTest.kt
new file mode 100644
index 0000000..d0fee22
--- /dev/null
+++ b/wear/wear/src/test/java/androidx/wear/ambient/AmbientLifecycleObserverTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 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.ambient
+
+import android.os.Bundle
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.android.wearable.compat.WearableActivityController
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AmbientLifecycleObserverTest {
+ private lateinit var scenario: ActivityScenario<AmbientLifecycleObserverTestActivity>
+
+ @Before
+ fun setUp() {
+ scenario = AmbientTestActivityUtil.launchActivity(
+ AmbientLifecycleObserverTestActivity::class.java)
+ }
+
+ private fun resetState(controller: WearableActivityController) {
+ controller.mCreateCalled = false
+ controller.mDestroyCalled = false
+ controller.mPauseCalled = false
+ controller.mResumeCalled = false
+ controller.mStopCalled = false
+ }
+
+ @Test
+ fun testEnterAmbientCallback() {
+ scenario.onActivity { activity ->
+ WearableActivityController.getLastInstance().enterAmbient()
+ assertTrue(activity.enterAmbientCalled)
+ assertFalse(activity.exitAmbientCalled)
+ assertFalse(activity.updateAmbientCalled)
+
+ assertNotNull(activity.enterAmbientArgs)
+
+ // Nothing in the bundle, both should be false.
+ assertFalse(activity.enterAmbientArgs!!.burnInProtectionRequired)
+ assertFalse(activity.enterAmbientArgs!!.deviceHasLowBitAmbient)
+ }
+ }
+
+ @Test
+ fun testEnterAmbientCallbackWithArgs() {
+ scenario.onActivity { activity ->
+ val bundle = Bundle()
+ bundle.putBoolean(WearableActivityController.EXTRA_LOWBIT_AMBIENT, true)
+ bundle.putBoolean(WearableActivityController.EXTRA_BURN_IN_PROTECTION, true)
+
+ WearableActivityController.getLastInstance().enterAmbient(bundle)
+
+ assertTrue(activity.enterAmbientArgs!!.burnInProtectionRequired)
+ assertTrue(activity.enterAmbientArgs!!.deviceHasLowBitAmbient)
+ }
+ }
+
+ @Test
+ fun testExitAmbientCallback() {
+ scenario.onActivity { activity ->
+ WearableActivityController.getLastInstance().exitAmbient()
+ assertFalse(activity.enterAmbientCalled)
+ assertTrue(activity.exitAmbientCalled)
+ assertFalse(activity.updateAmbientCalled)
+ }
+ }
+
+ @Test
+ fun testUpdateAmbientCallback() {
+ scenario.onActivity { activity ->
+ WearableActivityController.getLastInstance().updateAmbient()
+ assertFalse(activity.enterAmbientCalled)
+ assertFalse(activity.exitAmbientCalled)
+ assertTrue(activity.updateAmbientCalled)
+ }
+ }
+
+ @Test
+ fun onCreateCanPassThrough() {
+ // Default after launch is that the activity is running.
+ val controller = WearableActivityController.getLastInstance()
+ assertTrue(controller.mCreateCalled)
+ assertFalse(controller.mDestroyCalled)
+ assertFalse(controller.mPauseCalled)
+ assertTrue(controller.mResumeCalled)
+ assertFalse(controller.mStopCalled)
+ }
+
+ @Test
+ fun onPauseCanPassThrough() {
+ val controller = WearableActivityController.getLastInstance()
+ resetState(controller)
+
+ // Note: STARTED is when the app is paused; RUNNING is when it's actually running.
+ scenario.moveToState(Lifecycle.State.STARTED)
+
+ assertFalse(controller.mCreateCalled)
+ assertFalse(controller.mDestroyCalled)
+ assertTrue(controller.mPauseCalled)
+ assertFalse(controller.mResumeCalled)
+ assertFalse(controller.mStopCalled)
+ }
+
+ @Test
+ fun onStopCanPassThrough() {
+ val controller = WearableActivityController.getLastInstance()
+ resetState(controller)
+
+ scenario.moveToState(Lifecycle.State.CREATED)
+
+ assertFalse(controller.mCreateCalled)
+ assertFalse(controller.mDestroyCalled)
+ assertTrue(controller.mPauseCalled)
+ assertFalse(controller.mResumeCalled)
+ assertTrue(controller.mStopCalled)
+ }
+
+ @Test
+ fun onDestroyCanPassThrough() {
+ val controller = WearableActivityController.getLastInstance()
+ resetState(controller)
+
+ scenario.moveToState(Lifecycle.State.DESTROYED)
+
+ assertFalse(controller.mCreateCalled)
+ assertTrue(controller.mDestroyCalled)
+ assertTrue(controller.mPauseCalled)
+ assertFalse(controller.mResumeCalled)
+ assertTrue(controller.mStopCalled)
+ }
+
+ @Test
+ fun canQueryInAmbient() {
+ scenario.onActivity { activity ->
+ val controller = WearableActivityController.getLastInstance()
+ assertFalse(activity.observer.isAmbient())
+ controller.isAmbient = true
+ assertTrue(activity.observer.isAmbient())
+ }
+ }
+}
diff --git a/wear/wear/src/test/java/androidx/wear/ambient/AmbientLifecycleObserverTestActivity.kt b/wear/wear/src/test/java/androidx/wear/ambient/AmbientLifecycleObserverTestActivity.kt
new file mode 100644
index 0000000..508c401
--- /dev/null
+++ b/wear/wear/src/test/java/androidx/wear/ambient/AmbientLifecycleObserverTestActivity.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 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.ambient
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+
+class AmbientLifecycleObserverTestActivity : ComponentActivity() {
+ private val callback = object : AmbientLifecycleObserverInterface.AmbientLifecycleCallback {
+ override fun onEnterAmbient(
+ ambientDetails: AmbientLifecycleObserverInterface.AmbientDetails
+ ) {
+ enterAmbientCalled = true
+ enterAmbientArgs = ambientDetails
+ }
+
+ override fun onUpdateAmbient() {
+ updateAmbientCalled = true
+ }
+
+ override fun onExitAmbient() {
+ exitAmbientCalled = true
+ }
+ }
+
+ val observer = AmbientLifecycleObserver(this, { r -> r.run() }, callback)
+
+ var enterAmbientCalled = false
+ var enterAmbientArgs: AmbientLifecycleObserverInterface.AmbientDetails? = null
+ var updateAmbientCalled = false
+ var exitAmbientCalled = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ lifecycle.addObserver(observer)
+ }
+}
\ No newline at end of file
diff --git a/wear/wear/src/test/java/com/google/android/wearable/compat/WearableActivityController.java b/wear/wear/src/test/java/com/google/android/wearable/compat/WearableActivityController.java
index eb4d559..79f2291 100644
--- a/wear/wear/src/test/java/com/google/android/wearable/compat/WearableActivityController.java
+++ b/wear/wear/src/test/java/com/google/android/wearable/compat/WearableActivityController.java
@@ -19,11 +19,17 @@
import android.app.Activity;
import android.os.Bundle;
+import androidx.annotation.Nullable;
+
/**
* Mock version of {@link WearableActivityController}. During instrumentation testing, the tests
* would end up using this instead of the version implemented on device.
*/
public class WearableActivityController {
+ public static final java.lang.String EXTRA_BURN_IN_PROTECTION =
+ "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
+ public static final java.lang.String EXTRA_LOWBIT_AMBIENT =
+ "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
private static WearableActivityController sLastInstance;
@@ -37,20 +43,44 @@
private boolean mAmbient = false;
private boolean mAmbientOffloadEnabled = false;
+ public boolean mCreateCalled = false;
+ public boolean mResumeCalled = false;
+ public boolean mPauseCalled = false;
+ public boolean mStopCalled = false;
+ public boolean mDestroyCalled = false;
+
public WearableActivityController(String tag, Activity activity, AmbientCallback callback) {
sLastInstance = this;
mCallback = callback;
}
// Methods required by the stub but not currently used in tests.
- public void onCreate() {}
- public void onResume() {}
- public void onPause() {}
- public void onStop() {}
- public void onDestroy() {}
+ public void onCreate() {
+ mCreateCalled = true;
+ }
+
+ public void onResume() {
+ mResumeCalled = true;
+ }
+
+ public void onPause() {
+ mPauseCalled = true;
+ }
+
+ public void onStop() {
+ mStopCalled = true;
+ }
+
+ public void onDestroy() {
+ mDestroyCalled = true;
+ }
public void enterAmbient() {
- mCallback.onEnterAmbient(null);
+ enterAmbient(null);
+ }
+
+ public void enterAmbient(@Nullable Bundle enterAmbientArgs) {
+ mCallback.onEnterAmbient(enterAmbientArgs);
}
public void exitAmbient() {