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