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

Commit 8c21e85f authored by Matt Sziklay's avatar Matt Sziklay Committed by Automerger Merge Worker
Browse files

Merge "Visual indicator of transition to fullscreen" into udc-dev am: da26654d am: ce036508

parents aa9c2876 ce036508
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ 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.
  -->
<shape android:shape="rectangle"
       xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#bf309fb5" />
    <corners android:radius="20dp" />
    <stroke android:width="1dp" color="#A00080FF"/>
</shape>
+6 −2
Original line number Diff line number Diff line
@@ -671,13 +671,17 @@ public abstract class WMShellModule {
            Context context,
            ShellInit shellInit,
            ShellController shellController,
            DisplayController displayController,
            ShellTaskOrganizer shellTaskOrganizer,
            SyncTransactionQueue syncQueue,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            Transitions transitions,
            @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
            @ShellMainThread ShellExecutor mainExecutor
    ) {
        return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
                transitions, desktopModeTaskRepository, mainExecutor);
        return new DesktopTasksController(context, shellInit, shellController, displayController,
                shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
                desktopModeTaskRepository, mainExecutor);
    }

    @WMSingleton
+227 −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.desktopmode;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;

/**
 * Animated visual indicator for Desktop Mode windowing transitions.
 */
public class DesktopModeVisualIndicator {

    private final Context mContext;
    private final DisplayController mDisplayController;
    private final ShellTaskOrganizer mTaskOrganizer;
    private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
    private final ActivityManager.RunningTaskInfo mTaskInfo;
    private final SurfaceControl mTaskSurface;
    private SurfaceControl mLeash;

    private final SyncTransactionQueue mSyncQueue;
    private SurfaceControlViewHost mViewHost;

    public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
            ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
            Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
        mSyncQueue = syncQueue;
        mTaskInfo = taskInfo;
        mDisplayController = displayController;
        mContext = context;
        mTaskSurface = taskSurface;
        mTaskOrganizer = taskOrganizer;
        mRootTdaOrganizer = taskDisplayAreaOrganizer;
    }

    /**
     * Create and animate the indicator for the exit desktop mode transition.
     */
    public void createFullscreenIndicator() {
        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        final Resources resources = mContext.getResources();
        final DisplayMetrics metrics = resources.getDisplayMetrics();
        final int screenWidth = metrics.widthPixels;
        final int screenHeight = metrics.heightPixels;
        final int padding = mDisplayController
                .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
        final ImageView v = new ImageView(mContext);
        v.setImageResource(R.drawable.desktop_windowing_transition_background);
        final SurfaceControl.Builder builder = new SurfaceControl.Builder();
        mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
        mLeash = builder
                .setName("Fullscreen Indicator")
                .setContainerLayer()
                .build();
        t.show(mLeash);
        final WindowManager.LayoutParams lp =
                new WindowManager.LayoutParams(screenWidth, screenHeight,
                        WindowManager.LayoutParams.TYPE_APPLICATION,
                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
        lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
        lp.setTrustedOverlay();
        final WindowlessWindowManager windowManager = new WindowlessWindowManager(
                mTaskInfo.configuration, mLeash,
                null /* hostInputToken */);
        mViewHost = new SurfaceControlViewHost(mContext,
                mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
                "FullscreenVisualIndicator");
        mViewHost.setView(v, lp);
        // We want this indicator to be behind the dragged task, but in front of all others.
        t.setRelativeLayer(mLeash, mTaskSurface, -1);

        mSyncQueue.runInSync(transaction -> {
            transaction.merge(t);
            t.close();
        });
        final Rect startBounds = new Rect(padding, padding,
                screenWidth - padding, screenHeight - padding);
        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
                startBounds);
        animator.start();
    }

    /**
     * Release the indicator and its components when it is no longer needed.
     */
    public void releaseFullscreenIndicator() {
        if (mViewHost == null) return;
        if (mViewHost != null) {
            mViewHost.release();
            mViewHost = null;
        }

        if (mLeash != null) {
            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
            t.remove(mLeash);
            mLeash = null;
            mSyncQueue.runInSync(transaction -> {
                transaction.merge(t);
                t.close();
            });
        }
    }
    /**
     * Animator for Desktop Mode transitions which supports bounds and alpha animation.
     */
    private static class VisualIndicatorAnimator extends ValueAnimator {
        private static final int FULLSCREEN_INDICATOR_DURATION = 200;
        private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
        private static final float INDICATOR_FINAL_OPACITY = 0.7f;

        private final ImageView mView;
        private final Rect mStartBounds;
        private final Rect mEndBounds;
        private final RectEvaluator mRectEvaluator;

        private VisualIndicatorAnimator(ImageView view, Rect startBounds,
                Rect endBounds) {
            mView = view;
            mStartBounds = new Rect(startBounds);
            mEndBounds = endBounds;
            setFloatValues(0, 1);
            mRectEvaluator = new RectEvaluator(new Rect());
        }

        /**
         * Create animator for visual indicator of fullscreen transition
         *
         * @param view        the view for this indicator
         * @param startBounds the starting bounds of the fullscreen indicator
         */
        public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
                Rect startBounds) {
            view.getDrawable().setBounds(startBounds);
            int width = startBounds.width();
            int height = startBounds.height();
            Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
                    (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
                    (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
                    (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
            VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
                    view, startBounds, endBounds);
            animator.setInterpolator(new DecelerateInterpolator());
            setupFullscreenIndicatorAnimation(animator);
            return animator;
        }

        /**
         * Add necessary listener for animation of fullscreen indicator
         */
        private static void setupFullscreenIndicatorAnimation(
                VisualIndicatorAnimator animator) {
            animator.addUpdateListener(a -> {
                if (animator.mView != null) {
                    animator.updateBounds(a.getAnimatedFraction(), animator.mView);
                    animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
                } else {
                    animator.cancel();
                }
            });
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    animator.mView.getDrawable().setBounds(animator.mEndBounds);
                }
            });
            animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
        }

        /**
         * Update bounds of view based on current animation fraction.
         * Use of delta is to animate bounds independently, in case we need to
         * run multiple animations simultaneously.
         *
         * @param fraction fraction to use, compared against previous fraction
         * @param view     the view to update
         */
        private void updateBounds(float fraction, ImageView view) {
            Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
            view.getDrawable().setBounds(currentBounds);
        }

        /**
         * Fade in the fullscreen indicator
         *
         * @param fraction current animation fraction
         */
        private void updateIndicatorAlpha(float fraction, View view) {
            view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
        }
    }
}
+61 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode

import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -37,11 +38,14 @@ import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
@@ -58,13 +62,17 @@ class DesktopTasksController(
        private val context: Context,
        shellInit: ShellInit,
        private val shellController: ShellController,
        private val displayController: DisplayController,
        private val shellTaskOrganizer: ShellTaskOrganizer,
        private val syncQueue: SyncTransactionQueue,
        private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
        private val transitions: Transitions,
        private val desktopModeTaskRepository: DesktopModeTaskRepository,
        @ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {

    private val desktopMode: DesktopModeImpl
    private var visualIndicator: DesktopModeVisualIndicator? = null

    init {
        desktopMode = DesktopModeImpl()
@@ -297,6 +305,52 @@ class DesktopTasksController(
        return desktopMode
    }

    /**
     * Perform checks required on drag move. Create/release fullscreen indicator as needed.
     *
     * @param taskInfo the task being dragged.
     * @param taskSurface SurfaceControl of dragged task.
     * @param y coordinate of dragged task. Used for checks against status bar height.
     */
    fun onDragPositioningMove(
            taskInfo: RunningTaskInfo,
            taskSurface: SurfaceControl,
            y: Float
    ) {
        val statusBarHeight = displayController
                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
        if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
            if (y <= statusBarHeight && visualIndicator == null) {
                visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                        displayController, context, taskSurface, shellTaskOrganizer,
                        rootTaskDisplayAreaOrganizer)
                visualIndicator?.createFullscreenIndicator()
            } else if (y > statusBarHeight && visualIndicator != null) {
                visualIndicator?.releaseFullscreenIndicator()
                visualIndicator = null
            }
        }
    }

    /**
     * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
     *
     * @param taskInfo the task being dragged.
     * @param y height of drag, to be checked against status bar height.
     */
    fun onDragPositioningEnd(
            taskInfo: RunningTaskInfo,
            y: Float
    ) {
        val statusBarHeight = displayController
                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
        if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
            moveToFullscreen(taskInfo.taskId)
            visualIndicator?.releaseFullscreenIndicator()
            visualIndicator = null
        }
    }

    /**
     * Adds a listener to find out about changes in the visibility of freeform tasks.
     *
+6 −10
Original line number Diff line number Diff line
@@ -300,7 +300,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    return false;
                }
                case MotionEvent.ACTION_MOVE: {
                    final DesktopModeWindowDecoration decoration =
                            mWindowDecorByTaskId.get(mTaskId);
                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
                            decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
                    mDragPositioningCallback.onDragPositioningMove(
                            e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                    mIsDragging = true;
@@ -309,18 +313,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL: {
                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                    final int statusBarHeight = mDisplayController
                            .getDisplayLayout(taskInfo.displayId).stableInsets().top;
                    mDragPositioningCallback.onDragPositioningEnd(
                            e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                    if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
                        if (DesktopModeStatus.isProto2Enabled()
                                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
                            // Switch a single task to fullscreen
                            mDesktopTasksController.ifPresent(
                                    c -> c.moveToFullscreen(taskInfo));
                        }
                    }
                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
                            e.getRawY(dragPointerIdx)));
                    final boolean wasDragging = mIsDragging;
                    mIsDragging = false;
                    return wasDragging;
Loading