1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.desktopmode; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 20 21 import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; 22 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType; 23 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isEnterDesktopModeTransition; 24 25 import android.animation.Animator; 26 import android.animation.AnimatorListenerAdapter; 27 import android.animation.RectEvaluator; 28 import android.animation.ValueAnimator; 29 import android.app.ActivityManager; 30 import android.graphics.Rect; 31 import android.os.IBinder; 32 import android.util.Slog; 33 import android.view.SurfaceControl; 34 import android.view.WindowManager; 35 import android.view.WindowManager.TransitionType; 36 import android.window.TransitionInfo; 37 import android.window.TransitionRequestInfo; 38 import android.window.WindowContainerTransaction; 39 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 43 import com.android.internal.jank.InteractionJankMonitor; 44 import com.android.internal.util.LatencyTracker; 45 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; 46 import com.android.wm.shell.transition.Transitions; 47 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.function.Supplier; 52 53 /** 54 * The {@link Transitions.TransitionHandler} that handles transitions for desktop mode tasks 55 * entering and exiting freeform. 56 */ 57 public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler { 58 59 private static final String TAG = "EnterDesktopTaskTransitionHandler"; 60 private final Transitions mTransitions; 61 private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; 62 63 public static final int FREEFORM_ANIMATION_DURATION = 336; 64 65 private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); 66 private final InteractionJankMonitor mInteractionJankMonitor; 67 private final LatencyTracker mLatencyTracker; 68 69 private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener; 70 EnterDesktopTaskTransitionHandler( Transitions transitions, InteractionJankMonitor interactionJankMonitor, LatencyTracker latencyTracker)71 public EnterDesktopTaskTransitionHandler( 72 Transitions transitions, 73 InteractionJankMonitor interactionJankMonitor, 74 LatencyTracker latencyTracker) { 75 this(transitions, interactionJankMonitor, latencyTracker, SurfaceControl.Transaction::new); 76 } 77 EnterDesktopTaskTransitionHandler( Transitions transitions, InteractionJankMonitor interactionJankMonitor, LatencyTracker latencyTracker, Supplier<SurfaceControl.Transaction> supplier)78 public EnterDesktopTaskTransitionHandler( 79 Transitions transitions, 80 InteractionJankMonitor interactionJankMonitor, 81 LatencyTracker latencyTracker, 82 Supplier<SurfaceControl.Transaction> supplier) { 83 mTransitions = transitions; 84 mInteractionJankMonitor = interactionJankMonitor; 85 mLatencyTracker = latencyTracker; 86 mTransactionSupplier = supplier; 87 } 88 setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener)89 void setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener) { 90 mOnTaskResizeAnimationListener = listener; 91 } 92 93 /** 94 * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP 95 * 96 * @param wct WindowContainerTransaction for transition 97 * @return the token representing the started transition 98 */ moveToDesktop( @onNull WindowContainerTransaction wct, DesktopModeTransitionSource transitionSource )99 public IBinder moveToDesktop( 100 @NonNull WindowContainerTransaction wct, 101 DesktopModeTransitionSource transitionSource 102 ) { 103 final IBinder token = mTransitions.startTransition(getEnterTransitionType(transitionSource), 104 wct, this); 105 mPendingTransitionTokens.add(token); 106 return token; 107 } 108 109 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)110 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 111 @NonNull SurfaceControl.Transaction startT, 112 @NonNull SurfaceControl.Transaction finishT, 113 @NonNull Transitions.TransitionFinishCallback finishCallback) { 114 boolean transitionHandled = false; 115 for (TransitionInfo.Change change : info.getChanges()) { 116 if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { 117 continue; 118 } 119 120 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 121 if (taskInfo == null || taskInfo.taskId == -1) { 122 continue; 123 } 124 125 if (change.getMode() == WindowManager.TRANSIT_CHANGE) { 126 transitionHandled |= startChangeTransition( 127 transition, info.getType(), change, startT, finishT, finishCallback); 128 } 129 } 130 131 if (transitionHandled 132 && info.getType() 133 == DesktopModeTransitionTypes 134 .TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON) { 135 mLatencyTracker.onActionEnd(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_MENU); 136 } 137 138 mPendingTransitionTokens.remove(transition); 139 140 return transitionHandled; 141 } 142 startChangeTransition( @onNull IBinder transition, @TransitionType int type, @NonNull TransitionInfo.Change change, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback)143 private boolean startChangeTransition( 144 @NonNull IBinder transition, 145 @TransitionType int type, 146 @NonNull TransitionInfo.Change change, 147 @NonNull SurfaceControl.Transaction startT, 148 @NonNull SurfaceControl.Transaction finishT, 149 @NonNull Transitions.TransitionFinishCallback finishCallback) { 150 if (!mPendingTransitionTokens.contains(transition)) { 151 return false; 152 } 153 154 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 155 if (isEnterDesktopModeTransition(type) 156 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { 157 return animateMoveToDesktop(change, startT, finishCallback); 158 } 159 160 return false; 161 } 162 animateMoveToDesktop( @onNull TransitionInfo.Change change, @NonNull SurfaceControl.Transaction startT, @NonNull Transitions.TransitionFinishCallback finishCallback)163 private boolean animateMoveToDesktop( 164 @NonNull TransitionInfo.Change change, 165 @NonNull SurfaceControl.Transaction startT, 166 @NonNull Transitions.TransitionFinishCallback finishCallback) { 167 final SurfaceControl leash = change.getLeash(); 168 final Rect startBounds = change.getStartAbsBounds(); 169 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 170 if (mOnTaskResizeAnimationListener == null) { 171 Slog.e(TAG, "onTaskResizeAnimationListener is not available for this transition"); 172 return false; 173 } 174 175 startT.setPosition(leash, startBounds.left, startBounds.top) 176 .setWindowCrop(leash, startBounds.width(), startBounds.height()) 177 .show(leash); 178 mOnTaskResizeAnimationListener.onAnimationStart(taskInfo.taskId, startT, startBounds); 179 final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(), 180 change.getStartAbsBounds(), change.getEndAbsBounds()); 181 animator.setDuration(FREEFORM_ANIMATION_DURATION); 182 SurfaceControl.Transaction t = mTransactionSupplier.get(); 183 animator.addUpdateListener(animation -> { 184 final Rect animationValue = (Rect) animator.getAnimatedValue(); 185 t.setPosition(leash, animationValue.left, animationValue.top) 186 .setWindowCrop(leash, animationValue.width(), animationValue.height()) 187 .show(leash); 188 mOnTaskResizeAnimationListener.onBoundsChange(taskInfo.taskId, t, animationValue); 189 }); 190 animator.addListener(new AnimatorListenerAdapter() { 191 @Override 192 public void onAnimationEnd(Animator animation) { 193 mOnTaskResizeAnimationListener.onAnimationEnd(taskInfo.taskId); 194 mTransitions.getMainExecutor().execute( 195 () -> finishCallback.onTransitionFinished(null)); 196 mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU); 197 } 198 }); 199 animator.start(); 200 return true; 201 } 202 203 @Nullable 204 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)205 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 206 @NonNull TransitionRequestInfo request) { 207 return null; 208 } 209 } 210