1 /* 2 * Copyright (C) 2014 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.systemui.recents.views; 18 19 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 20 21 import android.animation.Animator; 22 import android.animation.ObjectAnimator; 23 import android.animation.ValueAnimator; 24 import android.animation.ValueAnimator.AnimatorUpdateListener; 25 import android.app.ActivityOptions.OnAnimationStartedListener; 26 import android.content.Context; 27 import android.content.res.ColorStateList; 28 import android.graphics.Canvas; 29 import android.graphics.Color; 30 import android.graphics.Point; 31 import android.graphics.PointF; 32 import android.graphics.Rect; 33 import android.graphics.drawable.ColorDrawable; 34 import android.graphics.drawable.Drawable; 35 import android.util.ArraySet; 36 import android.util.AttributeSet; 37 import android.util.MathUtils; 38 import android.view.AppTransitionAnimationSpec; 39 import android.view.LayoutInflater; 40 import android.view.MotionEvent; 41 import android.view.View; 42 import android.view.ViewDebug; 43 import android.view.ViewPropertyAnimator; 44 import android.view.Window; 45 import android.view.WindowInsets; 46 import android.widget.FrameLayout; 47 import android.widget.TextView; 48 49 import com.android.internal.colorextraction.ColorExtractor; 50 import com.android.internal.colorextraction.drawable.GradientDrawable; 51 import com.android.internal.logging.MetricsLogger; 52 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 53 import com.android.settingslib.Utils; 54 import com.android.systemui.Interpolators; 55 import com.android.systemui.R; 56 import com.android.systemui.recents.Recents; 57 import com.android.systemui.recents.RecentsActivity; 58 import com.android.systemui.recents.RecentsActivityLaunchState; 59 import com.android.systemui.recents.RecentsConfiguration; 60 import com.android.systemui.recents.RecentsDebugFlags; 61 import com.android.systemui.recents.events.EventBus; 62 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 63 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; 64 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 65 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; 66 import com.android.systemui.recents.events.activity.LaunchTaskEvent; 67 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; 68 import com.android.systemui.recents.events.activity.ShowEmptyViewEvent; 69 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; 70 import com.android.systemui.recents.events.component.ExpandPipEvent; 71 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; 72 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; 73 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; 74 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; 75 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 76 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; 77 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 78 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 79 import com.android.systemui.recents.misc.ReferenceCountedTrigger; 80 import com.android.systemui.recents.misc.SystemServicesProxy; 81 import com.android.systemui.recents.misc.Utilities; 82 import com.android.systemui.recents.model.Task; 83 import com.android.systemui.recents.model.TaskStack; 84 import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer; 85 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture; 86 import com.android.systemui.stackdivider.WindowManagerProxy; 87 import com.android.systemui.statusbar.FlingAnimationUtils; 88 import com.android.systemui.statusbar.phone.ScrimController; 89 90 import java.io.PrintWriter; 91 import java.util.ArrayList; 92 import java.util.List; 93 94 /** 95 * This view is the the top level layout that contains TaskStacks (which are laid out according 96 * to their SpaceNode bounds. 97 */ 98 public class RecentsView extends FrameLayout { 99 100 private static final String TAG = "RecentsView"; 101 102 private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200; 103 104 private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134; 105 private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100; 106 107 private static final int BUSY_RECENTS_TASK_COUNT = 3; 108 109 private TaskStackView mTaskStackView; 110 private TextView mStackActionButton; 111 private TextView mEmptyView; 112 private final float mStackButtonShadowRadius; 113 private final PointF mStackButtonShadowDistance; 114 private final int mStackButtonShadowColor; 115 116 private boolean mAwaitingFirstLayout = true; 117 private boolean mLastTaskLaunchedWasFreeform; 118 119 @ViewDebug.ExportedProperty(category="recents") 120 Rect mSystemInsets = new Rect(); 121 private int mDividerSize; 122 123 private float mBusynessFactor; 124 private GradientDrawable mBackgroundScrim; 125 private ColorDrawable mMultiWindowBackgroundScrim; 126 private ValueAnimator mBackgroundScrimAnimator; 127 private Point mTmpDisplaySize = new Point(); 128 129 private final AnimatorUpdateListener mUpdateBackgroundScrimAlpha = (animation) -> { 130 int alpha = (Integer) animation.getAnimatedValue(); 131 mBackgroundScrim.setAlpha(alpha); 132 mMultiWindowBackgroundScrim.setAlpha(alpha); 133 }; 134 135 private RecentsTransitionHelper mTransitionHelper; 136 @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") 137 private RecentsViewTouchHandler mTouchHandler; 138 private final FlingAnimationUtils mFlingAnimationUtils; 139 140 public RecentsView(Context context) { 141 this(context, null); 142 } 143 144 public RecentsView(Context context, AttributeSet attrs) { 145 this(context, attrs, 0); 146 } 147 148 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 149 this(context, attrs, defStyleAttr, 0); 150 } 151 152 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 153 super(context, attrs, defStyleAttr, defStyleRes); 154 setWillNotDraw(false); 155 156 SystemServicesProxy ssp = Recents.getSystemServices(); 157 mTransitionHelper = new RecentsTransitionHelper(getContext()); 158 mDividerSize = ssp.getDockedDividerSize(context); 159 mTouchHandler = new RecentsViewTouchHandler(this); 160 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f); 161 mBackgroundScrim = new GradientDrawable(context); 162 mMultiWindowBackgroundScrim = new ColorDrawable(); 163 164 LayoutInflater inflater = LayoutInflater.from(context); 165 mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false); 166 addView(mEmptyView); 167 168 if (RecentsDebugFlags.Static.EnableStackActionButton) { 169 if (mStackActionButton != null) { 170 removeView(mStackActionButton); 171 } 172 mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration() 173 .isLowRamDevice 174 ? R.layout.recents_low_ram_stack_action_button 175 : R.layout.recents_stack_action_button, 176 this, false); 177 178 mStackButtonShadowRadius = mStackActionButton.getShadowRadius(); 179 mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(), 180 mStackActionButton.getShadowDy()); 181 mStackButtonShadowColor = mStackActionButton.getShadowColor(); 182 addView(mStackActionButton); 183 } 184 185 reevaluateStyles(); 186 } 187 188 public void reevaluateStyles() { 189 int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor); 190 boolean usingDarkText = Color.luminance(textColor) < 0.5f; 191 192 mEmptyView.setTextColor(textColor); 193 mEmptyView.setCompoundDrawableTintList(new ColorStateList(new int[][]{ 194 {android.R.attr.state_enabled}}, new int[]{textColor})); 195 196 if (mStackActionButton != null) { 197 mStackActionButton.setTextColor(textColor); 198 // Enable/disable shadow if text color is already dark. 199 if (usingDarkText) { 200 mStackActionButton.setShadowLayer(0, 0, 0, 0); 201 } else { 202 mStackActionButton.setShadowLayer(mStackButtonShadowRadius, 203 mStackButtonShadowDistance.x, mStackButtonShadowDistance.y, 204 mStackButtonShadowColor); 205 } 206 } 207 208 // Let's also require dark status and nav bars if the text is dark 209 int systemBarsStyle = usingDarkText ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | 210 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0; 211 212 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | 213 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 214 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 215 systemBarsStyle); 216 } 217 218 /** 219 * Called from RecentsActivity when it is relaunched. 220 */ 221 public void onReload(TaskStack stack, boolean isResumingFromVisible) { 222 final RecentsConfiguration config = Recents.getConfiguration(); 223 final RecentsActivityLaunchState launchState = config.getLaunchState(); 224 final boolean isTaskStackEmpty = stack.getTaskCount() == 0; 225 226 if (mTaskStackView == null) { 227 isResumingFromVisible = false; 228 mTaskStackView = new TaskStackView(getContext()); 229 mTaskStackView.setSystemInsets(mSystemInsets); 230 addView(mTaskStackView); 231 } 232 233 // Reset the state 234 mAwaitingFirstLayout = !isResumingFromVisible; 235 mLastTaskLaunchedWasFreeform = false; 236 237 // Update the stack 238 mTaskStackView.onReload(isResumingFromVisible); 239 updateStack(stack, true /* setStackViewTasks */); 240 updateBusyness(); 241 242 if (isResumingFromVisible) { 243 // If we are already visible, then restore the background scrim 244 animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION); 245 } else { 246 // If we are already occluded by the app, then set the final background scrim alpha now. 247 // Otherwise, defer until the enter animation completes to animate the scrim alpha with 248 // the tasks for the home animation. 249 if (launchState.launchedViaDockGesture || launchState.launchedFromApp 250 || isTaskStackEmpty) { 251 mBackgroundScrim.setAlpha((int) (getOpaqueScrimAlpha() * 255)); 252 } else { 253 mBackgroundScrim.setAlpha(0); 254 } 255 mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha()); 256 } 257 } 258 259 /** 260 * Called from RecentsActivity when the task stack is updated. 261 */ 262 public void updateStack(TaskStack stack, boolean setStackViewTasks) { 263 if (setStackViewTasks) { 264 mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */); 265 } 266 267 // Update the top level view's visibilities 268 if (stack.getTaskCount() > 0) { 269 hideEmptyView(); 270 } else { 271 showEmptyView(R.string.recents_empty_message); 272 } 273 } 274 275 /** 276 * Animates the scrim opacity based on how many tasks are visible. 277 * Called from {@link RecentsActivity} when tasks are dismissed. 278 */ 279 public void updateScrimOpacity() { 280 if (updateBusyness()) { 281 animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION); 282 } 283 } 284 285 /** 286 * Updates the busyness factor. 287 * 288 * @return True if it changed. 289 */ 290 private boolean updateBusyness() { 291 final int taskCount = mTaskStackView.getStack().getStackTaskCount(); 292 final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT) 293 / (float) BUSY_RECENTS_TASK_COUNT; 294 if (mBusynessFactor == busyness) { 295 return false; 296 } else { 297 mBusynessFactor = busyness; 298 return true; 299 } 300 } 301 302 /** 303 * Returns the current TaskStack. 304 */ 305 public TaskStack getStack() { 306 return mTaskStackView.getStack(); 307 } 308 309 /** 310 * Returns the window background scrim. 311 */ 312 public void updateBackgroundScrim(Window window, boolean isInMultiWindow) { 313 if (isInMultiWindow) { 314 mBackgroundScrim.setCallback(null); 315 window.setBackgroundDrawable(mMultiWindowBackgroundScrim); 316 } else { 317 mMultiWindowBackgroundScrim.setCallback(null); 318 window.setBackgroundDrawable(mBackgroundScrim); 319 } 320 } 321 322 /** 323 * Returns whether the last task launched was in the freeform stack or not. 324 */ 325 public boolean isLastTaskLaunchedFreeform() { 326 return mLastTaskLaunchedWasFreeform; 327 } 328 329 /** Launches the focused task from the first stack if possible */ 330 public boolean launchFocusedTask(int logEvent) { 331 if (mTaskStackView != null) { 332 Task task = mTaskStackView.getFocusedTask(); 333 if (task != null) { 334 TaskView taskView = mTaskStackView.getChildViewForTask(task); 335 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, 336 INVALID_STACK_ID, false)); 337 338 if (logEvent != 0) { 339 MetricsLogger.action(getContext(), logEvent, 340 task.key.getComponent().toString()); 341 } 342 return true; 343 } 344 } 345 return false; 346 } 347 348 /** Launches the task that recents was launched from if possible */ 349 public boolean launchPreviousTask() { 350 if (Recents.getConfiguration().getLaunchState().launchedFromPipApp) { 351 // If the app auto-entered PiP on the way to Recents, then just re-expand it 352 EventBus.getDefault().send(new ExpandPipEvent()); 353 return true; 354 } 355 356 if (mTaskStackView != null) { 357 Task task = getStack().getLaunchTarget(); 358 if (task != null) { 359 TaskView taskView = mTaskStackView.getChildViewForTask(task); 360 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, 361 INVALID_STACK_ID, false)); 362 return true; 363 } 364 } 365 return false; 366 } 367 368 /** Launches a given task. */ 369 public boolean launchTask(Task task, Rect taskBounds, int destinationStack) { 370 if (mTaskStackView != null) { 371 // Iterate the stack views and try and find the given task. 372 List<TaskView> taskViews = mTaskStackView.getTaskViews(); 373 int taskViewCount = taskViews.size(); 374 for (int j = 0; j < taskViewCount; j++) { 375 TaskView tv = taskViews.get(j); 376 if (tv.getTask() == task) { 377 EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds, 378 destinationStack, false)); 379 return true; 380 } 381 } 382 } 383 return false; 384 } 385 386 /** 387 * Hides the task stack and shows the empty view. 388 */ 389 public void showEmptyView(int msgResId) { 390 mTaskStackView.setVisibility(View.INVISIBLE); 391 mEmptyView.setText(msgResId); 392 mEmptyView.setVisibility(View.VISIBLE); 393 mEmptyView.bringToFront(); 394 if (RecentsDebugFlags.Static.EnableStackActionButton) { 395 mStackActionButton.bringToFront(); 396 } 397 } 398 399 /** 400 * Shows the task stack and hides the empty view. 401 */ 402 public void hideEmptyView() { 403 mEmptyView.setVisibility(View.INVISIBLE); 404 mTaskStackView.setVisibility(View.VISIBLE); 405 mTaskStackView.bringToFront(); 406 if (RecentsDebugFlags.Static.EnableStackActionButton) { 407 mStackActionButton.bringToFront(); 408 } 409 } 410 411 /** 412 * Set the color of the scrim. 413 * 414 * @param scrimColors Colors to use. 415 * @param animated Interpolate colors if true. 416 */ 417 public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) { 418 mBackgroundScrim.setColors(scrimColors, animated); 419 int alpha = mMultiWindowBackgroundScrim.getAlpha(); 420 mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor()); 421 mMultiWindowBackgroundScrim.setAlpha(alpha); 422 } 423 424 @Override 425 protected void onAttachedToWindow() { 426 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 427 EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2); 428 super.onAttachedToWindow(); 429 } 430 431 @Override 432 protected void onDetachedFromWindow() { 433 super.onDetachedFromWindow(); 434 EventBus.getDefault().unregister(this); 435 EventBus.getDefault().unregister(mTouchHandler); 436 } 437 438 /** 439 * This is called with the full size of the window since we are handling our own insets. 440 */ 441 @Override 442 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 443 int width = MeasureSpec.getSize(widthMeasureSpec); 444 int height = MeasureSpec.getSize(heightMeasureSpec); 445 446 if (mTaskStackView.getVisibility() != GONE) { 447 mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec); 448 } 449 450 // Measure the empty view to the full size of the screen 451 if (mEmptyView.getVisibility() != GONE) { 452 measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), 453 MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); 454 } 455 456 if (RecentsDebugFlags.Static.EnableStackActionButton) { 457 // Measure the stack action button within the constraints of the space above the stack 458 Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(); 459 measureChild(mStackActionButton, 460 MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST), 461 MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST)); 462 } 463 464 setMeasuredDimension(width, height); 465 } 466 467 /** 468 * This is called with the full size of the window since we are handling our own insets. 469 */ 470 @Override 471 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 472 if (mTaskStackView.getVisibility() != GONE) { 473 mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight()); 474 } 475 476 // Layout the empty view 477 if (mEmptyView.getVisibility() != GONE) { 478 int leftRightInsets = mSystemInsets.left + mSystemInsets.right; 479 int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom; 480 int childWidth = mEmptyView.getMeasuredWidth(); 481 int childHeight = mEmptyView.getMeasuredHeight(); 482 int childLeft = left + mSystemInsets.left + 483 Math.max(0, (right - left - leftRightInsets - childWidth)) / 2; 484 int childTop = top + mSystemInsets.top + 485 Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2; 486 mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); 487 } 488 489 // Needs to know the screen size since the gradient never scales up or down 490 // even when bounds change. 491 mContext.getDisplay().getRealSize(mTmpDisplaySize); 492 mBackgroundScrim.setScreenSize(mTmpDisplaySize.x, mTmpDisplaySize.y); 493 mBackgroundScrim.setBounds(left, top, right, bottom); 494 mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); 495 496 if (RecentsDebugFlags.Static.EnableStackActionButton) { 497 // Layout the stack action button such that its drawable is start-aligned with the 498 // stack, vertically centered in the available space above the stack 499 Rect buttonBounds = getStackActionButtonBoundsFromStackLayout(); 500 mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right, 501 buttonBounds.bottom); 502 } 503 504 if (mAwaitingFirstLayout) { 505 mAwaitingFirstLayout = false; 506 // If launched via dragging from the nav bar, then we should translate the whole view 507 // down offscreen 508 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 509 if (launchState.launchedViaDragGesture) { 510 setTranslationY(getMeasuredHeight()); 511 } else { 512 setTranslationY(0f); 513 } 514 515 if (Recents.getConfiguration().isLowRamDevice 516 && mEmptyView.getVisibility() == View.VISIBLE) { 517 animateEmptyView(true /* show */, null /* postAnimationTrigger */); 518 } 519 } 520 } 521 522 @Override 523 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 524 mSystemInsets.set(insets.getSystemWindowInsets()); 525 mTaskStackView.setSystemInsets(mSystemInsets); 526 requestLayout(); 527 return insets; 528 } 529 530 @Override 531 public boolean onInterceptTouchEvent(MotionEvent ev) { 532 return mTouchHandler.onInterceptTouchEvent(ev); 533 } 534 535 @Override 536 public boolean onTouchEvent(MotionEvent ev) { 537 return mTouchHandler.onTouchEvent(ev); 538 } 539 540 @Override 541 public void onDrawForeground(Canvas canvas) { 542 super.onDrawForeground(canvas); 543 544 ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); 545 for (int i = visDockStates.size() - 1; i >= 0; i--) { 546 visDockStates.get(i).viewState.draw(canvas); 547 } 548 } 549 550 @Override 551 protected boolean verifyDrawable(Drawable who) { 552 ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); 553 for (int i = visDockStates.size() - 1; i >= 0; i--) { 554 Drawable d = visDockStates.get(i).viewState.dockAreaOverlay; 555 if (d == who) { 556 return true; 557 } 558 } 559 return super.verifyDrawable(who); 560 } 561 562 /**** EventBus Events ****/ 563 564 public final void onBusEvent(LaunchTaskEvent event) { 565 mLastTaskLaunchedWasFreeform = event.task.isFreeformTask(); 566 mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView, 567 event.taskView, event.screenPinningRequested, event.targetTaskStack); 568 if (Recents.getConfiguration().isLowRamDevice) { 569 EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */)); 570 } 571 } 572 573 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { 574 int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; 575 if (RecentsDebugFlags.Static.EnableStackActionButton) { 576 // Hide the stack action button 577 EventBus.getDefault().send(new HideStackActionButtonEvent()); 578 } 579 animateBackgroundScrim(0f, taskViewExitToHomeDuration); 580 581 if (Recents.getConfiguration().isLowRamDevice) { 582 animateEmptyView(false /* show */, event.getAnimationTrigger()); 583 } 584 } 585 586 public final void onBusEvent(DragStartEvent event) { 587 updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(), 588 true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, 589 TaskStack.DockState.NONE.viewState.hintTextAlpha, 590 true /* animateAlpha */, false /* animateBounds */); 591 592 // Temporarily hide the stack action button without changing visibility 593 if (mStackActionButton != null) { 594 mStackActionButton.animate() 595 .alpha(0f) 596 .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION) 597 .setInterpolator(Interpolators.ALPHA_OUT) 598 .start(); 599 } 600 } 601 602 public final void onBusEvent(DragDropTargetChangedEvent event) { 603 if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) { 604 updateVisibleDockRegions( 605 Recents.getConfiguration().getDockStatesForCurrentOrientation(), 606 true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, 607 TaskStack.DockState.NONE.viewState.hintTextAlpha, 608 true /* animateAlpha */, true /* animateBounds */); 609 } else { 610 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 611 updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, 612 false /* isDefaultDockState */, -1, -1, true /* animateAlpha */, 613 true /* animateBounds */); 614 } 615 if (mStackActionButton != null) { 616 event.addPostAnimationCallback(new Runnable() { 617 @Override 618 public void run() { 619 // Move the clear all button to its new position 620 Rect buttonBounds = getStackActionButtonBoundsFromStackLayout(); 621 mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top, 622 buttonBounds.right, buttonBounds.bottom); 623 } 624 }); 625 } 626 } 627 628 public final void onBusEvent(final DragEndEvent event) { 629 // Handle the case where we drop onto a dock region 630 if (event.dropTarget instanceof TaskStack.DockState) { 631 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 632 633 // Hide the dock region 634 updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1, 635 false /* animateAlpha */, false /* animateBounds */); 636 637 // We translated the view but we need to animate it back from the current layout-space 638 // rect to its final layout-space rect 639 Utilities.setViewFrameFromTranslation(event.taskView); 640 641 // Dock the task and launch it 642 SystemServicesProxy ssp = Recents.getSystemServices(); 643 if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) { 644 final OnAnimationStartedListener startedListener = 645 new OnAnimationStartedListener() { 646 @Override 647 public void onAnimationStarted() { 648 EventBus.getDefault().send(new DockedFirstAnimationFrameEvent()); 649 // Remove the task and don't bother relaying out, as all the tasks will be 650 // relaid out when the stack changes on the multiwindow change event 651 getStack().removeTask(event.task, null, true /* fromDockGesture */); 652 } 653 }; 654 655 final Rect taskRect = getTaskRect(event.taskView); 656 AppTransitionAnimationSpecsFuture future = 657 mTransitionHelper.getAppTransitionFuture( 658 new AnimationSpecComposer() { 659 @Override 660 public List<AppTransitionAnimationSpec> composeSpecs() { 661 return mTransitionHelper.composeDockAnimationSpec( 662 event.taskView, taskRect); 663 } 664 }); 665 ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(), 666 mTransitionHelper.wrapStartedListener(startedListener), 667 true /* scaleUp */); 668 669 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP, 670 event.task.getTopComponent().flattenToShortString()); 671 } else { 672 EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task, 673 event.taskView)); 674 } 675 } else { 676 // Animate the overlay alpha back to 0 677 updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1, 678 true /* animateAlpha */, false /* animateBounds */); 679 } 680 681 // Show the stack action button again without changing visibility 682 if (mStackActionButton != null) { 683 mStackActionButton.animate() 684 .alpha(1f) 685 .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION) 686 .setInterpolator(Interpolators.ALPHA_IN) 687 .start(); 688 } 689 } 690 691 public final void onBusEvent(final DragEndCancelledEvent event) { 692 // Animate the overlay alpha back to 0 693 updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1, 694 true /* animateAlpha */, false /* animateBounds */); 695 } 696 697 private Rect getTaskRect(TaskView taskView) { 698 int[] location = taskView.getLocationOnScreen(); 699 int viewX = location[0]; 700 int viewY = location[1]; 701 return new Rect(viewX, viewY, 702 (int) (viewX + taskView.getWidth() * taskView.getScaleX()), 703 (int) (viewY + taskView.getHeight() * taskView.getScaleY())); 704 } 705 706 public final void onBusEvent(DraggingInRecentsEvent event) { 707 if (mTaskStackView.getTaskViews().size() > 0) { 708 setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY()); 709 } 710 } 711 712 public final void onBusEvent(DraggingInRecentsEndedEvent event) { 713 ViewPropertyAnimator animator = animate(); 714 if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 715 animator.translationY(getHeight()); 716 animator.withEndAction(new Runnable() { 717 @Override 718 public void run() { 719 WindowManagerProxy.getInstance().maximizeDockedStack(); 720 } 721 }); 722 mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity); 723 } else { 724 animator.translationY(0f); 725 animator.setListener(null); 726 mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity); 727 } 728 animator.start(); 729 } 730 731 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { 732 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 733 if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp 734 && getStack().getTaskCount() > 0) { 735 animateBackgroundScrim(getOpaqueScrimAlpha(), 736 TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION); 737 } 738 } 739 740 public final void onBusEvent(AllTaskViewsDismissedEvent event) { 741 EventBus.getDefault().send(new HideStackActionButtonEvent()); 742 } 743 744 public final void onBusEvent(DismissAllTaskViewsEvent event) { 745 SystemServicesProxy ssp = Recents.getSystemServices(); 746 if (!ssp.hasDockedTask()) { 747 // Animate the background away only if we are dismissing Recents to home 748 animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION); 749 } 750 } 751 752 public final void onBusEvent(ShowStackActionButtonEvent event) { 753 if (!RecentsDebugFlags.Static.EnableStackActionButton) { 754 return; 755 } 756 757 showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate); 758 } 759 760 public final void onBusEvent(HideStackActionButtonEvent event) { 761 if (!RecentsDebugFlags.Static.EnableStackActionButton) { 762 return; 763 } 764 765 hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */); 766 } 767 768 public final void onBusEvent(MultiWindowStateChangedEvent event) { 769 updateStack(event.stack, false /* setStackViewTasks */); 770 } 771 772 public final void onBusEvent(ShowEmptyViewEvent event) { 773 showEmptyView(R.string.recents_empty_message); 774 } 775 776 /** 777 * Shows the stack action button. 778 */ 779 private void showStackActionButton(final int duration, final boolean translate) { 780 if (!RecentsDebugFlags.Static.EnableStackActionButton) { 781 return; 782 } 783 784 final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); 785 if (mStackActionButton.getVisibility() == View.INVISIBLE) { 786 mStackActionButton.setVisibility(View.VISIBLE); 787 mStackActionButton.setAlpha(0f); 788 if (translate) { 789 mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() * 790 (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f)); 791 } else { 792 mStackActionButton.setTranslationY(0f); 793 } 794 postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 795 @Override 796 public void run() { 797 if (translate) { 798 mStackActionButton.animate() 799 .translationY(0f); 800 } 801 mStackActionButton.animate() 802 .alpha(1f) 803 .setDuration(duration) 804 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 805 .start(); 806 } 807 }); 808 } 809 postAnimationTrigger.flushLastDecrementRunnables(); 810 } 811 812 /** 813 * Hides the stack action button. 814 */ 815 private void hideStackActionButton(int duration, boolean translate) { 816 if (!RecentsDebugFlags.Static.EnableStackActionButton) { 817 return; 818 } 819 820 final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); 821 hideStackActionButton(duration, translate, postAnimationTrigger); 822 postAnimationTrigger.flushLastDecrementRunnables(); 823 } 824 825 /** 826 * Hides the stack action button. 827 */ 828 private void hideStackActionButton(int duration, boolean translate, 829 final ReferenceCountedTrigger postAnimationTrigger) { 830 if (!RecentsDebugFlags.Static.EnableStackActionButton) { 831 return; 832 } 833 834 if (mStackActionButton.getVisibility() == View.VISIBLE) { 835 if (translate) { 836 mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight() 837 * (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f)); 838 } 839 mStackActionButton.animate() 840 .alpha(0f) 841 .setDuration(duration) 842 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 843 .withEndAction(new Runnable() { 844 @Override 845 public void run() { 846 mStackActionButton.setVisibility(View.INVISIBLE); 847 postAnimationTrigger.decrement(); 848 } 849 }) 850 .start(); 851 postAnimationTrigger.increment(); 852 } 853 } 854 855 /** 856 * Animates a translation in the Y direction and fades in/out for empty view to show or hide it. 857 * @param show whether to translate up and fade in the empty view to the center of the screen 858 * @param postAnimationTrigger to keep track of the animation 859 */ 860 private void animateEmptyView(boolean show, ReferenceCountedTrigger postAnimationTrigger) { 861 float start = mTaskStackView.getStackAlgorithm().getTaskRect().height() / 4; 862 mEmptyView.setTranslationY(show ? start : 0); 863 mEmptyView.setAlpha(show ? 0f : 1f); 864 ViewPropertyAnimator animator = mEmptyView.animate() 865 .setDuration(150) 866 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 867 .translationY(show ? 0 : start) 868 .alpha(show ? 1f : 0f); 869 870 if (postAnimationTrigger != null) { 871 animator.setListener(postAnimationTrigger.decrementOnAnimationEnd()); 872 postAnimationTrigger.increment(); 873 } 874 animator.start(); 875 } 876 877 /** 878 * Updates the dock region to match the specified dock state. 879 */ 880 private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, 881 boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha, 882 boolean animateAlpha, boolean animateBounds) { 883 ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates, 884 new ArraySet<TaskStack.DockState>()); 885 ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); 886 for (int i = visDockStates.size() - 1; i >= 0; i--) { 887 TaskStack.DockState dockState = visDockStates.get(i); 888 TaskStack.DockState.ViewState viewState = dockState.viewState; 889 if (newDockStates == null || !newDockStatesSet.contains(dockState)) { 890 // This is no longer visible, so hide it 891 viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION, 892 Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds); 893 } else { 894 // This state is now visible, update the bounds and show it 895 int areaAlpha = overrideAreaAlpha != -1 896 ? overrideAreaAlpha 897 : viewState.dockAreaAlpha; 898 int hintAlpha = overrideHintAlpha != -1 899 ? overrideHintAlpha 900 : viewState.hintTextAlpha; 901 Rect bounds = isDefaultDockState 902 ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(), 903 mSystemInsets) 904 : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(), 905 mDividerSize, mSystemInsets, getResources()); 906 if (viewState.dockAreaOverlay.getCallback() != this) { 907 viewState.dockAreaOverlay.setCallback(this); 908 viewState.dockAreaOverlay.setBounds(bounds); 909 } 910 viewState.startAnimation(bounds, areaAlpha, hintAlpha, 911 TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN, 912 animateAlpha, animateBounds); 913 } 914 } 915 } 916 917 /** 918 * Scrim alpha based on how busy recents is: 919 * Scrim will be {@link ScrimController#GRADIENT_SCRIM_ALPHA} when the stack is empty, 920 * and {@link ScrimController#GRADIENT_SCRIM_ALPHA_BUSY} when it's full. 921 * 922 * @return Alpha from 0 to 1. 923 */ 924 private float getOpaqueScrimAlpha() { 925 return MathUtils.map(0, 1, ScrimController.GRADIENT_SCRIM_ALPHA, 926 ScrimController.GRADIENT_SCRIM_ALPHA_BUSY, mBusynessFactor); 927 } 928 929 /** 930 * Animates the background scrim to the given {@param alpha}. 931 */ 932 private void animateBackgroundScrim(float alpha, int duration) { 933 Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator); 934 // Calculate the absolute alpha to animate from 935 final int fromAlpha = mBackgroundScrim.getAlpha(); 936 final int toAlpha = (int) (alpha * 255); 937 mBackgroundScrimAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha); 938 mBackgroundScrimAnimator.setDuration(duration); 939 mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha 940 ? Interpolators.ALPHA_IN 941 : Interpolators.ALPHA_OUT); 942 mBackgroundScrimAnimator.addUpdateListener(mUpdateBackgroundScrimAlpha); 943 mBackgroundScrimAnimator.start(); 944 } 945 946 /** 947 * @return the bounds of the stack action button. 948 */ 949 Rect getStackActionButtonBoundsFromStackLayout() { 950 Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect()); 951 int left, top; 952 if (Recents.getConfiguration().isLowRamDevice) { 953 Rect windowRect = Recents.getSystemServices().getWindowRect(); 954 int spaceLeft = windowRect.width() - mSystemInsets.left - mSystemInsets.right; 955 left = (spaceLeft - mStackActionButton.getMeasuredWidth()) / 2 + mSystemInsets.left; 956 top = windowRect.height() - (mStackActionButton.getMeasuredHeight() 957 + mSystemInsets.bottom + mStackActionButton.getPaddingBottom() / 2); 958 } else { 959 left = isLayoutRtl() 960 ? actionButtonRect.left - mStackActionButton.getPaddingLeft() 961 : actionButtonRect.right + mStackActionButton.getPaddingRight() 962 - mStackActionButton.getMeasuredWidth(); 963 top = actionButtonRect.top + 964 (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2; 965 } 966 actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(), 967 top + mStackActionButton.getMeasuredHeight()); 968 return actionButtonRect; 969 } 970 971 View getStackActionButton() { 972 return mStackActionButton; 973 } 974 975 @Override 976 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 977 super.requestDisallowInterceptTouchEvent(disallowIntercept); 978 mTouchHandler.cancelStackActionButtonClick(); 979 } 980 981 public void dump(String prefix, PrintWriter writer) { 982 String innerPrefix = prefix + " "; 983 String id = Integer.toHexString(System.identityHashCode(this)); 984 985 writer.print(prefix); writer.print(TAG); 986 writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N"); 987 writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets)); 988 writer.print(" [0x"); writer.print(id); writer.print("]"); 989 writer.println(); 990 991 if (getStack() != null) { 992 getStack().dump(innerPrefix, writer); 993 } 994 if (mTaskStackView != null) { 995 mTaskStackView.dump(innerPrefix, writer); 996 } 997 } 998 } 999