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