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