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