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