1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.views;
18 
19 import android.os.Build;
20 import android.view.View;
21 import android.view.ViewParent;
22 import android.view.ViewTreeObserver;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.RequiresApi;
26 import androidx.lifecycle.Lifecycle;
27 import androidx.lifecycle.LifecycleOwner;
28 import androidx.lifecycle.LifecycleRegistry;
29 import androidx.lifecycle.ViewTreeLifecycleOwner;
30 import androidx.savedstate.SavedStateRegistry;
31 import androidx.savedstate.SavedStateRegistryController;
32 import androidx.savedstate.SavedStateRegistryOwner;
33 import androidx.savedstate.ViewTreeSavedStateRegistryOwner;
34 
35 import com.android.launcher3.Utilities;
36 
37 /**
38  * An initializer to use Compose for classes implementing {@code ActivityContext}. This allows
39  * adding ComposeView to ViewTree outside a {@link androidx.activity.ComponentActivity}.
40  */
41 public final class ComposeInitializer {
42     /**
43      * Performs the initialization to use Compose in the ViewTree of {@code target}.
44      */
initCompose(ActivityContext target)45     public static void initCompose(ActivityContext target) {
46         getContentChild(target).addOnAttachStateChangeListener(
47                 new View.OnAttachStateChangeListener() {
48 
49                     @Override
50                     public void onViewAttachedToWindow(View v) {
51                         ComposeInitializer.onAttachedToWindow(v);
52                     }
53 
54                     @Override
55                     public void onViewDetachedFromWindow(View v) {
56                         ComposeInitializer.onDetachedFromWindow(v);
57                     }
58                 });
59     }
60 
61     /**
62      * Find the "content child" for {@code target}.
63      *
64      * @see "WindowRecomposer.android.kt: [View.contentChild]"
65      */
getContentChild(ActivityContext target)66     private static View getContentChild(ActivityContext target) {
67         View self = target.getDragLayer();
68         ViewParent parent = self.getParent();
69         while (parent instanceof View parentView) {
70             if (parentView.getId() == android.R.id.content) return self;
71             self = parentView;
72             parent = self.getParent();
73         }
74         return self;
75     }
76 
77     /**
78      * Function to be called on your window root view's [View.onAttachedToWindow] function.
79      */
onAttachedToWindow(View root)80     private static void onAttachedToWindow(View root) {
81         if (ViewTreeLifecycleOwner.get(root) != null) {
82             throw new IllegalStateException(
83                     "View " + root + " already has a LifecycleOwner");
84         }
85 
86         ViewParent parent = root.getParent();
87         if (parent instanceof View && ((View) parent).getId() != android.R.id.content) {
88             throw new IllegalStateException(
89                     "ComposeInitializer.onContentChildAttachedToWindow(View) must be called on "
90                             + "the content child. Outside of activities and dialogs, this is "
91                             + "usually the top-most View of a window.");
92         }
93 
94         // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root]
95         // is both visible and focused.
96         ViewLifecycleOwner lifecycleOwner = new ViewLifecycleOwner(root);
97 
98         // We must call [ViewLifecycleOwner.onCreate] after creating the
99         // [SavedStateRegistryOwner] because `onCreate` might move the lifecycle state to STARTED
100         // which will make [SavedStateRegistryController.performRestore] throw.
101         lifecycleOwner.onCreate();
102 
103         // Set the owners on the root. They will be reused by any ComposeView inside the root
104         // hierarchy.
105         ViewTreeLifecycleOwner.set(root, lifecycleOwner);
106         ViewTreeSavedStateRegistryOwner.set(root, lifecycleOwner);
107     }
108 
109     /**
110      * Function to be called on your window root view's [View.onDetachedFromWindow] function.
111      */
onDetachedFromWindow(View root)112     private static void onDetachedFromWindow(View root) {
113         final LifecycleOwner lifecycleOwner = ViewTreeLifecycleOwner.get(root);
114         if (lifecycleOwner != null) {
115             ((ViewLifecycleOwner) lifecycleOwner).onDestroy();
116         }
117         ViewTreeLifecycleOwner.set(root, null);
118         ViewTreeSavedStateRegistryOwner.set(root, null);
119     }
120 
121     /**
122      * A [LifecycleOwner] for a [View] that updates lifecycle state based on window state.
123      *
124      * Also a trivial implementation of [SavedStateRegistryOwner] that does not do any save or
125      * restore. This works for processes similar to the SystemUI process, which is always running
126      * and top-level windows using this initialization are created once, when the process is
127      * started.
128      *
129      * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
130      * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is
131      * called, the implementation monitors window state in the following way
132      * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
133      * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
134      * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
135      *
136      * Or in table format:
137      * ```
138      * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
139      * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
140      * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
141      * │ Not attached  │                 Any              │       N/A       │
142      * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
143      * │               │    Not visible    │     Any      │     CREATED     │
144      * │               ├───────────────────┼──────────────┼─────────────────┤
145      * │   Attached    │                   │   No focus   │     STARTED     │
146      * │               │      Visible      ├──────────────┼─────────────────┤
147      * │               │                   │  Has focus   │     RESUMED     │
148      * └───────────────┴───────────────────┴──────────────┴─────────────────┘
149      * ```
150      */
151     private static class ViewLifecycleOwner implements SavedStateRegistryOwner {
152         private final ViewTreeObserver.OnWindowFocusChangeListener mWindowFocusListener =
153                 hasFocus -> updateState();
154         private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
155 
156         private final SavedStateRegistryController mSavedStateRegistryController =
157                 SavedStateRegistryController.create(this);
158 
159         private final View mView;
160         private final Api34Impl mApi34Impl;
161 
ViewLifecycleOwner(View view)162         ViewLifecycleOwner(View view) {
163             mView = view;
164             if (Utilities.ATLEAST_U) {
165                 mApi34Impl = new Api34Impl();
166             } else {
167                 mApi34Impl = null;
168             }
169 
170             mSavedStateRegistryController.performRestore(null);
171         }
172 
173         @NonNull
174         @Override
getLifecycle()175         public Lifecycle getLifecycle() {
176             return mLifecycleRegistry;
177         }
178 
179         @NonNull
180         @Override
getSavedStateRegistry()181         public SavedStateRegistry getSavedStateRegistry() {
182             return mSavedStateRegistryController.getSavedStateRegistry();
183         }
184 
onCreate()185         void onCreate() {
186             mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
187             if (Utilities.ATLEAST_U) {
188                 mApi34Impl.addOnWindowVisibilityChangeListener();
189             }
190             mView.getViewTreeObserver().addOnWindowFocusChangeListener(
191                     mWindowFocusListener);
192             updateState();
193         }
194 
onDestroy()195         void onDestroy() {
196             if (Utilities.ATLEAST_U) {
197                 mApi34Impl.removeOnWindowVisibilityChangeListener();
198             }
199             mView.getViewTreeObserver().removeOnWindowFocusChangeListener(
200                     mWindowFocusListener);
201             mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
202         }
203 
updateState()204         private void updateState() {
205             Lifecycle.State state =
206                     mView.getWindowVisibility() != View.VISIBLE ? Lifecycle.State.CREATED
207                             : (!mView.hasWindowFocus() ? Lifecycle.State.STARTED
208                                     : Lifecycle.State.RESUMED);
209             mLifecycleRegistry.setCurrentState(state);
210         }
211 
212         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
213         private class Api34Impl {
214             private final ViewTreeObserver.OnWindowVisibilityChangeListener
215                     mWindowVisibilityListener =
216                     visibility -> updateState();
217 
addOnWindowVisibilityChangeListener()218             void addOnWindowVisibilityChangeListener() {
219                 mView.getViewTreeObserver().addOnWindowVisibilityChangeListener(
220                         mWindowVisibilityListener);
221             }
222 
removeOnWindowVisibilityChangeListener()223             void removeOnWindowVisibilityChangeListener() {
224                 mView.getViewTreeObserver().removeOnWindowVisibilityChangeListener(
225                         mWindowVisibilityListener);
226             }
227         }
228     }
229 }
230