1 /* 2 * Copyright (C) 2018 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.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; 19 import static com.android.launcher3.LauncherState.FAST_OVERVIEW; 20 import static com.android.launcher3.LauncherState.OVERVIEW; 21 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; 22 import static com.android.launcher3.anim.Interpolators.LINEAR; 23 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL; 24 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB; 25 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK; 26 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION; 27 28 import android.animation.AnimatorSet; 29 import android.animation.ObjectAnimator; 30 import android.annotation.TargetApi; 31 import android.app.ActivityManager.RunningTaskInfo; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.graphics.Rect; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.Looper; 39 import android.support.annotation.Nullable; 40 import android.support.annotation.UiThread; 41 import android.view.View; 42 43 import com.android.launcher3.BaseDraggingActivity; 44 import com.android.launcher3.DeviceProfile; 45 import com.android.launcher3.Launcher; 46 import com.android.launcher3.LauncherAppState; 47 import com.android.launcher3.LauncherInitListener; 48 import com.android.launcher3.LauncherState; 49 import com.android.launcher3.R; 50 import com.android.launcher3.allapps.AllAppsTransitionController; 51 import com.android.launcher3.allapps.DiscoveryBounce; 52 import com.android.launcher3.anim.AnimatorPlaybackController; 53 import com.android.launcher3.dragndrop.DragLayer; 54 import com.android.launcher3.uioverrides.FastOverviewState; 55 import com.android.launcher3.userevent.nano.LauncherLogProto; 56 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 57 import com.android.quickstep.TouchConsumer.InteractionType; 58 import com.android.quickstep.util.LayoutUtils; 59 import com.android.quickstep.util.TransformedRect; 60 import com.android.quickstep.util.RemoteAnimationProvider; 61 import com.android.quickstep.util.RemoteAnimationTargetSet; 62 import com.android.quickstep.views.LauncherLayoutListener; 63 import com.android.quickstep.views.LauncherRecentsView; 64 import com.android.quickstep.views.RecentsView; 65 import com.android.quickstep.views.RecentsViewContainer; 66 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 67 68 import java.util.Objects; 69 import java.util.function.BiPredicate; 70 import java.util.function.Consumer; 71 72 /** 73 * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. 74 */ 75 @TargetApi(Build.VERSION_CODES.P) 76 public interface ActivityControlHelper<T extends BaseDraggingActivity> { 77 78 LayoutListener createLayoutListener(T activity); 79 80 /** 81 * Updates the UI to indicate quick interaction. 82 */ 83 void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo, 84 boolean activityVisible); 85 86 float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, 87 Context context); 88 89 void executeOnWindowAvailable(T activity, Runnable action); 90 91 void onTransitionCancelled(T activity, boolean activityVisible); 92 93 int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, 94 @InteractionType int interactionType, TransformedRect outRect); 95 96 void onSwipeUpComplete(T activity); 97 98 AnimationFactory prepareRecentsUI(T activity, boolean activityVisible, 99 Consumer<AnimatorPlaybackController> callback); 100 101 ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener); 102 103 @Nullable 104 T getCreatedActivity(); 105 106 @UiThread 107 @Nullable 108 RecentsView getVisibleRecentsView(); 109 110 @UiThread 111 boolean switchToRecentsIfVisible(boolean fromRecentsButton); 112 113 Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target); 114 115 boolean shouldMinimizeSplitScreen(); 116 117 /** 118 * @return {@code true} if recents activity should be started immediately on touchDown, 119 * {@code false} if it should deferred until some threshold is crossed. 120 */ 121 boolean deferStartingActivity(int downHitTarget); 122 123 boolean supportsLongSwipe(T activity); 124 125 AlphaProperty getAlphaProperty(T activity); 126 127 /** 128 * Must return a non-null controller is supportsLongSwipe was true. 129 */ 130 LongSwipeHelper getLongSwipeController(T activity, RemoteAnimationTargetSet targetSet); 131 132 /** 133 * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} 134 */ 135 int getContainerType(); 136 137 class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> { 138 139 @Override 140 public LayoutListener createLayoutListener(Launcher activity) { 141 return new LauncherLayoutListener(activity); 142 } 143 144 @Override 145 public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo, 146 boolean activityVisible) { 147 LauncherState fromState = activity.getStateManager().getState(); 148 activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible); 149 150 QuickScrubController controller = activity.<RecentsView>getOverviewPanel() 151 .getQuickScrubController(); 152 controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this); 153 } 154 155 @Override 156 public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, 157 Context context) { 158 // The padding calculations are exactly same as that of RecentsView.setInsets 159 int topMargin = context.getResources() 160 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); 161 int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top; 162 int paddingBottom = dp.availableHeightPx + dp.getInsets().top - targetRect.rect.bottom; 163 164 return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop); 165 } 166 167 @Override 168 public void executeOnWindowAvailable(Launcher activity, Runnable action) { 169 activity.getWorkspace().runOnOverlayHidden(action); 170 } 171 172 @Override 173 public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, 174 @InteractionType int interactionType, TransformedRect outRect) { 175 LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect); 176 if (interactionType == INTERACTION_QUICK_SCRUB) { 177 outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context); 178 } 179 if (dp.isVerticalBarLayout()) { 180 Rect targetInsets = dp.getInsets(); 181 int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right; 182 return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset; 183 } else { 184 return dp.heightPx - outRect.rect.bottom; 185 } 186 } 187 188 @Override 189 public void onTransitionCancelled(Launcher activity, boolean activityVisible) { 190 LauncherState startState = activity.getStateManager().getRestState(); 191 activity.getStateManager().goToState(startState, activityVisible); 192 } 193 194 @Override 195 public void onSwipeUpComplete(Launcher activity) { 196 // Re apply state in case we did something funky during the transition. 197 activity.getStateManager().reapplyState(); 198 DiscoveryBounce.showForOverviewIfNeeded(activity); 199 } 200 201 @Override 202 public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible, 203 Consumer<AnimatorPlaybackController> callback) { 204 final LauncherState startState = activity.getStateManager().getState(); 205 206 LauncherState resetState = startState; 207 if (startState.disableRestore) { 208 resetState = activity.getStateManager().getRestState(); 209 } 210 activity.getStateManager().setRestState(resetState); 211 212 if (!activityVisible) { 213 // Since the launcher is not visible, we can safely reset the scroll position. 214 // This ensures then the next swipe up to all-apps starts from scroll 0. 215 activity.getAppsView().reset(false /* animate */); 216 activity.getStateManager().goToState(OVERVIEW, false); 217 218 // Optimization, hide the all apps view to prevent layout while initializing 219 activity.getAppsView().getContentView().setVisibility(View.GONE); 220 } 221 222 return new AnimationFactory() { 223 @Override 224 public void createActivityController(long transitionLength, 225 @InteractionType int interactionType) { 226 createActivityControllerInternal(activity, activityVisible, startState, 227 transitionLength, interactionType, callback); 228 } 229 230 @Override 231 public void onTransitionCancelled() { 232 activity.getStateManager().goToState(startState, false /* animate */); 233 } 234 }; 235 } 236 237 private void createActivityControllerInternal(Launcher activity, boolean wasVisible, 238 LauncherState startState, long transitionLength, 239 @InteractionType int interactionType, 240 Consumer<AnimatorPlaybackController> callback) { 241 LauncherState endState = interactionType == INTERACTION_QUICK_SCRUB 242 ? FAST_OVERVIEW : OVERVIEW; 243 if (wasVisible) { 244 DeviceProfile dp = activity.getDeviceProfile(); 245 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); 246 activity.getStateManager().goToState(startState, false); 247 callback.accept(activity.getStateManager() 248 .createAnimationToNewWorkspace(endState, accuracy)); 249 return; 250 } 251 252 if (activity.getDeviceProfile().isVerticalBarLayout()) { 253 return; 254 } 255 256 AllAppsTransitionController controller = activity.getAllAppsController(); 257 AnimatorSet anim = new AnimatorSet(); 258 259 float scrollRange = Math.max(controller.getShiftRange(), 1); 260 float progressDelta = (transitionLength / scrollRange); 261 262 float endProgress = endState.getVerticalProgress(activity); 263 float startProgress = endProgress + progressDelta; 264 ObjectAnimator shiftAnim = ObjectAnimator.ofFloat( 265 controller, ALL_APPS_PROGRESS, startProgress, endProgress); 266 shiftAnim.setInterpolator(LINEAR); 267 anim.play(shiftAnim); 268 269 anim.setDuration(transitionLength * 2); 270 activity.getStateManager().setCurrentAnimation(anim); 271 callback.accept(AnimatorPlaybackController.wrap(anim, transitionLength * 2)); 272 } 273 274 @Override 275 public ActivityInitListener createActivityInitListener( 276 BiPredicate<Launcher, Boolean> onInitListener) { 277 return new LauncherInitListener(onInitListener); 278 } 279 280 @Nullable 281 @Override 282 public Launcher getCreatedActivity() { 283 LauncherAppState app = LauncherAppState.getInstanceNoCreate(); 284 if (app == null) { 285 return null; 286 } 287 return (Launcher) app.getModel().getCallback(); 288 } 289 290 @Nullable 291 @UiThread 292 private Launcher getVisibleLaucher() { 293 Launcher launcher = getCreatedActivity(); 294 return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ? 295 launcher : null; 296 } 297 298 @Nullable 299 @Override 300 public RecentsView getVisibleRecentsView() { 301 Launcher launcher = getVisibleLaucher(); 302 return launcher != null && launcher.getStateManager().getState().overviewUi 303 ? launcher.getOverviewPanel() : null; 304 } 305 306 @Override 307 public boolean switchToRecentsIfVisible(boolean fromRecentsButton) { 308 Launcher launcher = getVisibleLaucher(); 309 if (launcher != null) { 310 if (fromRecentsButton) { 311 launcher.getUserEventDispatcher().logActionCommand( 312 LauncherLogProto.Action.Command.RECENTS_BUTTON, 313 getContainerType(), 314 LauncherLogProto.ContainerType.TASKSWITCHER); 315 } 316 launcher.getStateManager().goToState(OVERVIEW); 317 return true; 318 } 319 return false; 320 } 321 322 @Override 323 public boolean deferStartingActivity(int downHitTarget) { 324 return downHitTarget == HIT_TARGET_BACK || downHitTarget == HIT_TARGET_ROTATION; 325 } 326 327 @Override 328 public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) { 329 return homeBounds; 330 } 331 332 @Override 333 public boolean shouldMinimizeSplitScreen() { 334 return true; 335 } 336 337 @Override 338 public boolean supportsLongSwipe(Launcher activity) { 339 return !activity.getDeviceProfile().isVerticalBarLayout(); 340 } 341 342 @Override 343 public LongSwipeHelper getLongSwipeController(Launcher activity, 344 RemoteAnimationTargetSet targetSet) { 345 if (activity.getDeviceProfile().isVerticalBarLayout()) { 346 return null; 347 } 348 return new LongSwipeHelper(activity, targetSet); 349 } 350 351 @Override 352 public AlphaProperty getAlphaProperty(Launcher activity) { 353 return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP); 354 } 355 356 @Override 357 public int getContainerType() { 358 final Launcher launcher = getVisibleLaucher(); 359 return launcher != null ? launcher.getStateManager().getState().containerType 360 : LauncherLogProto.ContainerType.APP; 361 } 362 } 363 364 class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> { 365 366 private final ComponentName mHomeComponent; 367 private final Handler mUiHandler = new Handler(Looper.getMainLooper()); 368 369 public FallbackActivityControllerHelper(ComponentName homeComponent) { 370 mHomeComponent = homeComponent; 371 } 372 373 @Override 374 public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo, 375 boolean activityVisible) { 376 QuickScrubController controller = activity.<RecentsView>getOverviewPanel() 377 .getQuickScrubController(); 378 379 // TODO: match user is as well 380 boolean startingFromHome = !activityVisible && 381 (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent)); 382 controller.onQuickScrubStart(startingFromHome, this); 383 if (activityVisible) { 384 mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub, 385 OVERVIEW_TRANSITION_MS); 386 } 387 } 388 389 @Override 390 public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, 391 Context context) { 392 return 0; 393 } 394 395 @Override 396 public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) { 397 action.run(); 398 } 399 400 @Override 401 public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) { 402 // TODO: 403 } 404 405 @Override 406 public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, 407 @InteractionType int interactionType, TransformedRect outRect) { 408 LayoutUtils.calculateFallbackTaskSize(context, dp, outRect.rect); 409 if (dp.isVerticalBarLayout()) { 410 Rect targetInsets = dp.getInsets(); 411 int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right; 412 return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset; 413 } else { 414 return dp.heightPx - outRect.rect.bottom; 415 } 416 } 417 418 @Override 419 public void onSwipeUpComplete(RecentsActivity activity) { 420 // TODO: 421 } 422 423 @Override 424 public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible, 425 Consumer<AnimatorPlaybackController> callback) { 426 if (activityVisible) { 427 return (transitionLength, interactionType) -> { }; 428 } 429 430 RecentsViewContainer rv = activity.getOverviewPanelContainer(); 431 rv.setContentAlpha(0); 432 433 return new AnimationFactory() { 434 435 boolean isAnimatingHome = false; 436 437 @Override 438 public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { 439 isAnimatingHome = targets != null && targets.isAnimatingHome(); 440 if (!isAnimatingHome) { 441 rv.setContentAlpha(1); 442 } 443 createActivityController(getSwipeUpDestinationAndLength( 444 activity.getDeviceProfile(), activity, INTERACTION_NORMAL, 445 new TransformedRect()), INTERACTION_NORMAL); 446 } 447 448 @Override 449 public void createActivityController(long transitionLength, int interactionType) { 450 if (!isAnimatingHome) { 451 return; 452 } 453 454 ObjectAnimator anim = ObjectAnimator 455 .ofFloat(rv, RecentsViewContainer.CONTENT_ALPHA, 0, 1); 456 anim.setDuration(transitionLength).setInterpolator(LINEAR); 457 AnimatorSet animatorSet = new AnimatorSet(); 458 animatorSet.play(anim); 459 callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength)); 460 } 461 }; 462 } 463 464 @Override 465 public LayoutListener createLayoutListener(RecentsActivity activity) { 466 // We do not change anything as part of layout changes in fallback activity. Return a 467 // default layout listener. 468 return new LayoutListener() { 469 @Override 470 public void open() { } 471 472 @Override 473 public void setHandler(WindowTransformSwipeHandler handler) { } 474 475 @Override 476 public void finish() { } 477 }; 478 } 479 480 @Override 481 public ActivityInitListener createActivityInitListener( 482 BiPredicate<RecentsActivity, Boolean> onInitListener) { 483 return new RecentsActivityTracker(onInitListener); 484 } 485 486 @Nullable 487 @Override 488 public RecentsActivity getCreatedActivity() { 489 return RecentsActivityTracker.getCurrentActivity(); 490 } 491 492 @Nullable 493 @Override 494 public RecentsView getVisibleRecentsView() { 495 RecentsActivity activity = getCreatedActivity(); 496 if (activity != null && activity.hasWindowFocus()) { 497 return activity.getOverviewPanel(); 498 } 499 return null; 500 } 501 502 @Override 503 public boolean switchToRecentsIfVisible(boolean fromRecentsButton) { 504 return false; 505 } 506 507 @Override 508 public boolean deferStartingActivity(int downHitTarget) { 509 // Always defer starting the activity when using fallback 510 return true; 511 } 512 513 @Override 514 public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) { 515 // TODO: Remove this once b/77875376 is fixed 516 return target.sourceContainerBounds; 517 } 518 519 @Override 520 public boolean shouldMinimizeSplitScreen() { 521 // TODO: Remove this once b/77875376 is fixed 522 return false; 523 } 524 525 @Override 526 public boolean supportsLongSwipe(RecentsActivity activity) { 527 return false; 528 } 529 530 @Override 531 public LongSwipeHelper getLongSwipeController(RecentsActivity activity, 532 RemoteAnimationTargetSet targetSet) { 533 return null; 534 } 535 536 @Override 537 public AlphaProperty getAlphaProperty(RecentsActivity activity) { 538 return activity.getDragLayer().getAlphaProperty(0); 539 } 540 541 @Override 542 public int getContainerType() { 543 return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER; 544 } 545 } 546 547 interface LayoutListener { 548 549 void open(); 550 551 void setHandler(WindowTransformSwipeHandler handler); 552 553 void finish(); 554 } 555 556 interface ActivityInitListener { 557 558 void register(); 559 560 void unregister(); 561 562 void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider, 563 Context context, Handler handler, long duration); 564 } 565 566 interface AnimationFactory { 567 568 default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { } 569 570 void createActivityController(long transitionLength, @InteractionType int interactionType); 571 572 default void onTransitionCancelled() { } 573 } 574 } 575