Merge "Add Lifecycle and SavedState support in Launcher ActivityContext." into udc-dev
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 29b0f08..ffd56cc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -214,6 +214,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.ComposeInitializer;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.FloatingSurfaceView;
 import com.android.launcher3.views.OptionsPopupView;
@@ -553,6 +554,8 @@
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
         setContentView(getRootView());
+        ComposeInitializer.initCompose(this);
+
         if (mOnInitialBindListener != null) {
             getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
         }
diff --git a/src/com/android/launcher3/views/ComposeInitializer.java b/src/com/android/launcher3/views/ComposeInitializer.java
new file mode 100644
index 0000000..0929885
--- /dev/null
+++ b/src/com/android/launcher3/views/ComposeInitializer.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2023 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 com.android.launcher3.views;
+
+import android.os.Build;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewTreeLifecycleOwner;
+import androidx.savedstate.SavedStateRegistry;
+import androidx.savedstate.SavedStateRegistryController;
+import androidx.savedstate.SavedStateRegistryOwner;
+import androidx.savedstate.ViewTreeSavedStateRegistryOwner;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * An initializer to use Compose for classes implementing {@code ActivityContext}. This allows
+ * adding ComposeView to ViewTree outside a {@link androidx.activity.ComponentActivity}.
+ */
+public final class ComposeInitializer {
+    /**
+     * Performs the initialization to use Compose in the ViewTree of {@code target}.
+     */
+    public static void initCompose(ActivityContext target) {
+        getContentChild(target).addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        ComposeInitializer.onAttachedToWindow(v);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {
+                        ComposeInitializer.onDetachedFromWindow(v);
+                    }
+                });
+    }
+
+    /**
+     * Find the "content child" for {@code target}.
+     *
+     * @see "WindowRecomposer.android.kt: [View.contentChild]"
+     */
+    private static View getContentChild(ActivityContext target) {
+        View self = target.getDragLayer();
+        ViewParent parent = self.getParent();
+        while (parent instanceof View parentView) {
+            if (parentView.getId() == android.R.id.content) return self;
+            self = parentView;
+            parent = self.getParent();
+        }
+        return self;
+    }
+
+    /**
+     * Function to be called on your window root view's [View.onAttachedToWindow] function.
+     */
+    private static void onAttachedToWindow(View root) {
+        if (ViewTreeLifecycleOwner.get(root) != null) {
+            throw new IllegalStateException(
+                    "View " + root + " already has a LifecycleOwner");
+        }
+
+        ViewParent parent = root.getParent();
+        if (parent instanceof View && ((View) parent).getId() != android.R.id.content) {
+            throw new IllegalStateException(
+                    "ComposeInitializer.onContentChildAttachedToWindow(View) must be called on "
+                            + "the content child. Outside of activities and dialogs, this is "
+                            + "usually the top-most View of a window.");
+        }
+
+        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root]
+        // is both visible and focused.
+        ViewLifecycleOwner lifecycleOwner = new ViewLifecycleOwner(root);
+
+        // We must call [ViewLifecycleOwner.onCreate] after creating the
+        // [SavedStateRegistryOwner] because `onCreate` might move the lifecycle state to STARTED
+        // which will make [SavedStateRegistryController.performRestore] throw.
+        lifecycleOwner.onCreate();
+
+        // Set the owners on the root. They will be reused by any ComposeView inside the root
+        // hierarchy.
+        ViewTreeLifecycleOwner.set(root, lifecycleOwner);
+        ViewTreeSavedStateRegistryOwner.set(root, lifecycleOwner);
+    }
+
+    /**
+     * Function to be called on your window root view's [View.onDetachedFromWindow] function.
+     */
+    private static void onDetachedFromWindow(View root) {
+        final LifecycleOwner lifecycleOwner = ViewTreeLifecycleOwner.get(root);
+        if (lifecycleOwner != null) {
+            ((ViewLifecycleOwner) lifecycleOwner).onDestroy();
+        }
+        ViewTreeLifecycleOwner.set(root, null);
+        ViewTreeSavedStateRegistryOwner.set(root, null);
+    }
+
+    /**
+     * A [LifecycleOwner] for a [View] that updates lifecycle state based on window state.
+     *
+     * Also a trivial implementation of [SavedStateRegistryOwner] that does not do any save or
+     * restore. This works for processes similar to the SystemUI process, which is always running
+     * and top-level windows using this initialization are created once, when the process is
+     * started.
+     *
+     * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
+     * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is
+     * called, the implementation monitors window state in the following way
+     * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
+     * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
+     * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
+     *
+     * Or in table format:
+     * ```
+     * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
+     * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
+     * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
+     * │ Not attached  │                 Any              │       N/A       │
+     * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
+     * │               │    Not visible    │     Any      │     CREATED     │
+     * │               ├───────────────────┼──────────────┼─────────────────┤
+     * │   Attached    │                   │   No focus   │     STARTED     │
+     * │               │      Visible      ├──────────────┼─────────────────┤
+     * │               │                   │  Has focus   │     RESUMED     │
+     * └───────────────┴───────────────────┴──────────────┴─────────────────┘
+     * ```
+     */
+    private static class ViewLifecycleOwner implements SavedStateRegistryOwner {
+        private final ViewTreeObserver.OnWindowFocusChangeListener mWindowFocusListener =
+                hasFocus -> updateState();
+        private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+
+        private final SavedStateRegistryController mSavedStateRegistryController =
+                SavedStateRegistryController.create(this);
+
+        private final View mView;
+        private final Api34Impl mApi34Impl;
+
+        ViewLifecycleOwner(View view) {
+            mView = view;
+            if (Utilities.ATLEAST_U) {
+                mApi34Impl = new Api34Impl();
+            } else {
+                mApi34Impl = null;
+            }
+
+            mSavedStateRegistryController.performRestore(null);
+        }
+
+        @NonNull
+        @Override
+        public Lifecycle getLifecycle() {
+            return mLifecycleRegistry;
+        }
+
+        @NonNull
+        @Override
+        public SavedStateRegistry getSavedStateRegistry() {
+            return mSavedStateRegistryController.getSavedStateRegistry();
+        }
+
+        void onCreate() {
+            mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+            if (Utilities.ATLEAST_U) {
+                mApi34Impl.addOnWindowVisibilityChangeListener();
+            }
+            mView.getViewTreeObserver().addOnWindowFocusChangeListener(
+                    mWindowFocusListener);
+            updateState();
+        }
+
+        void onDestroy() {
+            if (Utilities.ATLEAST_U) {
+                mApi34Impl.removeOnWindowVisibilityChangeListener();
+            }
+            mView.getViewTreeObserver().removeOnWindowFocusChangeListener(
+                    mWindowFocusListener);
+            mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
+        }
+
+        private void updateState() {
+            Lifecycle.State state =
+                    mView.getWindowVisibility() != View.VISIBLE ? Lifecycle.State.CREATED
+                            : (!mView.hasWindowFocus() ? Lifecycle.State.STARTED
+                                    : Lifecycle.State.RESUMED);
+            mLifecycleRegistry.setCurrentState(state);
+        }
+
+        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        private class Api34Impl {
+            private final ViewTreeObserver.OnWindowVisibilityChangeListener
+                    mWindowVisibilityListener =
+                    visibility -> updateState();
+
+            void addOnWindowVisibilityChangeListener() {
+                mView.getViewTreeObserver().addOnWindowVisibilityChangeListener(
+                        mWindowVisibilityListener);
+            }
+
+            void removeOnWindowVisibilityChangeListener() {
+                mView.getViewTreeObserver().removeOnWindowVisibilityChangeListener(
+                        mWindowVisibilityListener);
+            }
+        }
+    }
+}