Migrate AmbientModeSupport to use LifecycleObserver.

This CL deprecates the AmbientModeSupport classes, instead creating
AmbientLifecycleObserver and DefaultAmbinetLifecycleObserver. These
classes use lifecycles instead, so should be easier to use. Moreover,
by splitting the implementation, it is possible to instead use testing
fakes for these classes.

Test: ./gradlew :wear:wear:test.
Relnote: "Migrate AmbientModeSupport to use LifecycleObserver. Deprecate
AmbientModeSupport in favour of the new lifecycle-aware classes."

Change-Id: I1593b4a089004310d965c9b79f79d1a70caba3ed
diff --git a/wear/wear-samples-ambient/build.gradle b/wear/wear-samples-ambient/build.gradle
index 00d04e6..4233ff8 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 0b8d171..521e1da 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() {