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

Commit fd6831c3 authored by Sukesh Ram's avatar Sukesh Ram
Browse files

Animate Handle Menu Open & Caption Handle Vanish

Animate the handle menu’s opening. For full screen, the caption handle should expand until it assumes the shape of the top handle menu pill (app info pill). The spec for freeform is not yet specified. As the handle menu opens, the caption handle vanishes.

Test: Manually tested in freeform, splitscreen, and fullscreen modes
Bug: 301494758
Change-Id: I8cc7305813c083a2b2ad6c19a523173162bc021e
parent b8625193
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/app_info_pill"
        android:layout_width="match_parent"
        android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
        android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
@@ -66,6 +67,7 @@
    </LinearLayout>

    <LinearLayout
        android:id="@+id/windowing_pill"
        android:layout_width="match_parent"
        android:layout_height="@dimen/desktop_mode_handle_menu_windowing_pill_height"
        android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
@@ -116,6 +118,7 @@
    </LinearLayout>

    <LinearLayout
        android:id="@+id/more_actions_pill"
        android:layout_width="match_parent"
        android:layout_height="@dimen/desktop_mode_handle_menu_more_actions_pill_height"
        android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
+5 −2
Original line number Diff line number Diff line
@@ -455,7 +455,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
    }

    /**
     * Create and display handle menu window
     * Create and display handle menu window.
     */
    void createHandleMenu() {
        mHandleMenu = new HandleMenu.Builder(this)
@@ -466,15 +466,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                .setLayoutId(mRelayoutParams.mLayoutResId)
                .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
                .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
                .setCaptionHeight(mResult.mCaptionHeight)
                .build();
        mWindowDecorViewHolder.onHandleMenuOpened();
        mHandleMenu.show();
    }

    /**
     * Close the handle menu window
     * Close the handle menu window.
     */
    void closeHandleMenu() {
        if (!isHandleMenuActive()) return;
        mWindowDecorViewHolder.onHandleMenuClosed();
        mHandleMenu.close();
        mHandleMenu = null;
    }
+28 −2
Original line number Diff line number Diff line
@@ -71,10 +71,13 @@ class HandleMenu {
    private int mMenuHeight;
    private int mMenuWidth;

    private final int mCaptionHeight;


    HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
            View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
            Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
            Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill,
            int captionHeight) {
        mParentDecor = parentDecor;
        mContext = mParentDecor.mDecorWindowContext;
        mTaskInfo = mParentDecor.mTaskInfo;
@@ -86,6 +89,7 @@ class HandleMenu {
        mAppIcon = appIcon;
        mAppName = appName;
        mShouldShowWindowingPill = shouldShowWindowingPill;
        mCaptionHeight = captionHeight;
        loadHandleMenuDimensions();
        updateHandleMenuPillPositions();
    }
@@ -98,6 +102,7 @@ class HandleMenu {
        ssg.addTransaction(t);
        ssg.markSyncReady();
        setupHandleMenu();
        animateHandleMenu();
    }

    private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
@@ -108,6 +113,21 @@ class HandleMenu {
                t, ssg, x, y, mMenuWidth, mMenuHeight);
    }

    /**
     * Animates the appearance of the handle menu and its three pills.
     */
    private void animateHandleMenu() {
        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
        final HandleMenuAnimator handleMenuAnimator = new HandleMenuAnimator(handleMenuView,
                mMenuWidth, mCaptionHeight);
        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
            handleMenuAnimator.animateCaptionHandleExpandToOpen();
        } else {
            handleMenuAnimator.animateOpen();
        }
    }

    /**
     * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
     * pill.
@@ -322,6 +342,7 @@ class HandleMenu {
        private int mCaptionX;
        private int mCaptionY;
        private boolean mShowWindowingPill;
        private int mCaptionHeight;


        Builder(@NonNull WindowDecoration parent) {
@@ -364,9 +385,14 @@ class HandleMenu {
            return this;
        }

        Builder setCaptionHeight(int captionHeight) {
            mCaptionHeight = captionHeight;
            return this;
        }

        HandleMenu build() {
            return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
        }
    }
}
+245 −0
Original line number Diff line number Diff line
/*
 * 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.wm.shell.windowdecor

import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View
import android.view.View.ALPHA
import android.view.View.SCALE_X
import android.view.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
import androidx.core.view.children
import com.android.wm.shell.R
import com.android.wm.shell.animation.Interpolators

/** Animates the Handle Menu opening. */
class HandleMenuAnimator(
    private val handleMenu: View,
    private val menuWidth: Int,
    private val captionHeight: Float
) {
    companion object {
        private const val MENU_Y_TRANSLATION_DURATION: Long = 150
        private const val HEADER_NONFREEFORM_SCALE_DURATION: Long = 150
        private const val HEADER_FREEFORM_SCALE_DURATION: Long = 217
        private const val HEADER_ELEVATION_DURATION: Long = 83
        private const val HEADER_CONTENT_ALPHA_DURATION: Long = 100
        private const val BODY_SCALE_DURATION: Long = 180
        private const val BODY_ALPHA_DURATION: Long = 150
        private const val BODY_ELEVATION_DURATION: Long = 83
        private const val BODY_CONTENT_ALPHA_DURATION: Long = 167

        private const val ELEVATION_DELAY: Long = 33
        private const val HEADER_CONTENT_ALPHA_DELAY: Long = 67
        private const val BODY_SCALE_DELAY: Long = 50
        private const val BODY_ALPHA_DELAY: Long = 133

        private const val HALF_INITIAL_SCALE: Float = 0.5f
        private const val NONFREEFORM_HEADER_INITIAL_SCALE_X: Float = 0.6f
        private const val NONFREEFORM_HEADER_INITIAL_SCALE_Y: Float = 0.05f
    }

    private val animators: MutableList<Animator> = mutableListOf()

    private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
    private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
    private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)

    /** Animates the opening of the handle menu. */
    fun animateOpen() {
        prepareMenuForAnimation()
        appInfoPillExpand()
        animateAppInfoPill()
        animateWindowingPill()
        animateMoreActionsPill()
        runAnimations()
    }

    /**
     * Animates the opening of the handle menu. The caption handle in full screen and split screen
     * will expand until it assumes the shape of the app info pill. Then, the other two pills will
     * appear.
     */
    fun animateCaptionHandleExpandToOpen() {
        prepareMenuForAnimation()
        captionHandleExpandIntoAppInfoPill()
        animateAppInfoPill()
        animateWindowingPill()
        animateMoreActionsPill()
        runAnimations()
    }

    /**
     * Prepares the handle menu for animation. Presets the opacity of necessary menu components.
     * Presets pivots of handle menu and body pills for scaling animation.
     */
    private fun prepareMenuForAnimation() {
        // Preset opacity
        appInfoPill.children.forEach { it.alpha = 0f }
        windowingPill.alpha = 0f
        moreActionsPill.alpha = 0f

        // Setup pivots.
        handleMenu.pivotX = menuWidth / 2f
        handleMenu.pivotY = 0f

        windowingPill.pivotX = menuWidth / 2f
        windowingPill.pivotY = appInfoPill.measuredHeight.toFloat()

        moreActionsPill.pivotX = menuWidth / 2f
        moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
    }

    private fun animateAppInfoPill() {
        // Header Elevation Animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, TRANSLATION_Z, 1f).apply {
                startDelay = ELEVATION_DELAY
                duration = HEADER_ELEVATION_DURATION
            }

        // Content Opacity Animation
        appInfoPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
                    startDelay = HEADER_CONTENT_ALPHA_DELAY
                    duration = HEADER_CONTENT_ALPHA_DURATION
                }
        }
    }

    private fun captionHandleExpandIntoAppInfoPill() {
        // Header scaling animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, NONFREEFORM_HEADER_INITIAL_SCALE_X, 1f)
                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }

        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, NONFREEFORM_HEADER_INITIAL_SCALE_Y, 1f)
                .apply { duration = HEADER_NONFREEFORM_SCALE_DURATION }

        // Downward y-translation animation
        val yStart: Float = -captionHeight / 2
        animators +=
            ObjectAnimator.ofFloat(handleMenu, TRANSLATION_Y, yStart, 0f).apply {
                duration = MENU_Y_TRANSLATION_DURATION
            }
    }

    private fun appInfoPillExpand() {
        // Header scaling animation
        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
                duration = HEADER_FREEFORM_SCALE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(appInfoPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
                duration = HEADER_FREEFORM_SCALE_DURATION
            }
    }

    private fun animateWindowingPill() {
        // Windowing X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(windowingPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
            }

        // Windowing Opacity Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, ALPHA, 1f).apply {
                startDelay = BODY_ALPHA_DELAY
                duration = BODY_ALPHA_DURATION
            }

        // Windowing Elevation Animation
        animators +=
            ObjectAnimator.ofFloat(windowingPill, TRANSLATION_Z, 1f).apply {
                startDelay = ELEVATION_DELAY
                duration = BODY_ELEVATION_DURATION
            }

        // Windowing Content Opacity Animation
        windowingPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
                    startDelay = BODY_ALPHA_DELAY
                    duration = BODY_CONTENT_ALPHA_DURATION
                    interpolator = Interpolators.FAST_OUT_SLOW_IN
                }
        }
    }

    private fun animateMoreActionsPill() {
        // More Actions X & Y Scaling Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
            }

        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
                startDelay = BODY_SCALE_DELAY
                duration = BODY_SCALE_DURATION
            }

        // More Actions Opacity Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, ALPHA, 1f).apply {
                startDelay = BODY_ALPHA_DELAY
                duration = BODY_ALPHA_DURATION
            }

        // More Actions Elevation Animation
        animators +=
            ObjectAnimator.ofFloat(moreActionsPill, TRANSLATION_Z, 1f).apply {
                startDelay = ELEVATION_DELAY
                duration = BODY_ELEVATION_DURATION
            }

        // More Actions Content Opacity Animation
        moreActionsPill.children.forEach {
            animators +=
                ObjectAnimator.ofFloat(it, ALPHA, 1f).apply {
                    startDelay = BODY_ALPHA_DELAY
                    duration = BODY_CONTENT_ALPHA_DURATION
                    interpolator = Interpolators.FAST_OUT_SLOW_IN
                }
        }
    }

    /** Runs the list of animators concurrently. */
    private fun runAnimations() {
        val animatorSet = AnimatorSet()
        animatorSet.playTogether(animators)
        animatorSet.start()
        animators.clear()
    }
}
+6 −4
Original line number Diff line number Diff line
@@ -269,10 +269,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
                    .build();
        }

        final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
        outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
        final int captionWidth = taskBounds.width();

        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
        startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
                .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                .show(mCaptionContainerSurface);

@@ -283,7 +283,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
            mCaptionInsetsRect.set(taskBounds);
            if (mIsCaptionVisible) {
                mCaptionInsetsRect.bottom =
                        mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
                        mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
                wct.addInsetsSource(mTaskInfo.token,
                        mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
                wct.addInsetsSource(mTaskInfo.token,
@@ -348,7 +348,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        // Caption view
        mCaptionWindowManager.setConfiguration(taskConfig);
        final WindowManager.LayoutParams lp =
                new WindowManager.LayoutParams(captionWidth, captionHeight,
                new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
                        WindowManager.LayoutParams.TYPE_APPLICATION,
                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
        lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
@@ -569,6 +569,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
    }

    static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
        int mCaptionHeight;
        int mWidth;
        int mHeight;
        T mRootView;
@@ -576,6 +577,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
        void reset() {
            mWidth = 0;
            mHeight = 0;
            mCaptionHeight = 0;
            mRootView = null;
        }
    }
Loading