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