Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4017df44 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use Expandable instead of View in FooterActionsViewModel (1/2)" into tm-qpr-dev

parents e5af956a e6106974
Loading
Loading
Loading
Loading
+42 −94
Original line number Original line Diff line number Diff line
@@ -25,7 +25,6 @@ import android.graphics.Rect
import android.os.Looper
import android.os.Looper
import android.util.Log
import android.util.Log
import android.util.MathUtils
import android.util.MathUtils
import android.view.GhostView
import android.view.View
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -86,6 +85,9 @@ constructor(
         */
         */
        val sourceIdentity: Any
        val sourceIdentity: Any


        /** The CUJ associated to this controller. */
        val cuj: DialogCuj?

        /**
        /**
         * Move the drawing of the source in the overlay of [viewGroup].
         * Move the drawing of the source in the overlay of [viewGroup].
         *
         *
@@ -142,7 +144,31 @@ constructor(
         * controlled by this controller.
         * controlled by this controller.
         */
         */
        // TODO(b/252723237): Make this non-nullable
        // TODO(b/252723237): Make this non-nullable
        fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
        fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder?

        companion object {
            /**
             * Create a [Controller] that can animate [source] to and from a dialog.
             *
             * Important: The view must be attached to a [ViewGroup] when calling this function and
             * during the animation. For safety, this method will return null when it is not.
             *
             * Note: The background of [view] should be a (rounded) rectangle so that it can be
             * properly animated.
             */
            fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
                if (source.parent !is ViewGroup) {
                    Log.e(
                        TAG,
                        "Skipping animation as view $source is not attached to a ViewGroup",
                        Exception(),
                    )
                    return null
                }

                return ViewDialogLaunchAnimatorController(source, cuj)
            }
        }
    }
    }


    /**
    /**
@@ -172,7 +198,12 @@ constructor(
        cuj: DialogCuj? = null,
        cuj: DialogCuj? = null,
        animateBackgroundBoundsChange: Boolean = false
        animateBackgroundBoundsChange: Boolean = false
    ) {
    ) {
        show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
        val controller = Controller.fromView(view, cuj)
        if (controller == null) {
            dialog.show()
        } else {
            show(dialog, controller, animateBackgroundBoundsChange)
        }
    }
    }


    /**
    /**
@@ -187,10 +218,10 @@ constructor(
     * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
     * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
     * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
     * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
     */
     */
    @JvmOverloads
    fun show(
    fun show(
        dialog: Dialog,
        dialog: Dialog,
        controller: Controller,
        controller: Controller,
        cuj: DialogCuj? = null,
        animateBackgroundBoundsChange: Boolean = false
        animateBackgroundBoundsChange: Boolean = false
    ) {
    ) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
@@ -207,7 +238,10 @@ constructor(
                it.dialog.window.decorView.viewRootImpl == controller.viewRoot
                it.dialog.window.decorView.viewRootImpl == controller.viewRoot
            }
            }
        val animateFrom =
        val animateFrom =
            animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
            animatedParent?.dialogContentWithBackground?.let {
                Controller.fromView(it, controller.cuj)
            }
                ?: controller


        if (animatedParent == null && animateFrom !is LaunchableView) {
        if (animatedParent == null && animateFrom !is LaunchableView) {
            // Make sure the View we launch from implements LaunchableView to avoid visibility
            // Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -244,96 +278,12 @@ constructor(
                animateBackgroundBoundsChange,
                animateBackgroundBoundsChange,
                animatedParent,
                animatedParent,
                isForTesting,
                isForTesting,
                cuj,
            )
            )


        openedDialogs.add(animatedDialog)
        openedDialogs.add(animatedDialog)
        animatedDialog.start()
        animatedDialog.start()
    }
    }


    /** Create a [Controller] that can animate [source] to & from a dialog. */
    private fun createController(source: View): Controller {
        return object : Controller {
            override val viewRoot: ViewRootImpl
                get() = source.viewRootImpl

            override val sourceIdentity: Any = source

            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
                // Create a temporary ghost of the source (which will make it invisible) and add it
                // to the host dialog.
                GhostView.addGhost(source, viewGroup)

                // The ghost of the source was just created, so the source is currently invisible.
                // We need to make sure that it stays invisible as long as the dialog is shown or
                // animating.
                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
            }

            override fun stopDrawingInOverlay() {
                // Note: here we should remove the ghost from the overlay, but in practice this is
                // already done by the launch controllers created below.

                // Make sure we allow the source to change its visibility again.
                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
                source.visibility = View.VISIBLE
            }

            override fun createLaunchController(): LaunchAnimator.Controller {
                val delegate = GhostedViewLaunchAnimatorController(source)
                return object : LaunchAnimator.Controller by delegate {
                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
                        // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
                        // ghost (that ghosts only the source content, and not its background) will
                        // be added right after this by the delegate and will be animated.
                        GhostView.removeGhost(source)
                        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
                    }

                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)

                        // We hide the source when the dialog is showing. We will make this view
                        // visible again when dismissing the dialog. This does nothing if the source
                        // implements [LaunchableView], as it's already INVISIBLE in that case.
                        source.visibility = View.INVISIBLE
                    }
                }
            }

            override fun createExitController(): LaunchAnimator.Controller {
                return GhostedViewLaunchAnimatorController(source)
            }

            override fun shouldAnimateExit(): Boolean {
                // The source should be invisible by now, if it's not then something else changed
                // its visibility and we probably don't want to run the animation.
                if (source.visibility != View.INVISIBLE) {
                    return false
                }

                return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
            }

            override fun onExitAnimationCancelled() {
                // Make sure we allow the source to change its visibility again.
                (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)

                // If the view is invisible it's probably because of us, so we make it visible
                // again.
                if (source.visibility == View.INVISIBLE) {
                    source.visibility = View.VISIBLE
                }
            }

            override fun jankConfigurationBuilder(
                cuj: Int
            ): InteractionJankMonitor.Configuration.Builder? {
                return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
            }
        }
    }

    /**
    /**
     * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
     * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
     * allow for dismissing the whole stack.
     * allow for dismissing the whole stack.
@@ -563,9 +513,6 @@ private class AnimatedDialog(
     * Whether synchronization should be disabled, which can be useful if we are running in a test.
     * Whether synchronization should be disabled, which can be useful if we are running in a test.
     */
     */
    private val forceDisableSynchronization: Boolean,
    private val forceDisableSynchronization: Boolean,

    /** Interaction to which the dialog animation is associated. */
    private val cuj: DialogCuj? = null
) {
) {
    /**
    /**
     * The DecorView of this dialog window.
     * The DecorView of this dialog window.
@@ -618,8 +565,9 @@ private class AnimatedDialog(
    private var hasInstrumentedJank = false
    private var hasInstrumentedJank = false


    fun start() {
    fun start() {
        val cuj = controller.cuj
        if (cuj != null) {
        if (cuj != null) {
            val config = controller.jankConfigurationBuilder(cuj.cujType)
            val config = controller.jankConfigurationBuilder()
            if (config != null) {
            if (config != null) {
                if (cuj.tag != null) {
                if (cuj.tag != null) {
                    config.setTag(cuj.tag)
                    config.setTag(cuj.tag)
@@ -917,7 +865,7 @@ private class AnimatedDialog(
                }
                }


                if (hasInstrumentedJank) {
                if (hasInstrumentedJank) {
                    interactionJankMonitor.end(cuj!!.cujType)
                    interactionJankMonitor.end(controller.cuj!!.cujType)
                }
                }
            }
            }
        )
        )
+13 −1
Original line number Original line Diff line number Diff line
@@ -30,7 +30,12 @@ interface Expandable {
     */
     */
    fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
    fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?


    // TODO(b/230830644): Introduce DialogLaunchAnimator and a function to expose it here.
    /**
     * Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
     * a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is
     * currently not attached or visible).
     */
    fun dialogLaunchController(cuj: DialogCuj? = null): DialogLaunchAnimator.Controller?


    companion object {
    companion object {
        /**
        /**
@@ -39,6 +44,7 @@ interface Expandable {
         * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
         * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
         * animated.
         * animated.
         */
         */
        @JvmStatic
        fun fromView(view: View): Expandable {
        fun fromView(view: View): Expandable {
            return object : Expandable {
            return object : Expandable {
                override fun activityLaunchController(
                override fun activityLaunchController(
@@ -46,6 +52,12 @@ interface Expandable {
                ): ActivityLaunchAnimator.Controller? {
                ): ActivityLaunchAnimator.Controller? {
                    return ActivityLaunchAnimator.Controller.fromView(view, cujType)
                    return ActivityLaunchAnimator.Controller.fromView(view, cujType)
                }
                }

                override fun dialogLaunchController(
                    cuj: DialogCuj?
                ): DialogLaunchAnimator.Controller? {
                    return DialogLaunchAnimator.Controller.fromView(view, cuj)
                }
            }
            }
        }
        }
    }
    }
+107 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.animation

import android.view.GhostView
import android.view.View
import android.view.ViewGroup
import android.view.ViewRootImpl
import com.android.internal.jank.InteractionJankMonitor

/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
class ViewDialogLaunchAnimatorController
internal constructor(
    private val source: View,
    override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
    override val viewRoot: ViewRootImpl
        get() = source.viewRootImpl

    override val sourceIdentity: Any = source

    override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
        // Create a temporary ghost of the source (which will make it invisible) and add it
        // to the host dialog.
        GhostView.addGhost(source, viewGroup)

        // The ghost of the source was just created, so the source is currently invisible.
        // We need to make sure that it stays invisible as long as the dialog is shown or
        // animating.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
    }

    override fun stopDrawingInOverlay() {
        // Note: here we should remove the ghost from the overlay, but in practice this is
        // already done by the launch controllers created below.

        // Make sure we allow the source to change its visibility again.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
        source.visibility = View.VISIBLE
    }

    override fun createLaunchController(): LaunchAnimator.Controller {
        val delegate = GhostedViewLaunchAnimatorController(source)
        return object : LaunchAnimator.Controller by delegate {
            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
                // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
                // ghost (that ghosts only the source content, and not its background) will
                // be added right after this by the delegate and will be animated.
                GhostView.removeGhost(source)
                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
            }

            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)

                // We hide the source when the dialog is showing. We will make this view
                // visible again when dismissing the dialog. This does nothing if the source
                // implements [LaunchableView], as it's already INVISIBLE in that case.
                source.visibility = View.INVISIBLE
            }
        }
    }

    override fun createExitController(): LaunchAnimator.Controller {
        return GhostedViewLaunchAnimatorController(source)
    }

    override fun shouldAnimateExit(): Boolean {
        // The source should be invisible by now, if it's not then something else changed
        // its visibility and we probably don't want to run the animation.
        if (source.visibility != View.INVISIBLE) {
            return false
        }

        return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
    }

    override fun onExitAnimationCancelled() {
        // Make sure we allow the source to change its visibility again.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)

        // If the view is invisible it's probably because of us, so we make it visible
        // again.
        if (source.visibility == View.INVISIBLE) {
            source.visibility = View.VISIBLE
        }
    }

    override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
        val type = cuj?.cujType ?: return null
        return InteractionJankMonitor.Configuration.Builder.withView(type, source)
    }
}
+28 −17
Original line number Original line Diff line number Diff line
@@ -40,17 +40,16 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.roundToInt
import kotlin.math.roundToInt


/** A controller that can control animated launches. */
/** A controller that can control animated launches from an [Expandable]. */
interface ExpandableController {
interface ExpandableController {
    /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
    /** The [Expandable] controlled by this controller. */
    fun forActivity(): ActivityLaunchAnimator.Controller
    val expandable: Expandable

    /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
    fun forDialog(): DialogLaunchAnimator.Controller
}
}


/**
/**
@@ -120,12 +119,25 @@ internal class ExpandableControllerImpl(
    private val layoutDirection: LayoutDirection,
    private val layoutDirection: LayoutDirection,
    private val isComposed: State<Boolean>,
    private val isComposed: State<Boolean>,
) : ExpandableController {
) : ExpandableController {
    override fun forActivity(): ActivityLaunchAnimator.Controller {
    override val expandable: Expandable =
        return activityController()
        object : Expandable {
            override fun activityLaunchController(
                cujType: Int?,
            ): ActivityLaunchAnimator.Controller? {
                if (!isComposed.value) {
                    return null
                }

                return activityController(cujType)
            }
            }


    override fun forDialog(): DialogLaunchAnimator.Controller {
            override fun dialogLaunchController(cuj: DialogCuj?): DialogLaunchAnimator.Controller? {
        return dialogController()
                if (!isComposed.value) {
                    return null
                }

                return dialogController(cuj)
            }
        }
        }


    /**
    /**
@@ -233,7 +245,7 @@ internal class ExpandableControllerImpl(
    }
    }


    /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
    /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
    private fun activityController(): ActivityLaunchAnimator.Controller {
    private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
        val delegate = launchController()
        val delegate = launchController()
        return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
        return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -248,10 +260,11 @@ internal class ExpandableControllerImpl(
        }
        }
    }
    }


    private fun dialogController(): DialogLaunchAnimator.Controller {
    private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
        return object : DialogLaunchAnimator.Controller {
        return object : DialogLaunchAnimator.Controller {
            override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
            override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
            override val sourceIdentity: Any = this@ExpandableControllerImpl
            override val sourceIdentity: Any = this@ExpandableControllerImpl
            override val cuj: DialogCuj? = cuj


            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
                val newOverlay = viewGroup.overlay as ViewGroupOverlay
                val newOverlay = viewGroup.overlay as ViewGroupOverlay
@@ -294,9 +307,7 @@ internal class ExpandableControllerImpl(
                isDialogShowing.value = false
                isDialogShowing.value = false
            }
            }


            override fun jankConfigurationBuilder(
            override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
                cuj: Int
            ): InteractionJankMonitor.Configuration.Builder? {
                // TODO(b/252723237): Add support for jank monitoring when animating from a
                // TODO(b/252723237): Add support for jank monitoring when animating from a
                // Composable.
                // Composable.
                return null
                return null
+12 −8
Original line number Original line Diff line number Diff line
@@ -116,6 +116,7 @@ import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.Expandable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -448,10 +449,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
     *
     *
     * @param keyguardShowing     True if keyguard is showing
     * @param keyguardShowing     True if keyguard is showing
     * @param isDeviceProvisioned True if device is provisioned
     * @param isDeviceProvisioned True if device is provisioned
     * @param view                The view from which we should animate the dialog when showing it
     * @param expandable          The expandable from which we should animate the dialog when
     *                            showing it
     */
     */
    public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
    public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
            @Nullable View view) {
            @Nullable Expandable expandable) {
        mKeyguardShowing = keyguardShowing;
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        mDeviceProvisioned = isDeviceProvisioned;
        if (mDialog != null && mDialog.isShowing()) {
        if (mDialog != null && mDialog.isShowing()) {
@@ -463,7 +465,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
            mDialog.dismiss();
            mDialog.dismiss();
            mDialog = null;
            mDialog = null;
        } else {
        } else {
            handleShow(view);
            handleShow(expandable);
        }
        }
    }
    }


@@ -495,7 +497,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
        }
        }
    }
    }


    protected void handleShow(@Nullable View view) {
    protected void handleShow(@Nullable Expandable expandable) {
        awakenIfNecessary();
        awakenIfNecessary();
        mDialog = createDialog();
        mDialog = createDialog();
        prepareDialog();
        prepareDialog();
@@ -507,10 +509,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
        // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
        // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
        mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
        mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);


        if (view != null) {
        DialogLaunchAnimator.Controller controller =
            mDialogLaunchAnimator.showFromView(mDialog, view,
                expandable != null ? expandable.dialogLaunchController(
                        new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
                        new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
                            INTERACTION_JANK_TAG));
                                INTERACTION_JANK_TAG)) : null;
        if (controller != null) {
            mDialogLaunchAnimator.show(mDialog, controller);
        } else {
        } else {
            mDialog.show();
            mDialog.show();
        }
        }
Loading