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