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

Commit bdf91148 authored by Ben Lin's avatar Ben Lin Committed by Android (Google) Code Review
Browse files

Merge "Animate App Header to menu." into main

parents c278558c b9a98b9c
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -1619,7 +1619,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            openInAppOrBrowserIntent = null;
        }
        final View captionView = isAppHandle(mWindowDecorViewHolder)
                ? asAppHandle(mWindowDecorViewHolder).getCaptionHandle() : null;
                ? asAppHandle(mWindowDecorViewHolder).getCaptionHandle()
                : asAppHeader(mWindowDecorViewHolder).getRootView();
        mHandleMenu = mHandleMenuFactory.create(
                mMainDispatcher,
                mMainScope,
+8 −8
Original line number Diff line number Diff line
@@ -117,7 +117,7 @@ private constructor(
    private val isBrowserApp: Boolean,
    private val openInAppOrBrowserIntent: Intent?,
    private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
    private val captionView: View?,
    private val captionView: View,
    private val captionWidth: Int,
    private val captionHeight: Int,
    captionX: Int,
@@ -528,7 +528,7 @@ private constructor(
        private val windowDecorationActions: WindowDecorationActions,
        private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
        menuWidth: Int,
        private val captionView: View?,
        private val captionView: View,
        captionHeight: Int,
        private val shouldShowWindowingPill: Boolean,
        private val shouldShowBrowserPill: Boolean,
@@ -830,18 +830,18 @@ private constructor(
        /** Animates the menu opening. */
        fun animateOpenMenu() {
            if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
                animator.animateCaptionHandleExpandToOpen(requireNotNull(captionView))
                animator.animateCaptionHandleExpandToOpen(captionView)
            } else {
                animator.animateOpen()
                animator.animateCaptionHeaderExpandToOpen(captionView)
            }
        }

        /** Animates the menu closing. */
        fun animateCloseMenu(onAnimFinish: () -> Unit) {
            if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
                animator.animateCollapseIntoHandleClose(requireNotNull(captionView), onAnimFinish)
                animator.animateCollapseIntoHandleClose(captionView, onAnimFinish)
            } else {
                animator.animateClose(onAnimFinish)
                animator.animateCollapseIntoHeaderClose(captionView, onAnimFinish)
            }
        }

@@ -1132,7 +1132,7 @@ private constructor(
            isBrowserApp: Boolean,
            openInAppOrBrowserIntent: Intent?,
            desktopModeUiEventLogger: DesktopModeUiEventLogger,
            captionView: View?,
            captionView: View,
            captionWidth: Int,
            captionHeight: Int,
            captionX: Int,
@@ -1198,7 +1198,7 @@ private constructor(
            isBrowserApp: Boolean,
            openInAppOrBrowserIntent: Intent?,
            desktopModeUiEventLogger: DesktopModeUiEventLogger,
            captionView: View?,
            captionView: View,
            captionWidth: Int,
            captionHeight: Int,
            captionX: Int,
+178 −34
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@ package com.android.wm.shell.windowdecor

import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ArgbEvaluator
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.view.View
import android.view.View.ALPHA
import android.view.View.SCALE_X
@@ -29,9 +31,12 @@ import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.widget.LinearLayout
import android.widget.TextView
import android.window.DesktopExperienceFlags
import androidx.core.animation.doOnEnd
import androidx.core.view.children
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.wm.shell.R
@@ -76,10 +81,15 @@ class HandleMenuAnimator(

        // Handle->menu animation constants
        private const val WIDTH_SWAP_FRACTION = 0.19f
        private const val HEADER_ICON_NAME_SWAP_FRACTION = 0.1f
        private const val HEADER_MENU_NAME_LATE_FADE_IN_FRACTION = 0.8f
        private const val HANDLE_MENU_OPEN_CLOSE_DURATION: Long = 600

        private const val HEADER_MENU_OPEN_CLOSE_DURATION: Long = 600
    }

    private val animators: MutableList<Animator> = mutableListOf()
    private val argbEvaluator: ArgbEvaluator = ArgbEvaluator.getInstance()
    private var runningAnimation: AnimatorSet? = null

    private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
@@ -100,24 +110,56 @@ class HandleMenuAnimator(
        handleMenu.context.getColor(com.android.internal.R.color.materialColorSurfaceBright)
    private val marginMenuTop =
        context.resources.getDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
    private val marginMenuLeftRightPadding =
        context.resources.getDimensionPixelSize(
            R.dimen.desktop_mode_handle_menu_padding_left_bottom_right
        )
    private val menuItemElevation: Int =
        context.getResources().getDimensionPixelSize(R.dimen.app_menu_elevation)
    private val menuAppIconHeight: Int =
        context.resources.getDimensionPixelSize(R.dimen.desktop_mode_handle_menu_icon_radius)
    private val headerAppIconHeight: Int =
        context.resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)

    /** Animates the opening of the handle menu. */
    fun animateOpen() {
    /**
     * Animates the App Header from resting state to the menu, giving an illusion they are the same
     * surface.
     */
    fun animateCaptionHeaderExpandToOpen(headerView: View) {
        if (
            DesktopExperienceFlags.ENABLE_DRAWING_APP_HANDLE.isTrue &&
                DesktopExperienceFlags.ENABLE_TALL_APP_HEADERS.isTrue
        ) {
            setupHeaderAnimator(headerView, true /* expand */)
        } else {
            prepareMenuForAnimation()
            appInfoPillExpand()
            animateAppInfoPillOpen()
            animateWindowingPillOpen()
            animateMoreActionsPillOpen()
            animateOpenInAppOrBrowserPill()
        runAnimations {
        }
        val animationCallback: () -> Unit = {
            appInfoPill.post {
                appInfoPill
                    .requireViewById<View>(R.id.collapse_menu_button)
                    .sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
            }
        }
        if (!handleMenu.isAttachedToWindow) {
            handleMenu.addOnAttachStateChangeListener(
                object : View.OnAttachStateChangeListener {
                    override fun onViewAttachedToWindow(v: View) {
                        runAnimations(animationCallback)
                        handleMenu.removeOnAttachStateChangeListener(this)
                    }

                    override fun onViewDetachedFromWindow(v: View) {}
                }
            )
        } else {
            runAnimations(animationCallback)
        }
    }

    /**
@@ -127,10 +169,8 @@ class HandleMenuAnimator(
     */
    fun animateCaptionHandleExpandToOpen(handleView: View) {
        if (DesktopExperienceFlags.ENABLE_DRAWING_APP_HANDLE.isTrue) {
            val showMenuAnimation = ValueAnimator.ofFloat(0f, 1f)
            showMenuAnimation.setDuration(HANDLE_MENU_OPEN_CLOSE_DURATION)
            showMenuAnimation.setInterpolator(EMPHASIZED)
            setupAnimator(showMenuAnimation, handleView)

            setupHandleAnimator(handleView, true /* expand */)
        } else {
            prepareMenuForAnimation()
            captionHandleExpandIntoAppInfoPill()
@@ -171,10 +211,7 @@ class HandleMenuAnimator(
     */
    fun animateCollapseIntoHandleClose(handleView: View, after: () -> Unit) {
        if (DesktopExperienceFlags.ENABLE_DRAWING_APP_HANDLE.isTrue) {
            val closeMenuAnimation = ValueAnimator.ofFloat(1f, 0f)
            closeMenuAnimation.setDuration(HANDLE_MENU_OPEN_CLOSE_DURATION)
            closeMenuAnimation.setInterpolator(EMPHASIZED)
            setupAnimator(closeMenuAnimation, handleView)
            setupHandleAnimator(handleView, false /* expand */)
        } else {
            appInfoCollapseToHandle()
            animateAppInfoPillFadeOut()
@@ -192,16 +229,33 @@ class HandleMenuAnimator(
     *
     * @param after runs after animation finishes.
     */
    fun animateClose(after: () -> Unit) {
    fun animateCollapseIntoHeaderClose(headerView: View, after: () -> Unit) {
        if (
            DesktopExperienceFlags.ENABLE_DRAWING_APP_HANDLE.isTrue &&
                DesktopExperienceFlags.ENABLE_TALL_APP_HEADERS.isTrue
        ) {
            setupHeaderAnimator(headerView, false /* expand */)
        } else {
            appInfoPillCollapse()
            animateAppInfoPillFadeOut()
            windowingPillClose()
            moreActionsPillClose()
            openInAppOrBrowserPillClose()
        }
        runAnimations(after)
    }

    private fun setupAnimator(showMenuAnimation: ValueAnimator, handleView: View) {
    private fun setupHandleAnimator(handleView: View, expand: Boolean) {
        val showMenuAnimation =
            if (expand) {
                    ValueAnimator.ofFloat(0f, 1f)
                } else {
                    ValueAnimator.ofFloat(1f, 0f)
                }
                .apply {
                    duration = HANDLE_MENU_OPEN_CLOSE_DURATION
                    interpolator = EMPHASIZED
                }
        val widthDiff: Int = (handleMenuWidth - handleView.width).toInt()
        val targetWidth: Float = handleView.width + widthDiff * WIDTH_SWAP_FRACTION
        val targetHeight: Float = targetWidth * appInfoPillHeight / handleMenuWidth
@@ -236,23 +290,113 @@ class HandleMenuAnimator(
                handleMenu.translationY = diff * (1 - menuAnimationProgress)
                handleMenu.pivotY = 0f
                handleMenu.pivotX = handleMenu.width.toFloat() / 2
                animateFromStartScale(currentMenuScale, menuAnimationProgress)
                handleMenu.scaleX = currentMenuScale
                handleMenu.scaleY = currentMenuScale

                appInfoPill.children.forEach { it.alpha = progress }
                appInfoPill.elevation = menuItemElevation * progress
                animateRestOfTheMenu(menuAnimationProgress)
            }
        }
        animators += showMenuAnimation
    }

    private fun animateFromStartScale(currentScale: Float, progress: Float) {
        val SHOW_MENU_STAGES_COUNT = 3
        handleMenu.scaleX = currentScale
        handleMenu.scaleY = currentScale
    private fun setupHeaderAnimator(headerView: View, expand: Boolean) {
        val showMenuAnimation =
            if (expand) {
                    ValueAnimator.ofFloat(0f, 1f)
                } else {
                    ValueAnimator.ofFloat(1f, 0f)
                }
                .apply {
                    duration = HEADER_MENU_OPEN_CLOSE_DURATION
                    interpolator = EMPHASIZED
                }
        val openMenuView = headerView.findViewById<LinearLayout>(R.id.open_menu_button)
        val headerAppIcon = openMenuView.findViewById<View>(R.id.application_icon)
        val headerAppName = openMenuView.findViewById<TextView>(R.id.application_name)
        val expandMenuButton = openMenuView.findViewById<View>(R.id.expand_menu_button)
        val menuAppIcon = appInfoPill.findViewById<View>(R.id.application_icon)
        val menuAppName = appInfoPill.findViewById<TextView>(R.id.application_name)
        val startingMenuAppNameColor = headerAppName.textColors.defaultColor
        val finalMenuAppNameColor = menuAppName.textColors.defaultColor

        appInfoPill.children.forEach { it.alpha = progress }
        val shouldLateFadeInAppName =
            headerAppName.isGone || headerAppName.text == null || headerAppName.text.isEmpty()
        val startingScaleForMenu = headerAppIconHeight.toFloat() / menuAppIconHeight.toFloat()

        showMenuAnimation.addUpdateListener { animator ->
            val progress: Float = animator.animatedValue as Float
            if (progress < HEADER_ICON_NAME_SWAP_FRACTION) {
                val headerAppNameAndIconProgress = 1f - progress / HEADER_ICON_NAME_SWAP_FRACTION
                headerAppIcon.alpha = headerAppNameAndIconProgress
                headerAppName.alpha = headerAppNameAndIconProgress
                expandMenuButton.alpha = headerAppNameAndIconProgress
                val menuIconsAndNameProgress = progress / HEADER_ICON_NAME_SWAP_FRACTION
                menuAppIcon.alpha = menuIconsAndNameProgress
                if (!shouldLateFadeInAppName) {
                    menuAppName.alpha = menuIconsAndNameProgress
                }
            } else {
                headerAppIcon.alpha = 0f
                headerAppName.alpha = 0f
                expandMenuButton.alpha = 0f
                menuAppIcon.alpha = 1f
                if (!shouldLateFadeInAppName) {
                    menuAppName.alpha = 1f
                }
            }

            val currentMenuScale = (1 - startingScaleForMenu) * progress + startingScaleForMenu
            // First, scale and translate the menu itself accordingly
            handleMenu.pivotY = 0f
            handleMenu.pivotX = marginMenuLeftRightPadding.toFloat() * (1 / startingScaleForMenu)
            handleMenu.scaleX = currentMenuScale
            handleMenu.scaleY = currentMenuScale
            handleMenu.translationY = -marginMenuTop.toFloat() / 2 * (1 - progress)
            handleMenu.translationX =
                marginMenuLeftRightPadding.toFloat() / 3f *
                    (1 / startingScaleForMenu) *
                    (1 - progress)

            // Next, transform the individual parts of the app pill
            // appInfoPill: fade in the background
            // appName: since we want to keep the text the same size, we are un-scaling it, and
            // move it in the x-axis to keep the spacing the same at the beginning
            // CollapseMenuButton: fade it in, but after the handoff fraction period
            appInfoPill.background.alpha = (progress * 255f).toInt()
            appInfoPill.elevation = menuItemElevation * progress
            menuAppName.scaleX = 1f / currentMenuScale
            menuAppName.scaleY = 1f / currentMenuScale
            menuAppName.translationX = marginMenuLeftRightPadding.toFloat() * (3f) * (1f - progress)
            val color =
                argbEvaluator.evaluate(progress, startingMenuAppNameColor, finalMenuAppNameColor)
                    as Int
            menuAppName.setTextColor(ColorStateList.valueOf(color))
            if (shouldLateFadeInAppName) {
                if (progress > (1 - HEADER_MENU_NAME_LATE_FADE_IN_FRACTION)) {
                    menuAppName.alpha =
                        (progress - (1 - HEADER_MENU_NAME_LATE_FADE_IN_FRACTION)) /
                            HEADER_MENU_NAME_LATE_FADE_IN_FRACTION
                } else {
                    menuAppName.alpha = 0f
                }
            }
            val collapsedMenuButtonAnimationProgress: Float =
                (progress - HEADER_ICON_NAME_SWAP_FRACTION) / (1 - HEADER_ICON_NAME_SWAP_FRACTION)
            val collapseMenuButton = appInfoPill.findViewById<View>(R.id.collapse_menu_button)
            collapseMenuButton.alpha = collapsedMenuButtonAnimationProgress

            animateRestOfTheMenu(progress)
        }
        animators += showMenuAnimation
    }

    private fun animateRestOfTheMenu(progress: Float) {
        val showMenuStagesCount = 3
        val actionsBackgroundAlpha =
            max(0f, (progress - 1f / SHOW_MENU_STAGES_COUNT) * (SHOW_MENU_STAGES_COUNT - 1))
        val actionItemsAlpha =
            max(0f, (progress - 2f / SHOW_MENU_STAGES_COUNT) * SHOW_MENU_STAGES_COUNT)
            max(0f, (progress - 1f / showMenuStagesCount) * (showMenuStagesCount - 1))
        val actionItemsAlpha = max(0f, (progress - 2f / showMenuStagesCount) * showMenuStagesCount)
        windowingPill.setAlpha(actionsBackgroundAlpha)
        windowingPill.setElevation(menuItemElevation * actionsBackgroundAlpha)
        windowingPill.children.forEach { it.alpha = actionItemsAlpha }
+1 −1
Original line number Diff line number Diff line
@@ -523,7 +523,7 @@ class AppHeaderController(
                    isBrowserApp = isBrowserApp,
                    openInAppOrBrowserIntent = openInAppOrBrowserIntent,
                    desktopModeUiEventLogger = desktopModeUiEventLogger,
                    captionView = null,
                    captionView = viewHolder.rootView,
                    captionWidth = captionLayoutResult.captionWidth,
                    captionHeight = captionLayoutResult.captionHeight,
                    captionX = captionLayoutResult.captionX,
+3 −2
Original line number Diff line number Diff line
@@ -301,6 +301,7 @@ class HandleMenuTest : ShellTestCase() {
                }
                else -> error("Invalid windowing mode")
            }
        val captionView = LayoutInflater.from(mContext).inflate(layoutId, null)
        val handleMenu =
            if (DesktopExperienceFlags.ENABLE_WINDOW_DECORATION_REFACTOR.isTrue) {
                handleMenuFactory.create(
@@ -324,7 +325,7 @@ class HandleMenuTest : ShellTestCase() {
                    isBrowserApp = false,
                    openInAppOrBrowserIntent = null,
                    mockDesktopModeUiEventLogger,
                    captionView = mock(),
                    captionView = captionView,
                    captionWidth = HANDLE_WIDTH,
                    captionHeight = 50,
                    captionX = captionX,
@@ -350,7 +351,7 @@ class HandleMenuTest : ShellTestCase() {
                    isBrowserApp = false,
                    openInAppOrBrowserIntent = null,
                    mockDesktopModeUiEventLogger,
                    captionView = mock(),
                    captionView = captionView,
                    captionWidth = HANDLE_WIDTH,
                    captionHeight = 50,
                    captionX = captionX,