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