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.FREEFORM_WORKSPACE_STACK_ID; 20 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; 21 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ObjectAnimator; 26 import android.animation.ValueAnimator; 27 import android.annotation.IntDef; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.res.Configuration; 31 import android.content.res.Resources; 32 import android.graphics.Canvas; 33 import android.graphics.Rect; 34 import android.graphics.drawable.Drawable; 35 import android.graphics.drawable.GradientDrawable; 36 import android.os.Bundle; 37 import android.provider.Settings; 38 import android.util.ArrayMap; 39 import android.util.ArraySet; 40 import android.util.MutableBoolean; 41 import android.view.LayoutInflater; 42 import android.view.MotionEvent; 43 import android.view.View; 44 import android.view.ViewDebug; 45 import android.view.ViewGroup; 46 import android.view.accessibility.AccessibilityEvent; 47 import android.view.accessibility.AccessibilityNodeInfo; 48 import android.widget.FrameLayout; 49 import android.widget.ScrollView; 50 51 import com.android.internal.logging.MetricsLogger; 52 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 53 import com.android.systemui.Interpolators; 54 import com.android.systemui.R; 55 import com.android.systemui.recents.Recents; 56 import com.android.systemui.recents.RecentsActivity; 57 import com.android.systemui.recents.RecentsActivityLaunchState; 58 import com.android.systemui.recents.RecentsConfiguration; 59 import com.android.systemui.recents.RecentsDebugFlags; 60 import com.android.systemui.recents.RecentsImpl; 61 import com.android.systemui.recents.events.EventBus; 62 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; 63 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; 64 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 65 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 66 import com.android.systemui.recents.events.activity.HideRecentsEvent; 67 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; 68 import com.android.systemui.recents.events.activity.IterateRecentsEvent; 69 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; 70 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; 71 import com.android.systemui.recents.events.activity.LaunchTaskEvent; 72 import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent; 73 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; 74 import com.android.systemui.recents.events.activity.PackagesChangedEvent; 75 import com.android.systemui.recents.events.activity.ShowEmptyViewEvent; 76 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; 77 import com.android.systemui.recents.events.component.ActivityPinnedEvent; 78 import com.android.systemui.recents.events.component.ExpandPipEvent; 79 import com.android.systemui.recents.events.component.HidePipMenuEvent; 80 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 81 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; 82 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; 83 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; 84 import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 85 import com.android.systemui.recents.events.ui.RecentsGrowingEvent; 86 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; 87 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent; 88 import com.android.systemui.recents.events.ui.UserInteractionEvent; 89 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 90 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; 91 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 92 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 93 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent; 94 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; 95 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; 96 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; 97 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; 98 import com.android.systemui.recents.misc.DozeTrigger; 99 import com.android.systemui.recents.misc.ReferenceCountedTrigger; 100 import com.android.systemui.recents.misc.SystemServicesProxy; 101 import com.android.systemui.recents.misc.Utilities; 102 import com.android.systemui.recents.model.Task; 103 import com.android.systemui.recents.model.TaskStack; 104 import com.android.systemui.recents.views.grid.GridTaskView; 105 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; 106 import com.android.systemui.recents.views.grid.TaskViewFocusFrame; 107 108 import java.io.PrintWriter; 109 import java.lang.annotation.Retention; 110 import java.lang.annotation.RetentionPolicy; 111 import java.util.ArrayList; 112 import java.util.List; 113 114 115 /* The visual representation of a task stack view */ 116 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 117 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 118 TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks, 119 ViewPool.ViewPoolConsumer<TaskView, Task> { 120 121 private static final String TAG = "TaskStackView"; 122 123 // The thresholds at which to show/hide the stack action button. 124 private static final float SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f; 125 private static final float HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f; 126 127 public static final int DEFAULT_SYNC_STACK_DURATION = 200; 128 public static final int SLOW_SYNC_STACK_DURATION = 250; 129 private static final int DRAG_SCALE_DURATION = 175; 130 static final float DRAG_SCALE_FACTOR = 1.05f; 131 132 private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216; 133 private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32; 134 135 // The actions to perform when resetting to initial state, 136 @Retention(RetentionPolicy.SOURCE) 137 @IntDef({INITIAL_STATE_UPDATE_NONE, INITIAL_STATE_UPDATE_ALL, INITIAL_STATE_UPDATE_LAYOUT_ONLY}) 138 public @interface InitialStateAction {} 139 /** Do not update the stack and layout to the initial state. */ 140 private static final int INITIAL_STATE_UPDATE_NONE = 0; 141 /** Update both the stack and layout to the initial state. */ 142 private static final int INITIAL_STATE_UPDATE_ALL = 1; 143 /** Update only the layout to the initial state. */ 144 private static final int INITIAL_STATE_UPDATE_LAYOUT_ONLY = 2; 145 146 private LayoutInflater mInflater; 147 private TaskStack mStack = new TaskStack(); 148 @ViewDebug.ExportedProperty(deepExport=true, prefix="layout_") 149 TaskStackLayoutAlgorithm mLayoutAlgorithm; 150 // The stable layout algorithm is only used to calculate the task rect with the stable bounds 151 private TaskStackLayoutAlgorithm mStableLayoutAlgorithm; 152 @ViewDebug.ExportedProperty(deepExport=true, prefix="scroller_") 153 private TaskStackViewScroller mStackScroller; 154 @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") 155 private TaskStackViewTouchHandler mTouchHandler; 156 private TaskStackAnimationHelper mAnimationHelper; 157 private GradientDrawable mFreeformWorkspaceBackground; 158 private ObjectAnimator mFreeformWorkspaceBackgroundAnimator; 159 private ViewPool<TaskView, Task> mViewPool; 160 161 private ArrayList<TaskView> mTaskViews = new ArrayList<>(); 162 private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); 163 private ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); 164 private AnimationProps mDeferredTaskViewLayoutAnimation = null; 165 166 @ViewDebug.ExportedProperty(deepExport=true, prefix="doze_") 167 private DozeTrigger mUIDozeTrigger; 168 @ViewDebug.ExportedProperty(deepExport=true, prefix="focused_task_") 169 private Task mFocusedTask; 170 171 private int mTaskCornerRadiusPx; 172 private int mDividerSize; 173 private int mStartTimerIndicatorDuration; 174 175 @ViewDebug.ExportedProperty(category="recents") 176 private boolean mTaskViewsClipDirty = true; 177 @ViewDebug.ExportedProperty(category="recents") 178 private boolean mEnterAnimationComplete = false; 179 @ViewDebug.ExportedProperty(category="recents") 180 private boolean mStackReloaded = false; 181 @ViewDebug.ExportedProperty(category="recents") 182 private boolean mFinishedLayoutAfterStackReload = false; 183 @ViewDebug.ExportedProperty(category="recents") 184 private boolean mLaunchNextAfterFirstMeasure = false; 185 @ViewDebug.ExportedProperty(category="recents") 186 @InitialStateAction 187 private int mInitialState = INITIAL_STATE_UPDATE_ALL; 188 @ViewDebug.ExportedProperty(category="recents") 189 private boolean mInMeasureLayout = false; 190 @ViewDebug.ExportedProperty(category="recents") 191 boolean mTouchExplorationEnabled; 192 @ViewDebug.ExportedProperty(category="recents") 193 boolean mScreenPinningEnabled; 194 195 // The stable stack bounds are the full bounds that we were measured with from RecentsView 196 @ViewDebug.ExportedProperty(category="recents") 197 private Rect mStableStackBounds = new Rect(); 198 // The current stack bounds are dynamic and may change as the user drags and drops 199 @ViewDebug.ExportedProperty(category="recents") 200 private Rect mStackBounds = new Rect(); 201 // The current window bounds at the point we were measured 202 @ViewDebug.ExportedProperty(category="recents") 203 private Rect mStableWindowRect = new Rect(); 204 // The current window bounds are dynamic and may change as the user drags and drops 205 @ViewDebug.ExportedProperty(category="recents") 206 private Rect mWindowRect = new Rect(); 207 // The current display bounds 208 @ViewDebug.ExportedProperty(category="recents") 209 private Rect mDisplayRect = new Rect(); 210 // The current display orientation 211 @ViewDebug.ExportedProperty(category="recents") 212 private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; 213 214 private Rect mTmpRect = new Rect(); 215 private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>(); 216 private List<TaskView> mTmpTaskViews = new ArrayList<>(); 217 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 218 private int[] mTmpIntPair = new int[2]; 219 private boolean mResetToInitialStateWhenResized; 220 private int mLastWidth; 221 private int mLastHeight; 222 223 // We keep track of the task view focused by user interaction and draw a frame around it in the 224 // grid layout. 225 private TaskViewFocusFrame mTaskViewFocusFrame; 226 227 private Task mPrefetchingTask; 228 private final float mFastFlingVelocity; 229 230 // A convenience update listener to request updating clipping of tasks 231 private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = 232 new ValueAnimator.AnimatorUpdateListener() { 233 @Override 234 public void onAnimationUpdate(ValueAnimator animation) { 235 if (!mTaskViewsClipDirty) { 236 mTaskViewsClipDirty = true; 237 invalidate(); 238 } 239 } 240 }; 241 242 // The drop targets for a task drag 243 private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() { 244 @Override 245 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, 246 boolean isCurrentTarget) { 247 // This drop target has a fixed bounds and should be checked last, so just fall through 248 // if it is the current target 249 if (!isCurrentTarget) { 250 return mLayoutAlgorithm.mFreeformRect.contains(x, y); 251 } 252 return false; 253 } 254 }; 255 256 private DropTarget mStackDropTarget = new DropTarget() { 257 @Override 258 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, 259 boolean isCurrentTarget) { 260 // This drop target has a fixed bounds and should be checked last, so just fall through 261 // if it is the current target 262 if (!isCurrentTarget) { 263 return mLayoutAlgorithm.mStackRect.contains(x, y); 264 } 265 return false; 266 } 267 }; 268 269 public TaskStackView(Context context) { 270 super(context); 271 SystemServicesProxy ssp = Recents.getSystemServices(); 272 Resources res = context.getResources(); 273 274 // Set the stack first 275 mStack.setCallbacks(this); 276 mViewPool = new ViewPool<>(context, this); 277 mInflater = LayoutInflater.from(context); 278 mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this); 279 mStableLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null); 280 mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm); 281 mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller); 282 mAnimationHelper = new TaskStackAnimationHelper(context, this); 283 mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ? 284 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) : 285 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); 286 mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); 287 mDividerSize = ssp.getDockedDividerSize(context); 288 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation; 289 mDisplayRect = ssp.getDisplayRect(); 290 291 // Create a frame to draw around the focused task view 292 if (Recents.getConfiguration().isGridEnabled) { 293 mTaskViewFocusFrame = new TaskViewFocusFrame(mContext, this, 294 mLayoutAlgorithm.mTaskGridLayoutAlgorithm); 295 addView(mTaskViewFocusFrame); 296 getViewTreeObserver().addOnGlobalFocusChangeListener(mTaskViewFocusFrame); 297 } 298 299 int taskBarDismissDozeDelaySeconds = getResources().getInteger( 300 R.integer.recents_task_bar_dismiss_delay_seconds); 301 mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() { 302 @Override 303 public void run() { 304 // Show the task bar dismiss buttons 305 List<TaskView> taskViews = getTaskViews(); 306 int taskViewCount = taskViews.size(); 307 for (int i = 0; i < taskViewCount; i++) { 308 TaskView tv = taskViews.get(i); 309 tv.startNoUserInteractionAnimation(); 310 } 311 } 312 }); 313 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 314 if (ssp.hasFreeformWorkspaceSupport()) { 315 setWillNotDraw(false); 316 } 317 318 mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable( 319 R.drawable.recents_freeform_workspace_bg); 320 mFreeformWorkspaceBackground.setCallback(this); 321 if (ssp.hasFreeformWorkspaceSupport()) { 322 mFreeformWorkspaceBackground.setColor( 323 getContext().getColor(R.color.recents_freeform_workspace_bg_color)); 324 } 325 } 326 327 @Override 328 protected void onAttachedToWindow() { 329 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 330 super.onAttachedToWindow(); 331 readSystemFlags(); 332 } 333 334 @Override 335 protected void onDetachedFromWindow() { 336 super.onDetachedFromWindow(); 337 EventBus.getDefault().unregister(this); 338 } 339 340 /** 341 * Called from RecentsActivity when it is relaunched. 342 */ 343 void onReload(boolean isResumingFromVisible) { 344 if (!isResumingFromVisible) { 345 // Reset the focused task 346 resetFocusedTask(getFocusedTask()); 347 } 348 349 // Reset the state of each of the task views 350 List<TaskView> taskViews = new ArrayList<>(); 351 taskViews.addAll(getTaskViews()); 352 taskViews.addAll(mViewPool.getViews()); 353 for (int i = taskViews.size() - 1; i >= 0; i--) { 354 taskViews.get(i).onReload(isResumingFromVisible); 355 } 356 357 // Reset the stack state 358 readSystemFlags(); 359 mTaskViewsClipDirty = true; 360 mUIDozeTrigger.stopDozing(); 361 if (isResumingFromVisible) { 362 // Animate in the freeform workspace 363 int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; 364 animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, 365 Interpolators.FAST_OUT_SLOW_IN)); 366 } else { 367 mStackScroller.reset(); 368 mStableLayoutAlgorithm.reset(); 369 mLayoutAlgorithm.reset(); 370 } 371 372 // Since we always animate to the same place in (the initial state), always reset the stack 373 // to the initial state when resuming 374 mStackReloaded = true; 375 mFinishedLayoutAfterStackReload = false; 376 mLaunchNextAfterFirstMeasure = false; 377 mInitialState = INITIAL_STATE_UPDATE_ALL; 378 requestLayout(); 379 } 380 381 /** 382 * Sets the stack tasks of this TaskStackView from the given TaskStack. 383 */ 384 public void setTasks(TaskStack stack, boolean allowNotifyStackChanges) { 385 boolean isInitialized = mLayoutAlgorithm.isInitialized(); 386 387 // Only notify if we are already initialized, otherwise, everything will pick up all the 388 // new and old tasks when we next layout 389 mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized); 390 } 391 392 /** Returns the task stack. */ 393 public TaskStack getStack() { 394 return mStack; 395 } 396 397 /** 398 * Updates this TaskStackView to the initial state. 399 */ 400 public void updateToInitialState() { 401 mStackScroller.setStackScrollToInitialState(); 402 mLayoutAlgorithm.setTaskOverridesForInitialState(mStack, false /* ignoreScrollToFront */); 403 } 404 405 /** Updates the list of task views */ 406 void updateTaskViewsList() { 407 mTaskViews.clear(); 408 int childCount = getChildCount(); 409 for (int i = 0; i < childCount; i++) { 410 View v = getChildAt(i); 411 if (v instanceof TaskView) { 412 mTaskViews.add((TaskView) v); 413 } 414 } 415 } 416 417 /** Gets the list of task views */ 418 List<TaskView> getTaskViews() { 419 return mTaskViews; 420 } 421 422 /** 423 * Returns the front most task view. 424 * 425 * @param stackTasksOnly if set, will return the front most task view in the stack (by default 426 * the front most task view will be freeform since they are placed above 427 * stack tasks) 428 */ 429 private TaskView getFrontMostTaskView(boolean stackTasksOnly) { 430 List<TaskView> taskViews = getTaskViews(); 431 int taskViewCount = taskViews.size(); 432 for (int i = taskViewCount - 1; i >= 0; i--) { 433 TaskView tv = taskViews.get(i); 434 Task task = tv.getTask(); 435 if (stackTasksOnly && task.isFreeformTask()) { 436 continue; 437 } 438 return tv; 439 } 440 return null; 441 } 442 443 /** 444 * Finds the child view given a specific {@param task}. 445 */ 446 public TaskView getChildViewForTask(Task t) { 447 List<TaskView> taskViews = getTaskViews(); 448 int taskViewCount = taskViews.size(); 449 for (int i = 0; i < taskViewCount; i++) { 450 TaskView tv = taskViews.get(i); 451 if (tv.getTask() == t) { 452 return tv; 453 } 454 } 455 return null; 456 } 457 458 /** Returns the stack algorithm for this task stack. */ 459 public TaskStackLayoutAlgorithm getStackAlgorithm() { 460 return mLayoutAlgorithm; 461 } 462 463 /** Returns the grid algorithm for this task stack. */ 464 public TaskGridLayoutAlgorithm getGridAlgorithm() { 465 return mLayoutAlgorithm.mTaskGridLayoutAlgorithm; 466 } 467 468 /** 469 * Returns the touch handler for this task stack. 470 */ 471 public TaskStackViewTouchHandler getTouchHandler() { 472 return mTouchHandler; 473 } 474 475 /** 476 * Adds a task to the ignored set. 477 */ 478 void addIgnoreTask(Task task) { 479 mIgnoreTasks.add(task.key); 480 } 481 482 /** 483 * Removes a task from the ignored set. 484 */ 485 void removeIgnoreTask(Task task) { 486 mIgnoreTasks.remove(task.key); 487 } 488 489 /** 490 * Returns whether the specified {@param task} is ignored. 491 */ 492 boolean isIgnoredTask(Task task) { 493 return mIgnoreTasks.contains(task.key); 494 } 495 496 /** 497 * Computes the task transforms at the current stack scroll for all visible tasks. If a valid 498 * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the 499 * visible range includes all tasks at the target stack scroll. This is useful for ensure that 500 * all views necessary for a transition or animation will be visible at the start. 501 * 502 * This call ignores freeform tasks. 503 * 504 * @param taskTransforms The set of task view transforms to reuse, this list will be sized to 505 * match the size of {@param tasks} 506 * @param tasks The set of tasks for which to generate transforms 507 * @param curStackScroll The current stack scroll 508 * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to. 509 * The range of the union of the visible views at the current and 510 * target stack scrolls will be returned. 511 * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range. 512 * Transforms will still be calculated for the ignore tasks. 513 * @return the front and back most visible task indices (there may be non visible tasks in 514 * between this range) 515 */ 516 int[] computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, 517 ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, 518 ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides) { 519 int taskCount = tasks.size(); 520 int[] visibleTaskRange = mTmpIntPair; 521 visibleTaskRange[0] = -1; 522 visibleTaskRange[1] = -1; 523 boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; 524 525 // We can reuse the task transforms where possible to reduce object allocation 526 Utilities.matchTaskListSize(tasks, taskTransforms); 527 528 // Update the stack transforms 529 TaskViewTransform frontTransform = null; 530 TaskViewTransform frontTransformAtTarget = null; 531 TaskViewTransform transform = null; 532 TaskViewTransform transformAtTarget = null; 533 for (int i = taskCount - 1; i >= 0; i--) { 534 Task task = tasks.get(i); 535 536 // Calculate the current and (if necessary) the target transform for the task 537 transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, 538 taskTransforms.get(i), frontTransform, ignoreTaskOverrides); 539 if (useTargetStackScroll && !transform.visible) { 540 // If we have a target stack scroll and the task is not currently visible, then we 541 // just update the transform at the new scroll 542 // TODO: Optimize this 543 transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll, 544 new TaskViewTransform(), frontTransformAtTarget); 545 if (transformAtTarget.visible) { 546 transform.copyFrom(transformAtTarget); 547 } 548 } 549 550 // For ignore tasks, only calculate the stack transform and skip the calculation of the 551 // visible stack indices 552 if (ignoreTasksSet.contains(task.key)) { 553 continue; 554 } 555 556 // For freeform tasks, only calculate the stack transform and skip the calculation of 557 // the visible stack indices 558 if (task.isFreeformTask()) { 559 continue; 560 } 561 562 frontTransform = transform; 563 frontTransformAtTarget = transformAtTarget; 564 if (transform.visible) { 565 if (visibleTaskRange[0] < 0) { 566 visibleTaskRange[0] = i; 567 } 568 visibleTaskRange[1] = i; 569 } 570 } 571 return visibleTaskRange; 572 } 573 574 /** 575 * Binds the visible {@link TaskView}s at the given target scroll. 576 */ 577 void bindVisibleTaskViews(float targetStackScroll) { 578 bindVisibleTaskViews(targetStackScroll, false /* ignoreTaskOverrides */); 579 } 580 581 /** 582 * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the 583 * current {@link TaskStack}. This call does not continue on to update their position to the 584 * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will 585 * be added/removed from the view hierarchy and placed in the correct Z order and initial 586 * position (if not currently on screen). 587 * 588 * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s 589 * includes those visible at the current stack scroll, and all at the 590 * target stack scroll. 591 * @param ignoreTaskOverrides If set, the visible task computation will get the transforms for 592 * tasks at their non-overridden task progress 593 */ 594 void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) { 595 // Get all the task transforms 596 ArrayList<Task> tasks = mStack.getStackTasks(); 597 int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks, 598 mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks, 599 ignoreTaskOverrides); 600 601 // Return all the invisible children to the pool 602 mTmpTaskViewMap.clear(); 603 List<TaskView> taskViews = getTaskViews(); 604 int lastFocusedTaskIndex = -1; 605 int taskViewCount = taskViews.size(); 606 for (int i = taskViewCount - 1; i >= 0; i--) { 607 TaskView tv = taskViews.get(i); 608 Task task = tv.getTask(); 609 610 // Skip ignored tasks 611 if (mIgnoreTasks.contains(task.key)) { 612 continue; 613 } 614 615 // It is possible for the set of lingering TaskViews to differ from the stack if the 616 // stack was updated before the relayout. If the task view is no longer in the stack, 617 // then just return it back to the view pool. 618 int taskIndex = mStack.indexOfStackTask(task); 619 TaskViewTransform transform = null; 620 if (taskIndex != -1) { 621 transform = mCurrentTaskTransforms.get(taskIndex); 622 } 623 624 if (task.isFreeformTask() || (transform != null && transform.visible)) { 625 mTmpTaskViewMap.put(task.key, tv); 626 } else { 627 if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) { 628 lastFocusedTaskIndex = taskIndex; 629 resetFocusedTask(task); 630 } 631 mViewPool.returnViewToPool(tv); 632 } 633 } 634 635 // Pick up all the newly visible children 636 for (int i = tasks.size() - 1; i >= 0; i--) { 637 Task task = tasks.get(i); 638 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 639 640 // Skip ignored tasks 641 if (mIgnoreTasks.contains(task.key)) { 642 continue; 643 } 644 645 // Skip the invisible non-freeform stack tasks 646 if (!task.isFreeformTask() && !transform.visible) { 647 continue; 648 } 649 650 TaskView tv = mTmpTaskViewMap.get(task.key); 651 if (tv == null) { 652 tv = mViewPool.pickUpViewFromPool(task, task); 653 if (task.isFreeformTask()) { 654 updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE); 655 } else { 656 if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { 657 updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), 658 AnimationProps.IMMEDIATE); 659 } else { 660 updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), 661 AnimationProps.IMMEDIATE); 662 } 663 } 664 } else { 665 // Reattach it in the right z order 666 final int taskIndex = mStack.indexOfStackTask(task); 667 final int insertIndex = findTaskViewInsertIndex(task, taskIndex); 668 if (insertIndex != getTaskViews().indexOf(tv)){ 669 detachViewFromParent(tv); 670 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 671 updateTaskViewsList(); 672 } 673 } 674 } 675 676 updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]); 677 678 // Update the focus if the previous focused task was returned to the view pool 679 if (lastFocusedTaskIndex != -1) { 680 int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1]) 681 ? visibleTaskRange[1] 682 : visibleTaskRange[0]; 683 setFocusedTask(newFocusedTaskIndex, false /* scrollToTask */, 684 true /* requestViewFocus */); 685 TaskView focusedTaskView = getChildViewForTask(mFocusedTask); 686 if (focusedTaskView != null) { 687 focusedTaskView.requestAccessibilityFocus(); 688 } 689 } 690 } 691 692 /** 693 * @see #relayoutTaskViews(AnimationProps, ArrayMap<Task, AnimationProps>, boolean) 694 */ 695 public void relayoutTaskViews(AnimationProps animation) { 696 relayoutTaskViews(animation, null /* animationOverrides */, 697 false /* ignoreTaskOverrides */); 698 } 699 700 /** 701 * Relayout the the visible {@link TaskView}s to their current transforms as specified by the 702 * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any 703 * animations that are current running on those task views, and will ensure that the children 704 * {@link TaskView}s will match the set of visible tasks in the stack. If a {@link Task} has 705 * an animation provided in {@param animationOverrides}, that will be used instead. 706 */ 707 private void relayoutTaskViews(AnimationProps animation, 708 ArrayMap<Task, AnimationProps> animationOverrides, 709 boolean ignoreTaskOverrides) { 710 // If we had a deferred animation, cancel that 711 cancelDeferredTaskViewLayoutAnimation(); 712 713 // Synchronize the current set of TaskViews 714 bindVisibleTaskViews(mStackScroller.getStackScroll(), 715 ignoreTaskOverrides /* ignoreTaskOverrides */); 716 717 // Animate them to their final transforms with the given animation 718 List<TaskView> taskViews = getTaskViews(); 719 int taskViewCount = taskViews.size(); 720 for (int i = 0; i < taskViewCount; i++) { 721 TaskView tv = taskViews.get(i); 722 Task task = tv.getTask(); 723 724 if (mIgnoreTasks.contains(task.key)) { 725 continue; 726 } 727 728 int taskIndex = mStack.indexOfStackTask(task); 729 TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); 730 if (animationOverrides != null && animationOverrides.containsKey(task)) { 731 animation = animationOverrides.get(task); 732 } 733 734 updateTaskViewToTransform(tv, transform, animation); 735 } 736 } 737 738 /** 739 * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. 740 */ 741 void relayoutTaskViewsOnNextFrame(AnimationProps animation) { 742 mDeferredTaskViewLayoutAnimation = animation; 743 invalidate(); 744 } 745 746 /** 747 * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a 748 * given set of {@link AnimationProps} properties. 749 */ 750 public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, 751 AnimationProps animation) { 752 if (taskView.isAnimatingTo(transform)) { 753 return; 754 } 755 taskView.cancelTransformAnimation(); 756 taskView.updateViewPropertiesToTaskTransform(transform, animation, 757 mRequestUpdateClippingListener); 758 } 759 760 /** 761 * Returns the current task transforms of all tasks, falling back to the stack layout if there 762 * is no {@link TaskView} for the task. 763 */ 764 public void getCurrentTaskTransforms(ArrayList<Task> tasks, 765 ArrayList<TaskViewTransform> transformsOut) { 766 Utilities.matchTaskListSize(tasks, transformsOut); 767 int focusState = mLayoutAlgorithm.getFocusState(); 768 for (int i = tasks.size() - 1; i >= 0; i--) { 769 Task task = tasks.get(i); 770 TaskViewTransform transform = transformsOut.get(i); 771 TaskView tv = getChildViewForTask(task); 772 if (tv != null) { 773 transform.fillIn(tv); 774 } else { 775 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), 776 focusState, transform, null, true /* forceUpdate */, 777 false /* ignoreTaskOverrides */); 778 } 779 transform.visible = true; 780 } 781 } 782 783 /** 784 * Returns the task transforms for all the tasks in the stack if the stack was at the given 785 * {@param stackScroll} and {@param focusState}. 786 */ 787 public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, 788 boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) { 789 Utilities.matchTaskListSize(tasks, transformsOut); 790 for (int i = tasks.size() - 1; i >= 0; i--) { 791 Task task = tasks.get(i); 792 TaskViewTransform transform = transformsOut.get(i); 793 mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null, 794 true /* forceUpdate */, ignoreTaskOverrides); 795 transform.visible = true; 796 } 797 } 798 799 /** 800 * Cancels the next deferred task view layout. 801 */ 802 void cancelDeferredTaskViewLayoutAnimation() { 803 mDeferredTaskViewLayoutAnimation = null; 804 } 805 806 /** 807 * Cancels all {@link TaskView} animations. 808 */ 809 void cancelAllTaskViewAnimations() { 810 List<TaskView> taskViews = getTaskViews(); 811 for (int i = taskViews.size() - 1; i >= 0; i--) { 812 final TaskView tv = taskViews.get(i); 813 if (!mIgnoreTasks.contains(tv.getTask().key)) { 814 tv.cancelTransformAnimation(); 815 } 816 } 817 } 818 819 /** 820 * Updates the clip for each of the task views from back to front. 821 */ 822 private void clipTaskViews() { 823 // We never clip task views in grid layout 824 if (Recents.getConfiguration().isGridEnabled) { 825 return; 826 } 827 828 // Update the clip on each task child 829 List<TaskView> taskViews = getTaskViews(); 830 TaskView tmpTv = null; 831 TaskView prevVisibleTv = null; 832 int taskViewCount = taskViews.size(); 833 for (int i = 0; i < taskViewCount; i++) { 834 TaskView tv = taskViews.get(i); 835 TaskView frontTv = null; 836 int clipBottom = 0; 837 838 if (isIgnoredTask(tv.getTask())) { 839 // For each of the ignore tasks, update the translationZ of its TaskView to be 840 // between the translationZ of the tasks immediately underneath it 841 if (prevVisibleTv != null) { 842 tv.setTranslationZ(Math.max(tv.getTranslationZ(), 843 prevVisibleTv.getTranslationZ() + 0.1f)); 844 } 845 } 846 847 if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) { 848 // Find the next view to clip against 849 for (int j = i + 1; j < taskViewCount; j++) { 850 tmpTv = taskViews.get(j); 851 852 if (tmpTv.shouldClipViewInStack()) { 853 frontTv = tmpTv; 854 break; 855 } 856 } 857 858 // Clip against the next view, this is just an approximation since we are 859 // stacked and we can make assumptions about the visibility of the this 860 // task relative to the ones in front of it. 861 if (frontTv != null) { 862 float taskBottom = tv.getBottom(); 863 float frontTaskTop = frontTv.getTop(); 864 if (frontTaskTop < taskBottom) { 865 // Map the stack view space coordinate (the rects) to view space 866 clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx; 867 } 868 } 869 } 870 tv.getViewBounds().setClipBottom(clipBottom); 871 tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom()); 872 prevVisibleTv = tv; 873 } 874 mTaskViewsClipDirty = false; 875 } 876 877 public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) { 878 updateLayoutAlgorithm(boundScrollToNewMinMax, Recents.getConfiguration().getLaunchState()); 879 } 880 881 /** 882 * Updates the layout algorithm min and max virtual scroll bounds. 883 */ 884 public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax, 885 RecentsActivityLaunchState launchState) { 886 // Compute the min and max scroll values 887 mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState); 888 889 // Update the freeform workspace background 890 SystemServicesProxy ssp = Recents.getSystemServices(); 891 if (ssp.hasFreeformWorkspaceSupport()) { 892 mTmpRect.set(mLayoutAlgorithm.mFreeformRect); 893 mFreeformWorkspaceBackground.setBounds(mTmpRect); 894 } 895 896 if (boundScrollToNewMinMax) { 897 mStackScroller.boundScroll(); 898 } 899 } 900 901 /** 902 * Updates the stack layout to its stable places. 903 */ 904 private void updateLayoutToStableBounds() { 905 mWindowRect.set(mStableWindowRect); 906 mStackBounds.set(mStableStackBounds); 907 mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets); 908 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, 909 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 910 updateLayoutAlgorithm(true /* boundScroll */); 911 } 912 913 /** Returns the scroller. */ 914 public TaskStackViewScroller getScroller() { 915 return mStackScroller; 916 } 917 918 /** 919 * Sets the focused task to the provided (bounded taskIndex). 920 * 921 * @return whether or not the stack will scroll as a part of this focus change 922 */ 923 public boolean setFocusedTask(int taskIndex, boolean scrollToTask, 924 final boolean requestViewFocus) { 925 return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0); 926 } 927 928 /** 929 * Sets the focused task to the provided (bounded focusTaskIndex). 930 * 931 * @return whether or not the stack will scroll as a part of this focus change 932 */ 933 public boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask, 934 boolean requestViewFocus, int timerIndicatorDuration) { 935 // Find the next task to focus 936 int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? 937 Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1; 938 final Task newFocusedTask = (newFocusedTaskIndex != -1) ? 939 mStack.getStackTasks().get(newFocusedTaskIndex) : null; 940 941 // Reset the last focused task state if changed 942 if (mFocusedTask != null) { 943 // Cancel the timer indicator, if applicable 944 if (timerIndicatorDuration > 0) { 945 final TaskView tv = getChildViewForTask(mFocusedTask); 946 if (tv != null) { 947 tv.getHeaderView().cancelFocusTimerIndicator(); 948 } 949 } 950 951 resetFocusedTask(mFocusedTask); 952 } 953 954 boolean willScroll = false; 955 mFocusedTask = newFocusedTask; 956 957 if (newFocusedTask != null) { 958 // Start the timer indicator, if applicable 959 if (timerIndicatorDuration > 0) { 960 final TaskView tv = getChildViewForTask(mFocusedTask); 961 if (tv != null) { 962 tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration); 963 } else { 964 // The view is null; set a flag for later 965 mStartTimerIndicatorDuration = timerIndicatorDuration; 966 } 967 } 968 969 if (scrollToTask) { 970 // Cancel any running enter animations at this point when we scroll or change focus 971 if (!mEnterAnimationComplete) { 972 cancelAllTaskViewAnimations(); 973 } 974 975 mLayoutAlgorithm.clearUnfocusedTaskOverrides(); 976 willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask, 977 requestViewFocus); 978 } else { 979 // Focus the task view 980 TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask); 981 if (newFocusedTaskView != null) { 982 newFocusedTaskView.setFocusedState(true, requestViewFocus); 983 } 984 } 985 // Any time a task view gets the focus, we move the focus frame around it. 986 if (mTaskViewFocusFrame != null) { 987 mTaskViewFocusFrame.moveGridTaskViewFocus(getChildViewForTask(newFocusedTask)); 988 } 989 } 990 return willScroll; 991 } 992 993 /** 994 * Sets the focused task relative to the currently focused task. 995 * 996 * @param forward whether to go to the next task in the stack (along the curve) or the previous 997 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 998 * if the currently focused task is not a stack task, will set the focus 999 * to the first visible stack task 1000 * @param animated determines whether to actually draw the highlight along with the change in 1001 * focus. 1002 */ 1003 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) { 1004 setRelativeFocusedTask(forward, stackTasksOnly, animated, false, 0); 1005 } 1006 1007 /** 1008 * Sets the focused task relative to the currently focused task. 1009 * 1010 * @param forward whether to go to the next task in the stack (along the curve) or the previous 1011 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 1012 * if the currently focused task is not a stack task, will set the focus 1013 * to the first visible stack task 1014 * @param animated determines whether to actually draw the highlight along with the change in 1015 * focus. 1016 * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll 1017 * happens. 1018 * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator 1019 */ 1020 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, 1021 boolean cancelWindowAnimations, int timerIndicatorDuration) { 1022 Task focusedTask = getFocusedTask(); 1023 int newIndex = mStack.indexOfStackTask(focusedTask); 1024 if (focusedTask != null) { 1025 if (stackTasksOnly) { 1026 List<Task> tasks = mStack.getStackTasks(); 1027 if (focusedTask.isFreeformTask()) { 1028 // Try and focus the front most stack task 1029 TaskView tv = getFrontMostTaskView(stackTasksOnly); 1030 if (tv != null) { 1031 newIndex = mStack.indexOfStackTask(tv.getTask()); 1032 } 1033 } else { 1034 // Try the next task if it is a stack task 1035 int tmpNewIndex = newIndex + (forward ? -1 : 1); 1036 if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { 1037 Task t = tasks.get(tmpNewIndex); 1038 if (!t.isFreeformTask()) { 1039 newIndex = tmpNewIndex; 1040 } 1041 } 1042 } 1043 } else { 1044 // No restrictions, lets just move to the new task (looping forward/backwards if 1045 // necessary) 1046 int taskCount = mStack.getTaskCount(); 1047 newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount; 1048 } 1049 } else { 1050 // We don't have a focused task 1051 float stackScroll = mStackScroller.getStackScroll(); 1052 ArrayList<Task> tasks = mStack.getStackTasks(); 1053 int taskCount = tasks.size(); 1054 if (useGridLayout()) { 1055 // For the grid layout, we directly set focus to the most recently used task 1056 // no matter we're moving forwards or backwards. 1057 newIndex = taskCount - 1; 1058 } else { 1059 // For the grid layout we pick a proper task to focus, according to the current 1060 // stack scroll. 1061 if (forward) { 1062 // Walk backwards and focus the next task smaller than the current stack scroll 1063 for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) { 1064 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex)); 1065 if (Float.compare(taskP, stackScroll) <= 0) { 1066 break; 1067 } 1068 } 1069 } else { 1070 // Walk forwards and focus the next task larger than the current stack scroll 1071 for (newIndex = 0; newIndex < taskCount; newIndex++) { 1072 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex)); 1073 if (Float.compare(taskP, stackScroll) >= 0) { 1074 break; 1075 } 1076 } 1077 } 1078 } 1079 } 1080 if (newIndex != -1) { 1081 boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */, 1082 true /* requestViewFocus */, timerIndicatorDuration); 1083 if (willScroll && cancelWindowAnimations) { 1084 // As we iterate to the next/previous task, cancel any current/lagging window 1085 // transition animations 1086 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 1087 } 1088 } 1089 } 1090 1091 /** 1092 * Resets the focused task. 1093 */ 1094 public void resetFocusedTask(Task task) { 1095 if (task != null) { 1096 TaskView tv = getChildViewForTask(task); 1097 if (tv != null) { 1098 tv.setFocusedState(false, false /* requestViewFocus */); 1099 } 1100 } 1101 if (mTaskViewFocusFrame != null) { 1102 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 1103 } 1104 mFocusedTask = null; 1105 } 1106 1107 /** 1108 * Returns the focused task. 1109 */ 1110 public Task getFocusedTask() { 1111 return mFocusedTask; 1112 } 1113 1114 /** 1115 * Returns the accessibility focused task. 1116 */ 1117 Task getAccessibilityFocusedTask() { 1118 List<TaskView> taskViews = getTaskViews(); 1119 int taskViewCount = taskViews.size(); 1120 for (int i = 0; i < taskViewCount; i++) { 1121 TaskView tv = taskViews.get(i); 1122 if (Utilities.isDescendentAccessibilityFocused(tv)) { 1123 return tv.getTask(); 1124 } 1125 } 1126 TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */); 1127 if (frontTv != null) { 1128 return frontTv.getTask(); 1129 } 1130 return null; 1131 } 1132 1133 @Override 1134 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1135 super.onInitializeAccessibilityEvent(event); 1136 List<TaskView> taskViews = getTaskViews(); 1137 int taskViewCount = taskViews.size(); 1138 if (taskViewCount > 0) { 1139 TaskView backMostTask = taskViews.get(0); 1140 TaskView frontMostTask = taskViews.get(taskViewCount - 1); 1141 event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask())); 1142 event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask())); 1143 event.setContentDescription(frontMostTask.getTask().title); 1144 } 1145 event.setItemCount(mStack.getTaskCount()); 1146 1147 int stackHeight = mLayoutAlgorithm.mStackRect.height(); 1148 event.setScrollY((int) (mStackScroller.getStackScroll() * stackHeight)); 1149 event.setMaxScrollY((int) (mLayoutAlgorithm.mMaxScrollP * stackHeight)); 1150 } 1151 1152 @Override 1153 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1154 super.onInitializeAccessibilityNodeInfo(info); 1155 List<TaskView> taskViews = getTaskViews(); 1156 int taskViewCount = taskViews.size(); 1157 if (taskViewCount > 1) { 1158 // Find the accessibility focused task 1159 Task focusedTask = getAccessibilityFocusedTask(); 1160 info.setScrollable(true); 1161 int focusedTaskIndex = mStack.indexOfStackTask(focusedTask); 1162 if (focusedTaskIndex > 0) { 1163 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 1164 } 1165 if (0 <= focusedTaskIndex && focusedTaskIndex < mStack.getTaskCount() - 1) { 1166 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 1167 } 1168 } 1169 } 1170 1171 @Override 1172 public CharSequence getAccessibilityClassName() { 1173 return ScrollView.class.getName(); 1174 } 1175 1176 @Override 1177 public boolean performAccessibilityAction(int action, Bundle arguments) { 1178 if (super.performAccessibilityAction(action, arguments)) { 1179 return true; 1180 } 1181 Task focusedTask = getAccessibilityFocusedTask(); 1182 int taskIndex = mStack.indexOfStackTask(focusedTask); 1183 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 1184 switch (action) { 1185 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 1186 setFocusedTask(taskIndex + 1, true /* scrollToTask */, true /* requestViewFocus */, 1187 0); 1188 return true; 1189 } 1190 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 1191 setFocusedTask(taskIndex - 1, true /* scrollToTask */, true /* requestViewFocus */, 1192 0); 1193 return true; 1194 } 1195 } 1196 } 1197 return false; 1198 } 1199 1200 @Override 1201 public boolean onInterceptTouchEvent(MotionEvent ev) { 1202 return mTouchHandler.onInterceptTouchEvent(ev); 1203 } 1204 1205 @Override 1206 public boolean onTouchEvent(MotionEvent ev) { 1207 return mTouchHandler.onTouchEvent(ev); 1208 } 1209 1210 @Override 1211 public boolean onGenericMotionEvent(MotionEvent ev) { 1212 return mTouchHandler.onGenericMotionEvent(ev); 1213 } 1214 1215 @Override 1216 public void computeScroll() { 1217 if (mStackScroller.computeScroll()) { 1218 // Notify accessibility 1219 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 1220 Recents.getTaskLoader().getHighResThumbnailLoader().setFlingingFast( 1221 mStackScroller.getScrollVelocity() > mFastFlingVelocity); 1222 } 1223 if (mDeferredTaskViewLayoutAnimation != null) { 1224 relayoutTaskViews(mDeferredTaskViewLayoutAnimation); 1225 mTaskViewsClipDirty = true; 1226 mDeferredTaskViewLayoutAnimation = null; 1227 } 1228 if (mTaskViewsClipDirty) { 1229 clipTaskViews(); 1230 } 1231 } 1232 1233 /** 1234 * Computes the maximum number of visible tasks and thumbnails. Requires that 1235 * updateLayoutForStack() is called first. 1236 */ 1237 public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { 1238 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks()); 1239 } 1240 1241 /** 1242 * Updates the system insets. 1243 */ 1244 public void setSystemInsets(Rect systemInsets) { 1245 boolean changed = false; 1246 changed |= mStableLayoutAlgorithm.setSystemInsets(systemInsets); 1247 changed |= mLayoutAlgorithm.setSystemInsets(systemInsets); 1248 if (changed) { 1249 requestLayout(); 1250 } 1251 } 1252 1253 /** 1254 * This is called with the full window width and height to allow stack view children to 1255 * perform the full screen transition down. 1256 */ 1257 @Override 1258 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1259 mInMeasureLayout = true; 1260 int width = MeasureSpec.getSize(widthMeasureSpec); 1261 int height = MeasureSpec.getSize(heightMeasureSpec); 1262 1263 // Update the stable stack bounds, but only update the current stack bounds if the stable 1264 // bounds have changed. This is because we may get spurious measures while dragging where 1265 // our current stack bounds reflect the target drop region. 1266 mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height), 1267 mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left, 1268 mLayoutAlgorithm.mSystemInsets.right, mTmpRect); 1269 if (!mTmpRect.equals(mStableStackBounds)) { 1270 mStableStackBounds.set(mTmpRect); 1271 mStackBounds.set(mTmpRect); 1272 mStableWindowRect.set(0, 0, width, height); 1273 mWindowRect.set(0, 0, width, height); 1274 } 1275 1276 // Compute the rects in the stack algorithm 1277 mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds, 1278 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1279 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, 1280 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 1281 updateLayoutAlgorithm(false /* boundScroll */); 1282 1283 // If this is the first layout, then scroll to the front of the stack, then update the 1284 // TaskViews with the stack so that we can lay them out 1285 boolean resetToInitialState = (width != mLastWidth || height != mLastHeight) 1286 && mResetToInitialStateWhenResized; 1287 if (!mFinishedLayoutAfterStackReload || mInitialState != INITIAL_STATE_UPDATE_NONE 1288 || resetToInitialState) { 1289 if (mInitialState != INITIAL_STATE_UPDATE_LAYOUT_ONLY || resetToInitialState) { 1290 updateToInitialState(); 1291 mResetToInitialStateWhenResized = false; 1292 } 1293 if (mFinishedLayoutAfterStackReload) { 1294 mInitialState = INITIAL_STATE_UPDATE_NONE; 1295 } 1296 } 1297 // If we got the launch-next event before the first layout pass, then re-send it after the 1298 // initial state has been updated 1299 if (mLaunchNextAfterFirstMeasure) { 1300 mLaunchNextAfterFirstMeasure = false; 1301 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 1302 } 1303 1304 // Rebind all the views, including the ignore ones 1305 bindVisibleTaskViews(mStackScroller.getStackScroll(), false /* ignoreTaskOverrides */); 1306 1307 // Measure each of the TaskViews 1308 mTmpTaskViews.clear(); 1309 mTmpTaskViews.addAll(getTaskViews()); 1310 mTmpTaskViews.addAll(mViewPool.getViews()); 1311 int taskViewCount = mTmpTaskViews.size(); 1312 for (int i = 0; i < taskViewCount; i++) { 1313 measureTaskView(mTmpTaskViews.get(i)); 1314 } 1315 if (mTaskViewFocusFrame != null) { 1316 mTaskViewFocusFrame.measure(); 1317 } 1318 1319 setMeasuredDimension(width, height); 1320 mLastWidth = width; 1321 mLastHeight = height; 1322 mInMeasureLayout = false; 1323 } 1324 1325 /** 1326 * Measures a TaskView. 1327 */ 1328 private void measureTaskView(TaskView tv) { 1329 Rect padding = new Rect(); 1330 if (tv.getBackground() != null) { 1331 tv.getBackground().getPadding(padding); 1332 } 1333 mTmpRect.set(mStableLayoutAlgorithm.getTaskRect()); 1334 mTmpRect.union(mLayoutAlgorithm.getTaskRect()); 1335 tv.measure( 1336 MeasureSpec.makeMeasureSpec(mTmpRect.width() + padding.left + padding.right, 1337 MeasureSpec.EXACTLY), 1338 MeasureSpec.makeMeasureSpec(mTmpRect.height() + padding.top + padding.bottom, 1339 MeasureSpec.EXACTLY)); 1340 } 1341 1342 @Override 1343 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1344 // Layout each of the TaskViews 1345 mTmpTaskViews.clear(); 1346 mTmpTaskViews.addAll(getTaskViews()); 1347 mTmpTaskViews.addAll(mViewPool.getViews()); 1348 int taskViewCount = mTmpTaskViews.size(); 1349 for (int i = 0; i < taskViewCount; i++) { 1350 layoutTaskView(changed, mTmpTaskViews.get(i)); 1351 } 1352 if (mTaskViewFocusFrame != null) { 1353 mTaskViewFocusFrame.layout(); 1354 } 1355 1356 if (changed) { 1357 if (mStackScroller.isScrollOutOfBounds()) { 1358 mStackScroller.boundScroll(); 1359 } 1360 } 1361 1362 // Relayout all of the task views including the ignored ones 1363 relayoutTaskViews(AnimationProps.IMMEDIATE); 1364 clipTaskViews(); 1365 1366 if (!mFinishedLayoutAfterStackReload) { 1367 // Prepare the task enter animations (this can be called numerous times) 1368 mInitialState = INITIAL_STATE_UPDATE_NONE; 1369 onFirstLayout(); 1370 1371 if (mStackReloaded) { 1372 mFinishedLayoutAfterStackReload = true; 1373 tryStartEnterAnimation(); 1374 } 1375 } 1376 } 1377 1378 /** 1379 * Lays out a TaskView. 1380 */ 1381 private void layoutTaskView(boolean changed, TaskView tv) { 1382 if (changed) { 1383 Rect padding = new Rect(); 1384 if (tv.getBackground() != null) { 1385 tv.getBackground().getPadding(padding); 1386 } 1387 mTmpRect.set(mStableLayoutAlgorithm.getTaskRect()); 1388 mTmpRect.union(mLayoutAlgorithm.getTaskRect()); 1389 tv.cancelTransformAnimation(); 1390 tv.layout(mTmpRect.left - padding.left, mTmpRect.top - padding.top, 1391 mTmpRect.right + padding.right, mTmpRect.bottom + padding.bottom); 1392 } else { 1393 // If the layout has not changed, then just lay it out again in-place 1394 tv.layout(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1395 } 1396 } 1397 1398 /** Handler for the first layout. */ 1399 void onFirstLayout() { 1400 // Setup the view for the enter animation 1401 mAnimationHelper.prepareForEnterAnimation(); 1402 1403 // Animate in the freeform workspace 1404 int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; 1405 animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, 1406 Interpolators.FAST_OUT_SLOW_IN)); 1407 1408 // Set the task focused state without requesting view focus, and leave the focus animations 1409 // until after the enter-animation 1410 RecentsConfiguration config = Recents.getConfiguration(); 1411 RecentsActivityLaunchState launchState = config.getLaunchState(); 1412 1413 // We set the initial focused task view iff the following conditions are satisfied: 1414 // 1. Recents is showing task views in stack layout. 1415 // 2. Recents is launched with ALT + TAB. 1416 boolean setFocusOnFirstLayout = !useGridLayout() || 1417 Recents.getConfiguration().getLaunchState().launchedWithAltTab; 1418 if (setFocusOnFirstLayout) { 1419 int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(), 1420 useGridLayout()); 1421 if (focusedTaskIndex != -1) { 1422 setFocusedTask(focusedTaskIndex, false /* scrollToTask */, 1423 false /* requestViewFocus */); 1424 } 1425 } 1426 updateStackActionButtonVisibility(); 1427 } 1428 1429 public boolean isTouchPointInView(float x, float y, TaskView tv) { 1430 mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1431 mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY()); 1432 return mTmpRect.contains((int) x, (int) y); 1433 } 1434 1435 /** 1436 * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when 1437 * calculating the scroll position before and after a layout change. 1438 */ 1439 public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) { 1440 for (int i = tasks.size() - 1; i >= 0; i--) { 1441 Task task = tasks.get(i); 1442 1443 // Ignore deleting tasks 1444 if (isIgnoredTask(task)) { 1445 if (i == tasks.size() - 1) { 1446 isFrontMostTask.value = true; 1447 } 1448 continue; 1449 } 1450 return task; 1451 } 1452 return null; 1453 } 1454 1455 @Override 1456 protected void onDraw(Canvas canvas) { 1457 super.onDraw(canvas); 1458 1459 // Draw the freeform workspace background 1460 SystemServicesProxy ssp = Recents.getSystemServices(); 1461 if (ssp.hasFreeformWorkspaceSupport()) { 1462 if (mFreeformWorkspaceBackground.getAlpha() > 0) { 1463 mFreeformWorkspaceBackground.draw(canvas); 1464 } 1465 } 1466 } 1467 1468 @Override 1469 protected boolean verifyDrawable(Drawable who) { 1470 if (who == mFreeformWorkspaceBackground) { 1471 return true; 1472 } 1473 return super.verifyDrawable(who); 1474 } 1475 1476 /** 1477 * Launches the freeform tasks. 1478 */ 1479 public boolean launchFreeformTasks() { 1480 ArrayList<Task> tasks = mStack.getFreeformTasks(); 1481 if (!tasks.isEmpty()) { 1482 Task frontTask = tasks.get(tasks.size() - 1); 1483 if (frontTask != null && frontTask.isFreeformTask()) { 1484 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask), 1485 frontTask, null, INVALID_STACK_ID, false)); 1486 return true; 1487 } 1488 } 1489 return false; 1490 } 1491 1492 /**** TaskStackCallbacks Implementation ****/ 1493 1494 @Override 1495 public void onStackTaskAdded(TaskStack stack, Task newTask) { 1496 // Update the min/max scroll and animate other task views into their new positions 1497 updateLayoutAlgorithm(true /* boundScroll */); 1498 1499 // Animate all the tasks into place 1500 relayoutTaskViews(!mFinishedLayoutAfterStackReload 1501 ? AnimationProps.IMMEDIATE 1502 : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 1503 } 1504 1505 /** 1506 * We expect that the {@link TaskView} associated with the removed task is already hidden. 1507 */ 1508 @Override 1509 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, 1510 AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved) { 1511 if (mFocusedTask == removedTask) { 1512 resetFocusedTask(removedTask); 1513 } 1514 1515 // Remove the view associated with this task, we can't rely on updateTransforms 1516 // to work here because the task is no longer in the list 1517 TaskView tv = getChildViewForTask(removedTask); 1518 if (tv != null) { 1519 mViewPool.returnViewToPool(tv); 1520 } 1521 1522 // Remove the task from the ignored set 1523 removeIgnoreTask(removedTask); 1524 1525 // If requested, relayout with the given animation 1526 if (animation != null) { 1527 updateLayoutAlgorithm(true /* boundScroll */); 1528 relayoutTaskViews(animation); 1529 } 1530 1531 // Update the new front most task's action button 1532 if (mScreenPinningEnabled && newFrontMostTask != null) { 1533 TaskView frontTv = getChildViewForTask(newFrontMostTask); 1534 if (frontTv != null) { 1535 frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION); 1536 } 1537 } 1538 1539 // If there are no remaining tasks, then just close recents 1540 if (mStack.getTaskCount() == 0) { 1541 if (dismissRecentsIfAllRemoved) { 1542 EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture 1543 ? R.string.recents_empty_message 1544 : R.string.recents_empty_message_dismissed_all)); 1545 } else { 1546 EventBus.getDefault().send(new ShowEmptyViewEvent()); 1547 } 1548 } 1549 } 1550 1551 @Override 1552 public void onStackTasksRemoved(TaskStack stack) { 1553 // Reset the focused task 1554 resetFocusedTask(getFocusedTask()); 1555 1556 // Return all the views to the pool 1557 List<TaskView> taskViews = new ArrayList<>(); 1558 taskViews.addAll(getTaskViews()); 1559 for (int i = taskViews.size() - 1; i >= 0; i--) { 1560 mViewPool.returnViewToPool(taskViews.get(i)); 1561 } 1562 1563 // Remove all the ignore tasks 1564 mIgnoreTasks.clear(); 1565 1566 // If there are no remaining tasks, then just close recents 1567 EventBus.getDefault().send(new AllTaskViewsDismissedEvent( 1568 R.string.recents_empty_message_dismissed_all)); 1569 } 1570 1571 @Override 1572 public void onStackTasksUpdated(TaskStack stack) { 1573 if (!mFinishedLayoutAfterStackReload) { 1574 return; 1575 } 1576 1577 // Update the layout and immediately layout 1578 updateLayoutAlgorithm(false /* boundScroll */); 1579 relayoutTaskViews(AnimationProps.IMMEDIATE); 1580 1581 // Rebind all the task views. This will not trigger new resources to be loaded 1582 // unless they have actually changed 1583 List<TaskView> taskViews = getTaskViews(); 1584 int taskViewCount = taskViews.size(); 1585 for (int i = 0; i < taskViewCount; i++) { 1586 TaskView tv = taskViews.get(i); 1587 bindTaskView(tv, tv.getTask()); 1588 } 1589 } 1590 1591 /**** ViewPoolConsumer Implementation ****/ 1592 1593 @Override 1594 public TaskView createView(Context context) { 1595 if (Recents.getConfiguration().isGridEnabled) { 1596 return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false); 1597 } else { 1598 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1599 } 1600 } 1601 1602 @Override 1603 public void onReturnViewToPool(TaskView tv) { 1604 final Task task = tv.getTask(); 1605 1606 // Unbind the task from the task view 1607 unbindTaskView(tv, task); 1608 1609 // Reset the view properties and view state 1610 tv.clearAccessibilityFocus(); 1611 tv.resetViewProperties(); 1612 tv.setFocusedState(false, false /* requestViewFocus */); 1613 tv.setClipViewInStack(false); 1614 if (mScreenPinningEnabled) { 1615 tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null); 1616 } 1617 1618 // Detach the view from the hierarchy 1619 detachViewFromParent(tv); 1620 // Update the task views list after removing the task view 1621 updateTaskViewsList(); 1622 } 1623 1624 @Override 1625 public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) { 1626 // Find the index where this task should be placed in the stack 1627 int taskIndex = mStack.indexOfStackTask(task); 1628 int insertIndex = findTaskViewInsertIndex(task, taskIndex); 1629 1630 // Add/attach the view to the hierarchy 1631 if (isNewView) { 1632 if (mInMeasureLayout) { 1633 // If we are measuring the layout, then just add the view normally as it will be 1634 // laid out during the layout pass 1635 addView(tv, insertIndex); 1636 } else { 1637 // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout 1638 // pass, and we should layout the new child ourselves 1639 ViewGroup.LayoutParams params = tv.getLayoutParams(); 1640 if (params == null) { 1641 params = generateDefaultLayoutParams(); 1642 } 1643 addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */); 1644 measureTaskView(tv); 1645 layoutTaskView(true /* changed */, tv); 1646 } 1647 } else { 1648 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1649 } 1650 // Update the task views list after adding the new task view 1651 updateTaskViewsList(); 1652 1653 // Bind the task view to the new task 1654 bindTaskView(tv, task); 1655 1656 // Set the new state for this view, including the callbacks and view clipping 1657 tv.setCallbacks(this); 1658 tv.setTouchEnabled(true); 1659 tv.setClipViewInStack(true); 1660 if (mFocusedTask == task) { 1661 tv.setFocusedState(true, false /* requestViewFocus */); 1662 if (mStartTimerIndicatorDuration > 0) { 1663 // The timer indicator couldn't be started before, so start it now 1664 tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration); 1665 mStartTimerIndicatorDuration = 0; 1666 } 1667 } 1668 1669 // Restore the action button visibility if it is the front most task view 1670 if (mScreenPinningEnabled && tv.getTask() == 1671 mStack.getStackFrontMostTask(false /* includeFreeform */)) { 1672 tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); 1673 } 1674 } 1675 1676 @Override 1677 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1678 return (tv.getTask() == preferredData); 1679 } 1680 1681 private void bindTaskView(TaskView tv, Task task) { 1682 // Rebind the task and request that this task's data be filled into the TaskView 1683 tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect); 1684 1685 // If the doze trigger has already fired, then update the state for this task view 1686 if (mUIDozeTrigger.isAsleep() || 1687 Recents.getSystemServices().hasFreeformWorkspaceSupport() || 1688 useGridLayout()) { 1689 tv.setNoUserInteractionState(); 1690 } 1691 1692 if (task == mPrefetchingTask) { 1693 task.notifyTaskDataLoaded(task.thumbnail, task.icon); 1694 } else { 1695 // Load the task data 1696 Recents.getTaskLoader().loadTaskData(task); 1697 } 1698 Recents.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task); 1699 } 1700 1701 private void unbindTaskView(TaskView tv, Task task) { 1702 if (task != mPrefetchingTask) { 1703 // Report that this task's data is no longer being used 1704 Recents.getTaskLoader().unloadTaskData(task); 1705 } 1706 Recents.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task); 1707 } 1708 1709 private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) { 1710 Task t = null; 1711 boolean somethingVisible = frontIndex != -1 && backIndex != -1; 1712 if (somethingVisible && frontIndex < tasks.size() - 1) { 1713 t = tasks.get(frontIndex + 1); 1714 } 1715 if (mPrefetchingTask != t) { 1716 if (mPrefetchingTask != null) { 1717 int index = tasks.indexOf(mPrefetchingTask); 1718 if (index < backIndex || index > frontIndex) { 1719 Recents.getTaskLoader().unloadTaskData(mPrefetchingTask); 1720 } 1721 } 1722 mPrefetchingTask = t; 1723 if (t != null) { 1724 Recents.getTaskLoader().loadTaskData(t); 1725 } 1726 } 1727 } 1728 1729 private void clearPrefetchingTask() { 1730 if (mPrefetchingTask != null) { 1731 Recents.getTaskLoader().unloadTaskData(mPrefetchingTask); 1732 } 1733 mPrefetchingTask = null; 1734 } 1735 1736 /**** TaskViewCallbacks Implementation ****/ 1737 1738 @Override 1739 public void onTaskViewClipStateChanged(TaskView tv) { 1740 if (!mTaskViewsClipDirty) { 1741 mTaskViewsClipDirty = true; 1742 invalidate(); 1743 } 1744 } 1745 1746 /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/ 1747 1748 @Override 1749 public void onFocusStateChanged(int prevFocusState, int curFocusState) { 1750 if (mDeferredTaskViewLayoutAnimation == null) { 1751 mUIDozeTrigger.poke(); 1752 relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE); 1753 } 1754 } 1755 1756 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1757 1758 @Override 1759 public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) { 1760 mUIDozeTrigger.poke(); 1761 if (animation != null) { 1762 relayoutTaskViewsOnNextFrame(animation); 1763 } 1764 1765 // In grid layout, the stack action button always remains visible. 1766 if (mEnterAnimationComplete && !useGridLayout()) { 1767 if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1768 curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1769 mStack.getTaskCount() > 0) { 1770 EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */)); 1771 } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1772 curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) { 1773 EventBus.getDefault().send(new HideStackActionButtonEvent()); 1774 } 1775 } 1776 } 1777 1778 /**** EventBus Events ****/ 1779 1780 public final void onBusEvent(PackagesChangedEvent event) { 1781 // Compute which components need to be removed 1782 ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved( 1783 event.packageName, event.userId); 1784 1785 // For other tasks, just remove them directly if they no longer exist 1786 ArrayList<Task> tasks = mStack.getStackTasks(); 1787 for (int i = tasks.size() - 1; i >= 0; i--) { 1788 final Task t = tasks.get(i); 1789 if (removedComponents.contains(t.key.getComponent())) { 1790 final TaskView tv = getChildViewForTask(t); 1791 if (tv != null) { 1792 // For visible children, defer removing the task until after the animation 1793 tv.dismissTask(); 1794 } else { 1795 // Otherwise, remove the task from the stack immediately 1796 mStack.removeTask(t, AnimationProps.IMMEDIATE, false /* fromDockGesture */); 1797 } 1798 } 1799 } 1800 } 1801 1802 public final void onBusEvent(LaunchTaskEvent event) { 1803 // Cancel any doze triggers once a task is launched 1804 mUIDozeTrigger.stopDozing(); 1805 } 1806 1807 public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) { 1808 if (mStack.getTaskCount() > 0) { 1809 Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */); 1810 launchTask(mostRecentTask); 1811 } 1812 } 1813 1814 public final void onBusEvent(LaunchNextTaskRequestEvent event) { 1815 if (!mFinishedLayoutAfterStackReload) { 1816 mLaunchNextAfterFirstMeasure = true; 1817 return; 1818 } 1819 1820 if (mStack.getTaskCount() == 0) { 1821 if (RecentsImpl.getLastPipTime() != -1) { 1822 EventBus.getDefault().send(new ExpandPipEvent()); 1823 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, 1824 "pip"); 1825 } else { 1826 // If there are no tasks, then just hide recents back to home. 1827 EventBus.getDefault().send(new HideRecentsEvent(false, true)); 1828 } 1829 return; 1830 } 1831 1832 if (!Recents.getConfiguration().getLaunchState().launchedFromPipApp 1833 && mStack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime())) { 1834 // If the launch task is in the pinned stack, then expand the PiP now 1835 EventBus.getDefault().send(new ExpandPipEvent()); 1836 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, "pip"); 1837 } else { 1838 final Task launchTask = mStack.getNextLaunchTarget(); 1839 if (launchTask != null) { 1840 // Defer launching the task until the PiP menu has been dismissed (if it is 1841 // showing at all) 1842 HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent(); 1843 hideMenuEvent.addPostAnimationCallback(() -> { 1844 launchTask(launchTask); 1845 }); 1846 EventBus.getDefault().send(hideMenuEvent); 1847 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, 1848 launchTask.key.getComponent().toString()); 1849 } 1850 } 1851 } 1852 1853 public final void onBusEvent(LaunchTaskStartedEvent event) { 1854 mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested, 1855 event.getAnimationTrigger()); 1856 } 1857 1858 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { 1859 // Stop any scrolling 1860 mTouchHandler.cancelNonDismissTaskAnimations(); 1861 mStackScroller.stopScroller(); 1862 mStackScroller.stopBoundScrollAnimation(); 1863 cancelDeferredTaskViewLayoutAnimation(); 1864 1865 // Start the task animations 1866 mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); 1867 1868 // Dismiss the freeform workspace background 1869 int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; 1870 animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration, 1871 Interpolators.FAST_OUT_SLOW_IN)); 1872 1873 // Dismiss the grid task view focus frame 1874 if (mTaskViewFocusFrame != null) { 1875 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 1876 } 1877 } 1878 1879 public final void onBusEvent(DismissFocusedTaskViewEvent event) { 1880 if (mFocusedTask != null) { 1881 if (mTaskViewFocusFrame != null) { 1882 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 1883 } 1884 TaskView tv = getChildViewForTask(mFocusedTask); 1885 if (tv != null) { 1886 tv.dismissTask(); 1887 } 1888 resetFocusedTask(mFocusedTask); 1889 } 1890 } 1891 1892 public final void onBusEvent(DismissTaskViewEvent event) { 1893 // For visible children, defer removing the task until after the animation 1894 mAnimationHelper.startDeleteTaskAnimation( 1895 event.taskView, useGridLayout(), event.getAnimationTrigger()); 1896 } 1897 1898 public final void onBusEvent(final DismissAllTaskViewsEvent event) { 1899 // Keep track of the tasks which will have their data removed 1900 ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks()); 1901 mAnimationHelper.startDeleteAllTasksAnimation( 1902 getTaskViews(), useGridLayout(), event.getAnimationTrigger()); 1903 event.addPostAnimationCallback(new Runnable() { 1904 @Override 1905 public void run() { 1906 // Announce for accessibility 1907 announceForAccessibility(getContext().getString( 1908 R.string.accessibility_recents_all_items_dismissed)); 1909 1910 // Remove all tasks and delete the task data for all tasks 1911 mStack.removeAllTasks(true /* notifyStackChanges */); 1912 for (int i = tasks.size() - 1; i >= 0; i--) { 1913 EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i))); 1914 } 1915 1916 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL); 1917 } 1918 }); 1919 1920 } 1921 1922 public final void onBusEvent(TaskViewDismissedEvent event) { 1923 // Announce for accessibility 1924 announceForAccessibility(getContext().getString( 1925 R.string.accessibility_recents_item_dismissed, event.task.title)); 1926 1927 if (useGridLayout() && event.animation != null) { 1928 event.animation.setListener(new AnimatorListenerAdapter() { 1929 public void onAnimationEnd(Animator animator) { 1930 if (mTaskViewFocusFrame != null) { 1931 // Resize the grid layout task view focus frame 1932 mTaskViewFocusFrame.resize(); 1933 } 1934 } 1935 }); 1936 } 1937 1938 // Remove the task from the stack 1939 mStack.removeTask(event.task, event.animation, false /* fromDockGesture */); 1940 EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); 1941 1942 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS, 1943 event.task.key.getComponent().toString()); 1944 } 1945 1946 public final void onBusEvent(FocusNextTaskViewEvent event) { 1947 // Stop any scrolling 1948 mStackScroller.stopScroller(); 1949 mStackScroller.stopBoundScrollAnimation(); 1950 1951 setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 1952 event.timerIndicatorDuration); 1953 } 1954 1955 public final void onBusEvent(FocusPreviousTaskViewEvent event) { 1956 // Stop any scrolling 1957 mStackScroller.stopScroller(); 1958 mStackScroller.stopBoundScrollAnimation(); 1959 1960 setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */); 1961 } 1962 1963 public final void onBusEvent(NavigateTaskViewEvent event) { 1964 if (useGridLayout()) { 1965 final int taskCount = mStack.getTaskCount(); 1966 final int currentIndex = mStack.indexOfStackTask(getFocusedTask()); 1967 final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount, 1968 currentIndex, event.direction); 1969 setFocusedTask(nextIndex, false, true); 1970 } else { 1971 switch (event.direction) { 1972 case UP: 1973 EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); 1974 break; 1975 case DOWN: 1976 EventBus.getDefault().send( 1977 new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */)); 1978 break; 1979 } 1980 } 1981 } 1982 1983 public final void onBusEvent(UserInteractionEvent event) { 1984 // Poke the doze trigger on user interaction 1985 mUIDozeTrigger.poke(); 1986 1987 RecentsDebugFlags debugFlags = Recents.getDebugFlags(); 1988 if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) { 1989 TaskView tv = getChildViewForTask(mFocusedTask); 1990 if (tv != null) { 1991 tv.getHeaderView().cancelFocusTimerIndicator(); 1992 } 1993 } 1994 } 1995 1996 public final void onBusEvent(DragStartEvent event) { 1997 // Ensure that the drag task is not animated 1998 addIgnoreTask(event.task); 1999 2000 if (event.task.isFreeformTask()) { 2001 // Animate to the front of the stack 2002 mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null); 2003 } 2004 2005 // Enlarge the dragged view slightly 2006 float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; 2007 mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), 2008 mTmpTransform, null); 2009 mTmpTransform.scale = finalScale; 2010 mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; 2011 mTmpTransform.dimAlpha = 0f; 2012 updateTaskViewToTransform(event.taskView, mTmpTransform, 2013 new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 2014 } 2015 2016 public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { 2017 SystemServicesProxy ssp = Recents.getSystemServices(); 2018 if (ssp.hasFreeformWorkspaceSupport()) { 2019 event.handler.registerDropTargetForCurrentDrag(mStackDropTarget); 2020 event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget); 2021 } 2022 } 2023 2024 public final void onBusEvent(DragDropTargetChangedEvent event) { 2025 AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION, 2026 Interpolators.FAST_OUT_SLOW_IN); 2027 boolean ignoreTaskOverrides = false; 2028 if (event.dropTarget instanceof TaskStack.DockState) { 2029 // Calculate the new task stack bounds that matches the window size that Recents will 2030 // have after the drop 2031 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; 2032 Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets); 2033 // When docked, the nav bar insets are consumed and the activity is measured without 2034 // insets. However, the window bounds include the insets, so we need to subtract them 2035 // here to make them identical. 2036 int height = getMeasuredHeight(); 2037 height -= systemInsets.bottom; 2038 systemInsets.bottom = 0; 2039 mStackBounds.set(dockState.getDockedTaskStackBounds(mDisplayRect, getMeasuredWidth(), 2040 height, mDividerSize, systemInsets, 2041 mLayoutAlgorithm, getResources(), mWindowRect)); 2042 mLayoutAlgorithm.setSystemInsets(systemInsets); 2043 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds, 2044 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); 2045 updateLayoutAlgorithm(true /* boundScroll */); 2046 ignoreTaskOverrides = true; 2047 } else { 2048 // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging 2049 // task view, so add it back to the ignore set after updating the layout 2050 removeIgnoreTask(event.task); 2051 updateLayoutToStableBounds(); 2052 addIgnoreTask(event.task); 2053 } 2054 relayoutTaskViews(animation, null /* animationOverrides */, ignoreTaskOverrides); 2055 } 2056 2057 public final void onBusEvent(final DragEndEvent event) { 2058 // We don't handle drops on the dock regions 2059 if (event.dropTarget instanceof TaskStack.DockState) { 2060 // However, we do need to reset the overrides, since the last state of this task stack 2061 // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler) 2062 mLayoutAlgorithm.clearUnfocusedTaskOverrides(); 2063 return; 2064 } 2065 2066 boolean isFreeformTask = event.task.isFreeformTask(); 2067 boolean hasChangedStacks = 2068 (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) || 2069 (isFreeformTask && event.dropTarget == mStackDropTarget); 2070 2071 if (hasChangedStacks) { 2072 // Move the task to the right position in the stack (ie. the front of the stack if 2073 // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks 2074 // before we update their stack ids, otherwise, the keys will have changed. 2075 if (event.dropTarget == mFreeformWorkspaceDropTarget) { 2076 mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID); 2077 } else if (event.dropTarget == mStackDropTarget) { 2078 mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID); 2079 } 2080 updateLayoutAlgorithm(true /* boundScroll */); 2081 2082 // Move the task to the new stack in the system after the animation completes 2083 event.addPostAnimationCallback(new Runnable() { 2084 @Override 2085 public void run() { 2086 SystemServicesProxy ssp = Recents.getSystemServices(); 2087 ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId); 2088 } 2089 }); 2090 } 2091 2092 // Restore the task, so that relayout will apply to it below 2093 removeIgnoreTask(event.task); 2094 2095 // Convert the dragging task view back to its final layout-space rect 2096 Utilities.setViewFrameFromTranslation(event.taskView); 2097 2098 // Animate all the tasks into place 2099 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>(); 2100 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION, 2101 Interpolators.FAST_OUT_SLOW_IN, 2102 event.getAnimationTrigger().decrementOnAnimationEnd())); 2103 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION, 2104 Interpolators.FAST_OUT_SLOW_IN)); 2105 event.getAnimationTrigger().increment(); 2106 } 2107 2108 public final void onBusEvent(final DragEndCancelledEvent event) { 2109 // Restore the pre-drag task stack bounds, including the dragging task view 2110 removeIgnoreTask(event.task); 2111 updateLayoutToStableBounds(); 2112 2113 // Convert the dragging task view back to its final layout-space rect 2114 Utilities.setViewFrameFromTranslation(event.taskView); 2115 2116 // Animate all the tasks into place 2117 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>(); 2118 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION, 2119 Interpolators.FAST_OUT_SLOW_IN, 2120 event.getAnimationTrigger().decrementOnAnimationEnd())); 2121 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION, 2122 Interpolators.FAST_OUT_SLOW_IN)); 2123 event.getAnimationTrigger().increment(); 2124 } 2125 2126 public final void onBusEvent(IterateRecentsEvent event) { 2127 if (!mEnterAnimationComplete) { 2128 // Cancel the previous task's window transition before animating the focused state 2129 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 2130 } 2131 } 2132 2133 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { 2134 mEnterAnimationComplete = true; 2135 tryStartEnterAnimation(); 2136 } 2137 2138 private void tryStartEnterAnimation() { 2139 if (!mStackReloaded || !mFinishedLayoutAfterStackReload || !mEnterAnimationComplete) { 2140 return; 2141 } 2142 2143 if (mStack.getTaskCount() > 0) { 2144 // Start the task enter animations 2145 ReferenceCountedTrigger trigger = new ReferenceCountedTrigger(); 2146 mAnimationHelper.startEnterAnimation(trigger); 2147 2148 // Add a runnable to the post animation ref counter to clear all the views 2149 trigger.addLastDecrementRunnable(() -> { 2150 // Start the dozer to trigger to trigger any UI that shows after a timeout 2151 if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) { 2152 mUIDozeTrigger.startDozing(); 2153 } 2154 2155 // Update the focused state here -- since we only set the focused task without 2156 // requesting view focus in onFirstLayout(), actually request view focus and 2157 // animate the focused state if we are alt-tabbing now, after the window enter 2158 // animation is completed 2159 if (mFocusedTask != null) { 2160 RecentsConfiguration config = Recents.getConfiguration(); 2161 RecentsActivityLaunchState launchState = config.getLaunchState(); 2162 setFocusedTask(mStack.indexOfStackTask(mFocusedTask), 2163 false /* scrollToTask */, launchState.launchedWithAltTab); 2164 TaskView focusedTaskView = getChildViewForTask(mFocusedTask); 2165 if (mTouchExplorationEnabled && focusedTaskView != null) { 2166 focusedTaskView.requestAccessibilityFocus(); 2167 } 2168 } 2169 }); 2170 } 2171 2172 // This flag is only used to choreograph the enter animation, so we can reset it here 2173 mStackReloaded = false; 2174 } 2175 2176 public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) { 2177 List<TaskView> taskViews = getTaskViews(); 2178 int taskViewCount = taskViews.size(); 2179 for (int i = 0; i < taskViewCount; i++) { 2180 TaskView tv = taskViews.get(i); 2181 Task task = tv.getTask(); 2182 if (task.isFreeformTask()) { 2183 tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE); 2184 } 2185 } 2186 } 2187 2188 public final void onBusEvent(final MultiWindowStateChangedEvent event) { 2189 if (event.inMultiWindow || !event.showDeferredAnimation) { 2190 setTasks(event.stack, true /* allowNotifyStackChanges */); 2191 } else { 2192 // Reset the launch state before handling the multiwindow change 2193 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); 2194 launchState.reset(); 2195 2196 // Defer until the next frame to ensure that we have received all the system insets, and 2197 // initial layout updates 2198 event.getAnimationTrigger().increment(); 2199 post(new Runnable() { 2200 @Override 2201 public void run() { 2202 // Scroll the stack to the front to see the undocked task 2203 mAnimationHelper.startNewStackScrollAnimation(event.stack, 2204 event.getAnimationTrigger()); 2205 event.getAnimationTrigger().decrement(); 2206 } 2207 }); 2208 } 2209 } 2210 2211 public final void onBusEvent(ConfigurationChangedEvent event) { 2212 if (event.fromDeviceOrientationChange) { 2213 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation; 2214 mDisplayRect = Recents.getSystemServices().getDisplayRect(); 2215 2216 // Always stop the scroller, otherwise, we may continue setting the stack scroll to the 2217 // wrong bounds in the new layout 2218 mStackScroller.stopScroller(); 2219 } 2220 reloadOnConfigurationChange(); 2221 2222 // Notify the task views of the configuration change so they can reload their resources 2223 if (!event.fromMultiWindow) { 2224 mTmpTaskViews.clear(); 2225 mTmpTaskViews.addAll(getTaskViews()); 2226 mTmpTaskViews.addAll(mViewPool.getViews()); 2227 int taskViewCount = mTmpTaskViews.size(); 2228 for (int i = 0; i < taskViewCount; i++) { 2229 mTmpTaskViews.get(i).onConfigurationChanged(); 2230 } 2231 } 2232 2233 // Update the Clear All button in case we're switching in or out of grid layout. 2234 updateStackActionButtonVisibility(); 2235 2236 // Trigger a new layout and update to the initial state if necessary 2237 if (event.fromMultiWindow) { 2238 mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; 2239 requestLayout(); 2240 } else if (event.fromDeviceOrientationChange) { 2241 mInitialState = INITIAL_STATE_UPDATE_ALL; 2242 requestLayout(); 2243 } 2244 } 2245 2246 public final void onBusEvent(RecentsGrowingEvent event) { 2247 mResetToInitialStateWhenResized = true; 2248 } 2249 2250 public final void onBusEvent(RecentsVisibilityChangedEvent event) { 2251 if (!event.visible) { 2252 if (mTaskViewFocusFrame != null) { 2253 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 2254 } 2255 2256 List<TaskView> taskViews = new ArrayList<>(getTaskViews()); 2257 for (int i = 0; i < taskViews.size(); i++) { 2258 mViewPool.returnViewToPool(taskViews.get(i)); 2259 } 2260 clearPrefetchingTask(); 2261 2262 // We can not reset mEnterAnimationComplete in onReload() because when docking the top 2263 // task, we can receive the enter animation callback before onReload(), so reset it 2264 // here onces Recents is not visible 2265 mEnterAnimationComplete = false; 2266 } 2267 } 2268 2269 public final void onBusEvent(ActivityPinnedEvent event) { 2270 // If an activity enters PiP while Recents is open, remove the stack task associated with 2271 // the new PiP task 2272 Task removeTask = mStack.findTaskWithId(event.taskId); 2273 if (removeTask != null) { 2274 // In this case, we remove the task, but if the last task is removed, don't dismiss 2275 // Recents to home 2276 mStack.removeTask(removeTask, AnimationProps.IMMEDIATE, false /* fromDockGesture */, 2277 false /* dismissRecentsIfAllRemoved */); 2278 } 2279 updateLayoutAlgorithm(false /* boundScroll */); 2280 updateToInitialState(); 2281 } 2282 2283 public void reloadOnConfigurationChange() { 2284 mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext()); 2285 mLayoutAlgorithm.reloadOnConfigurationChange(getContext()); 2286 } 2287 2288 /** 2289 * Starts an alpha animation on the freeform workspace background. 2290 */ 2291 private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, 2292 AnimationProps animation) { 2293 if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) { 2294 return; 2295 } 2296 2297 Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator); 2298 mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground, 2299 Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha); 2300 mFreeformWorkspaceBackgroundAnimator.setStartDelay( 2301 animation.getDuration(AnimationProps.ALPHA)); 2302 mFreeformWorkspaceBackgroundAnimator.setDuration( 2303 animation.getDuration(AnimationProps.ALPHA)); 2304 mFreeformWorkspaceBackgroundAnimator.setInterpolator( 2305 animation.getInterpolator(AnimationProps.ALPHA)); 2306 mFreeformWorkspaceBackgroundAnimator.start(); 2307 } 2308 2309 /** 2310 * Returns the insert index for the task in the current set of task views. If the given task 2311 * is already in the task view list, then this method returns the insert index assuming it 2312 * is first removed at the previous index. 2313 * 2314 * @param task the task we are finding the index for 2315 * @param taskIndex the index of the task in the stack 2316 */ 2317 private int findTaskViewInsertIndex(Task task, int taskIndex) { 2318 if (taskIndex != -1) { 2319 List<TaskView> taskViews = getTaskViews(); 2320 boolean foundTaskView = false; 2321 int taskViewCount = taskViews.size(); 2322 for (int i = 0; i < taskViewCount; i++) { 2323 Task tvTask = taskViews.get(i).getTask(); 2324 if (tvTask == task) { 2325 foundTaskView = true; 2326 } else if (taskIndex < mStack.indexOfStackTask(tvTask)) { 2327 if (foundTaskView) { 2328 return i - 1; 2329 } else { 2330 return i; 2331 } 2332 } 2333 } 2334 } 2335 return -1; 2336 } 2337 2338 private void launchTask(Task task) { 2339 // Stop all animations 2340 cancelAllTaskViewAnimations(); 2341 2342 float curScroll = mStackScroller.getStackScroll(); 2343 float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(task); 2344 float absScrollDiff = Math.abs(targetScroll - curScroll); 2345 if (getChildViewForTask(task) == null || absScrollDiff > 0.35f) { 2346 int duration = (int) (LAUNCH_NEXT_SCROLL_BASE_DURATION + 2347 absScrollDiff * LAUNCH_NEXT_SCROLL_INCR_DURATION); 2348 mStackScroller.animateScroll(targetScroll, 2349 duration, new Runnable() { 2350 @Override 2351 public void run() { 2352 EventBus.getDefault().send(new LaunchTaskEvent( 2353 getChildViewForTask(task), task, null, 2354 INVALID_STACK_ID, false /* screenPinningRequested */)); 2355 } 2356 }); 2357 } else { 2358 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), 2359 task, null, INVALID_STACK_ID, false /* screenPinningRequested */)); 2360 } 2361 } 2362 2363 /** 2364 * Check whether we should use the grid layout. 2365 */ 2366 public boolean useGridLayout() { 2367 return mLayoutAlgorithm.useGridLayout(); 2368 } 2369 2370 /** 2371 * Reads current system flags related to accessibility and screen pinning. 2372 */ 2373 private void readSystemFlags() { 2374 SystemServicesProxy ssp = Recents.getSystemServices(); 2375 mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); 2376 mScreenPinningEnabled = ssp.getSystemSetting(getContext(), 2377 Settings.System.LOCK_TO_APP_ENABLED) != 0; 2378 } 2379 2380 private void updateStackActionButtonVisibility() { 2381 // Always show the button in grid layout. 2382 if (useGridLayout() || 2383 (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 2384 mStack.getTaskCount() > 0)) { 2385 EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); 2386 } else { 2387 EventBus.getDefault().send(new HideStackActionButtonEvent()); 2388 } 2389 } 2390 2391 public void dump(String prefix, PrintWriter writer) { 2392 String innerPrefix = prefix + " "; 2393 String id = Integer.toHexString(System.identityHashCode(this)); 2394 2395 writer.print(prefix); writer.print(TAG); 2396 writer.print(" hasDefRelayout="); 2397 writer.print(mDeferredTaskViewLayoutAnimation != null ? "Y" : "N"); 2398 writer.print(" clipDirty="); writer.print(mTaskViewsClipDirty ? "Y" : "N"); 2399 writer.print(" awaitingStackReload="); writer.print(mFinishedLayoutAfterStackReload ? "Y" : "N"); 2400 writer.print(" initialState="); writer.print(mInitialState); 2401 writer.print(" inMeasureLayout="); writer.print(mInMeasureLayout ? "Y" : "N"); 2402 writer.print(" enterAnimCompleted="); writer.print(mEnterAnimationComplete ? "Y" : "N"); 2403 writer.print(" touchExplorationOn="); writer.print(mTouchExplorationEnabled ? "Y" : "N"); 2404 writer.print(" screenPinningOn="); writer.print(mScreenPinningEnabled ? "Y" : "N"); 2405 writer.print(" numIgnoreTasks="); writer.print(mIgnoreTasks.size()); 2406 writer.print(" numViewPool="); writer.print(mViewPool.getViews().size()); 2407 writer.print(" stableStackBounds="); writer.print(Utilities.dumpRect(mStableStackBounds)); 2408 writer.print(" stackBounds="); writer.print(Utilities.dumpRect(mStackBounds)); 2409 writer.print(" stableWindow="); writer.print(Utilities.dumpRect(mStableWindowRect)); 2410 writer.print(" window="); writer.print(Utilities.dumpRect(mWindowRect)); 2411 writer.print(" display="); writer.print(Utilities.dumpRect(mDisplayRect)); 2412 writer.print(" orientation="); writer.print(mDisplayOrientation); 2413 writer.print(" [0x"); writer.print(id); writer.print("]"); 2414 writer.println(); 2415 2416 if (mFocusedTask != null) { 2417 writer.print(innerPrefix); 2418 writer.print("Focused task: "); 2419 mFocusedTask.dump("", writer); 2420 } 2421 2422 int numTaskViews = mTaskViews.size(); 2423 for (int i = 0; i < numTaskViews; i++) { 2424 mTaskViews.get(i).dump(innerPrefix, writer); 2425 } 2426 2427 mLayoutAlgorithm.dump(innerPrefix, writer); 2428 mStackScroller.dump(innerPrefix, writer); 2429 } 2430 } 2431