| /* |
| * 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); |
| } |
| } |
| } |
| } |