1 /* 2 * Copyright (C) 2020 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 package com.android.quickstep; 17 18 import static com.android.app.animation.Interpolators.EXAGGERATED_EASE; 19 import static com.android.app.animation.Interpolators.LINEAR; 20 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; 21 import static com.android.launcher3.LauncherState.NORMAL; 22 import static com.android.launcher3.Utilities.mapBoundToRange; 23 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; 24 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; 25 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; 26 27 import android.animation.AnimatorSet; 28 import android.content.Context; 29 import android.graphics.Rect; 30 import android.graphics.RectF; 31 import android.os.IBinder; 32 import android.os.UserHandle; 33 import android.util.Size; 34 import android.view.RemoteAnimationTarget; 35 import android.view.View; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 40 import com.android.launcher3.LauncherState; 41 import com.android.launcher3.anim.AnimatorPlaybackController; 42 import com.android.launcher3.model.data.ItemInfo; 43 import com.android.launcher3.states.StateAnimationConfig; 44 import com.android.launcher3.uioverrides.QuickstepLauncher; 45 import com.android.launcher3.util.ObjectWrapper; 46 import com.android.launcher3.views.ClipIconView; 47 import com.android.launcher3.views.FloatingIconView; 48 import com.android.launcher3.views.FloatingView; 49 import com.android.launcher3.widget.LauncherAppWidgetHostView; 50 import com.android.quickstep.util.RectFSpringAnim; 51 import com.android.quickstep.util.ScalingWorkspaceRevealAnim; 52 import com.android.quickstep.util.StaggeredWorkspaceAnim; 53 import com.android.quickstep.util.TaskViewSimulator; 54 import com.android.quickstep.views.FloatingWidgetView; 55 import com.android.quickstep.views.RecentsView; 56 import com.android.quickstep.views.TaskView; 57 import com.android.systemui.shared.system.InputConsumerController; 58 59 import java.util.Collections; 60 import java.util.List; 61 62 /** 63 * Temporary class to allow easier refactoring 64 */ 65 public class LauncherSwipeHandlerV2 extends 66 AbsSwipeUpHandler<QuickstepLauncher, RecentsView, LauncherState> { 67 LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer)68 public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState, 69 TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, 70 boolean continuingLastGesture, InputConsumerController inputConsumer) { 71 super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs, 72 continuingLastGesture, inputConsumer); 73 } 74 75 76 @Override createHomeAnimationFactory( List<IBinder> launchCookies, long duration, boolean isTargetTranslucent, boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget, @Nullable TaskView targetTaskView)77 protected HomeAnimationFactory createHomeAnimationFactory( 78 List<IBinder> launchCookies, 79 long duration, 80 boolean isTargetTranslucent, 81 boolean appCanEnterPip, 82 RemoteAnimationTarget runningTaskTarget, 83 @Nullable TaskView targetTaskView) { 84 if (mContainer == null) { 85 mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, 86 isPresent -> mRecentsView.startHome()); 87 return new HomeAnimationFactory() { 88 @Override 89 public AnimatorPlaybackController createActivityAnimationToHome() { 90 return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); 91 } 92 }; 93 } 94 95 TaskView sourceTaskView = mRecentsView == null && targetTaskView == null 96 ? null 97 : targetTaskView == null 98 ? mRecentsView.getRunningTaskView() 99 : targetTaskView; 100 final View workspaceView = findWorkspaceView( 101 targetTaskView == null ? launchCookies : Collections.emptyList(), 102 sourceTaskView); 103 boolean canUseWorkspaceView = workspaceView != null 104 && workspaceView.isAttachedToWindow() 105 && workspaceView.getHeight() > 0 106 && (mContainer.getDesktopVisibilityController() == null 107 || !mContainer.getDesktopVisibilityController().areDesktopTasksVisible()); 108 109 mContainer.getRootView().setForceHideBackArrow(true); 110 if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 111 mContainer.setHintUserWillBeActive(); 112 } 113 114 if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) { 115 return new LauncherHomeAnimationFactory() { 116 117 @Nullable 118 @Override 119 public TaskView getTargetTaskView() { 120 return targetTaskView; 121 } 122 }; 123 } 124 if (workspaceView instanceof LauncherAppWidgetHostView) { 125 return createWidgetHomeAnimationFactory((LauncherAppWidgetHostView) workspaceView, 126 isTargetTranslucent, runningTaskTarget); 127 } 128 return createIconHomeAnimationFactory(workspaceView, targetTaskView); 129 } 130 131 private HomeAnimationFactory createIconHomeAnimationFactory( 132 View workspaceView, @Nullable TaskView targetTaskView) { 133 RectF iconLocation = new RectF(); 134 FloatingIconView floatingIconView = getFloatingIconView(mContainer, workspaceView, null, 135 mContainer.getTaskbarUIController() == null 136 ? null 137 : mContainer.getTaskbarUIController().findMatchingView(workspaceView), 138 true /* hideOriginal */, iconLocation, false /* isOpening */); 139 140 // We want the window alpha to be 0 once this threshold is met, so that the 141 // FolderIconView can be seen morphing into the icon shape. 142 float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION; 143 144 return new FloatingViewHomeAnimationFactory(floatingIconView) { 145 @Nullable 146 private RectF mTargetRect; 147 @Nullable 148 private RectFSpringAnim mSiblingAnimation; 149 150 @Nullable 151 @Override 152 protected View getViewIgnoredInWorkspaceRevealAnimation() { 153 return workspaceView; 154 } 155 156 @Override 157 public boolean isInHotseat() { 158 return workspaceView.getTag() instanceof ItemInfo 159 && ((ItemInfo) workspaceView.getTag()).isInHotseat(); 160 } 161 162 @NonNull 163 @Override 164 public RectF getWindowTargetRect() { 165 if (enableScalingRevealHomeAnimation()) { 166 if (mTargetRect == null) { 167 mTargetRect = new RectF(iconLocation); 168 } 169 return mTargetRect; 170 } else { 171 return iconLocation; 172 } 173 } 174 175 @Override 176 protected void playScalingRevealAnimation() { 177 if (mContainer != null) { 178 new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation, 179 getWindowTargetRect()).start(); 180 } 181 } 182 183 @Override 184 public void setAnimation(RectFSpringAnim anim) { 185 super.setAnimation(anim); 186 mSiblingAnimation = anim; 187 mSiblingAnimation.addAnimatorListener(floatingIconView); 188 floatingIconView.setOnTargetChangeListener( 189 mSiblingAnimation::onTargetPositionChanged); 190 floatingIconView.setFastFinishRunnable(mSiblingAnimation::end); 191 } 192 193 @Override 194 public void update( 195 RectF currentRect, 196 float progress, 197 float radius, 198 int overlayAlpha) { 199 floatingIconView.update(1f /* alpha */, currentRect, progress, windowAlphaThreshold, 200 radius, false, overlayAlpha); 201 } 202 203 @Override 204 public boolean isAnimationReady() { 205 return floatingIconView.isLaidOut(); 206 } 207 208 @Override 209 public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) { 210 super.setTaskViewArtist(taskViewArtist); 211 floatingIconView.setOverlayArtist(taskViewArtist); 212 } 213 214 @Override 215 public boolean isAnimatingIntoIcon() { 216 return true; 217 } 218 219 @Nullable 220 @Override 221 public TaskView getTargetTaskView() { 222 return targetTaskView; 223 } 224 }; 225 } 226 227 private HomeAnimationFactory createWidgetHomeAnimationFactory( 228 LauncherAppWidgetHostView hostView, boolean isTargetTranslucent, 229 RemoteAnimationTarget runningTaskTarget) { 230 final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1; 231 RectF backgroundLocation = new RectF(); 232 Rect crop = new Rect(); 233 // We can assume there is only one remote target here because staged split never animates 234 // into the app icon, only into the homescreen 235 RemoteTargetGluer.RemoteTargetHandle remoteTargetHandle = mRemoteTargetHandles[0]; 236 TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator(); 237 // This is to set up the inverse matrix in the simulator 238 tvs.apply(remoteTargetHandle.getTransformParams()); 239 tvs.getCurrentCropRect().roundOut(crop); 240 Size windowSize = new Size(crop.width(), crop.height()); 241 int fallbackBackgroundColor = 242 FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget); 243 FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mContainer, 244 hostView, backgroundLocation, windowSize, tvs.getCurrentCornerRadius(), 245 isTargetTranslucent, fallbackBackgroundColor); 246 247 return new FloatingViewHomeAnimationFactory(floatingWidgetView) { 248 249 @Override 250 @Nullable 251 protected View getViewIgnoredInWorkspaceRevealAnimation() { 252 return hostView; 253 } 254 255 @Override 256 public RectF getWindowTargetRect() { 257 super.getWindowTargetRect(); 258 return backgroundLocation; 259 } 260 261 @Override 262 public float getEndRadius(RectF cropRectF) { 263 return floatingWidgetView.getInitialCornerRadius(); 264 } 265 266 @Override 267 public void setAnimation(RectFSpringAnim anim) { 268 super.setAnimation(anim); 269 270 anim.addAnimatorListener(floatingWidgetView); 271 floatingWidgetView.setOnTargetChangeListener(anim::onTargetPositionChanged); 272 floatingWidgetView.setFastFinishRunnable(anim::end); 273 } 274 275 @Override 276 public void update(RectF currentRect, float progress, float radius, int overlayAlpha) { 277 super.update(currentRect, progress, radius, overlayAlpha); 278 final float fallbackBackgroundAlpha = 279 1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE); 280 final float foregroundAlpha = 281 mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE); 282 floatingWidgetView.update(currentRect, floatingWidgetAlpha, foregroundAlpha, 283 fallbackBackgroundAlpha, 1 - progress); 284 } 285 286 @Override 287 protected float getWindowAlpha(float progress) { 288 return 1 - mapBoundToRange(progress, 0, 0.5f, 0, 1, LINEAR); 289 } 290 }; 291 } 292 293 /** 294 * Returns the associated view on the workspace matching one of the launch cookies, or the app 295 * associated with the running task. 296 */ 297 @Nullable 298 private View findWorkspaceView(List<IBinder> launchCookies, TaskView sourceTaskView) { 299 if (mIsSwipingPipToHome) { 300 // Disable if swiping to PIP 301 return null; 302 } 303 if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) { 304 // Disable if it's an invalid task 305 return null; 306 } 307 308 // Find the associated item info for the launch cookie (if available), note that predicted 309 // apps actually have an id of -1, so use another default id here 310 int launchCookieItemId = NO_MATCHING_ID; 311 for (IBinder cookie : launchCookies) { 312 Integer itemId = ObjectWrapper.unwrap(cookie); 313 if (itemId != null) { 314 launchCookieItemId = itemId; 315 break; 316 } 317 } 318 319 return mContainer.getFirstMatchForAppClose(launchCookieItemId, 320 sourceTaskView.getFirstTask().key.getComponent().getPackageName(), 321 UserHandle.of(sourceTaskView.getFirstTask().key.userId), 322 false /* supportsAllAppsState */); 323 } 324 325 @Override 326 protected void finishRecentsControllerToHome(Runnable callback) { 327 mRecentsView.cleanupRemoteTargets(); 328 mRecentsAnimationController.finish( 329 true /* toRecents */, callback, true /* sendUserLeaveHint */); 330 } 331 332 private class FloatingViewHomeAnimationFactory extends LauncherHomeAnimationFactory { 333 334 private final FloatingView mFloatingView; 335 336 FloatingViewHomeAnimationFactory(FloatingView floatingView) { 337 mFloatingView = floatingView; 338 } 339 340 @Override 341 public void onCancel() { 342 mFloatingView.fastFinish(); 343 } 344 } 345 346 private class LauncherHomeAnimationFactory extends HomeAnimationFactory { 347 348 /** 349 * Returns a view which should be excluded from the Workspace animation, or null if there 350 * is no view to exclude. 351 */ 352 @Nullable 353 protected View getViewIgnoredInWorkspaceRevealAnimation() { 354 return null; 355 } 356 357 @NonNull 358 @Override 359 public AnimatorPlaybackController createActivityAnimationToHome() { 360 // Return an empty APC here since we have an non-user controlled animation 361 // to home. 362 long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx); 363 return mContainer.getStateManager().createAnimationToNewWorkspace( 364 NORMAL, accuracy, StateAnimationConfig.SKIP_ALL_ANIMATIONS); 365 } 366 367 @Override 368 public void playAtomicAnimation(float velocity) { 369 if (enableScalingRevealHomeAnimation()) { 370 playScalingRevealAnimation(); 371 } else { 372 new StaggeredWorkspaceAnim(mContainer, velocity, true /* animateOverviewScrim */, 373 getViewIgnoredInWorkspaceRevealAnimation()) 374 .start(); 375 } 376 } 377 378 /** 379 * Extracted in a different method so subclasses that have a custom window animation with a 380 * target (icons, widgets) can pass the optional parameters. 381 */ 382 protected void playScalingRevealAnimation() { 383 if (mContainer != null) { 384 new ScalingWorkspaceRevealAnim( 385 mContainer, null /* siblingAnimation */, 386 null /* windowTargetRect */).start(); 387 } 388 } 389 } 390 } 391