3c0f30a2bb
And call initCompose() in Launcher.java. Therefore there is no behavior change for BaseActivity, or other ActivityContext subtypes that don't call initCompose(). This should work for non-activity subtypes of ActivityContext. It's likely that the Launcher activity will require more complete supoort for Lifecycle and SavedState. Note: In order to test the ComposeView, need to build this CL together with ag/22461463. 1. `ViewTreeLifecycleOwner.set(..., ...);` is necessary to fix the following crash: java.lang.IllegalStateException: ViewTreeLifecycleOwner not found This error comes from WindowRecomposer.android.kt - http://cs/androidx-platform-dev/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/WindowRecomposer.android.kt;l=351-354;rcl=5673af1441ebe04c776f528193dfc5f85c0c9a66 2. `ViewTreeSavedStateRegistryOwner.set(..., ...);` is necessary to fix the following crash: java.lang.IllegalStateException: Composed into the View which doesn't propagateViewTreeSavedStateRegistryOwner! This error comes from AndroidComposeView.android.kt. - http://cs/androidx-platform-dev/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt;l=1178-1183;rcl=5673af1441ebe04c776f528193dfc5f85c0c9a66 3. The lifecycle needs to be STARTED. Otherwise the ComposeView doesn't show up in its parent (i.e., isn't drawn). - In ComponentActivity, `onSaveInstanceState` starts the lifecycle. - In ComposeInitializeImpl (from SystemUI), ViewLifecycleOwner.updateState() starts the lifecycle when the window is visible and in focus. The implementation in this CL does the same thing as the ComposeInitializeImpl. Bug: 275905704 Test: on device, trigger a card such as the timer Test: RUN_ERROR_PRONE=True m out/soong/.intermediates/packages/apps/Launcher3/Launcher3/android_common/lint/lint-report.xml Test: RUN_ERROR_PRONE=True m out/soong/.intermediates/vendor/google/gms/packages/SearchLauncher/SearchLauncher/android_common/lint/lint-report.html Test: Presubmit Change-Id: Ieced66ad5238916a82ea13c01f69146bbdcf287a
230 lines
10 KiB
Java
230 lines
10 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|