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