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() {