1 /* 2 * Copyright (C) 2017 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.quickstep.views; 18 19 import static android.view.Surface.ROTATION_0; 20 import static android.view.View.MeasureSpec.EXACTLY; 21 import static android.view.View.MeasureSpec.makeMeasureSpec; 22 23 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; 24 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; 25 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 26 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 27 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 28 import static com.android.launcher3.Utilities.EDGE_NAV_BAR; 29 import static com.android.launcher3.Utilities.mapToRange; 30 import static com.android.launcher3.Utilities.squaredHypot; 31 import static com.android.launcher3.Utilities.squaredTouchSlop; 32 import static com.android.launcher3.anim.Interpolators.ACCEL; 33 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; 34 import static com.android.launcher3.anim.Interpolators.ACCEL_2; 35 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 36 import static com.android.launcher3.anim.Interpolators.LINEAR; 37 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 38 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; 39 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; 40 import static com.android.launcher3.statehandlers.DepthController.DEPTH; 41 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; 42 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; 43 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON; 44 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 45 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; 46 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; 47 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING; 48 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; 49 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; 50 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; 51 52 import android.animation.AnimatorSet; 53 import android.animation.LayoutTransition; 54 import android.animation.LayoutTransition.TransitionListener; 55 import android.animation.ObjectAnimator; 56 import android.animation.ValueAnimator; 57 import android.annotation.TargetApi; 58 import android.app.ActivityManager; 59 import android.content.ComponentName; 60 import android.content.Context; 61 import android.content.Intent; 62 import android.content.res.Configuration; 63 import android.graphics.Canvas; 64 import android.graphics.Point; 65 import android.graphics.PointF; 66 import android.graphics.Rect; 67 import android.graphics.Typeface; 68 import android.graphics.drawable.Drawable; 69 import android.os.Build; 70 import android.os.Handler; 71 import android.os.UserHandle; 72 import android.text.Layout; 73 import android.text.StaticLayout; 74 import android.text.TextPaint; 75 import android.util.AttributeSet; 76 import android.util.FloatProperty; 77 import android.util.Property; 78 import android.util.SparseBooleanArray; 79 import android.view.HapticFeedbackConstants; 80 import android.view.KeyEvent; 81 import android.view.LayoutInflater; 82 import android.view.MotionEvent; 83 import android.view.View; 84 import android.view.ViewDebug; 85 import android.view.ViewGroup; 86 import android.view.accessibility.AccessibilityEvent; 87 import android.view.accessibility.AccessibilityNodeInfo; 88 import android.view.animation.Interpolator; 89 import android.widget.ListView; 90 91 import androidx.annotation.Nullable; 92 93 import com.android.launcher3.BaseActivity; 94 import com.android.launcher3.DeviceProfile; 95 import com.android.launcher3.Insettable; 96 import com.android.launcher3.InvariantDeviceProfile; 97 import com.android.launcher3.PagedView; 98 import com.android.launcher3.R; 99 import com.android.launcher3.Utilities; 100 import com.android.launcher3.anim.AnimationSuccessListener; 101 import com.android.launcher3.anim.AnimatorPlaybackController; 102 import com.android.launcher3.anim.PendingAnimation; 103 import com.android.launcher3.anim.PendingAnimation.EndState; 104 import com.android.launcher3.anim.PropertyListBuilder; 105 import com.android.launcher3.anim.SpringProperty; 106 import com.android.launcher3.compat.AccessibilityManagerCompat; 107 import com.android.launcher3.config.FeatureFlags; 108 import com.android.launcher3.statehandlers.DepthController; 109 import com.android.launcher3.statemanager.StatefulActivity; 110 import com.android.launcher3.touch.PagedOrientationHandler; 111 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; 112 import com.android.launcher3.userevent.nano.LauncherLogProto; 113 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; 114 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; 115 import com.android.launcher3.util.ComponentKey; 116 import com.android.launcher3.util.DynamicResource; 117 import com.android.launcher3.util.MultiValueAlpha; 118 import com.android.launcher3.util.OverScroller; 119 import com.android.launcher3.util.Themes; 120 import com.android.launcher3.util.ViewPool; 121 import com.android.quickstep.BaseActivityInterface; 122 import com.android.quickstep.RecentsAnimationController; 123 import com.android.quickstep.RecentsAnimationTargets; 124 import com.android.quickstep.RecentsModel; 125 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; 126 import com.android.quickstep.SystemUiProxy; 127 import com.android.quickstep.TaskThumbnailCache; 128 import com.android.quickstep.TaskUtils; 129 import com.android.quickstep.ViewUtils; 130 import com.android.quickstep.util.LayoutUtils; 131 import com.android.quickstep.util.RecentsOrientedState; 132 import com.android.quickstep.util.SplitScreenBounds; 133 import com.android.quickstep.util.SurfaceTransactionApplier; 134 import com.android.quickstep.util.TransformParams; 135 import com.android.systemui.plugins.ResourceProvider; 136 import com.android.systemui.shared.recents.IPinnedStackAnimationListener; 137 import com.android.systemui.shared.recents.model.Task; 138 import com.android.systemui.shared.recents.model.ThumbnailData; 139 import com.android.systemui.shared.system.ActivityManagerWrapper; 140 import com.android.systemui.shared.system.LauncherEventUtil; 141 import com.android.systemui.shared.system.PackageManagerWrapper; 142 import com.android.systemui.shared.system.TaskStackChangeListener; 143 144 import java.util.ArrayList; 145 import java.util.function.Consumer; 146 147 /** 148 * A list of recent tasks. 149 */ 150 @TargetApi(Build.VERSION_CODES.P) 151 public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements 152 Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, 153 InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener, 154 SplitScreenBounds.OnChangeListener { 155 156 private static final String TAG = RecentsView.class.getSimpleName(); 157 158 public static final FloatProperty<RecentsView> CONTENT_ALPHA = 159 new FloatProperty<RecentsView>("contentAlpha") { 160 @Override 161 public void setValue(RecentsView view, float v) { 162 view.setContentAlpha(v); 163 } 164 165 @Override 166 public Float get(RecentsView view) { 167 return view.getContentAlpha(); 168 } 169 }; 170 171 public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS = 172 new FloatProperty<RecentsView>("fullscreenProgress") { 173 @Override 174 public void setValue(RecentsView recentsView, float v) { 175 recentsView.setFullscreenProgress(v); 176 } 177 178 @Override 179 public Float get(RecentsView recentsView) { 180 return recentsView.mFullscreenProgress; 181 } 182 }; 183 184 public static final FloatProperty<RecentsView> TASK_MODALNESS = 185 new FloatProperty<RecentsView>("taskModalness") { 186 @Override 187 public void setValue(RecentsView recentsView, float v) { 188 recentsView.setTaskModalness(v); 189 } 190 191 @Override 192 public Float get(RecentsView recentsView) { 193 return recentsView.mTaskModalness; 194 } 195 }; 196 197 public static final FloatProperty<RecentsView> ADJACENT_PAGE_OFFSET = 198 new FloatProperty<RecentsView>("adjacentPageOffset") { 199 @Override 200 public void setValue(RecentsView recentsView, float v) { 201 if (recentsView.mAdjacentPageOffset != v) { 202 recentsView.mAdjacentPageOffset = v; 203 recentsView.updatePageOffsets(); 204 } 205 } 206 207 @Override 208 public Float get(RecentsView recentsView) { 209 return recentsView.mAdjacentPageOffset; 210 } 211 }; 212 213 protected RecentsOrientedState mOrientationState; 214 protected final BaseActivityInterface mSizeStrategy; 215 protected RecentsAnimationController mRecentsAnimationController; 216 protected RecentsAnimationTargets mRecentsAnimationTargets; 217 protected SurfaceTransactionApplier mSyncTransactionApplier; 218 protected int mTaskWidth; 219 protected int mTaskHeight; 220 protected boolean mEnableDrawingLiveTile = false; 221 protected final Rect mTempRect = new Rect(); 222 private final PointF mTempPointF = new PointF(); 223 224 private static final int DISMISS_TASK_DURATION = 300; 225 private static final int ADDITION_TASK_DURATION = 200; 226 // The threshold at which we update the SystemUI flags when animating from the task into the app 227 public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f; 228 229 protected final T mActivity; 230 private final float mFastFlingVelocity; 231 private final RecentsModel mModel; 232 private final int mTaskTopMargin; 233 private final ClearAllButton mClearAllButton; 234 private final Rect mClearAllButtonDeadZoneRect = new Rect(); 235 private final Rect mTaskViewDeadZoneRect = new Rect(); 236 237 private final ScrollState mScrollState = new ScrollState(); 238 // Keeps track of the previously known visible tasks for purposes of loading/unloading task data 239 private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); 240 241 private final InvariantDeviceProfile mIdp; 242 243 private final ViewPool<TaskView> mTaskViewPool; 244 245 private boolean mDwbToastShown; 246 protected boolean mDisallowScrollToClearAll; 247 private boolean mOverlayEnabled; 248 protected boolean mFreezeViewVisibility; 249 250 private float mAdjacentPageOffset = 0; 251 252 /** 253 * TODO: Call reloadIdNeeded in onTaskStackChanged. 254 */ 255 private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 256 @Override 257 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { 258 if (!mHandleTaskStackChanges) { 259 return; 260 } 261 // Check this is for the right user 262 if (!checkCurrentOrManagedUserId(userId, getContext())) { 263 return; 264 } 265 266 // Remove the task immediately from the task list 267 TaskView taskView = getTaskView(taskId); 268 if (taskView != null) { 269 removeView(taskView); 270 } 271 } 272 273 @Override 274 public void onActivityUnpinned() { 275 if (!mHandleTaskStackChanges) { 276 return; 277 } 278 279 reloadIfNeeded(); 280 enableLayoutTransitions(); 281 } 282 283 @Override 284 public void onTaskRemoved(int taskId) { 285 if (!mHandleTaskStackChanges) { 286 return; 287 } 288 289 UI_HELPER_EXECUTOR.execute(() -> { 290 TaskView taskView = getTaskView(taskId); 291 if (taskView == null) { 292 return; 293 } 294 Handler handler = taskView.getHandler(); 295 if (handler == null) { 296 return; 297 } 298 299 // TODO: Add callbacks from AM reflecting adding/removing from the recents list, and 300 // remove all these checks 301 Task.TaskKey taskKey = taskView.getTask().key; 302 if (PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(), 303 taskKey.userId) == null) { 304 // The package was uninstalled 305 handler.post(() -> 306 dismissTask(taskView, true /* animate */, false /* removeTask */)); 307 } else { 308 mModel.findTaskWithId(taskKey.id, (key) -> { 309 if (key == null) { 310 // The task was removed from the recents list 311 handler.post(() -> dismissTask(taskView, true /* animate */, 312 false /* removeTask */)); 313 } 314 }); 315 } 316 }); 317 } 318 }; 319 320 private final PinnedStackAnimationListener mIPinnedStackAnimationListener = 321 new PinnedStackAnimationListener(); 322 323 // Used to keep track of the last requested task list id, so that we do not request to load the 324 // tasks again if we have already requested it and the task list has not changed 325 private int mTaskListChangeId = -1; 326 327 // Only valid until the launcher state changes to NORMAL 328 protected int mRunningTaskId = -1; 329 protected boolean mRunningTaskTileHidden; 330 private Task mTmpRunningTask; 331 332 private boolean mRunningTaskIconScaledDown = false; 333 334 private boolean mOverviewStateEnabled; 335 private boolean mHandleTaskStackChanges; 336 private boolean mSwipeDownShouldLaunchApp; 337 private boolean mTouchDownToStartHome; 338 private final float mSquaredTouchSlop; 339 private int mDownX; 340 private int mDownY; 341 342 private PendingAnimation mPendingAnimation; 343 private LayoutTransition mLayoutTransition; 344 345 @ViewDebug.ExportedProperty(category = "launcher") 346 protected float mContentAlpha = 1; 347 @ViewDebug.ExportedProperty(category = "launcher") 348 protected float mFullscreenProgress = 0; 349 /** 350 * How modal is the current task to be displayed, 1 means the task is fully modal and no other 351 * tasks are show. 0 means the task is displays in context in the list with other tasks. 352 */ 353 @ViewDebug.ExportedProperty(category = "launcher") 354 protected float mTaskModalness = 0; 355 356 // Keeps track of task id whose visual state should not be reset 357 private int mIgnoreResetTaskId = -1; 358 359 // Variables for empty state 360 private final Drawable mEmptyIcon; 361 private final CharSequence mEmptyMessage; 362 private final TextPaint mEmptyMessagePaint; 363 private final Point mLastMeasureSize = new Point(); 364 private final int mEmptyMessagePadding; 365 private boolean mShowEmptyMessage; 366 private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener; 367 private Layout mEmptyTextLayout; 368 private boolean mLiveTileOverlayAttached; 369 370 // Keeps track of the index where the first TaskView should be 371 private int mTaskViewStartIndex = 0; 372 private OverviewActionsView mActionsView; 373 374 private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = 375 (inMultiWindowMode) -> { 376 if (mOrientationState != null) { 377 mOrientationState.setMultiWindowMode(inMultiWindowMode); 378 setLayoutRotation(mOrientationState.getTouchRotation(), 379 mOrientationState.getDisplayRotation()); 380 rotateAllChildTasks(); 381 } 382 if (!inMultiWindowMode && mOverviewStateEnabled) { 383 // TODO: Re-enable layout transitions for addition of the unpinned task 384 reloadIfNeeded(); 385 } 386 }; 387 RecentsView(Context context, AttributeSet attrs, int defStyleAttr, BaseActivityInterface sizeStrategy)388 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, 389 BaseActivityInterface sizeStrategy) { 390 super(context, attrs, defStyleAttr); 391 setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); 392 setEnableFreeScroll(true); 393 mSizeStrategy = sizeStrategy; 394 mActivity = BaseActivity.fromContext(context); 395 mOrientationState = new RecentsOrientedState( 396 context, mSizeStrategy, this::animateRecentsRotationInPlace); 397 mOrientationState.setActivityConfiguration(context.getResources().getConfiguration()); 398 399 mFastFlingVelocity = getResources() 400 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); 401 mModel = RecentsModel.INSTANCE.get(context); 402 mIdp = InvariantDeviceProfile.INSTANCE.get(context); 403 404 mClearAllButton = (ClearAllButton) LayoutInflater.from(context) 405 .inflate(R.layout.overview_clear_all_button, this, false); 406 mClearAllButton.setOnClickListener(this::dismissAllTasks); 407 mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 408 10 /* initial size */); 409 410 mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); 411 setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); 412 mTaskTopMargin = getResources() 413 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); 414 mSquaredTouchSlop = squaredTouchSlop(context); 415 416 mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); 417 mEmptyIcon.setCallback(this); 418 mEmptyMessage = context.getText(R.string.recents_empty_message); 419 mEmptyMessagePaint = new TextPaint(); 420 mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); 421 mEmptyMessagePaint.setTextSize(getResources() 422 .getDimension(R.dimen.recents_empty_message_text_size)); 423 mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context), 424 Typeface.NORMAL)); 425 mEmptyMessagePadding = getResources() 426 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); 427 setWillNotDraw(false); 428 updateEmptyMessage(); 429 mOrientationHandler = mOrientationState.getOrientationHandler(); 430 431 // Initialize quickstep specific cache params here, as this is constructed only once 432 mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); 433 } 434 getScroller()435 public OverScroller getScroller() { 436 return mScroller; 437 } 438 isRtl()439 public boolean isRtl() { 440 return mIsRtl; 441 } 442 443 @Override onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)444 public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { 445 if (mHandleTaskStackChanges) { 446 TaskView taskView = getTaskView(taskId); 447 if (taskView != null) { 448 Task task = taskView.getTask(); 449 taskView.getThumbnail().setThumbnail(task, thumbnailData); 450 return task; 451 } 452 } 453 return null; 454 } 455 456 @Override onTaskIconChanged(String pkg, UserHandle user)457 public void onTaskIconChanged(String pkg, UserHandle user) { 458 for (int i = 0; i < getTaskViewCount(); i++) { 459 TaskView tv = getTaskViewAt(i); 460 Task task = tv.getTask(); 461 if (task != null && task.key != null && pkg.equals(task.key.getPackageName()) 462 && task.key.userId == user.getIdentifier()) { 463 task.icon = null; 464 if (tv.getIconView().getDrawable() != null) { 465 tv.onTaskListVisibilityChanged(true /* visible */); 466 } 467 } 468 } 469 } 470 471 /** 472 * Update the thumbnail of the task. 473 * @param refreshNow Refresh immediately if it's true. 474 */ updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow)475 public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) { 476 TaskView taskView = getTaskView(taskId); 477 if (taskView != null) { 478 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow); 479 } 480 return taskView; 481 } 482 483 /** See {@link #updateThumbnail(int, ThumbnailData, boolean)} */ updateThumbnail(int taskId, ThumbnailData thumbnailData)484 public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) { 485 return updateThumbnail(taskId, thumbnailData, true /* refreshNow */); 486 } 487 488 @Override onWindowVisibilityChanged(int visibility)489 protected void onWindowVisibilityChanged(int visibility) { 490 super.onWindowVisibilityChanged(visibility); 491 updateTaskStackListenerState(); 492 } 493 494 @Override onIdpChanged(int changeFlags, InvariantDeviceProfile idp)495 public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) { 496 if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) { 497 return; 498 } 499 mModel.getIconCache().clear(); 500 unloadVisibleTaskData(); 501 loadVisibleTaskData(); 502 } 503 init(OverviewActionsView actionsView)504 public void init(OverviewActionsView actionsView) { 505 mActionsView = actionsView; 506 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); 507 } 508 509 @Override onAttachedToWindow()510 protected void onAttachedToWindow() { 511 super.onAttachedToWindow(); 512 updateTaskStackListenerState(); 513 mModel.getThumbnailCache().getHighResLoadingState().addCallback(this); 514 mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 515 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); 516 mSyncTransactionApplier = new SurfaceTransactionApplier(this); 517 RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); 518 mIdp.addOnChangeListener(this); 519 mIPinnedStackAnimationListener.setActivity(mActivity); 520 SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( 521 mIPinnedStackAnimationListener); 522 mOrientationState.initListeners(); 523 SplitScreenBounds.INSTANCE.addOnChangeListener(this); 524 } 525 526 @Override onDetachedFromWindow()527 protected void onDetachedFromWindow() { 528 super.onDetachedFromWindow(); 529 updateTaskStackListenerState(); 530 mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); 531 mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 532 ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); 533 mSyncTransactionApplier = null; 534 RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); 535 mIdp.removeOnChangeListener(this); 536 SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); 537 SplitScreenBounds.INSTANCE.removeOnChangeListener(this); 538 mIPinnedStackAnimationListener.setActivity(null); 539 mOrientationState.destroyListeners(); 540 } 541 542 @Override onViewRemoved(View child)543 public void onViewRemoved(View child) { 544 super.onViewRemoved(child); 545 546 // Clear the task data for the removed child if it was visible 547 if (child instanceof TaskView) { 548 TaskView taskView = (TaskView) child; 549 mHasVisibleTaskData.delete(taskView.getTask().key.id); 550 mTaskViewPool.recycle(taskView); 551 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); 552 } 553 updateTaskStartIndex(child); 554 } 555 556 @Override onViewAdded(View child)557 public void onViewAdded(View child) { 558 super.onViewAdded(child); 559 child.setAlpha(mContentAlpha); 560 // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the 561 // child direction back to match system settings. 562 child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL); 563 updateTaskStartIndex(child); 564 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false); 565 updateEmptyMessage(); 566 } 567 568 @Override draw(Canvas canvas)569 public void draw(Canvas canvas) { 570 maybeDrawEmptyMessage(canvas); 571 super.draw(canvas); 572 } 573 updateTaskStartIndex(View affectingView)574 private void updateTaskStartIndex(View affectingView) { 575 if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) { 576 int childCount = getChildCount(); 577 578 mTaskViewStartIndex = 0; 579 while (mTaskViewStartIndex < childCount 580 && !(getChildAt(mTaskViewStartIndex) instanceof TaskView)) { 581 mTaskViewStartIndex++; 582 } 583 } 584 } 585 isTaskViewVisible(TaskView tv)586 public boolean isTaskViewVisible(TaskView tv) { 587 // For now, just check if it's the active task or an adjacent task 588 return Math.abs(indexOfChild(tv) - getNextPage()) <= 1; 589 } 590 getTaskView(int taskId)591 public TaskView getTaskView(int taskId) { 592 for (int i = 0; i < getTaskViewCount(); i++) { 593 TaskView tv = getTaskViewAt(i); 594 if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) { 595 return tv; 596 } 597 } 598 return null; 599 } 600 setOverviewStateEnabled(boolean enabled)601 public void setOverviewStateEnabled(boolean enabled) { 602 mOverviewStateEnabled = enabled; 603 updateTaskStackListenerState(); 604 mOrientationState.setRotationWatcherEnabled(enabled); 605 if (!enabled) { 606 // Reset the running task when leaving overview since it can still have a reference to 607 // its thumbnail 608 mTmpRunningTask = null; 609 } 610 } 611 onDigitalWellbeingToastShown()612 public void onDigitalWellbeingToastShown() { 613 if (!mDwbToastShown) { 614 mDwbToastShown = true; 615 mActivity.getUserEventDispatcher().logActionTip( 616 LauncherEventUtil.VISIBLE, 617 LauncherLogProto.TipType.DWB_TOAST); 618 } 619 } 620 621 /** 622 * Whether the Clear All button is hidden or fully visible. Used to determine if center 623 * displayed page is a task or the Clear All button. 624 * 625 * @return True = Clear All button not fully visible, center page is a task. False = Clear All 626 * button fully visible, center page is Clear All button. 627 */ isClearAllHidden()628 public boolean isClearAllHidden() { 629 return mClearAllButton.getAlpha() != 1f; 630 } 631 632 @Override onPageBeginTransition()633 protected void onPageBeginTransition() { 634 super.onPageBeginTransition(); 635 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true); 636 } 637 638 @Override onPageEndTransition()639 protected void onPageEndTransition() { 640 super.onPageEndTransition(); 641 if (isClearAllHidden()) { 642 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); 643 } 644 if (getNextPage() > 0) { 645 setSwipeDownShouldLaunchApp(true); 646 } 647 } 648 649 @Override onTouchEvent(MotionEvent ev)650 public boolean onTouchEvent(MotionEvent ev) { 651 super.onTouchEvent(ev); 652 final int x = (int) ev.getX(); 653 final int y = (int) ev.getY(); 654 switch (ev.getAction()) { 655 case MotionEvent.ACTION_UP: 656 if (mTouchDownToStartHome) { 657 startHome(); 658 } 659 mTouchDownToStartHome = false; 660 break; 661 case MotionEvent.ACTION_CANCEL: 662 mTouchDownToStartHome = false; 663 break; 664 case MotionEvent.ACTION_MOVE: 665 // Passing the touch slop will not allow dismiss to home 666 if (mTouchDownToStartHome && 667 (isHandlingTouch() || 668 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) { 669 mTouchDownToStartHome = false; 670 } 671 break; 672 case MotionEvent.ACTION_DOWN: 673 // Touch down anywhere but the deadzone around the visible clear all button and 674 // between the task views will start home on touch up 675 if (!isHandlingTouch() && !isModal()) { 676 if (mShowEmptyMessage) { 677 mTouchDownToStartHome = true; 678 } else { 679 updateDeadZoneRects(); 680 final boolean clearAllButtonDeadZoneConsumed = 681 mClearAllButton.getAlpha() == 1 682 && mClearAllButtonDeadZoneRect.contains(x, y); 683 final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; 684 if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar 685 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) { 686 mTouchDownToStartHome = true; 687 } 688 } 689 } 690 mDownX = x; 691 mDownY = y; 692 break; 693 } 694 695 696 // Do not let touch escape to siblings below this view. 697 return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev); 698 } 699 700 @Override determineScrollingStart(MotionEvent ev, float touchSlopScale)701 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { 702 // Enables swiping to the left or right only if the task overlay is not modal. 703 if (!isModal()) { 704 super.determineScrollingStart(ev, touchSlopScale); 705 } 706 } shouldStealTouchFromSiblingsBelow(MotionEvent ev)707 protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) { 708 return true; 709 } 710 applyLoadPlan(ArrayList<Task> tasks)711 protected void applyLoadPlan(ArrayList<Task> tasks) { 712 if (mPendingAnimation != null) { 713 mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks)); 714 return; 715 } 716 717 if (tasks == null || tasks.isEmpty()) { 718 removeTasksViewsAndClearAllButton(); 719 onTaskStackUpdated(); 720 return; 721 } 722 723 // Unload existing visible task data 724 unloadVisibleTaskData(); 725 726 TaskView ignoreResetTaskView = 727 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId); 728 729 final int requiredTaskCount = tasks.size(); 730 if (getTaskViewCount() != requiredTaskCount) { 731 if (indexOfChild(mClearAllButton) != -1) { 732 removeView(mClearAllButton); 733 } 734 for (int i = getTaskViewCount(); i < requiredTaskCount; i++) { 735 addView(mTaskViewPool.getView()); 736 } 737 while (getTaskViewCount() > requiredTaskCount) { 738 removeView(getChildAt(getChildCount() - 1)); 739 } 740 if (requiredTaskCount > 0) { 741 addView(mClearAllButton); 742 } 743 } 744 745 // Rebind and reset all task views 746 for (int i = requiredTaskCount - 1; i >= 0; i--) { 747 final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex; 748 final Task task = tasks.get(i); 749 final TaskView taskView = (TaskView) getChildAt(pageIndex); 750 taskView.bind(task, mOrientationState); 751 } 752 753 if (mNextPage == INVALID_PAGE) { 754 // Set the current page to the running task, but not if settling on new task. 755 TaskView runningTaskView = getRunningTaskView(); 756 if (runningTaskView != null) { 757 setCurrentPage(indexOfChild(runningTaskView)); 758 } else if (getTaskViewCount() > 0) { 759 setCurrentPage(indexOfChild(getTaskViewAt(0))); 760 } 761 } 762 763 if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) { 764 // If the taskView mapping is changing, do not preserve the visuals. Since we are 765 // mostly preserving the first task, and new taskViews are added to the end, it should 766 // generally map to the same task. 767 mIgnoreResetTaskId = -1; 768 } 769 resetTaskVisuals(); 770 onTaskStackUpdated(); 771 updateEnabledOverlays(); 772 } 773 isModal()774 private boolean isModal() { 775 return mTaskModalness > 0; 776 } 777 removeTasksViewsAndClearAllButton()778 private void removeTasksViewsAndClearAllButton() { 779 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 780 removeView(getTaskViewAt(i)); 781 } 782 if (indexOfChild(mClearAllButton) != -1) { 783 removeView(mClearAllButton); 784 } 785 } 786 getTaskViewCount()787 public int getTaskViewCount() { 788 int taskViewCount = getChildCount() - mTaskViewStartIndex; 789 if (indexOfChild(mClearAllButton) != -1) { 790 taskViewCount--; 791 } 792 return taskViewCount; 793 } 794 onTaskStackUpdated()795 protected void onTaskStackUpdated() { 796 // Lazily update the empty message only when the task stack is reapplied 797 updateEmptyMessage(); 798 } 799 resetTaskVisuals()800 public void resetTaskVisuals() { 801 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 802 TaskView taskView = getTaskViewAt(i); 803 if (mIgnoreResetTaskId != taskView.getTask().key.id) { 804 taskView.resetViewTransforms(); 805 taskView.setStableAlpha(mContentAlpha); 806 taskView.setFullscreenProgress(mFullscreenProgress); 807 taskView.setModalness(mTaskModalness); 808 } 809 } 810 if (mRunningTaskTileHidden) { 811 setRunningTaskHidden(mRunningTaskTileHidden); 812 } 813 814 // Force apply the scale. 815 if (mIgnoreResetTaskId != mRunningTaskId) { 816 applyRunningTaskIconScale(); 817 } 818 819 updateCurveProperties(); 820 // Update the set of visible task's data 821 loadVisibleTaskData(); 822 setTaskModalness(0); 823 } 824 setFullscreenProgress(float fullscreenProgress)825 public void setFullscreenProgress(float fullscreenProgress) { 826 mFullscreenProgress = fullscreenProgress; 827 int taskCount = getTaskViewCount(); 828 for (int i = 0; i < taskCount; i++) { 829 getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress); 830 } 831 // Fade out the actions view quickly (0.1 range) 832 mActionsView.getFullscreenAlpha().setValue( 833 mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR)); 834 } 835 updateTaskStackListenerState()836 private void updateTaskStackListenerState() { 837 boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() 838 && getWindowVisibility() == VISIBLE; 839 if (handleTaskStackChanges != mHandleTaskStackChanges) { 840 mHandleTaskStackChanges = handleTaskStackChanges; 841 if (handleTaskStackChanges) { 842 reloadIfNeeded(); 843 } 844 } 845 } 846 847 @Override setInsets(Rect insets)848 public void setInsets(Rect insets) { 849 mInsets.set(insets); 850 resetPaddingFromTaskSize(); 851 } 852 resetPaddingFromTaskSize()853 private void resetPaddingFromTaskSize() { 854 DeviceProfile dp = mActivity.getDeviceProfile(); 855 getTaskSize(mTempRect); 856 mTaskWidth = mTempRect.width(); 857 mTaskHeight = mTempRect.height(); 858 859 mTempRect.top -= mTaskTopMargin; 860 setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top, 861 dp.widthPx - mInsets.right - mTempRect.right, 862 dp.heightPx - mInsets.bottom - mTempRect.bottom); 863 } 864 getTaskSize(Rect outRect)865 public void getTaskSize(Rect outRect) { 866 mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect, 867 mOrientationHandler); 868 } 869 870 /** Gets the task size for modal state. */ getModalTaskSize(Rect outRect)871 public void getModalTaskSize(Rect outRect) { 872 mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect); 873 } 874 875 @Override computeScrollHelper()876 protected boolean computeScrollHelper() { 877 boolean scrolling = super.computeScrollHelper(); 878 boolean isFlingingFast = false; 879 updateCurveProperties(); 880 if (scrolling || isHandlingTouch()) { 881 if (scrolling) { 882 // Check if we are flinging quickly to disable high res thumbnail loading 883 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity; 884 } 885 886 // After scrolling, update the visible task's data 887 loadVisibleTaskData(); 888 } 889 890 // Update the high res thumbnail loader state 891 mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast); 892 return scrolling; 893 } 894 895 /** 896 * Scales and adjusts translation of adjacent pages as if on a curved carousel. 897 */ updateCurveProperties()898 public void updateCurveProperties() { 899 if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) { 900 return; 901 } 902 mOrientationHandler.getCurveProperties(this, mInsets, mScrollState); 903 mScrollState.scrollFromEdge = 904 mIsRtl ? mScrollState.scroll : (mMaxScroll - mScrollState.scroll); 905 906 final int pageCount = getPageCount(); 907 for (int i = 0; i < pageCount; i++) { 908 View page = getPageAt(i); 909 mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page), 910 mPageSpacing); 911 ((PageCallbacks) page).onPageScroll(mScrollState); 912 } 913 } 914 915 /** 916 * Iterates through all the tasks, and loads the associated task data for newly visible tasks, 917 * and unloads the associated task data for tasks that are no longer visible. 918 */ loadVisibleTaskData()919 public void loadVisibleTaskData() { 920 if (!mOverviewStateEnabled || mTaskListChangeId == -1) { 921 // Skip loading visible task data if we've already left the overview state, or if the 922 // task list hasn't been loaded yet (the task views will not reflect the task list) 923 return; 924 } 925 926 int centerPageIndex = getPageNearestToCenterOfScreen(); 927 int numChildren = getChildCount(); 928 int lower = Math.max(0, centerPageIndex - 2); 929 int upper = Math.min(centerPageIndex + 2, numChildren - 1); 930 931 // Update the task data for the in/visible children 932 for (int i = 0; i < getTaskViewCount(); i++) { 933 TaskView taskView = getTaskViewAt(i); 934 Task task = taskView.getTask(); 935 int index = indexOfChild(taskView); 936 boolean visible = lower <= index && index <= upper; 937 if (visible) { 938 if (task == mTmpRunningTask) { 939 // Skip loading if this is the task that we are animating into 940 continue; 941 } 942 if (!mHasVisibleTaskData.get(task.key.id)) { 943 taskView.onTaskListVisibilityChanged(true /* visible */); 944 } 945 mHasVisibleTaskData.put(task.key.id, visible); 946 } else { 947 if (mHasVisibleTaskData.get(task.key.id)) { 948 taskView.onTaskListVisibilityChanged(false /* visible */); 949 } 950 mHasVisibleTaskData.delete(task.key.id); 951 } 952 } 953 } 954 955 /** 956 * Unloads any associated data from the currently visible tasks 957 */ unloadVisibleTaskData()958 private void unloadVisibleTaskData() { 959 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 960 if (mHasVisibleTaskData.valueAt(i)) { 961 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); 962 if (taskView != null) { 963 taskView.onTaskListVisibilityChanged(false /* visible */); 964 } 965 } 966 } 967 mHasVisibleTaskData.clear(); 968 } 969 970 @Override onHighResLoadingStateChanged(boolean enabled)971 public void onHighResLoadingStateChanged(boolean enabled) { 972 // Whenever the high res loading state changes, poke each of the visible tasks to see if 973 // they want to updated their thumbnail state 974 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 975 if (mHasVisibleTaskData.valueAt(i)) { 976 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); 977 if (taskView != null) { 978 // Poke the view again, which will trigger it to load high res if the state 979 // is enabled 980 taskView.onTaskListVisibilityChanged(true /* visible */); 981 } 982 } 983 } 984 } 985 startHome()986 public abstract void startHome(); 987 988 /** `true` if there is a +1 space available in overview. */ hasRecentsExtraCard()989 public boolean hasRecentsExtraCard() { 990 return false; 991 } 992 reset()993 public void reset() { 994 setCurrentTask(-1); 995 mIgnoreResetTaskId = -1; 996 mTaskListChangeId = -1; 997 998 mRecentsAnimationController = null; 999 mRecentsAnimationTargets = null; 1000 1001 unloadVisibleTaskData(); 1002 setCurrentPage(0); 1003 mDwbToastShown = false; 1004 mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0); 1005 LayoutUtils.setViewEnabled(mActionsView, true); 1006 if (mOrientationState.setGestureActive(false)) { 1007 updateOrientationHandler(); 1008 } 1009 } 1010 getRunningTaskView()1011 public @Nullable TaskView getRunningTaskView() { 1012 return getTaskView(mRunningTaskId); 1013 } 1014 getRunningTaskIndex()1015 public int getRunningTaskIndex() { 1016 return getTaskIndexForId(mRunningTaskId); 1017 } 1018 1019 /** 1020 * Get the index of the task view whose id matches {@param taskId}. 1021 * @return -1 if there is no task view for the task id, else the index of the task view. 1022 */ getTaskIndexForId(int taskId)1023 public int getTaskIndexForId(int taskId) { 1024 TaskView tv = getTaskView(taskId); 1025 return tv == null ? -1 : indexOfChild(tv); 1026 } 1027 getTaskViewStartIndex()1028 public int getTaskViewStartIndex() { 1029 return mTaskViewStartIndex; 1030 } 1031 1032 /** 1033 * Reloads the view if anything in recents changed. 1034 */ reloadIfNeeded()1035 public void reloadIfNeeded() { 1036 if (!mModel.isTaskListValid(mTaskListChangeId)) { 1037 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); 1038 } 1039 } 1040 1041 /** 1042 * Called when a gesture from an app is starting. 1043 */ onGestureAnimationStart(int runningTaskId)1044 public void onGestureAnimationStart(int runningTaskId) { 1045 // This needs to be called before the other states are set since it can create the task view 1046 if (mOrientationState.setGestureActive(true)) { 1047 updateOrientationHandler(); 1048 } 1049 1050 showCurrentTask(runningTaskId); 1051 setEnableFreeScroll(false); 1052 setEnableDrawingLiveTile(false); 1053 setRunningTaskHidden(true); 1054 setRunningTaskIconScaledDown(true); 1055 mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, true); 1056 } 1057 1058 /** 1059 * Called only when a swipe-up gesture from an app has completed. Only called after 1060 * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. 1061 */ onSwipeUpAnimationSuccess()1062 public void onSwipeUpAnimationSuccess() { 1063 if (getRunningTaskView() != null) { 1064 float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached 1065 ? LiveTileOverlay.INSTANCE.cancelIconAnimation() 1066 : 0f; 1067 animateUpRunningTaskIconScale(startProgress); 1068 } 1069 setSwipeDownShouldLaunchApp(true); 1070 } 1071 animateRecentsRotationInPlace(int newRotation)1072 private void animateRecentsRotationInPlace(int newRotation) { 1073 if (mOrientationState.canRecentsActivityRotate()) { 1074 // Let system take care of the rotation 1075 return; 1076 } 1077 AnimatorSet pa = setRecentsChangedOrientation(true); 1078 pa.addListener(AnimationSuccessListener.forRunnable(() -> { 1079 setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); 1080 mActivity.getDragLayer().recreateControllers(); 1081 rotateAllChildTasks(); 1082 setRecentsChangedOrientation(false).start(); 1083 })); 1084 pa.start(); 1085 } 1086 setRecentsChangedOrientation(boolean fadeInChildren)1087 public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) { 1088 getRunningTaskIndex(); 1089 int runningIndex = getCurrentPage(); 1090 AnimatorSet as = new AnimatorSet(); 1091 for (int i = 0; i < getTaskViewCount(); i++) { 1092 if (runningIndex == i) { 1093 continue; 1094 } 1095 View taskView = getTaskViewAt(i); 1096 as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1)); 1097 } 1098 return as; 1099 } 1100 1101 rotateAllChildTasks()1102 private void rotateAllChildTasks() { 1103 for (int i = 0; i < getTaskViewCount(); i++) { 1104 getTaskViewAt(i).setOrientationState(mOrientationState); 1105 } 1106 } 1107 1108 /** 1109 * Called when a gesture from an app has finished. 1110 */ onGestureAnimationEnd()1111 public void onGestureAnimationEnd() { 1112 if (mOrientationState.setGestureActive(false)) { 1113 updateOrientationHandler(); 1114 } 1115 1116 setOnScrollChangeListener(null); 1117 setEnableFreeScroll(true); 1118 setEnableDrawingLiveTile(true); 1119 if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1120 setRunningTaskViewShowScreenshot(true); 1121 } 1122 setRunningTaskHidden(false); 1123 animateUpRunningTaskIconScale(); 1124 animateActionsViewIn(); 1125 } 1126 1127 /** 1128 * Returns true if we should add a dummy taskView for the running task id 1129 */ shouldAddDummyTaskView(int runningTaskId)1130 protected boolean shouldAddDummyTaskView(int runningTaskId) { 1131 return getTaskView(runningTaskId) == null; 1132 } 1133 1134 /** 1135 * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. 1136 * 1137 * All subsequent calls to reload will keep the task as the first item until {@link #reset()} 1138 * is called. Also scrolls the view to this task. 1139 */ showCurrentTask(int runningTaskId)1140 public void showCurrentTask(int runningTaskId) { 1141 if (shouldAddDummyTaskView(runningTaskId)) { 1142 boolean wasEmpty = getChildCount() == 0; 1143 // Add an empty view for now until the task plan is loaded and applied 1144 final TaskView taskView = mTaskViewPool.getView(); 1145 addView(taskView, mTaskViewStartIndex); 1146 if (wasEmpty) { 1147 addView(mClearAllButton); 1148 } 1149 // The temporary running task is only used for the duration between the start of the 1150 // gesture and the task list is loaded and applied 1151 mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(), 1152 new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0, 1153 false, true, false, false, new ActivityManager.TaskDescription(), 0, 1154 new ComponentName("", ""), false); 1155 taskView.bind(mTmpRunningTask, mOrientationState); 1156 1157 // Measure and layout immediately so that the scroll values is updated instantly 1158 // as the user might be quick-switching 1159 measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), 1160 makeMeasureSpec(getMeasuredHeight(), EXACTLY)); 1161 layout(getLeft(), getTop(), getRight(), getBottom()); 1162 } 1163 1164 boolean runningTaskTileHidden = mRunningTaskTileHidden; 1165 setCurrentTask(runningTaskId); 1166 setCurrentPage(getRunningTaskIndex()); 1167 setRunningTaskViewShowScreenshot(false); 1168 setRunningTaskHidden(runningTaskTileHidden); 1169 1170 // Reload the task list 1171 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); 1172 } 1173 1174 /** 1175 * Sets the running task id, cleaning up the old running task if necessary. 1176 * @param runningTaskId 1177 */ setCurrentTask(int runningTaskId)1178 public void setCurrentTask(int runningTaskId) { 1179 if (mRunningTaskId == runningTaskId) { 1180 return; 1181 } 1182 1183 if (mRunningTaskId != -1) { 1184 // Reset the state on the old running task view 1185 setRunningTaskIconScaledDown(false); 1186 setRunningTaskViewShowScreenshot(true); 1187 setRunningTaskHidden(false); 1188 } 1189 mRunningTaskId = runningTaskId; 1190 } 1191 1192 /** 1193 * Hides the tile associated with {@link #mRunningTaskId} 1194 */ setRunningTaskHidden(boolean isHidden)1195 public void setRunningTaskHidden(boolean isHidden) { 1196 mRunningTaskTileHidden = isHidden; 1197 TaskView runningTask = getRunningTaskView(); 1198 if (runningTask != null) { 1199 runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha); 1200 if (!isHidden) { 1201 AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask, 1202 AccessibilityEvent.TYPE_VIEW_FOCUSED, null); 1203 } 1204 } 1205 } 1206 setRunningTaskViewShowScreenshot(boolean showScreenshot)1207 private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { 1208 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1209 TaskView runningTaskView = getRunningTaskView(); 1210 if (runningTaskView != null) { 1211 runningTaskView.setShowScreenshot(showScreenshot); 1212 } 1213 } 1214 } 1215 showNextTask()1216 public void showNextTask() { 1217 TaskView runningTaskView = getRunningTaskView(); 1218 if (runningTaskView == null) { 1219 // Launch the first task 1220 if (getTaskViewCount() > 0) { 1221 getTaskViewAt(0).launchTask(true); 1222 } 1223 } else { 1224 if (getNextTaskView() != null) { 1225 getNextTaskView().launchTask(true); 1226 } else { 1227 runningTaskView.launchTask(true); 1228 } 1229 } 1230 } 1231 setRunningTaskIconScaledDown(boolean isScaledDown)1232 public void setRunningTaskIconScaledDown(boolean isScaledDown) { 1233 if (mRunningTaskIconScaledDown != isScaledDown) { 1234 mRunningTaskIconScaledDown = isScaledDown; 1235 applyRunningTaskIconScale(); 1236 } 1237 } 1238 isTaskIconScaledDown(TaskView taskView)1239 public boolean isTaskIconScaledDown(TaskView taskView) { 1240 return mRunningTaskIconScaledDown && getRunningTaskView() == taskView; 1241 } 1242 applyRunningTaskIconScale()1243 private void applyRunningTaskIconScale() { 1244 TaskView firstTask = getRunningTaskView(); 1245 if (firstTask != null) { 1246 firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1); 1247 } 1248 } 1249 animateActionsViewIn()1250 private void animateActionsViewIn() { 1251 mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, false); 1252 ObjectAnimator anim = ObjectAnimator.ofFloat( 1253 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1); 1254 anim.setDuration(TaskView.SCALE_ICON_DURATION); 1255 anim.start(); 1256 } 1257 animateUpRunningTaskIconScale()1258 public void animateUpRunningTaskIconScale() { 1259 animateUpRunningTaskIconScale(0); 1260 } 1261 animateUpRunningTaskIconScale(float startProgress)1262 public void animateUpRunningTaskIconScale(float startProgress) { 1263 mRunningTaskIconScaledDown = false; 1264 TaskView firstTask = getRunningTaskView(); 1265 if (firstTask != null) { 1266 firstTask.animateIconScaleAndDimIntoView(); 1267 firstTask.setIconScaleAnimStartProgress(startProgress); 1268 } 1269 } 1270 enableLayoutTransitions()1271 private void enableLayoutTransitions() { 1272 if (mLayoutTransition == null) { 1273 mLayoutTransition = new LayoutTransition(); 1274 mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING); 1275 mLayoutTransition.setDuration(ADDITION_TASK_DURATION); 1276 mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0); 1277 1278 mLayoutTransition.addTransitionListener(new TransitionListener() { 1279 @Override 1280 public void startTransition(LayoutTransition transition, ViewGroup viewGroup, 1281 View view, int i) { 1282 } 1283 1284 @Override 1285 public void endTransition(LayoutTransition transition, ViewGroup viewGroup, 1286 View view, int i) { 1287 // When the unpinned task is added, snap to first page and disable transitions 1288 if (view instanceof TaskView) { 1289 snapToPage(0); 1290 disableLayoutTransitions(); 1291 } 1292 1293 } 1294 }); 1295 } 1296 setLayoutTransition(mLayoutTransition); 1297 } 1298 disableLayoutTransitions()1299 private void disableLayoutTransitions() { 1300 setLayoutTransition(null); 1301 } 1302 setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)1303 public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) { 1304 mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp; 1305 } 1306 shouldSwipeDownLaunchApp()1307 public boolean shouldSwipeDownLaunchApp() { 1308 return mSwipeDownShouldLaunchApp; 1309 } 1310 1311 public interface PageCallbacks { 1312 1313 /** 1314 * Updates the page UI based on scroll params. 1315 */ onPageScroll(ScrollState scrollState)1316 default void onPageScroll(ScrollState scrollState) {} 1317 } 1318 1319 public static class ScrollState extends CurveProperties { 1320 1321 /** 1322 * The progress from 0 to 1, where 0 is the center 1323 * of the screen and 1 is the edge of the screen. 1324 */ 1325 public float linearInterpolation; 1326 1327 /** 1328 * The amount by which all the content is scrolled relative to the end of the list. 1329 */ 1330 public float scrollFromEdge; 1331 1332 /** 1333 * Updates linearInterpolation for the provided child position 1334 */ updateInterpolation(float childStart, int pageSpacing)1335 public void updateInterpolation(float childStart, int pageSpacing) { 1336 float pageCenter = childStart + halfPageSize; 1337 float distanceFromScreenCenter = screenCenter - pageCenter; 1338 float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing; 1339 linearInterpolation = Math.min(1, 1340 Math.abs(distanceFromScreenCenter) / distanceToReachEdge); 1341 } 1342 } 1343 setIgnoreResetTask(int taskId)1344 public void setIgnoreResetTask(int taskId) { 1345 mIgnoreResetTaskId = taskId; 1346 } 1347 clearIgnoreResetTask(int taskId)1348 public void clearIgnoreResetTask(int taskId) { 1349 if (mIgnoreResetTaskId == taskId) { 1350 mIgnoreResetTaskId = -1; 1351 } 1352 } 1353 addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim)1354 private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) { 1355 // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's 1356 // alpha is set to 0 so that it can be recycled in the view pool properly 1357 anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2); 1358 FloatProperty<View> secondaryViewTranslate = 1359 mOrientationHandler.getSecondaryViewTranslate(); 1360 int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); 1361 int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor(); 1362 1363 ResourceProvider rp = DynamicResource.provider(mActivity); 1364 SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START) 1365 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio)) 1366 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness)); 1367 1368 anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, 1369 verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp); 1370 } 1371 removeTask(TaskView taskView, int index, EndState endState)1372 private void removeTask(TaskView taskView, int index, EndState endState) { 1373 if (taskView.getTask() != null) { 1374 ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id); 1375 ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key); 1376 mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( 1377 endState.logAction, Direction.UP, index, compKey); 1378 mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo()) 1379 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); 1380 } 1381 } 1382 createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration)1383 public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, 1384 boolean shouldRemoveTask, long duration) { 1385 if (mPendingAnimation != null) { 1386 mPendingAnimation.finish(false, Touch.SWIPE); 1387 } 1388 PendingAnimation anim = new PendingAnimation(duration); 1389 1390 int count = getPageCount(); 1391 if (count == 0) { 1392 return anim; 1393 } 1394 1395 int[] oldScroll = new int[count]; 1396 int[] newScroll = new int[count]; 1397 getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); 1398 getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView); 1399 int taskCount = getTaskViewCount(); 1400 int scrollDiffPerPage = 0; 1401 if (count > 1) { 1402 scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); 1403 } 1404 int draggedIndex = indexOfChild(taskView); 1405 1406 boolean needsCurveUpdates = false; 1407 for (int i = 0; i < count; i++) { 1408 View child = getChildAt(i); 1409 if (child == taskView) { 1410 if (animateTaskView) { 1411 addDismissedTaskAnimations(taskView, duration, anim); 1412 } 1413 } else { 1414 // If we just take newScroll - oldScroll, everything to the right of dragged task 1415 // translates to the left. We need to offset this in some cases: 1416 // - In RTL, add page offset to all pages, since we want pages to move to the right 1417 // Additionally, add a page offset if: 1418 // - Current page is rightmost page (leftmost for RTL) 1419 // - Dragging an adjacent page on the left side (right side for RTL) 1420 int offset = mIsRtl ? scrollDiffPerPage : 0; 1421 if (mCurrentPage == draggedIndex) { 1422 int lastPage = taskCount - 1; 1423 if (mCurrentPage == lastPage) { 1424 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 1425 } 1426 } else { 1427 // Dragging an adjacent page. 1428 int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR) 1429 if (draggedIndex == negativeAdjacent) { 1430 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 1431 } 1432 } 1433 int scrollDiff = newScroll[i] - oldScroll[i] + offset; 1434 if (scrollDiff != 0) { 1435 Property translationProperty = mOrientationHandler.getPrimaryViewTranslate(); 1436 1437 ResourceProvider rp = DynamicResource.provider(mActivity); 1438 SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END) 1439 .setDampingRatio( 1440 rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio)) 1441 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness)); 1442 anim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff) 1443 .setDuration(duration), ACCEL, sp); 1444 needsCurveUpdates = true; 1445 } 1446 } 1447 } 1448 1449 if (needsCurveUpdates) { 1450 anim.addOnFrameCallback(this::updateCurveProperties); 1451 } 1452 1453 // Add a tiny bit of translation Z, so that it draws on top of other views 1454 if (animateTaskView) { 1455 taskView.setTranslationZ(0.1f); 1456 } 1457 1458 mPendingAnimation = anim; 1459 mPendingAnimation.addEndListener(new Consumer<EndState>() { 1460 @Override 1461 public void accept(EndState endState) { 1462 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && 1463 taskView.isRunningTask() && endState.isSuccess) { 1464 finishRecentsAnimation(true /* toHome */, () -> onEnd(endState)); 1465 } else { 1466 onEnd(endState); 1467 } 1468 } 1469 1470 @SuppressWarnings("WrongCall") 1471 private void onEnd(EndState endState) { 1472 if (endState.isSuccess) { 1473 if (shouldRemoveTask) { 1474 removeTask(taskView, draggedIndex, endState); 1475 } 1476 1477 int pageToSnapTo = mCurrentPage; 1478 if (draggedIndex < pageToSnapTo || 1479 pageToSnapTo == (getTaskViewCount() - 1)) { 1480 pageToSnapTo -= 1; 1481 } 1482 removeViewInLayout(taskView); 1483 1484 if (getTaskViewCount() == 0) { 1485 removeViewInLayout(mClearAllButton); 1486 startHome(); 1487 } else { 1488 snapToPageImmediately(pageToSnapTo); 1489 } 1490 // Update the layout synchronously so that the position of next view is 1491 // immediately available. 1492 onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); 1493 } 1494 resetTaskVisuals(); 1495 mPendingAnimation = null; 1496 } 1497 }); 1498 return anim; 1499 } 1500 createAllTasksDismissAnimation(long duration)1501 public PendingAnimation createAllTasksDismissAnimation(long duration) { 1502 if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { 1503 throw new IllegalStateException("Another pending animation is still running"); 1504 } 1505 PendingAnimation anim = new PendingAnimation(duration); 1506 1507 int count = getTaskViewCount(); 1508 for (int i = 0; i < count; i++) { 1509 addDismissedTaskAnimations(getTaskViewAt(i), duration, anim); 1510 } 1511 1512 mPendingAnimation = anim; 1513 mPendingAnimation.addEndListener((endState) -> { 1514 if (endState.isSuccess) { 1515 // Remove all the task views now 1516 ActivityManagerWrapper.getInstance().removeAllRecentTasks(); 1517 removeTasksViewsAndClearAllButton(); 1518 startHome(); 1519 } 1520 mPendingAnimation = null; 1521 }); 1522 return anim; 1523 } 1524 snapToPageRelative(int pageCount, int delta, boolean cycle)1525 private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) { 1526 if (pageCount == 0) { 1527 return false; 1528 } 1529 final int newPageUnbound = getNextPage() + delta; 1530 if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) { 1531 return false; 1532 } 1533 snapToPage((newPageUnbound + pageCount) % pageCount); 1534 getChildAt(getNextPage()).requestFocus(); 1535 return true; 1536 } 1537 runDismissAnimation(PendingAnimation pendingAnim)1538 protected void runDismissAnimation(PendingAnimation pendingAnim) { 1539 AnimatorPlaybackController controller = pendingAnim.createPlaybackController(); 1540 controller.dispatchOnStart(); 1541 controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE)); 1542 controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN); 1543 controller.start(); 1544 } 1545 dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask)1546 public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { 1547 runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask, 1548 DISMISS_TASK_DURATION)); 1549 } 1550 1551 @SuppressWarnings("unused") dismissAllTasks(View view)1552 private void dismissAllTasks(View view) { 1553 runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION)); 1554 mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON); 1555 } 1556 dismissCurrentTask()1557 private void dismissCurrentTask() { 1558 TaskView taskView = getNextPageTaskView(); 1559 if (taskView != null) { 1560 dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/); 1561 } 1562 } 1563 1564 @Override dispatchKeyEvent(KeyEvent event)1565 public boolean dispatchKeyEvent(KeyEvent event) { 1566 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1567 switch (event.getKeyCode()) { 1568 case KeyEvent.KEYCODE_TAB: 1569 return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1, 1570 event.isAltPressed() /* cycle */); 1571 case KeyEvent.KEYCODE_DPAD_RIGHT: 1572 return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */); 1573 case KeyEvent.KEYCODE_DPAD_LEFT: 1574 return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */); 1575 case KeyEvent.KEYCODE_DEL: 1576 case KeyEvent.KEYCODE_FORWARD_DEL: 1577 dismissCurrentTask(); 1578 return true; 1579 case KeyEvent.KEYCODE_NUMPAD_DOT: 1580 if (event.isAltPressed()) { 1581 // Numpad DEL pressed while holding Alt. 1582 dismissCurrentTask(); 1583 return true; 1584 } 1585 } 1586 } 1587 return super.dispatchKeyEvent(event); 1588 } 1589 1590 @Override onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)1591 protected void onFocusChanged(boolean gainFocus, int direction, 1592 @Nullable Rect previouslyFocusedRect) { 1593 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 1594 if (gainFocus && getChildCount() > 0) { 1595 switch (direction) { 1596 case FOCUS_FORWARD: 1597 setCurrentPage(0); 1598 break; 1599 case FOCUS_BACKWARD: 1600 case FOCUS_RIGHT: 1601 case FOCUS_LEFT: 1602 setCurrentPage(getChildCount() - 1); 1603 break; 1604 } 1605 } 1606 } 1607 getContentAlpha()1608 public float getContentAlpha() { 1609 return mContentAlpha; 1610 } 1611 setContentAlpha(float alpha)1612 public void setContentAlpha(float alpha) { 1613 if (alpha == mContentAlpha) { 1614 return; 1615 } 1616 alpha = Utilities.boundToRange(alpha, 0, 1); 1617 mContentAlpha = alpha; 1618 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 1619 TaskView child = getTaskViewAt(i); 1620 if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) { 1621 child.setStableAlpha(alpha); 1622 } 1623 } 1624 mClearAllButton.setContentAlpha(mContentAlpha); 1625 int alphaInt = Math.round(alpha * 255); 1626 mEmptyMessagePaint.setAlpha(alphaInt); 1627 mEmptyIcon.setAlpha(alphaInt); 1628 mActionsView.getContentAlpha().setValue(mContentAlpha); 1629 1630 if (alpha > 0) { 1631 setVisibility(VISIBLE); 1632 } else if (!mFreezeViewVisibility) { 1633 setVisibility(GONE); 1634 } 1635 } 1636 1637 /** 1638 * Freezes the view visibility change. When frozen, the view will not change its visibility 1639 * to gone due to alpha changes. 1640 */ setFreezeViewVisibility(boolean freezeViewVisibility)1641 public void setFreezeViewVisibility(boolean freezeViewVisibility) { 1642 if (mFreezeViewVisibility != freezeViewVisibility) { 1643 mFreezeViewVisibility = freezeViewVisibility; 1644 if (!mFreezeViewVisibility) { 1645 setVisibility(mContentAlpha > 0 ? VISIBLE : GONE); 1646 } 1647 } 1648 } 1649 1650 @Override setVisibility(int visibility)1651 public void setVisibility(int visibility) { 1652 super.setVisibility(visibility); 1653 if (mActionsView != null) { 1654 mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE); 1655 } 1656 } 1657 1658 @Override onConfigurationChanged(Configuration newConfig)1659 protected void onConfigurationChanged(Configuration newConfig) { 1660 super.onConfigurationChanged(newConfig); 1661 if (mOrientationState.setActivityConfiguration(newConfig)) { 1662 updateOrientationHandler(); 1663 } 1664 } 1665 setLayoutRotation(int touchRotation, int displayRotation)1666 public void setLayoutRotation(int touchRotation, int displayRotation) { 1667 if (mOrientationState.update(touchRotation, displayRotation)) { 1668 updateOrientationHandler(); 1669 } 1670 } 1671 updateOrientationHandler()1672 private void updateOrientationHandler() { 1673 mOrientationHandler = mOrientationState.getOrientationHandler(); 1674 mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); 1675 setLayoutDirection(mIsRtl 1676 ? View.LAYOUT_DIRECTION_RTL 1677 : View.LAYOUT_DIRECTION_LTR); 1678 mClearAllButton.setLayoutDirection(mIsRtl 1679 ? View.LAYOUT_DIRECTION_LTR 1680 : View.LAYOUT_DIRECTION_RTL); 1681 mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated()); 1682 mActivity.getDragLayer().recreateControllers(); 1683 boolean isInLandscape = mOrientationState.getTouchRotation() != 0 1684 || mOrientationState.getRecentsActivityRotation() != ROTATION_0; 1685 mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, 1686 !mOrientationState.canRecentsActivityRotate() && isInLandscape); 1687 resetPaddingFromTaskSize(); 1688 requestLayout(); 1689 // Reapply the current page to update page scrolls. 1690 setCurrentPage(mCurrentPage); 1691 } 1692 getPagedViewOrientedState()1693 public RecentsOrientedState getPagedViewOrientedState() { 1694 return mOrientationState; 1695 } 1696 getPagedOrientationHandler()1697 public PagedOrientationHandler getPagedOrientationHandler() { 1698 return mOrientationHandler; 1699 } 1700 1701 @Nullable getNextTaskView()1702 public TaskView getNextTaskView() { 1703 return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1); 1704 } 1705 1706 @Nullable getCurrentPageTaskView()1707 public TaskView getCurrentPageTaskView() { 1708 return getTaskViewAtByAbsoluteIndex(getCurrentPage()); 1709 } 1710 1711 @Nullable getNextPageTaskView()1712 public TaskView getNextPageTaskView() { 1713 return getTaskViewAtByAbsoluteIndex(getNextPage()); 1714 } 1715 1716 @Nullable getTaskViewNearestToCenterOfScreen()1717 public TaskView getTaskViewNearestToCenterOfScreen() { 1718 return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen()); 1719 } 1720 1721 /** 1722 * Returns null instead of indexOutOfBoundsError when index is not in range 1723 */ 1724 @Nullable getTaskViewAt(int index)1725 public TaskView getTaskViewAt(int index) { 1726 return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex); 1727 } 1728 1729 @Nullable getTaskViewAtByAbsoluteIndex(int index)1730 private TaskView getTaskViewAtByAbsoluteIndex(int index) { 1731 if (index < getChildCount() && index >= 0) { 1732 View child = getChildAt(index); 1733 return child instanceof TaskView ? (TaskView) child : null; 1734 } 1735 return null; 1736 } 1737 setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener)1738 public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) { 1739 mOnEmptyMessageUpdatedListener = listener; 1740 } 1741 updateEmptyMessage()1742 public void updateEmptyMessage() { 1743 boolean isEmpty = getTaskViewCount() == 0; 1744 boolean hasSizeChanged = mLastMeasureSize.x != getWidth() 1745 || mLastMeasureSize.y != getHeight(); 1746 if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { 1747 return; 1748 } 1749 setContentDescription(isEmpty ? mEmptyMessage : ""); 1750 mShowEmptyMessage = isEmpty; 1751 updateEmptyStateUi(hasSizeChanged); 1752 invalidate(); 1753 1754 if (mOnEmptyMessageUpdatedListener != null) { 1755 mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage); 1756 } 1757 } 1758 1759 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1760 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1761 super.onLayout(changed, left, top, right, bottom); 1762 1763 updateEmptyStateUi(changed); 1764 1765 // Update the pivots such that when the task is scaled, it fills the full page 1766 getTaskSize(mTempRect); 1767 getPagedViewOrientedState().getFullScreenScaleAndPivot( 1768 mTempRect, mActivity.getDeviceProfile(), mTempPointF); 1769 setPivotX(mTempPointF.x); 1770 setPivotY(mTempPointF.y); 1771 setTaskModalness(mTaskModalness); 1772 updatePageOffsets(); 1773 setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO 1774 : IMPORTANT_FOR_ACCESSIBILITY_AUTO); 1775 } 1776 updatePageOffsets()1777 private void updatePageOffsets() { 1778 float offset = mAdjacentPageOffset * getWidth(); 1779 float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth(); 1780 if (mIsRtl) { 1781 offset = -offset; 1782 modalOffset = -modalOffset; 1783 } 1784 int count = getChildCount(); 1785 1786 TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden 1787 ? null : getTaskView(mRunningTaskId); 1788 int midPoint = runningTask == null ? -1 : indexOfChild(runningTask); 1789 int currentPage = getCurrentPage(); 1790 1791 for (int i = 0; i < count; i++) { 1792 float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset); 1793 float modalTranslation = 1794 i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset); 1795 getChildAt(i).setTranslationX(translation + modalTranslation); 1796 } 1797 updateCurveProperties(); 1798 } 1799 1800 /** 1801 * TODO: Do not assume motion across X axis for adjacent page 1802 */ getPageOffsetScale()1803 public float getPageOffsetScale() { 1804 return Math.max(getWidth(), 1); 1805 } 1806 1807 /** 1808 * Resets the visuals when exit modal state. 1809 */ resetModalVisuals()1810 public void resetModalVisuals() { 1811 TaskView taskView = getCurrentPageTaskView(); 1812 if (taskView != null) { 1813 taskView.getThumbnail().getTaskOverlay().resetModalVisuals(); 1814 } 1815 } 1816 updateDeadZoneRects()1817 private void updateDeadZoneRects() { 1818 // Get the deadzone rect surrounding the clear all button to not dismiss overview to home 1819 mClearAllButtonDeadZoneRect.setEmpty(); 1820 if (mClearAllButton.getWidth() > 0) { 1821 int verticalMargin = getResources() 1822 .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin); 1823 mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect); 1824 mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin); 1825 } 1826 1827 // Get the deadzone rect between the task views 1828 mTaskViewDeadZoneRect.setEmpty(); 1829 int count = getTaskViewCount(); 1830 if (count > 0) { 1831 final View taskView = getTaskViewAt(0); 1832 getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect); 1833 mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(), 1834 taskView.getBottom()); 1835 } 1836 } 1837 updateEmptyStateUi(boolean sizeChanged)1838 private void updateEmptyStateUi(boolean sizeChanged) { 1839 boolean hasValidSize = getWidth() > 0 && getHeight() > 0; 1840 if (sizeChanged && hasValidSize) { 1841 mEmptyTextLayout = null; 1842 mLastMeasureSize.set(getWidth(), getHeight()); 1843 } 1844 1845 if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { 1846 int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; 1847 mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), 1848 mEmptyMessagePaint, availableWidth) 1849 .setAlignment(Layout.Alignment.ALIGN_CENTER) 1850 .build(); 1851 int totalHeight = mEmptyTextLayout.getHeight() 1852 + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight(); 1853 1854 int top = (mLastMeasureSize.y - totalHeight) / 2; 1855 int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2; 1856 mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(), 1857 top + mEmptyIcon.getIntrinsicHeight()); 1858 } 1859 } 1860 1861 @Override verifyDrawable(Drawable who)1862 protected boolean verifyDrawable(Drawable who) { 1863 return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon); 1864 } 1865 maybeDrawEmptyMessage(Canvas canvas)1866 protected void maybeDrawEmptyMessage(Canvas canvas) { 1867 if (mShowEmptyMessage && mEmptyTextLayout != null) { 1868 // Offset to center in the visible (non-padded) part of RecentsView 1869 mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(), 1870 mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom()); 1871 canvas.save(); 1872 canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2, 1873 (mTempRect.top - mTempRect.bottom) / 2); 1874 mEmptyIcon.draw(canvas); 1875 canvas.translate(mEmptyMessagePadding, 1876 mEmptyIcon.getBounds().bottom + mEmptyMessagePadding); 1877 mEmptyTextLayout.draw(canvas); 1878 canvas.restore(); 1879 } 1880 } 1881 1882 /** 1883 * Animate adjacent tasks off screen while scaling up. 1884 * 1885 * If launching one of the adjacent tasks, parallax the center task and other adjacent task 1886 * to the right. 1887 */ createAdjacentPageAnimForTaskLaunch(TaskView tv)1888 public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) { 1889 AnimatorSet anim = new AnimatorSet(); 1890 1891 int taskIndex = indexOfChild(tv); 1892 int centerTaskIndex = getCurrentPage(); 1893 boolean launchingCenterTask = taskIndex == centerTaskIndex; 1894 1895 float toScale = getMaxScaleForFullScreen(); 1896 if (launchingCenterTask) { 1897 RecentsView recentsView = tv.getRecentsView(); 1898 anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale)); 1899 anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1)); 1900 } else { 1901 // We are launching an adjacent task, so parallax the center and other adjacent task. 1902 float displacementX = tv.getWidth() * (toScale - tv.getCurveScale()); 1903 anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X, 1904 mIsRtl ? -displacementX : displacementX)); 1905 1906 int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex); 1907 if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) { 1908 anim.play(new PropertyListBuilder() 1909 .translationX(mIsRtl ? -displacementX : displacementX) 1910 .scale(1) 1911 .build(getPageAt(otherAdjacentTaskIndex))); 1912 } 1913 } 1914 return anim; 1915 } 1916 1917 /** 1918 * Returns the scale up required on the view, so that it coves the screen completely 1919 */ getMaxScaleForFullScreen()1920 public float getMaxScaleForFullScreen() { 1921 getTaskSize(mTempRect); 1922 return getPagedViewOrientedState().getFullScreenScaleAndPivot( 1923 mTempRect, mActivity.getDeviceProfile(), mTempPointF); 1924 } 1925 createTaskLaunchAnimation( TaskView tv, long duration, Interpolator interpolator)1926 public PendingAnimation createTaskLaunchAnimation( 1927 TaskView tv, long duration, Interpolator interpolator) { 1928 if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { 1929 throw new IllegalStateException("Another pending animation is still running"); 1930 } 1931 1932 int count = getTaskViewCount(); 1933 if (count == 0) { 1934 return new PendingAnimation(duration); 1935 } 1936 1937 int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags(); 1938 final boolean[] passedOverviewThreshold = new boolean[] {false}; 1939 ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1); 1940 progressAnim.addUpdateListener(animator -> { 1941 // Once we pass a certain threshold, update the sysui flags to match the target 1942 // tasks' flags 1943 mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 1944 animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD 1945 ? targetSysUiFlags 1946 : 0); 1947 1948 onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv); 1949 1950 // Passing the threshold from taskview to fullscreen app will vibrate 1951 final boolean passed = animator.getAnimatedFraction() >= 1952 SUCCESS_TRANSITION_PROGRESS; 1953 if (passed != passedOverviewThreshold[0]) { 1954 passedOverviewThreshold[0] = passed; 1955 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 1956 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 1957 } 1958 }); 1959 1960 AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv); 1961 1962 DepthController depthController = getDepthController(); 1963 if (depthController != null) { 1964 ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH, 1965 BACKGROUND_APP.getDepth(mActivity)); 1966 anim.play(depthAnimator); 1967 } 1968 anim.play(progressAnim); 1969 anim.setInterpolator(interpolator); 1970 1971 mPendingAnimation = new PendingAnimation(duration); 1972 mPendingAnimation.add(anim); 1973 mPendingAnimation.addEndListener((endState) -> { 1974 if (endState.isSuccess) { 1975 Consumer<Boolean> onLaunchResult = (result) -> { 1976 onTaskLaunchAnimationEnd(result); 1977 if (!result) { 1978 tv.notifyTaskLaunchFailed(TAG); 1979 } 1980 }; 1981 tv.launchTask(false, onLaunchResult, getHandler()); 1982 Task task = tv.getTask(); 1983 if (task != null) { 1984 mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( 1985 endState.logAction, Direction.DOWN, indexOfChild(tv), 1986 TaskUtils.getLaunchComponentKeyForTask(task.key)); 1987 mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo()) 1988 .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN); 1989 } 1990 } else { 1991 onTaskLaunchAnimationEnd(false); 1992 } 1993 mPendingAnimation = null; 1994 }); 1995 return mPendingAnimation; 1996 } 1997 onTaskLaunchAnimationUpdate(float progress, TaskView tv)1998 protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) { 1999 } 2000 shouldUseMultiWindowTaskSizeStrategy()2001 public abstract boolean shouldUseMultiWindowTaskSizeStrategy(); 2002 onTaskLaunchAnimationEnd(boolean success)2003 protected void onTaskLaunchAnimationEnd(boolean success) { 2004 if (success) { 2005 resetTaskVisuals(); 2006 } 2007 } 2008 2009 /** 2010 * Called when task activity is launched 2011 */ onTaskLaunched(Task task)2012 public void onTaskLaunched(Task task){ } 2013 2014 @Override notifyPageSwitchListener(int prevPage)2015 protected void notifyPageSwitchListener(int prevPage) { 2016 super.notifyPageSwitchListener(prevPage); 2017 loadVisibleTaskData(); 2018 updateEnabledOverlays(); 2019 } 2020 2021 @Override getCurrentPageDescription()2022 protected String getCurrentPageDescription() { 2023 return ""; 2024 } 2025 2026 @Override addChildrenForAccessibility(ArrayList<View> outChildren)2027 public void addChildrenForAccessibility(ArrayList<View> outChildren) { 2028 // Add children in reverse order 2029 for (int i = getChildCount() - 1; i >= 0; --i) { 2030 outChildren.add(getChildAt(i)); 2031 } 2032 } 2033 2034 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)2035 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 2036 super.onInitializeAccessibilityNodeInfo(info); 2037 final AccessibilityNodeInfo.CollectionInfo 2038 collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( 2039 1, getTaskViewCount(), false, 2040 AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE); 2041 info.setCollectionInfo(collectionInfo); 2042 } 2043 2044 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)2045 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 2046 super.onInitializeAccessibilityEvent(event); 2047 2048 final int taskViewCount = getTaskViewCount(); 2049 event.setScrollable(taskViewCount > 0); 2050 2051 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 2052 final int[] visibleTasks = getVisibleChildrenRange(); 2053 event.setFromIndex(taskViewCount - visibleTasks[1]); 2054 event.setToIndex(taskViewCount - visibleTasks[0]); 2055 event.setItemCount(taskViewCount); 2056 } 2057 } 2058 2059 @Override getAccessibilityClassName()2060 public CharSequence getAccessibilityClassName() { 2061 // To hear position-in-list related feedback from Talkback. 2062 return ListView.class.getName(); 2063 } 2064 2065 @Override isPageOrderFlipped()2066 protected boolean isPageOrderFlipped() { 2067 return true; 2068 } 2069 setEnableDrawingLiveTile(boolean enableDrawingLiveTile)2070 public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) { 2071 mEnableDrawingLiveTile = enableDrawingLiveTile; 2072 } 2073 redrawLiveTile(boolean mightNeedToRefill)2074 public void redrawLiveTile(boolean mightNeedToRefill) { } 2075 2076 // TODO: To be removed in a follow up CL setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets)2077 public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, 2078 RecentsAnimationTargets recentsAnimationTargets) { 2079 mRecentsAnimationController = recentsAnimationController; 2080 mRecentsAnimationTargets = recentsAnimationTargets; 2081 } 2082 setLiveTileOverlayAttached(boolean liveTileOverlayAttached)2083 public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) { 2084 mLiveTileOverlayAttached = liveTileOverlayAttached; 2085 } 2086 updateLiveTileIcon(Drawable icon)2087 public void updateLiveTileIcon(Drawable icon) { 2088 if (mLiveTileOverlayAttached) { 2089 LiveTileOverlay.INSTANCE.setIcon(icon); 2090 } 2091 } 2092 finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete)2093 public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) { 2094 if (mRecentsAnimationController == null) { 2095 if (onFinishComplete != null) { 2096 onFinishComplete.run(); 2097 } 2098 return; 2099 } 2100 2101 mRecentsAnimationController.finish(toRecents, () -> { 2102 if (onFinishComplete != null) { 2103 onFinishComplete.run(); 2104 // After we finish the recents animation, the current task id should be correctly 2105 // reset so that when the task is launched from Overview later, it goes through the 2106 // flow of starting a new task instead of finishing recents animation to app. A 2107 // typical example of this is (1) user swipes up from app to Overview (2) user 2108 // taps on QSB (3) user goes back to Overview and launch the most recent task. 2109 setCurrentTask(-1); 2110 } 2111 }); 2112 } 2113 setDisallowScrollToClearAll(boolean disallowScrollToClearAll)2114 public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { 2115 if (mDisallowScrollToClearAll != disallowScrollToClearAll) { 2116 mDisallowScrollToClearAll = disallowScrollToClearAll; 2117 updateMinAndMaxScrollX(); 2118 } 2119 } 2120 2121 @Override computeMinScroll()2122 protected int computeMinScroll() { 2123 if (getTaskViewCount() > 0) { 2124 if (mDisallowScrollToClearAll) { 2125 // We aren't showing the clear all button, 2126 // so use the leftmost task as the min scroll. 2127 if (mIsRtl) { 2128 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1))); 2129 } 2130 return getScrollForPage(mTaskViewStartIndex); 2131 } 2132 if (mIsRtl) { 2133 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1); 2134 } 2135 return getScrollForPage(mTaskViewStartIndex); 2136 } 2137 return super.computeMinScroll(); 2138 } 2139 2140 @Override computeMaxScroll()2141 protected int computeMaxScroll() { 2142 if (getTaskViewCount() > 0) { 2143 if (mDisallowScrollToClearAll) { 2144 // We aren't showing the clear all button, 2145 // so use the rightmost task as the min scroll. 2146 if (mIsRtl) { 2147 return getScrollForPage(mTaskViewStartIndex); 2148 } 2149 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1))); 2150 } 2151 if (mIsRtl) { 2152 return getScrollForPage(mTaskViewStartIndex); 2153 } 2154 return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1); 2155 } 2156 return super.computeMaxScroll(); 2157 } 2158 getClearAllButton()2159 public ClearAllButton getClearAllButton() { 2160 return mClearAllButton; 2161 } 2162 2163 @Override onOverscroll(int amount)2164 protected boolean onOverscroll(int amount) { 2165 // overscroll should only be accepted on -1 direction (for clear all button) 2166 if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false; 2167 return super.onOverscroll(amount); 2168 } 2169 2170 /** 2171 * @return How many pixels the running task is offset on the currently laid out dominant axis. 2172 */ getScrollOffset()2173 public int getScrollOffset() { 2174 return getScrollOffset(getRunningTaskIndex()); 2175 } 2176 2177 /** 2178 * @return How many pixels the page is offset on the currently laid out dominant axis. 2179 */ getScrollOffset(int pageIndex)2180 public int getScrollOffset(int pageIndex) { 2181 if (pageIndex == -1) { 2182 return 0; 2183 } 2184 return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this); 2185 } 2186 getEventDispatcher(float navbarRotation)2187 public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) { 2188 float degreesRotated; 2189 if (navbarRotation == 0) { 2190 degreesRotated = mOrientationHandler.getDegreesRotated(); 2191 } else { 2192 degreesRotated = -navbarRotation; 2193 } 2194 if (degreesRotated == 0) { 2195 return super::onTouchEvent; 2196 } 2197 2198 // At this point the event coordinates have already been transformed, so we need to 2199 // undo that transformation since PagedView also accommodates for the transformation via 2200 // PagedOrientationHandler 2201 return e -> { 2202 if (navbarRotation != 0 2203 && mOrientationState.isMultipleOrientationSupportedByDevice() 2204 && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) { 2205 mOrientationState.flipVertical(e); 2206 super.onTouchEvent(e); 2207 mOrientationState.flipVertical(e); 2208 return; 2209 } 2210 mOrientationState.transformEvent(-degreesRotated, e, true); 2211 super.onTouchEvent(e); 2212 mOrientationState.transformEvent(-degreesRotated, e, false); 2213 }; 2214 } 2215 2216 public TransformParams getLiveTileParams( 2217 boolean mightNeedToRefill) { 2218 return null; 2219 } 2220 2221 private void updateEnabledOverlays() { 2222 int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; 2223 int taskCount = getTaskViewCount(); 2224 for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) { 2225 getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage); 2226 } 2227 } 2228 2229 public void setOverlayEnabled(boolean overlayEnabled) { 2230 if (mOverlayEnabled != overlayEnabled) { 2231 mOverlayEnabled = overlayEnabled; 2232 updateEnabledOverlays(); 2233 } 2234 } 2235 2236 /** If it's in the live tile mode, switch the running task into screenshot mode. */ 2237 public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) { 2238 TaskView taskView = getRunningTaskView(); 2239 if (taskView != null) { 2240 taskView.setShowScreenshot(true); 2241 if (thumbnailData != null) { 2242 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData); 2243 } else { 2244 taskView.getThumbnail().refresh(); 2245 } 2246 ViewUtils.postDraw(taskView, onFinishRunnable); 2247 } else { 2248 onFinishRunnable.run(); 2249 } 2250 } 2251 2252 /** 2253 * The current task is fully modal (modalness = 1) when it is shown on its own in a modal 2254 * way. Modalness 0 means the task is shown in context with all the other tasks. 2255 */ 2256 private void setTaskModalness(float modalness) { 2257 mTaskModalness = modalness; 2258 updatePageOffsets(); 2259 if (getCurrentPageTaskView() != null) { 2260 getCurrentPageTaskView().setModalness(modalness); 2261 } 2262 // Only show actions view when it's modal for in-place landscape mode. 2263 boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate() 2264 && mOrientationState.getTouchRotation() != ROTATION_0; 2265 mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape); 2266 } 2267 2268 @Nullable 2269 protected DepthController getDepthController() { 2270 return null; 2271 } 2272 2273 @Override 2274 public void onSecondaryWindowBoundsChanged() { 2275 // Invalidate the task view size 2276 setInsets(mInsets); 2277 requestLayout(); 2278 } 2279 2280 /** 2281 * Enables or disables modal state for RecentsView 2282 * @param isModalState 2283 */ 2284 public void setModalStateEnabled(boolean isModalState) { } 2285 2286 public BaseActivityInterface getSizeStrategy() { 2287 return mSizeStrategy; 2288 } 2289 2290 /** 2291 * Used to register callbacks for when our empty message state changes. 2292 * 2293 * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener) 2294 * @see #updateEmptyMessage() 2295 */ 2296 public interface OnEmptyMessageUpdatedListener { 2297 /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */ 2298 void onEmptyMessageUpdated(boolean isEmpty); 2299 } 2300 2301 private static class PinnedStackAnimationListener<T extends BaseActivity> extends 2302 IPinnedStackAnimationListener.Stub { 2303 private T mActivity; 2304 2305 public void setActivity(T activity) { 2306 mActivity = activity; 2307 } 2308 2309 @Override 2310 public void onPinnedStackAnimationStarted() { 2311 // Needed for activities that auto-enter PiP, which will not trigger a remote 2312 // animation to be created 2313 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 2314 } 2315 } 2316 } 2317