RecentsView.java revision 214f0f0dc8018b135e2394982c79f1bced4a5fec
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 android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.content.Context; 23import android.graphics.Canvas; 24import android.graphics.Color; 25import android.graphics.Outline; 26import android.graphics.Rect; 27import android.graphics.drawable.ColorDrawable; 28import android.graphics.drawable.Drawable; 29import android.os.Handler; 30import android.util.ArraySet; 31import android.util.AttributeSet; 32import android.view.LayoutInflater; 33import android.view.MotionEvent; 34import android.view.View; 35import android.view.ViewOutlineProvider; 36import android.view.ViewPropertyAnimator; 37import android.view.WindowInsets; 38import android.view.animation.AnimationUtils; 39import android.view.animation.Interpolator; 40import android.widget.FrameLayout; 41import android.widget.TextView; 42 43import com.android.internal.logging.MetricsLogger; 44import com.android.internal.logging.MetricsProto.MetricsEvent; 45import com.android.systemui.R; 46import com.android.systemui.recents.Recents; 47import com.android.systemui.recents.RecentsActivity; 48import com.android.systemui.recents.RecentsActivityLaunchState; 49import com.android.systemui.recents.RecentsAppWidgetHostView; 50import com.android.systemui.recents.RecentsConfiguration; 51import com.android.systemui.recents.RecentsDebugFlags; 52import com.android.systemui.recents.events.EventBus; 53import com.android.systemui.recents.events.activity.ClearHistoryEvent; 54import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 55import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 56import com.android.systemui.recents.events.activity.HideHistoryButtonEvent; 57import com.android.systemui.recents.events.activity.HideHistoryEvent; 58import com.android.systemui.recents.events.activity.LaunchTaskEvent; 59import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent; 60import com.android.systemui.recents.events.activity.ShowHistoryEvent; 61import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent; 62import com.android.systemui.recents.events.activity.ToggleHistoryEvent; 63import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 64import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent; 65import com.android.systemui.recents.events.ui.DraggingInRecentsEvent; 66import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent; 67import com.android.systemui.recents.events.ui.UpdateBackgroundScrimEvent; 68import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 69import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 70import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 71import com.android.systemui.recents.history.RecentsHistoryView; 72import com.android.systemui.recents.misc.ReferenceCountedTrigger; 73import com.android.systemui.recents.misc.SystemServicesProxy; 74import com.android.systemui.recents.misc.Utilities; 75import com.android.systemui.recents.model.Task; 76import com.android.systemui.recents.model.TaskStack; 77import com.android.systemui.stackdivider.WindowManagerProxy; 78import com.android.systemui.statusbar.FlingAnimationUtils; 79import com.android.systemui.statusbar.phone.PhoneStatusBar; 80 81import java.util.ArrayList; 82import java.util.List; 83 84import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 85 86/** 87 * This view is the the top level layout that contains TaskStacks (which are laid out according 88 * to their SpaceNode bounds. 89 */ 90public class RecentsView extends FrameLayout { 91 92 private static final int DOCK_AREA_OVERLAY_TRANSITION_DURATION = 135; 93 private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200; 94 private static final float DEFAULT_SCRIM_ALPHA = 0.33f; 95 96 private final Handler mHandler; 97 98 private TaskStack mStack; 99 private TaskStackView mTaskStackView; 100 private RecentsAppWidgetHostView mSearchBar; 101 private TextView mHistoryButton; 102 private TextView mHistoryClearAllButton; 103 private View mEmptyView; 104 private RecentsHistoryView mHistoryView; 105 106 private boolean mAwaitingFirstLayout = true; 107 private boolean mLastTaskLaunchedWasFreeform; 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 private RecentsViewTouchHandler mTouchHandler; 116 117 private final Interpolator mFastOutSlowInInterpolator; 118 private final Interpolator mFastOutLinearInInterpolator; 119 private final FlingAnimationUtils mFlingAnimationUtils; 120 121 public RecentsView(Context context) { 122 this(context, null); 123 } 124 125 public RecentsView(Context context, AttributeSet attrs) { 126 this(context, attrs, 0); 127 } 128 129 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 130 this(context, attrs, defStyleAttr, 0); 131 } 132 133 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 134 super(context, attrs, defStyleAttr, defStyleRes); 135 setWillNotDraw(false); 136 137 SystemServicesProxy ssp = Recents.getSystemServices(); 138 mHandler = new Handler(); 139 mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler); 140 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, 141 com.android.internal.R.interpolator.fast_out_slow_in); 142 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, 143 com.android.internal.R.interpolator.fast_out_linear_in); 144 mDividerSize = ssp.getDockedDividerSize(context); 145 mTouchHandler = new RecentsViewTouchHandler(this); 146 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f); 147 148 final float cornerRadius = context.getResources().getDimensionPixelSize( 149 R.dimen.recents_task_view_rounded_corners_radius); 150 LayoutInflater inflater = LayoutInflater.from(context); 151 mHistoryButton = (TextView) inflater.inflate(R.layout.recents_history_button, this, false); 152 mHistoryButton.setOnClickListener(new View.OnClickListener() { 153 @Override 154 public void onClick(View v) { 155 EventBus.getDefault().send(new ToggleHistoryEvent()); 156 } 157 }); 158 addView(mHistoryButton); 159 mHistoryButton.setClipToOutline(true); 160 mHistoryButton.setOutlineProvider(new ViewOutlineProvider() { 161 @Override 162 public void getOutline(View view, Outline outline) { 163 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius); 164 } 165 }); 166 mEmptyView = inflater.inflate(R.layout.recents_empty, this, false); 167 addView(mEmptyView); 168 169 setBackground(mBackgroundScrim); 170 } 171 172 /** Set/get the bsp root node */ 173 public void setTaskStack(TaskStack stack) { 174 RecentsConfiguration config = Recents.getConfiguration(); 175 RecentsActivityLaunchState launchState = config.getLaunchState(); 176 mStack = stack; 177 if (launchState.launchedReuseTaskStackViews) { 178 if (mTaskStackView != null) { 179 // If onRecentsHidden is not triggered, we need to the stack view again here 180 mTaskStackView.reset(); 181 mTaskStackView.setStack(stack); 182 } else { 183 mTaskStackView = new TaskStackView(getContext(), stack); 184 addView(mTaskStackView); 185 } 186 } else { 187 if (mTaskStackView != null) { 188 removeView(mTaskStackView); 189 } 190 mTaskStackView = new TaskStackView(getContext(), stack); 191 addView(mTaskStackView); 192 } 193 194 // If we are already occluded by the app, then just set the default background scrim now. 195 // Otherwise, defer until the enter animation completes to animate the scrim with the 196 // tasks for the home animation. 197 if (launchState.launchedFromAppWithThumbnail || mStack.getTaskCount() == 0) { 198 mBackgroundScrim.setAlpha((int) (DEFAULT_SCRIM_ALPHA * 255)); 199 } else { 200 mBackgroundScrim.setAlpha(0); 201 } 202 203 // Update the top level view's visibilities 204 if (stack.getTaskCount() > 0) { 205 hideEmptyView(); 206 } else { 207 showEmptyView(); 208 } 209 210 // Trigger a new layout 211 requestLayout(); 212 } 213 214 /** 215 * Returns whether the last task launched was in the freeform stack or not. 216 */ 217 public boolean isLastTaskLaunchedFreeform() { 218 return mLastTaskLaunchedWasFreeform; 219 } 220 221 /** 222 * Returns whether the history is visible or not. 223 */ 224 public boolean isHistoryVisible() { 225 return mHistoryView != null && mHistoryView.isVisible(); 226 } 227 228 /** 229 * Returns the currently set task stack. 230 */ 231 public TaskStack getTaskStack() { 232 return mStack; 233 } 234 235 /** Gets the next task in the stack - or if the last - the top task */ 236 public Task getNextTaskOrTopTask(Task taskToSearch) { 237 Task returnTask = null; 238 boolean found = false; 239 if (mTaskStackView != null) { 240 TaskStack stack = mTaskStackView.getStack(); 241 ArrayList<Task> taskList = stack.getStackTasks(); 242 // Iterate the stack views and try and find the focused task 243 for (int j = taskList.size() - 1; j >= 0; --j) { 244 Task task = taskList.get(j); 245 // Return the next task in the line. 246 if (found) 247 return task; 248 // Remember the first possible task as the top task. 249 if (returnTask == null) 250 returnTask = task; 251 if (task == taskToSearch) 252 found = true; 253 } 254 } 255 return returnTask; 256 } 257 258 /** Launches the focused task from the first stack if possible */ 259 public boolean launchFocusedTask() { 260 if (mTaskStackView != null) { 261 Task task = mTaskStackView.getFocusedTask(); 262 if (task != null) { 263 TaskView taskView = mTaskStackView.getChildViewForTask(task); 264 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, 265 INVALID_STACK_ID, false)); 266 return true; 267 } 268 } 269 return false; 270 } 271 272 /** Launches the task that recents was launched from if possible */ 273 public boolean launchPreviousTask() { 274 if (mTaskStackView != null) { 275 TaskStack stack = mTaskStackView.getStack(); 276 Task task = stack.getLaunchTarget(); 277 if (task != null) { 278 TaskView taskView = mTaskStackView.getChildViewForTask(task); 279 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, 280 INVALID_STACK_ID, false)); 281 return true; 282 } 283 } 284 return false; 285 } 286 287 /** Launches a given task. */ 288 public boolean launchTask(Task task, Rect taskBounds, int destinationStack) { 289 if (mTaskStackView != null) { 290 // Iterate the stack views and try and find the given task. 291 List<TaskView> taskViews = mTaskStackView.getTaskViews(); 292 int taskViewCount = taskViews.size(); 293 for (int j = 0; j < taskViewCount; j++) { 294 TaskView tv = taskViews.get(j); 295 if (tv.getTask() == task) { 296 EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds, 297 destinationStack, false)); 298 return true; 299 } 300 } 301 } 302 return false; 303 } 304 305 /** Adds the search bar */ 306 public void setSearchBar(RecentsAppWidgetHostView searchBar) { 307 // Remove the previous search bar if one exists 308 if (mSearchBar != null && indexOfChild(mSearchBar) > -1) { 309 removeView(mSearchBar); 310 } 311 // Add the new search bar 312 if (searchBar != null) { 313 mSearchBar = searchBar; 314 addView(mSearchBar); 315 } 316 } 317 318 /** Returns whether there is currently a search bar */ 319 public boolean hasValidSearchBar() { 320 return mSearchBar != null && !mSearchBar.isReinflateRequired(); 321 } 322 323 /** 324 * Hides the task stack and shows the empty view. 325 */ 326 public void showEmptyView() { 327 if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) { 328 mSearchBar.setVisibility(View.INVISIBLE); 329 } 330 mTaskStackView.setVisibility(View.INVISIBLE); 331 mEmptyView.setVisibility(View.VISIBLE); 332 mEmptyView.bringToFront(); 333 mHistoryButton.bringToFront(); 334 } 335 336 /** 337 * Shows the task stack and hides the empty view. 338 */ 339 public void hideEmptyView() { 340 mEmptyView.setVisibility(View.INVISIBLE); 341 mTaskStackView.setVisibility(View.VISIBLE); 342 if (RecentsDebugFlags.Static.EnableSearchBar && (mSearchBar != null)) { 343 mSearchBar.setVisibility(View.VISIBLE); 344 } 345 mTaskStackView.bringToFront(); 346 if (mSearchBar != null) { 347 mSearchBar.bringToFront(); 348 } 349 mHistoryButton.bringToFront(); 350 } 351 352 @Override 353 protected void onAttachedToWindow() { 354 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 355 EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2); 356 super.onAttachedToWindow(); 357 } 358 359 @Override 360 protected void onDetachedFromWindow() { 361 super.onDetachedFromWindow(); 362 EventBus.getDefault().unregister(this); 363 EventBus.getDefault().unregister(mTouchHandler); 364 } 365 366 /** 367 * This is called with the full size of the window since we are handling our own insets. 368 */ 369 @Override 370 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 371 RecentsConfiguration config = Recents.getConfiguration(); 372 int width = MeasureSpec.getSize(widthMeasureSpec); 373 int height = MeasureSpec.getSize(heightMeasureSpec); 374 375 // Get the search bar bounds and measure the search bar layout 376 Rect searchBarSpaceBounds = new Rect(); 377 if (mSearchBar != null) { 378 config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top, 379 searchBarSpaceBounds); 380 mSearchBar.measure( 381 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), 382 MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); 383 } 384 385 Rect taskStackBounds = new Rect(); 386 config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top, 387 mSystemInsets.right, searchBarSpaceBounds, taskStackBounds); 388 if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) { 389 mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets); 390 mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec); 391 } 392 393 // Measure the empty view to the full size of the screen 394 if (mEmptyView.getVisibility() != GONE) { 395 measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 396 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 397 } 398 399 // Measure the history view 400 if (mHistoryView != null && mHistoryView.getVisibility() != GONE) { 401 measureChild(mHistoryView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 402 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); 403 } 404 405 // Measure the history button within the constraints of the space above the stack 406 Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect; 407 measureChild(mHistoryButton, 408 MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST), 409 MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST)); 410 if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) { 411 measureChild(mHistoryClearAllButton, 412 MeasureSpec.makeMeasureSpec(historyButtonRect.width(), MeasureSpec.AT_MOST), 413 MeasureSpec.makeMeasureSpec(historyButtonRect.height(), MeasureSpec.AT_MOST)); 414 } 415 416 setMeasuredDimension(width, height); 417 } 418 419 /** 420 * This is called with the full size of the window since we are handling our own insets. 421 */ 422 @Override 423 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 424 RecentsConfiguration config = Recents.getConfiguration(); 425 426 // Get the search bar bounds so that we lay it out 427 Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight()); 428 Rect searchBarSpaceBounds = new Rect(); 429 if (mSearchBar != null) { 430 config.getSearchBarBounds(measuredRect, 431 mSystemInsets.top, searchBarSpaceBounds); 432 mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top, 433 searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); 434 } 435 436 if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) { 437 mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight()); 438 } 439 440 // Layout the empty view 441 if (mEmptyView.getVisibility() != GONE) { 442 mEmptyView.layout(left, top, right, bottom); 443 } 444 445 // Layout the history view 446 if (mHistoryView != null && mHistoryView.getVisibility() != GONE) { 447 mHistoryView.layout(left, top, right, bottom); 448 } 449 450 // Layout the history button such that its drawable is start-aligned with the stack, 451 // vertically centered in the available space above the stack 452 Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect; 453 int historyLeft = isLayoutRtl() 454 ? historyButtonRect.right + mHistoryButton.getPaddingStart() 455 - mHistoryButton.getMeasuredWidth() 456 : historyButtonRect.left - mHistoryButton.getPaddingStart(); 457 int historyTop = historyButtonRect.top + 458 (historyButtonRect.height() - mHistoryButton.getMeasuredHeight()) / 2; 459 mHistoryButton.layout(historyLeft, historyTop, 460 historyLeft + mHistoryButton.getMeasuredWidth(), 461 historyTop + mHistoryButton.getMeasuredHeight()); 462 463 // Layout the history clear all button such that it is end-aligned with the stack, 464 // vertically centered in the available space above the stack 465 if (mHistoryClearAllButton != null && mHistoryClearAllButton.getVisibility() != GONE) { 466 int clearAllLeft = isLayoutRtl() 467 ? historyButtonRect.left - mHistoryClearAllButton.getPaddingStart() 468 : historyButtonRect.right + mHistoryClearAllButton.getPaddingStart() 469 - mHistoryClearAllButton.getMeasuredWidth(); 470 int clearAllTop = historyButtonRect.top + 471 (historyButtonRect.height() - mHistoryClearAllButton.getMeasuredHeight()) / 2; 472 mHistoryClearAllButton.layout(clearAllLeft, clearAllTop, 473 clearAllLeft + mHistoryClearAllButton.getMeasuredWidth(), 474 clearAllTop + mHistoryClearAllButton.getMeasuredHeight()); 475 } 476 477 if (mAwaitingFirstLayout) { 478 mAwaitingFirstLayout = false; 479 480 // If launched via dragging from the nav bar, then we should translate the whole view 481 // down offscreen 482 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 483 if (launchState.launchedViaDragGesture) { 484 setTranslationY(getMeasuredHeight()); 485 } else { 486 setTranslationY(0f); 487 } 488 } 489 } 490 491 @Override 492 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 493 mSystemInsets.set(insets.getSystemWindowInsets()); 494 requestLayout(); 495 return insets; 496 } 497 498 @Override 499 public boolean onInterceptTouchEvent(MotionEvent ev) { 500 return mTouchHandler.onInterceptTouchEvent(ev); 501 } 502 503 @Override 504 public boolean onTouchEvent(MotionEvent ev) { 505 return mTouchHandler.onTouchEvent(ev); 506 } 507 508 @Override 509 protected void dispatchDraw(Canvas canvas) { 510 super.dispatchDraw(canvas); 511 ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); 512 for (int i = visDockStates.size() - 1; i >= 0; i--) { 513 Drawable d = visDockStates.get(i).viewState.dockAreaOverlay; 514 if (d.getAlpha() > 0) { 515 d.draw(canvas); 516 } 517 } 518 } 519 520 @Override 521 protected boolean verifyDrawable(Drawable who) { 522 ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); 523 for (int i = visDockStates.size() - 1; i >= 0; i--) { 524 Drawable d = visDockStates.get(i).viewState.dockAreaOverlay; 525 if (d == who) { 526 return true; 527 } 528 } 529 return super.verifyDrawable(who); 530 } 531 532 /**** EventBus Events ****/ 533 534 public final void onBusEvent(LaunchTaskEvent event) { 535 mLastTaskLaunchedWasFreeform = event.task.isFreeformTask(); 536 mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView, 537 event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack); 538 } 539 540 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { 541 // Hide the history button 542 int taskViewExitToHomeDuration = getResources().getInteger( 543 R.integer.recents_task_exit_to_home_duration); 544 hideHistoryButton(taskViewExitToHomeDuration, false /* translate */); 545 animateBackgroundScrim(0f, taskViewExitToHomeDuration); 546 } 547 548 public final void onBusEvent(DragStartEvent event) { 549 updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(), 550 true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, 551 true /* animateAlpha */, false /* animateBounds */); 552 } 553 554 public final void onBusEvent(DragDropTargetChangedEvent event) { 555 if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) { 556 updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(), 557 true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha, 558 true /* animateAlpha */, true /* animateBounds */); 559 } else { 560 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 561 updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, 562 false /* isDefaultDockState */, -1, true /* animateAlpha */, 563 true /* animateBounds */); 564 } 565 } 566 567 public final void onBusEvent(final DragEndEvent event) { 568 // Handle the case where we drop onto a dock region 569 if (event.dropTarget instanceof TaskStack.DockState) { 570 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 571 572 // Hide the dock region 573 updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, 574 false /* animateAlpha */, false /* animateBounds */); 575 576 TaskStackLayoutAlgorithm stackLayout = mTaskStackView.getStackAlgorithm(); 577 TaskStackViewScroller stackScroller = mTaskStackView.getScroller(); 578 TaskViewTransform tmpTransform = new TaskViewTransform(); 579 580 // We translated the view but we need to animate it back from the current layout-space 581 // rect to its final layout-space rect 582 int x = (int) event.taskView.getTranslationX(); 583 int y = (int) event.taskView.getTranslationY(); 584 Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(), 585 event.taskView.getRight(), event.taskView.getBottom()); 586 taskViewRect.offset(x, y); 587 event.taskView.setTranslationX(0); 588 event.taskView.setTranslationY(0); 589 event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top, 590 taskViewRect.right, taskViewRect.bottom); 591 592 // Remove the task view after it is docked 593 mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */); 594 stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform, 595 null); 596 tmpTransform.alpha = 0; 597 tmpTransform.scale = 1f; 598 tmpTransform.rect.set(taskViewRect); 599 mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform, 600 new TaskViewAnimation(125, PhoneStatusBar.ALPHA_OUT, 601 new AnimatorListenerAdapter() { 602 @Override 603 public void onAnimationEnd(Animator animation) { 604 // Dock the task and launch it 605 SystemServicesProxy ssp = Recents.getSystemServices(); 606 ssp.startTaskInDockedMode(getContext(), event.taskView, 607 event.task.key.id, dockState.createMode); 608 609 // Animate the stack accordingly 610 TaskViewAnimation stackAnim = new TaskViewAnimation( 611 TaskStackView.DEFAULT_SYNC_STACK_DURATION, 612 mFastOutSlowInInterpolator); 613 mTaskStackView.getStack().removeTask(event.task, stackAnim); 614 } 615 })); 616 617 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP); 618 } else { 619 // Animate the overlay alpha back to 0 620 updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, 621 true /* animateAlpha */, false /* animateBounds */); 622 } 623 } 624 625 public final void onBusEvent(DraggingInRecentsEvent event) { 626 if (mTaskStackView.getTaskViews().size() > 0) { 627 setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY()); 628 } 629 } 630 631 public final void onBusEvent(DraggingInRecentsEndedEvent event) { 632 ViewPropertyAnimator animator = animate(); 633 if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 634 animator.translationY(getHeight()); 635 animator.withEndAction(new Runnable() { 636 @Override 637 public void run() { 638 WindowManagerProxy.getInstance().maximizeDockedStack(); 639 } 640 }); 641 mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity); 642 } else { 643 animator.translationY(0f); 644 animator.setListener(null); 645 mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity); 646 } 647 animator.start(); 648 } 649 650 public final void onBusEvent(TaskStackUpdatedEvent event) { 651 mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */); 652 mStack.createAffiliatedGroupings(getContext()); 653 } 654 655 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { 656 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 657 if (!launchState.launchedFromAppWithThumbnail && mStack.getTaskCount() > 0) { 658 int taskViewEnterFromHomeDuration = getResources().getInteger( 659 R.integer.recents_task_enter_from_home_duration); 660 animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, taskViewEnterFromHomeDuration); 661 } 662 } 663 664 public final void onBusEvent(UpdateBackgroundScrimEvent event) { 665 animateBackgroundScrim(event.alpha, DEFAULT_UPDATE_SCRIM_DURATION); 666 } 667 668 public final void onBusEvent(ResetBackgroundScrimEvent event) { 669 animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION); 670 } 671 672 public final void onBusEvent(RecentsVisibilityChangedEvent event) { 673 if (!event.visible) { 674 // Reset the view state 675 mAwaitingFirstLayout = true; 676 mLastTaskLaunchedWasFreeform = false; 677 hideHistoryButton(0, false /* translate */); 678 } 679 } 680 681 public final void onBusEvent(ToggleHistoryEvent event) { 682 if (mHistoryView != null && mHistoryView.isVisible()) { 683 EventBus.getDefault().send(new HideHistoryEvent(true /* animate */)); 684 } else { 685 EventBus.getDefault().send(new ShowHistoryEvent()); 686 } 687 } 688 689 public final void onBusEvent(ShowHistoryEvent event) { 690 if (mHistoryView == null) { 691 LayoutInflater inflater = LayoutInflater.from(getContext()); 692 mHistoryView = (RecentsHistoryView) inflater.inflate(R.layout.recents_history, this, 693 false); 694 addView(mHistoryView); 695 696 final float cornerRadius = getResources().getDimensionPixelSize( 697 R.dimen.recents_task_view_rounded_corners_radius); 698 mHistoryClearAllButton = (TextView) inflater.inflate( 699 R.layout.recents_history_clear_all_button, this, false); 700 mHistoryClearAllButton.setOnClickListener(new View.OnClickListener() { 701 @Override 702 public void onClick(View v) { 703 EventBus.getDefault().send(new ClearHistoryEvent()); 704 } 705 }); 706 mHistoryClearAllButton.setClipToOutline(true); 707 mHistoryClearAllButton.setOutlineProvider(new ViewOutlineProvider() { 708 @Override 709 public void getOutline(View view, Outline outline) { 710 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), cornerRadius); 711 } 712 }); 713 addView(mHistoryClearAllButton); 714 715 // Since this history view is inflated by a view stub after the insets have already 716 // been applied, we have to set them ourselves initial from the insets that were last 717 // provided. 718 mHistoryView.setSystemInsets(mSystemInsets); 719 mHistoryView.setHeaderHeight(mHistoryButton.getMeasuredHeight()); 720 mHistoryButton.bringToFront(); 721 mHistoryClearAllButton.bringToFront(); 722 } 723 724 // Animate the empty view in parallel with the history view (the task view animations are 725 // handled in TaskStackView) 726 Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect; 727 if (mEmptyView.getVisibility() == View.VISIBLE) { 728 int historyTransitionDuration = getResources().getInteger( 729 R.integer.recents_history_transition_duration); 730 mEmptyView.animate() 731 .alpha(0f) 732 .translationY(stackRect.height() / 2) 733 .setDuration(historyTransitionDuration) 734 .setInterpolator(mFastOutSlowInInterpolator) 735 .withEndAction(new Runnable() { 736 @Override 737 public void run() { 738 mEmptyView.setVisibility(View.INVISIBLE); 739 } 740 }) 741 .start(); 742 } 743 744 mHistoryView.show(mStack, stackRect.height(), mHistoryClearAllButton); 745 } 746 747 public final void onBusEvent(HideHistoryEvent event) { 748 // Animate the empty view in parallel with the history view (the task view animations are 749 // handled in TaskStackView) 750 Rect stackRect = mTaskStackView.mLayoutAlgorithm.mStackRect; 751 if (mStack.getTaskCount() == 0) { 752 int historyTransitionDuration = getResources().getInteger( 753 R.integer.recents_history_transition_duration); 754 mEmptyView.setVisibility(View.VISIBLE); 755 mEmptyView.animate() 756 .alpha(1f) 757 .translationY(0) 758 .setDuration(historyTransitionDuration) 759 .setInterpolator(mFastOutSlowInInterpolator) 760 .start(); 761 } 762 763 mHistoryView.hide(event.animate, stackRect.height(), mHistoryClearAllButton); 764 } 765 766 public final void onBusEvent(ShowHistoryButtonEvent event) { 767 showHistoryButton(150, event.translate); 768 } 769 770 public final void onBusEvent(HideHistoryButtonEvent event) { 771 hideHistoryButton(100, true /* translate */); 772 } 773 774 /** 775 * Shows the history button. 776 */ 777 private void showHistoryButton(final int duration, final boolean translate) { 778 final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); 779 if (mHistoryButton.getVisibility() == View.INVISIBLE) { 780 mHistoryButton.setVisibility(View.VISIBLE); 781 mHistoryButton.setAlpha(0f); 782 if (translate) { 783 mHistoryButton.setTranslationY(-mHistoryButton.getMeasuredHeight() * 0.25f); 784 } else { 785 mHistoryButton.setTranslationY(0f); 786 } 787 postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 788 @Override 789 public void run() { 790 if (translate) { 791 mHistoryButton.animate() 792 .translationY(0f); 793 } 794 mHistoryButton.animate() 795 .alpha(1f) 796 .setDuration(duration) 797 .setInterpolator(mFastOutSlowInInterpolator) 798 .withLayer() 799 .start(); 800 } 801 }); 802 } 803 postAnimationTrigger.flushLastDecrementRunnables(); 804 } 805 806 /** 807 * Hides the history button. 808 */ 809 private void hideHistoryButton(int duration, boolean translate) { 810 final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); 811 hideHistoryButton(duration, translate, postAnimationTrigger); 812 postAnimationTrigger.flushLastDecrementRunnables(); 813 } 814 815 /** 816 * Hides the history button. 817 */ 818 private void hideHistoryButton(int duration, boolean translate, 819 final ReferenceCountedTrigger postAnimationTrigger) { 820 if (mHistoryButton.getVisibility() == View.VISIBLE) { 821 if (translate) { 822 mHistoryButton.animate() 823 .translationY(-mHistoryButton.getMeasuredHeight() * 0.25f); 824 } 825 mHistoryButton.animate() 826 .alpha(0f) 827 .setDuration(duration) 828 .setInterpolator(mFastOutSlowInInterpolator) 829 .withEndAction(new Runnable() { 830 @Override 831 public void run() { 832 mHistoryButton.setVisibility(View.INVISIBLE); 833 postAnimationTrigger.decrement(); 834 } 835 }) 836 .withLayer() 837 .start(); 838 postAnimationTrigger.increment(); 839 } 840 } 841 842 /** 843 * Updates the dock region to match the specified dock state. 844 */ 845 private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, 846 boolean isDefaultDockState, int overrideAlpha, boolean animateAlpha, 847 boolean animateBounds) { 848 ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates, 849 new ArraySet<TaskStack.DockState>()); 850 ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); 851 for (int i = visDockStates.size() - 1; i >= 0; i--) { 852 TaskStack.DockState dockState = visDockStates.get(i); 853 TaskStack.DockState.ViewState viewState = dockState.viewState; 854 if (newDockStates == null || !newDockStatesSet.contains(dockState)) { 855 // This is no longer visible, so hide it 856 viewState.startAnimation(null, 0, DOCK_AREA_OVERLAY_TRANSITION_DURATION, 857 PhoneStatusBar.ALPHA_OUT, animateAlpha, animateBounds); 858 } else { 859 // This state is now visible, update the bounds and show it 860 int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha); 861 Rect bounds = isDefaultDockState 862 ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight()) 863 : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(), 864 mDividerSize, mSystemInsets, getResources()); 865 if (viewState.dockAreaOverlay.getCallback() != this) { 866 viewState.dockAreaOverlay.setCallback(this); 867 viewState.dockAreaOverlay.setBounds(bounds); 868 } 869 viewState.startAnimation(bounds, alpha, DOCK_AREA_OVERLAY_TRANSITION_DURATION, 870 PhoneStatusBar.ALPHA_IN, animateAlpha, animateBounds); 871 } 872 } 873 } 874 875 /** 876 * Animates the background scrim to the given {@param alpha}. 877 */ 878 private void animateBackgroundScrim(float alpha, int duration) { 879 Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator); 880 int alphaInt = (int) (alpha * 255); 881 mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA, 882 mBackgroundScrim.getAlpha(), alphaInt); 883 mBackgroundScrimAnimator.setDuration(duration); 884 mBackgroundScrimAnimator.setInterpolator(alphaInt > mBackgroundScrim.getAlpha() 885 ? PhoneStatusBar.ALPHA_OUT 886 : PhoneStatusBar.ALPHA_IN); 887 mBackgroundScrimAnimator.start(); 888 } 889} 890