1 /* 2 * Copyright (C) 2019 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 package com.android.quickstep; 17 18 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 19 20 import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe; 21 import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe; 22 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS; 23 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; 24 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; 25 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW; 26 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; 27 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_ALL_APPS; 28 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME; 29 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK; 30 31 import android.content.Intent; 32 import android.os.SystemClock; 33 import android.view.Display; 34 import android.view.MotionEvent; 35 import android.view.RemoteAnimationTarget; 36 import android.window.TransitionInfo; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import com.android.launcher3.statemanager.BaseState; 42 import com.android.launcher3.statemanager.StatefulContainer; 43 import com.android.quickstep.TopTaskTracker.CachedTaskInfo; 44 import com.android.quickstep.fallback.window.RecentsWindowFlags; 45 import com.android.quickstep.util.ActiveGestureErrorDetector; 46 import com.android.quickstep.util.ActiveGestureLog; 47 import com.android.quickstep.util.ActiveGestureProtoLogProxy; 48 import com.android.quickstep.views.RecentsViewContainer; 49 import com.android.systemui.shared.recents.model.ThumbnailData; 50 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.HashMap; 55 import java.util.HashSet; 56 import java.util.List; 57 import java.util.Set; 58 import java.util.function.Predicate; 59 60 /** 61 * Manages the state for an active system gesture, listens for events from the system and Launcher, 62 * and fires events when the states change. 63 */ 64 public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { 65 66 final Predicate<RemoteAnimationTarget> mLastStartedTaskIdPredicate = new Predicate<>() { 67 @Override 68 public boolean test(RemoteAnimationTarget targetCompat) { 69 for (int taskId : mLastStartedTaskId) { 70 if (targetCompat.taskId == taskId) { 71 return true; 72 } 73 } 74 return false; 75 } 76 }; 77 78 /** 79 * Defines the end targets of a gesture and the associated state. 80 */ 81 public enum GestureEndTarget { 82 HOME(true, LAUNCHER_STATE_HOME, false), 83 84 RECENTS(true, LAUNCHER_STATE_OVERVIEW, true), 85 86 NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true), 87 88 LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true), 89 90 ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false); 91 GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow)92 GestureEndTarget(boolean isLauncher, int containerType, 93 boolean recentsAttachedToAppWindow) { 94 this.isLauncher = isLauncher; 95 this.containerType = containerType; 96 this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; 97 } 98 99 /** Whether the target is in the launcher activity. Implicitly, if the end target is going 100 to Launcher, then we can not interrupt the animation to start another gesture. */ 101 public final boolean isLauncher; 102 /** Used to log where the user ended up after the gesture ends */ 103 public final int containerType; 104 /** Whether RecentsView should be attached to the window as we animate to this target */ 105 public final boolean recentsAttachedToAppWindow; 106 } 107 108 private static final String TAG = "GestureState"; 109 110 private static final List<String> STATE_NAMES = new ArrayList<>(); 111 public static final GestureState DEFAULT_STATE = new GestureState(); 112 113 private static int FLAG_COUNT = 0; getNextStateFlag(String name)114 private static int getNextStateFlag(String name) { 115 if (DEBUG_STATES) { 116 STATE_NAMES.add(name); 117 } 118 int index = 1 << FLAG_COUNT; 119 FLAG_COUNT++; 120 return index; 121 } 122 123 // Called when the end target as been set 124 public static final int STATE_END_TARGET_SET = 125 getNextStateFlag("STATE_END_TARGET_SET"); 126 127 // Called when the end target animation has finished 128 public static final int STATE_END_TARGET_ANIMATION_FINISHED = 129 getNextStateFlag("STATE_END_TARGET_ANIMATION_FINISHED"); 130 131 // Called when the recents animation has been requested to start 132 public static final int STATE_RECENTS_ANIMATION_INITIALIZED = 133 getNextStateFlag("STATE_RECENTS_ANIMATION_INITIALIZED"); 134 135 // Called when the recents animation is started and the TaskAnimationManager has been updated 136 // with the controller and targets 137 public static final int STATE_RECENTS_ANIMATION_STARTED = 138 getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED"); 139 140 // Called when the recents animation is canceled 141 public static final int STATE_RECENTS_ANIMATION_CANCELED = 142 getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED"); 143 144 // Called when the recents animation finishes 145 public static final int STATE_RECENTS_ANIMATION_FINISHED = 146 getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED"); 147 148 // Always called when the recents animation ends (regardless of cancel or finish) 149 public static final int STATE_RECENTS_ANIMATION_ENDED = 150 getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED"); 151 152 // Called when RecentsView stops scrolling and settles on a TaskView. 153 public static final int STATE_RECENTS_SCROLLING_FINISHED = 154 getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED"); 155 156 // Needed to interact with the current activity 157 private final Intent mHomeIntent; 158 private final Intent mOverviewIntent; 159 private final BaseContainerInterface mContainerInterface; 160 private final MultiStateCallback mStateCallback; 161 private final int mGestureId; 162 private final int mDisplayId; 163 164 public enum TrackpadGestureType { 165 NONE, 166 THREE_FINGER, 167 FOUR_FINGER; 168 getTrackpadGestureType(MotionEvent event)169 public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) { 170 if (isTrackpadThreeFingerSwipe(event)) { 171 return TrackpadGestureType.THREE_FINGER; 172 } 173 if (isTrackpadFourFingerSwipe(event)) { 174 return TrackpadGestureType.FOUR_FINGER; 175 } 176 177 return TrackpadGestureType.NONE; 178 } 179 } 180 181 private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE; 182 private CachedTaskInfo mRunningTask; 183 private GestureEndTarget mEndTarget; 184 private RemoteAnimationTarget[] mLastAppearedTaskTargets; 185 private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>(); 186 private int[] mLastStartedTaskId = new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 187 private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots; 188 189 /** The time when the swipe up gesture is triggered. */ 190 private final long mSwipeUpStartTimeMs = SystemClock.uptimeMillis(); 191 192 private boolean mHandlingAtomicEvent; 193 private boolean mIsInExtendedSlopRegion; 194 GestureState(OverviewComponentObserver componentObserver, int displayId, int gestureId)195 public GestureState(OverviewComponentObserver componentObserver, int displayId, int gestureId) { 196 mDisplayId = displayId; 197 mHomeIntent = componentObserver.getHomeIntent(); 198 mOverviewIntent = componentObserver.getOverviewIntent(); 199 mContainerInterface = componentObserver.getContainerInterface(displayId); 200 mStateCallback = new MultiStateCallback( 201 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); 202 mGestureId = gestureId; 203 } 204 GestureState(GestureState other)205 public GestureState(GestureState other) { 206 mDisplayId = other.mDisplayId; 207 mHomeIntent = other.mHomeIntent; 208 mOverviewIntent = other.mOverviewIntent; 209 mContainerInterface = other.mContainerInterface; 210 mStateCallback = other.mStateCallback; 211 mGestureId = other.mGestureId; 212 mRunningTask = other.mRunningTask; 213 mEndTarget = other.mEndTarget; 214 mLastAppearedTaskTargets = other.mLastAppearedTaskTargets; 215 mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds; 216 mLastStartedTaskId = other.mLastStartedTaskId; 217 } 218 GestureState()219 public GestureState() { 220 // Do nothing, only used for initializing the gesture state prior to user unlock 221 mDisplayId = Display.DEFAULT_DISPLAY; 222 mHomeIntent = new Intent(); 223 mOverviewIntent = new Intent(); 224 mContainerInterface = null; 225 mStateCallback = new MultiStateCallback( 226 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState); 227 mGestureId = -1; 228 } 229 230 @Nullable getTrackedEventForState(int stateFlag)231 private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { 232 if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) { 233 return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED; 234 } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) { 235 return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED; 236 } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) { 237 return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED; 238 } 239 return null; 240 } 241 242 /** 243 * @return whether the gesture state has the provided {@param stateMask} flags set. 244 */ hasState(int stateMask)245 public boolean hasState(int stateMask) { 246 return mStateCallback.hasStates(stateMask); 247 } 248 249 /** 250 * Sets the given {@param stateFlag}s. 251 */ setState(int stateFlag)252 public void setState(int stateFlag) { 253 mStateCallback.setState(stateFlag); 254 } 255 256 /** 257 * Adds a callback for when the states matching the given {@param stateMask} is set. 258 */ runOnceAtState(int stateMask, Runnable callback)259 public void runOnceAtState(int stateMask, Runnable callback) { 260 mStateCallback.runOnceAtState(stateMask, callback); 261 } 262 263 /** 264 * @return the intent for the Home component. 265 */ getHomeIntent()266 public Intent getHomeIntent() { 267 return mHomeIntent; 268 } 269 270 /** 271 * @return the intent for the Overview component. 272 */ getOverviewIntent()273 public Intent getOverviewIntent() { 274 return mOverviewIntent; 275 } 276 277 /** 278 * @return the interface to the activity handing the UI updates for this gesture. 279 */ 280 public <S extends BaseState<S>, T extends RecentsViewContainer & StatefulContainer<S>> getContainerInterface()281 BaseContainerInterface<S, T> getContainerInterface() { 282 return mContainerInterface; 283 } 284 285 /** 286 * @return the id for this particular gesture. 287 */ getGestureId()288 public int getGestureId() { 289 return mGestureId; 290 } 291 292 /** 293 * @return the id for the display this particular gesture was performed on. 294 */ getDisplayId()295 public int getDisplayId() { 296 return mDisplayId; 297 } 298 299 /** 300 * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger 301 */ setTrackpadGestureType(TrackpadGestureType trackpadGestureType)302 public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) { 303 mTrackpadGestureType = trackpadGestureType; 304 } 305 isTrackpadGesture()306 public boolean isTrackpadGesture() { 307 return mTrackpadGestureType != TrackpadGestureType.NONE; 308 } 309 isThreeFingerTrackpadGesture()310 public boolean isThreeFingerTrackpadGesture() { 311 return mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; 312 } 313 isFourFingerTrackpadGesture()314 public boolean isFourFingerTrackpadGesture() { 315 return mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER; 316 } 317 318 /** 319 * Requests that handling for this gesture should use a synthetic transition, as in that it 320 * will need to start a recents transition that is not backed by a system transition. This is 321 * generally only needed in scenarios where a system transition can not be created due to no 322 * changes in the WM hierarchy (ie. starting recents transition when you are already over home). 323 */ useSyntheticRecentsTransition()324 public boolean useSyntheticRecentsTransition() { 325 return mRunningTask.isHomeTask() 326 && RecentsWindowFlags.Companion.getEnableOverviewInWindow(); 327 } 328 329 /** 330 * @return the running task for this gesture. 331 */ 332 @Nullable getRunningTask()333 public CachedTaskInfo getRunningTask() { 334 return mRunningTask; 335 } 336 337 /** 338 * @param getMultipleTasks Whether multiple tasks or not are to be returned (for split) 339 * @return the running task ids for this gesture. 340 */ getRunningTaskIds(boolean getMultipleTasks)341 public int[] getRunningTaskIds(boolean getMultipleTasks) { 342 if (mRunningTask == null) { 343 return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 344 } else { 345 if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) { 346 return mRunningTask.topGroupedTaskIds(); 347 } else { 348 int cachedTasksSize = mRunningTask.mAllCachedTasks.size(); 349 int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1); 350 int[] runningTaskIds = new int[count]; 351 for (int i = 0; i < count; i++) { 352 runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId; 353 } 354 return runningTaskIds; 355 } 356 } 357 } 358 359 /** 360 * @see #getRunningTaskIds(boolean) 361 * @return the single top-most running taskId for this gesture 362 */ getTopRunningTaskId()363 public int getTopRunningTaskId() { 364 return getRunningTaskIds(false /*getMultipleTasks*/)[0]; 365 } 366 367 /** 368 * Updates the running task for the gesture to be the given {@param runningTask}. 369 */ updateRunningTask(@onNull CachedTaskInfo runningTask)370 public void updateRunningTask(@NonNull CachedTaskInfo runningTask) { 371 mRunningTask = runningTask; 372 } 373 374 /** 375 * Updates the last task that appeared during this gesture. 376 */ updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets)377 public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) { 378 mLastAppearedTaskTargets = lastAppearedTaskTargets; 379 for (RemoteAnimationTarget target : lastAppearedTaskTargets) { 380 if (target == null) { 381 continue; 382 } 383 mPreviouslyAppearedTaskIds.add(target.taskId); 384 } 385 } 386 387 /** 388 * @return The id of the task that appeared during this gesture. 389 */ getLastAppearedTaskIds()390 public int[] getLastAppearedTaskIds() { 391 if (mLastAppearedTaskTargets == null) { 392 return new int[]{INVALID_TASK_ID, INVALID_TASK_ID}; 393 } else { 394 return Arrays.stream(mLastAppearedTaskTargets) 395 .mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray(); 396 } 397 } 398 updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds)399 public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) { 400 mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds; 401 } 402 getPreviouslyAppearedTaskIds()403 public Set<Integer> getPreviouslyAppearedTaskIds() { 404 return mPreviouslyAppearedTaskIds; 405 } 406 407 /** 408 * Updates the last task that we started via startActivityFromRecents() during this gesture. 409 */ updateLastStartedTaskIds(int[] lastStartedTaskId)410 public void updateLastStartedTaskIds(int[] lastStartedTaskId) { 411 mLastStartedTaskId = lastStartedTaskId; 412 } 413 414 /** 415 * @return The id of the task that was most recently started during this gesture, or -1 if 416 * no task has been started yet (i.e. we haven't settled on a new task). 417 */ getLastStartedTaskIds()418 public int[] getLastStartedTaskIds() { 419 return mLastStartedTaskId; 420 } 421 422 /** 423 * @return the end target for this gesture (if known). 424 */ getEndTarget()425 public GestureEndTarget getEndTarget() { 426 return mEndTarget; 427 } 428 429 /** 430 * Sets the end target of this gesture and immediately notifies the state changes. 431 */ setEndTarget(GestureEndTarget target)432 public void setEndTarget(GestureEndTarget target) { 433 setEndTarget(target, true /* isAtomic */); 434 } 435 436 /** 437 * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the 438 * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves. 439 */ setEndTarget(GestureEndTarget target, boolean isAtomic)440 public void setEndTarget(GestureEndTarget target, boolean isAtomic) { 441 mEndTarget = target; 442 mStateCallback.setState(STATE_END_TARGET_SET); 443 ActiveGestureProtoLogProxy.logSetEndTarget(mEndTarget.name()); 444 switch (mEndTarget) { 445 case HOME: 446 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_HOME); 447 break; 448 case NEW_TASK: 449 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_NEW_TASK); 450 break; 451 case ALL_APPS: 452 ActiveGestureLog.INSTANCE.trackEvent(SET_END_TARGET_ALL_APPS); 453 break; 454 case LAST_TASK: 455 case RECENTS: 456 default: 457 // No-Op 458 } 459 if (isAtomic) { 460 mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED); 461 } 462 } 463 464 /** 465 * Indicates if the gesture is handling an atomic event like a click and not a 466 * user controlled gesture. 467 */ setHandlingAtomicEvent(boolean handlingAtomicEvent)468 public void setHandlingAtomicEvent(boolean handlingAtomicEvent) { 469 mHandlingAtomicEvent = handlingAtomicEvent; 470 } 471 472 /** 473 * Returns true if the gesture is handling an atomic event like a click and not a 474 * user controlled gesture. 475 */ isHandlingAtomicEvent()476 public boolean isHandlingAtomicEvent() { 477 return mHandlingAtomicEvent; 478 } 479 480 /** 481 * @return whether the current gesture is still running a recents animation to a state in the 482 * Launcher or Recents activity. 483 */ isRunningAnimationToLauncher()484 public boolean isRunningAnimationToLauncher() { 485 return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher; 486 } 487 488 /** 489 * @return whether the recents animation is started but not yet ended 490 */ isRecentsAnimationRunning()491 public boolean isRecentsAnimationRunning() { 492 return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_STARTED) 493 && !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED); 494 } 495 496 @Override onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets, TransitionInfo info)497 public void onRecentsAnimationStart(RecentsAnimationController controller, 498 RecentsAnimationTargets targets, TransitionInfo info) { 499 mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED); 500 } 501 502 @Override onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)503 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 504 mRecentsAnimationCanceledSnapshots = thumbnailDatas; 505 mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED); 506 mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); 507 if (mRecentsAnimationCanceledSnapshots != null) { 508 mRecentsAnimationCanceledSnapshots = null; 509 } 510 } 511 512 @Override onRecentsAnimationFinished(RecentsAnimationController controller)513 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 514 mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED); 515 mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); 516 } 517 518 /** 519 * Set whether it's in long press nav handle (LPNH)'s extended touch slop region, e.g., second 520 * stage region in order to continue respect LPNH and ignore other touch slop logic. 521 * This will only be set to true when flag ENABLE_LPNH_TWO_STAGES is turned on. 522 */ setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion)523 public void setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion) { 524 if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) { 525 mIsInExtendedSlopRegion = isInExtendedSlopRegion; 526 } 527 } 528 529 /** 530 * Returns whether it's in LPNH's extended touch slop region. This is only valid when flag 531 * ENABLE_LPNH_TWO_STAGES is turned on. 532 */ isInExtendedSlopRegion()533 public boolean isInExtendedSlopRegion() { 534 return mIsInExtendedSlopRegion; 535 } 536 537 /** 538 * Returns and clears the canceled animation thumbnail data. This call only returns a value 539 * while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for 540 * calling {@link RecentsAnimationController#cleanupScreenshot()}. 541 */ 542 @Nullable consumeRecentsAnimationCanceledSnapshot()543 HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() { 544 if (mRecentsAnimationCanceledSnapshots != null) { 545 HashMap<Integer, ThumbnailData> data = 546 new HashMap<>(mRecentsAnimationCanceledSnapshots); 547 mRecentsAnimationCanceledSnapshots = null; 548 return data; 549 } 550 return null; 551 } 552 getSwipeUpStartTimeMs()553 long getSwipeUpStartTimeMs() { 554 return mSwipeUpStartTimeMs; 555 } 556 dump(String prefix, PrintWriter pw)557 public void dump(String prefix, PrintWriter pw) { 558 pw.println(prefix + "GestureState:"); 559 pw.println(prefix + "\tdisplayID=" + mDisplayId); 560 pw.println(prefix + "\tgestureID=" + mGestureId); 561 pw.println(prefix + "\trunningTask=" + mRunningTask); 562 pw.println(prefix + "\tendTarget=" + mEndTarget); 563 pw.println(prefix + "\tlastAppearedTaskTargetId=" 564 + Arrays.toString(mLastAppearedTaskTargets)); 565 pw.println(prefix + "\tlastStartedTaskId=" + Arrays.toString(mLastStartedTaskId)); 566 pw.println(prefix + "\tisRecentsAnimationRunning=" + isRecentsAnimationRunning()); 567 } 568 } 569