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