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.widget.Toast.LENGTH_SHORT; 20 21 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; 22 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; 23 import static com.android.launcher3.Utilities.comp; 24 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; 25 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; 26 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 27 import static com.android.launcher3.anim.Interpolators.LINEAR; 28 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS; 30 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; 31 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 32 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 33 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 34 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; 35 36 import static java.lang.annotation.RetentionPolicy.SOURCE; 37 38 import android.animation.Animator; 39 import android.animation.AnimatorListenerAdapter; 40 import android.animation.AnimatorSet; 41 import android.animation.ObjectAnimator; 42 import android.annotation.IdRes; 43 import android.app.ActivityOptions; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.graphics.Outline; 47 import android.graphics.Rect; 48 import android.graphics.RectF; 49 import android.graphics.drawable.Drawable; 50 import android.os.Bundle; 51 import android.util.AttributeSet; 52 import android.util.FloatProperty; 53 import android.util.Log; 54 import android.view.MotionEvent; 55 import android.view.TouchDelegate; 56 import android.view.View; 57 import android.view.ViewGroup; 58 import android.view.ViewOutlineProvider; 59 import android.view.accessibility.AccessibilityNodeInfo; 60 import android.view.animation.Interpolator; 61 import android.widget.FrameLayout; 62 import android.widget.Toast; 63 64 import androidx.annotation.IntDef; 65 import androidx.annotation.NonNull; 66 import androidx.annotation.Nullable; 67 68 import com.android.launcher3.AbstractFloatingView; 69 import com.android.launcher3.DeviceProfile; 70 import com.android.launcher3.LauncherSettings; 71 import com.android.launcher3.R; 72 import com.android.launcher3.Utilities; 73 import com.android.launcher3.anim.Interpolators; 74 import com.android.launcher3.model.data.WorkspaceItemInfo; 75 import com.android.launcher3.popup.SystemShortcut; 76 import com.android.launcher3.statemanager.StatefulActivity; 77 import com.android.launcher3.testing.TestLogging; 78 import com.android.launcher3.testing.TestProtocol; 79 import com.android.launcher3.touch.PagedOrientationHandler; 80 import com.android.launcher3.util.ActivityOptionsWrapper; 81 import com.android.launcher3.util.ComponentKey; 82 import com.android.launcher3.util.RunnableList; 83 import com.android.launcher3.util.SplitConfigurationOptions; 84 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 85 import com.android.launcher3.util.TransformingTouchDelegate; 86 import com.android.launcher3.util.ViewPool.Reusable; 87 import com.android.quickstep.RecentsModel; 88 import com.android.quickstep.RemoteAnimationTargets; 89 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; 90 import com.android.quickstep.SystemUiProxy; 91 import com.android.quickstep.TaskIconCache; 92 import com.android.quickstep.TaskOverlayFactory; 93 import com.android.quickstep.TaskThumbnailCache; 94 import com.android.quickstep.TaskUtils; 95 import com.android.quickstep.TaskViewUtils; 96 import com.android.quickstep.util.CancellableTask; 97 import com.android.quickstep.util.RecentsOrientedState; 98 import com.android.quickstep.util.TaskCornerRadius; 99 import com.android.quickstep.util.TransformParams; 100 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; 101 import com.android.systemui.shared.recents.model.Task; 102 import com.android.systemui.shared.recents.model.ThumbnailData; 103 import com.android.systemui.shared.system.ActivityManagerWrapper; 104 import com.android.systemui.shared.system.ActivityOptionsCompat; 105 import com.android.systemui.shared.system.QuickStepContract; 106 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 107 108 import java.lang.annotation.Retention; 109 import java.util.Arrays; 110 import java.util.Collections; 111 import java.util.HashMap; 112 import java.util.List; 113 import java.util.function.Consumer; 114 import java.util.stream.Stream; 115 116 /** 117 * A task in the Recents view. 118 */ 119 public class TaskView extends FrameLayout implements Reusable { 120 121 private static final String TAG = TaskView.class.getSimpleName(); 122 private static final boolean DEBUG = false; 123 124 public static final int FLAG_UPDATE_ICON = 1; 125 public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1; 126 127 public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL; 128 129 /** 130 * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more 131 * granularity on which components of this task require an update 132 */ 133 @Retention(SOURCE) 134 @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL}) 135 public @interface TaskDataChanges {} 136 137 /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */ 138 public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f; 139 140 /** 141 * Should the TaskView display clip off the left inset in RecentsView. 142 */ clipLeft(DeviceProfile deviceProfile)143 public static boolean clipLeft(DeviceProfile deviceProfile) { 144 return false; 145 } 146 147 /** 148 * Should the TaskView display clip off the top inset in RecentsView. 149 */ clipTop(DeviceProfile deviceProfile)150 public static boolean clipTop(DeviceProfile deviceProfile) { 151 return false; 152 } 153 154 /** 155 * Should the TaskView display clip off the right inset in RecentsView. 156 */ clipRight(DeviceProfile deviceProfile)157 public static boolean clipRight(DeviceProfile deviceProfile) { 158 return false; 159 } 160 161 /** 162 * Should the TaskView display clip off the bottom inset in RecentsView. 163 */ clipBottom(DeviceProfile deviceProfile)164 public static boolean clipBottom(DeviceProfile deviceProfile) { 165 return deviceProfile.isTablet; 166 } 167 168 /** 169 * Should the TaskView scale down to fit whole thumbnail in fullscreen. 170 */ useFullThumbnail(DeviceProfile deviceProfile)171 public static boolean useFullThumbnail(DeviceProfile deviceProfile) { 172 return deviceProfile.isTablet && !deviceProfile.isTaskbarPresentInApps; 173 } 174 175 private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f; 176 private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f; 177 178 public static final long SCALE_ICON_DURATION = 120; 179 private static final long DIM_ANIM_DURATION = 700; 180 181 private static final Interpolator GRID_INTERPOLATOR = ACCEL_DEACCEL; 182 183 /** 184 * This technically can be a vanilla {@link TouchDelegate} class, however that class requires 185 * setting the touch bounds at construction, so we'd repeatedly be created many instances 186 * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch 187 * delegated bounds only to be updated. 188 */ 189 private TransformingTouchDelegate mIconTouchDelegate; 190 191 private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT = 192 Collections.singletonList(new Rect()); 193 194 public static final FloatProperty<TaskView> FOCUS_TRANSITION = 195 new FloatProperty<TaskView>("focusTransition") { 196 @Override 197 public void setValue(TaskView taskView, float v) { 198 taskView.setIconAndDimTransitionProgress(v, false /* invert */); 199 } 200 201 @Override 202 public Float get(TaskView taskView) { 203 return taskView.mFocusTransitionProgress; 204 } 205 }; 206 207 private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_X = 208 new FloatProperty<TaskView>("splitSelectTranslationX") { 209 @Override 210 public void setValue(TaskView taskView, float v) { 211 taskView.setSplitSelectTranslationX(v); 212 } 213 214 @Override 215 public Float get(TaskView taskView) { 216 return taskView.mSplitSelectTranslationX; 217 } 218 }; 219 220 private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_Y = 221 new FloatProperty<TaskView>("splitSelectTranslationY") { 222 @Override 223 public void setValue(TaskView taskView, float v) { 224 taskView.setSplitSelectTranslationY(v); 225 } 226 227 @Override 228 public Float get(TaskView taskView) { 229 return taskView.mSplitSelectTranslationY; 230 } 231 }; 232 233 private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X = 234 new FloatProperty<TaskView>("dismissTranslationX") { 235 @Override 236 public void setValue(TaskView taskView, float v) { 237 taskView.setDismissTranslationX(v); 238 } 239 240 @Override 241 public Float get(TaskView taskView) { 242 return taskView.mDismissTranslationX; 243 } 244 }; 245 246 private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y = 247 new FloatProperty<TaskView>("dismissTranslationY") { 248 @Override 249 public void setValue(TaskView taskView, float v) { 250 taskView.setDismissTranslationY(v); 251 } 252 253 @Override 254 public Float get(TaskView taskView) { 255 return taskView.mDismissTranslationY; 256 } 257 }; 258 259 private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X = 260 new FloatProperty<TaskView>("taskOffsetTranslationX") { 261 @Override 262 public void setValue(TaskView taskView, float v) { 263 taskView.setTaskOffsetTranslationX(v); 264 } 265 266 @Override 267 public Float get(TaskView taskView) { 268 return taskView.mTaskOffsetTranslationX; 269 } 270 }; 271 272 private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y = 273 new FloatProperty<TaskView>("taskOffsetTranslationY") { 274 @Override 275 public void setValue(TaskView taskView, float v) { 276 taskView.setTaskOffsetTranslationY(v); 277 } 278 279 @Override 280 public Float get(TaskView taskView) { 281 return taskView.mTaskOffsetTranslationY; 282 } 283 }; 284 285 private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X = 286 new FloatProperty<TaskView>("taskResistanceTranslationX") { 287 @Override 288 public void setValue(TaskView taskView, float v) { 289 taskView.setTaskResistanceTranslationX(v); 290 } 291 292 @Override 293 public Float get(TaskView taskView) { 294 return taskView.mTaskResistanceTranslationX; 295 } 296 }; 297 298 private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y = 299 new FloatProperty<TaskView>("taskResistanceTranslationY") { 300 @Override 301 public void setValue(TaskView taskView, float v) { 302 taskView.setTaskResistanceTranslationY(v); 303 } 304 305 @Override 306 public Float get(TaskView taskView) { 307 return taskView.mTaskResistanceTranslationY; 308 } 309 }; 310 311 private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_X = 312 new FloatProperty<TaskView>("nonGridTranslationX") { 313 @Override 314 public void setValue(TaskView taskView, float v) { 315 taskView.setNonGridTranslationX(v); 316 } 317 318 @Override 319 public Float get(TaskView taskView) { 320 return taskView.mNonGridTranslationX; 321 } 322 }; 323 324 private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_Y = 325 new FloatProperty<TaskView>("nonGridTranslationY") { 326 @Override 327 public void setValue(TaskView taskView, float v) { 328 taskView.setNonGridTranslationY(v); 329 } 330 331 @Override 332 public Float get(TaskView taskView) { 333 return taskView.mNonGridTranslationY; 334 } 335 }; 336 337 public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X = 338 new FloatProperty<TaskView>("gridEndTranslationX") { 339 @Override 340 public void setValue(TaskView taskView, float v) { 341 taskView.setGridEndTranslationX(v); 342 } 343 344 @Override 345 public Float get(TaskView taskView) { 346 return taskView.mGridEndTranslationX; 347 } 348 }; 349 350 public static final FloatProperty<TaskView> SNAPSHOT_SCALE = 351 new FloatProperty<TaskView>("snapshotScale") { 352 @Override 353 public void setValue(TaskView taskView, float v) { 354 taskView.setSnapshotScale(v); 355 } 356 357 @Override 358 public Float get(TaskView taskView) { 359 return taskView.mSnapshotView.getScaleX(); 360 } 361 }; 362 363 private final TaskOutlineProvider mOutlineProvider; 364 365 @Nullable 366 protected Task mTask; 367 protected TaskThumbnailView mSnapshotView; 368 protected IconView mIconView; 369 protected final DigitalWellBeingToast mDigitalWellBeingToast; 370 private float mFullscreenProgress; 371 private float mGridProgress; 372 private float mNonGridScale = 1; 373 private float mDismissScale = 1; 374 protected final FullscreenDrawParams mCurrentFullscreenParams; 375 protected final StatefulActivity mActivity; 376 377 // Various causes of changing primary translation, which we aggregate to setTranslationX/Y(). 378 private float mDismissTranslationX; 379 private float mDismissTranslationY; 380 private float mTaskOffsetTranslationX; 381 private float mTaskOffsetTranslationY; 382 private float mTaskResistanceTranslationX; 383 private float mTaskResistanceTranslationY; 384 // The following translation variables should only be used in the same orientation as Launcher. 385 private float mBoxTranslationY; 386 // The following grid translations scales with mGridProgress. 387 private float mGridTranslationX; 388 private float mGridTranslationY; 389 // The following grid translation is used to animate closing the gap between grid and clear all. 390 private float mGridEndTranslationX; 391 // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick 392 // switch. 393 private float mNonGridTranslationX; 394 private float mNonGridTranslationY; 395 // Used when in SplitScreenSelectState 396 private float mSplitSelectTranslationY; 397 private float mSplitSelectTranslationX; 398 private float mSplitSelectScrollOffsetPrimary; 399 400 @Nullable 401 private ObjectAnimator mIconAndDimAnimator; 402 private float mIconScaleAnimStartProgress = 0; 403 private float mFocusTransitionProgress = 1; 404 private float mModalness = 0; 405 private float mStableAlpha = 1; 406 407 private int mTaskViewId = -1; 408 /** 409 * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right 410 */ 411 protected final int[] mTaskIdContainer = new int[]{-1, -1}; 412 protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer = 413 new TaskIdAttributeContainer[2]; 414 415 private boolean mShowScreenshot; 416 417 // The current background requests to load the task thumbnail and icon 418 @Nullable 419 private CancellableTask mThumbnailLoadRequest; 420 @Nullable 421 private CancellableTask mIconLoadRequest; 422 423 private boolean mEndQuickswitchCuj; 424 425 private final float[] mIconCenterCoords = new float[2]; 426 427 private boolean mIsClickableAsLiveTile = true; 428 TaskView(Context context)429 public TaskView(Context context) { 430 this(context, null); 431 } 432 TaskView(Context context, @Nullable AttributeSet attrs)433 public TaskView(Context context, @Nullable AttributeSet attrs) { 434 this(context, attrs, 0); 435 } 436 TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)437 public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 438 super(context, attrs, defStyleAttr); 439 mActivity = StatefulActivity.fromContext(context); 440 setOnClickListener(this::onClick); 441 442 mCurrentFullscreenParams = new FullscreenDrawParams(context); 443 mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this); 444 445 mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams, 446 mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx); 447 setOutlineProvider(mOutlineProvider); 448 } 449 setTaskViewId(int id)450 public void setTaskViewId(int id) { 451 this.mTaskViewId = id; 452 } 453 getTaskViewId()454 public int getTaskViewId() { 455 return mTaskViewId; 456 } 457 458 /** 459 * Builds proto for logging 460 */ getItemInfo()461 public WorkspaceItemInfo getItemInfo() { 462 return getItemInfo(mTask); 463 } 464 getItemInfo(Task task)465 protected WorkspaceItemInfo getItemInfo(Task task) { 466 ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key); 467 WorkspaceItemInfo stubInfo = new WorkspaceItemInfo(); 468 stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK; 469 stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER; 470 stubInfo.user = componentKey.user; 471 stubInfo.intent = new Intent().setComponent(componentKey.componentName); 472 stubInfo.title = task.title; 473 if (getRecentsView() != null) { 474 stubInfo.screenId = getRecentsView().indexOfChild(this); 475 } 476 return stubInfo; 477 } 478 479 @Override onFinishInflate()480 protected void onFinishInflate() { 481 super.onFinishInflate(); 482 mSnapshotView = findViewById(R.id.snapshot); 483 mIconView = findViewById(R.id.icon); 484 mIconTouchDelegate = new TransformingTouchDelegate(mIconView); 485 } 486 487 /** 488 * Whether the taskview should take the touch event from parent. Events passed to children 489 * that might require special handling. 490 */ offerTouchToChildren(MotionEvent event)491 public boolean offerTouchToChildren(MotionEvent event) { 492 if (event.getAction() == MotionEvent.ACTION_DOWN) { 493 computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate); 494 } 495 if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) { 496 return true; 497 } 498 return false; 499 } 500 computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords, TransformingTouchDelegate transformingTouchDelegate)501 protected void computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords, 502 TransformingTouchDelegate transformingTouchDelegate) { 503 float iconHalfSize = iconView.getWidth() / 2f; 504 tempCenterCoords[0] = tempCenterCoords[1] = iconHalfSize; 505 getDescendantCoordRelativeToAncestor(iconView, mActivity.getDragLayer(), tempCenterCoords, 506 false); 507 transformingTouchDelegate.setBounds( 508 (int) (tempCenterCoords[0] - iconHalfSize), 509 (int) (tempCenterCoords[1] - iconHalfSize), 510 (int) (tempCenterCoords[0] + iconHalfSize), 511 (int) (tempCenterCoords[1] + iconHalfSize)); 512 } 513 514 /** 515 * The modalness of this view is how it should be displayed when it is shown on its own in the 516 * modal state of overview. 517 * 518 * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own. 519 */ setModalness(float modalness)520 public void setModalness(float modalness) { 521 if (mModalness == modalness) { 522 return; 523 } 524 mModalness = modalness; 525 mIconView.setAlpha(comp(modalness)); 526 mDigitalWellBeingToast.updateBannerOffset(modalness, 527 mCurrentFullscreenParams.mCurrentDrawnInsets.top 528 + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom); 529 } 530 getDigitalWellBeingToast()531 public DigitalWellBeingToast getDigitalWellBeingToast() { 532 return mDigitalWellBeingToast; 533 } 534 535 /** 536 * Updates this task view to the given {@param task}. 537 * 538 * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after 539 * that issue is fixed 540 */ bind(Task task, RecentsOrientedState orientedState)541 public void bind(Task task, RecentsOrientedState orientedState) { 542 cancelPendingLoadTasks(); 543 mTask = task; 544 mTaskIdContainer[0] = mTask.key.id; 545 mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, 546 mIconView, STAGE_POSITION_UNDEFINED); 547 mSnapshotView.bind(task); 548 setOrientationState(orientedState); 549 } 550 getTaskIdAttributeContainers()551 public TaskIdAttributeContainer[] getTaskIdAttributeContainers() { 552 return mTaskIdAttributeContainer; 553 } 554 555 @Nullable getTask()556 public Task getTask() { 557 return mTask; 558 } 559 560 /** 561 * @return integer array of two elements to be size consistent with max number of tasks possible 562 * index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value 563 */ getTaskIds()564 public int[] getTaskIds() { 565 return mTaskIdContainer; 566 } 567 containsMultipleTasks()568 public boolean containsMultipleTasks() { 569 return mTaskIdContainer[1] != -1; 570 } 571 getThumbnail()572 public TaskThumbnailView getThumbnail() { 573 return mSnapshotView; 574 } 575 refreshThumbnails(@ullable HashMap<Integer, ThumbnailData> thumbnailDatas)576 void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) { 577 if (mTask != null && thumbnailDatas != null) { 578 final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id); 579 if (thumbnailData != null) { 580 mSnapshotView.setThumbnail(mTask, thumbnailData); 581 return; 582 } 583 } 584 585 mSnapshotView.refresh(); 586 } 587 588 /** TODO(b/197033698) Remove all usages of above method and migrate to this one */ getThumbnails()589 public TaskThumbnailView[] getThumbnails() { 590 return new TaskThumbnailView[]{mSnapshotView}; 591 } 592 getIconView()593 public IconView getIconView() { 594 return mIconView; 595 } 596 onClick(View view)597 private void onClick(View view) { 598 if (getTask() == null) { 599 return; 600 } 601 if (confirmSecondSplitSelectApp()) { 602 return; 603 } 604 RecentsView recentsView = getRecentsView(); 605 RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles; 606 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) { 607 if (!mIsClickableAsLiveTile) { 608 return; 609 } 610 611 // Reset the minimized state since we force-toggled the minimized state when entering 612 // overview, but never actually finished the recents animation 613 SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate(); 614 if (p != null) { 615 p.setSplitScreenMinimized(false); 616 } 617 618 mIsClickableAsLiveTile = false; 619 RemoteAnimationTargets targets; 620 if (remoteTargetHandles.length == 1) { 621 targets = remoteTargetHandles[0].getTransformParams().getTargetSet(); 622 } else { 623 TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams(); 624 TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams(); 625 RemoteAnimationTargetCompat[] apps = Stream.concat( 626 Arrays.stream(topLeftParams.getTargetSet().apps), 627 Arrays.stream(rightBottomParams.getTargetSet().apps)) 628 .toArray(RemoteAnimationTargetCompat[]::new); 629 RemoteAnimationTargetCompat[] wallpapers = Stream.concat( 630 Arrays.stream(topLeftParams.getTargetSet().wallpapers), 631 Arrays.stream(rightBottomParams.getTargetSet().wallpapers)) 632 .toArray(RemoteAnimationTargetCompat[]::new); 633 targets = new RemoteAnimationTargets(apps, wallpapers, 634 topLeftParams.getTargetSet().nonApps, 635 topLeftParams.getTargetSet().targetMode); 636 } 637 if (targets == null) { 638 // If the recents animation is cancelled somehow between the parent if block and 639 // here, try to launch the task as a non live tile task. 640 launchTaskAnimated(); 641 return; 642 } 643 644 AnimatorSet anim = new AnimatorSet(); 645 TaskViewUtils.composeRecentsLaunchAnimator( 646 anim, this, targets.apps, 647 targets.wallpapers, targets.nonApps, true /* launcherClosing */, 648 mActivity.getStateManager(), recentsView, 649 recentsView.getDepthController()); 650 anim.addListener(new AnimatorListenerAdapter() { 651 @Override 652 public void onAnimationStart(Animator animation) { 653 recentsView.runActionOnRemoteHandles( 654 (Consumer<RemoteTargetHandle>) remoteTargetHandle -> 655 remoteTargetHandle 656 .getTaskViewSimulator() 657 .setDrawsBelowRecents(false)); 658 } 659 660 @Override 661 public void onAnimationEnd(Animator animator) { 662 mIsClickableAsLiveTile = true; 663 } 664 }); 665 anim.start(); 666 recentsView.onTaskLaunchedInLiveTileMode(); 667 } else { 668 launchTaskAnimated(); 669 } 670 mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo()) 671 .log(LAUNCHER_TASK_LAUNCH_TAP); 672 } 673 674 /** 675 * @return {@code true} if user is already in split select mode and this tap was to choose the 676 * second app. {@code false} otherwise 677 */ confirmSecondSplitSelectApp()678 private boolean confirmSecondSplitSelectApp() { 679 boolean isSelectingSecondSplitApp = getRecentsView().isSplitSelectionActive(); 680 if (isSelectingSecondSplitApp) { 681 getRecentsView().confirmSplitSelect(this); 682 } 683 return isSelectingSecondSplitApp; 684 } 685 686 /** 687 * Starts the task associated with this view and animates the startup. 688 * @return CompletionStage to indicate the animation completion or null if the launch failed. 689 */ 690 @Nullable launchTaskAnimated()691 public RunnableList launchTaskAnimated() { 692 if (mTask != null) { 693 TestLogging.recordEvent( 694 TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); 695 ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null); 696 opts.options.setLaunchDisplayId(getRootViewDisplayId()); 697 if (ActivityManagerWrapper.getInstance() 698 .startActivityFromRecents(mTask.key, opts.options)) { 699 RecentsView recentsView = getRecentsView(); 700 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) { 701 recentsView.onTaskLaunchedInLiveTileMode(); 702 703 // Return a fresh callback in the live tile case, so that it's not accidentally 704 // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner. 705 RunnableList callbackList = new RunnableList(); 706 recentsView.addSideTaskLaunchCallback(callbackList); 707 return callbackList; 708 } 709 return opts.onEndCallback; 710 } else { 711 notifyTaskLaunchFailed(TAG); 712 return null; 713 } 714 } else { 715 return null; 716 } 717 } 718 719 /** 720 * Starts the task associated with this view without any animation 721 */ launchTask(@onNull Consumer<Boolean> callback)722 public void launchTask(@NonNull Consumer<Boolean> callback) { 723 launchTask(callback, false /* freezeTaskList */); 724 } 725 726 /** 727 * Starts the task associated with this view without any animation 728 */ launchTask(@onNull Consumer<Boolean> callback, boolean freezeTaskList)729 public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) { 730 if (mTask != null) { 731 TestLogging.recordEvent( 732 TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); 733 734 // Indicate success once the system has indicated that the transition has started 735 ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation( 736 getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler()); 737 opts.setLaunchDisplayId(getRootViewDisplayId()); 738 if (freezeTaskList) { 739 ActivityOptionsCompat.setFreezeRecentTasksList(opts); 740 } 741 Task.TaskKey key = mTask.key; 742 UI_HELPER_EXECUTOR.execute(() -> { 743 if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) { 744 // If the call to start activity failed, then post the result immediately, 745 // otherwise, wait for the animation start callback from the activity options 746 // above 747 MAIN_EXECUTOR.post(() -> { 748 notifyTaskLaunchFailed(TAG); 749 callback.accept(false); 750 }); 751 } 752 }); 753 } else { 754 callback.accept(false); 755 } 756 } 757 758 /** 759 * See {@link TaskDataChanges} 760 * @param visible If this task view will be visible to the user in overview or hidden 761 */ onTaskListVisibilityChanged(boolean visible)762 public void onTaskListVisibilityChanged(boolean visible) { 763 onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL); 764 } 765 766 /** 767 * See {@link TaskDataChanges} 768 * @param visible If this task view will be visible to the user in overview or hidden 769 */ onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes)770 public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) { 771 if (mTask == null) { 772 return; 773 } 774 cancelPendingLoadTasks(); 775 if (visible) { 776 // These calls are no-ops if the data is already loaded, try and load the high 777 // resolution thumbnail if the state permits 778 RecentsModel model = RecentsModel.INSTANCE.get(getContext()); 779 TaskThumbnailCache thumbnailCache = model.getThumbnailCache(); 780 TaskIconCache iconCache = model.getIconCache(); 781 782 if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { 783 mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground( 784 mTask, thumbnail -> { 785 mSnapshotView.setThumbnail(mTask, thumbnail); 786 }); 787 } 788 if (needsUpdate(changes, FLAG_UPDATE_ICON)) { 789 mIconLoadRequest = iconCache.updateIconInBackground(mTask, 790 (task) -> { 791 setIcon(mIconView, task.icon); 792 mDigitalWellBeingToast.initialize(mTask); 793 }); 794 } 795 } else { 796 if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { 797 mSnapshotView.setThumbnail(null, null); 798 // Reset the task thumbnail reference as well (it will be fetched from the cache or 799 // reloaded next time we need it) 800 mTask.thumbnail = null; 801 } 802 if (needsUpdate(changes, FLAG_UPDATE_ICON)) { 803 setIcon(mIconView, null); 804 } 805 } 806 } 807 needsUpdate(@askDataChanges int dataChange, @TaskDataChanges int flag)808 protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) { 809 return (dataChange & flag) == flag; 810 } 811 cancelPendingLoadTasks()812 protected void cancelPendingLoadTasks() { 813 if (mThumbnailLoadRequest != null) { 814 mThumbnailLoadRequest.cancel(); 815 mThumbnailLoadRequest = null; 816 } 817 if (mIconLoadRequest != null) { 818 mIconLoadRequest.cancel(); 819 mIconLoadRequest = null; 820 } 821 } 822 showTaskMenu(IconView iconView)823 private boolean showTaskMenu(IconView iconView) { 824 if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) { 825 // Don't show menu when selecting second split screen app 826 return true; 827 } 828 829 if (!mActivity.getDeviceProfile().overviewShowAsGrid 830 && !getRecentsView().isClearAllHidden()) { 831 getRecentsView().snapToPage(getRecentsView().indexOfChild(this)); 832 return false; 833 } else { 834 mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo()) 835 .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS); 836 return showTaskMenuWithContainer(iconView); 837 } 838 } 839 showTaskMenuWithContainer(IconView iconView)840 protected boolean showTaskMenuWithContainer(IconView iconView) { 841 TaskIdAttributeContainer menuContainer = 842 mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1]; 843 if (mActivity.getDeviceProfile().overviewShowAsGrid) { 844 boolean alignSecondRow = getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) 845 && mActivity.getDeviceProfile().isLandscape; 846 return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignSecondRow); 847 } else { 848 return TaskMenuView.showForTask(menuContainer); 849 } 850 } 851 setIcon(IconView iconView, @Nullable Drawable icon)852 protected void setIcon(IconView iconView, @Nullable Drawable icon) { 853 if (icon != null) { 854 iconView.setDrawable(icon); 855 iconView.setOnClickListener(v -> { 856 if (confirmSecondSplitSelectApp()) { 857 return; 858 } 859 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) { 860 RecentsView recentsView = getRecentsView(); 861 recentsView.switchToScreenshot( 862 () -> recentsView.finishRecentsAnimation(true /* toRecents */, 863 false /* shouldPip */, 864 () -> showTaskMenu(iconView))); 865 } else { 866 showTaskMenu(iconView); 867 } 868 }); 869 iconView.setOnLongClickListener(v -> { 870 requestDisallowInterceptTouchEvent(true); 871 return showTaskMenu(iconView); 872 }); 873 } else { 874 iconView.setDrawable(null); 875 iconView.setOnClickListener(null); 876 iconView.setOnLongClickListener(null); 877 } 878 } 879 setOrientationState(RecentsOrientedState orientationState)880 public void setOrientationState(RecentsOrientedState orientationState) { 881 PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler(); 882 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 883 LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); 884 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 885 snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; 886 boolean isGridTask = isGridTask(); 887 int taskIconHeight = deviceProfile.overviewTaskIconSizePx; 888 int taskMargin = isGridTask ? deviceProfile.overviewTaskMarginGridPx 889 : deviceProfile.overviewTaskMarginPx; 890 int taskIconMargin = snapshotParams.topMargin - taskIconHeight - taskMargin; 891 LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); 892 orientationHandler.setIconAndSnapshotParams(mIconView, taskIconMargin, taskIconHeight, 893 snapshotParams, isRtl); 894 mSnapshotView.setLayoutParams(snapshotParams); 895 iconParams.width = iconParams.height = taskIconHeight; 896 mIconView.setLayoutParams(iconParams); 897 mIconView.setRotation(orientationHandler.getDegreesRotated()); 898 int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx 899 : deviceProfile.overviewTaskIconDrawableSizePx; 900 mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize); 901 snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; 902 mSnapshotView.setLayoutParams(snapshotParams); 903 mSnapshotView.getTaskOverlay().updateOrientationState(orientationState); 904 } 905 906 /** 907 * Returns whether the task is part of overview grid and not being focused. 908 */ isGridTask()909 public boolean isGridTask() { 910 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 911 return deviceProfile.overviewShowAsGrid && !isFocusedTask(); 912 } 913 setIconAndDimTransitionProgress(float progress, boolean invert)914 protected void setIconAndDimTransitionProgress(float progress, boolean invert) { 915 if (invert) { 916 progress = 1 - progress; 917 } 918 mFocusTransitionProgress = progress; 919 float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION; 920 float lowerClamp = invert ? 1f - iconScalePercentage : 0; 921 float upperClamp = invert ? 1 : iconScalePercentage; 922 float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp) 923 .getInterpolation(progress); 924 mIconView.setAlpha(scale); 925 mDigitalWellBeingToast.updateBannerOffset(1f - scale, 926 mCurrentFullscreenParams.mCurrentDrawnInsets.top 927 + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom); 928 } 929 setIconScaleAnimStartProgress(float startProgress)930 public void setIconScaleAnimStartProgress(float startProgress) { 931 mIconScaleAnimStartProgress = startProgress; 932 } 933 animateIconScaleAndDimIntoView()934 public void animateIconScaleAndDimIntoView() { 935 if (mIconAndDimAnimator != null) { 936 mIconAndDimAnimator.cancel(); 937 } 938 mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1); 939 mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress); 940 mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR); 941 mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() { 942 @Override 943 public void onAnimationEnd(Animator animation) { 944 mIconAndDimAnimator = null; 945 } 946 }); 947 mIconAndDimAnimator.start(); 948 } 949 setIconScaleAndDim(float iconScale)950 protected void setIconScaleAndDim(float iconScale) { 951 setIconScaleAndDim(iconScale, false); 952 } 953 setIconScaleAndDim(float iconScale, boolean invert)954 private void setIconScaleAndDim(float iconScale, boolean invert) { 955 if (mIconAndDimAnimator != null) { 956 mIconAndDimAnimator.cancel(); 957 } 958 setIconAndDimTransitionProgress(iconScale, invert); 959 } 960 resetPersistentViewTransforms()961 protected void resetPersistentViewTransforms() { 962 mNonGridTranslationX = mNonGridTranslationY = 963 mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f; 964 resetViewTransforms(); 965 } 966 resetViewTransforms()967 protected void resetViewTransforms() { 968 // fullscreenTranslation and accumulatedTranslation should not be reset, as 969 // resetViewTransforms is called during Quickswitch scrolling. 970 mDismissTranslationX = mTaskOffsetTranslationX = 971 mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f; 972 mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = 973 mSplitSelectTranslationY = 0f; 974 setSnapshotScale(1f); 975 applyTranslationX(); 976 applyTranslationY(); 977 setTranslationZ(0); 978 setAlpha(mStableAlpha); 979 setIconScaleAndDim(1); 980 setColorTint(0, 0); 981 } 982 setStableAlpha(float parentAlpha)983 public void setStableAlpha(float parentAlpha) { 984 mStableAlpha = parentAlpha; 985 setAlpha(mStableAlpha); 986 } 987 988 @Override onRecycle()989 public void onRecycle() { 990 resetPersistentViewTransforms(); 991 // Clear any references to the thumbnail (it will be re-read either from the cache or the 992 // system on next bind) 993 mSnapshotView.setThumbnail(mTask, null); 994 setOverlayEnabled(false); 995 onTaskListVisibilityChanged(false); 996 } 997 getTaskCornerRadius()998 public float getTaskCornerRadius() { 999 return TaskCornerRadius.get(mActivity); 1000 } 1001 1002 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1003 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1004 super.onLayout(changed, left, top, right, bottom); 1005 if (mActivity.getDeviceProfile().overviewShowAsGrid) { 1006 setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left); 1007 setPivotY(mSnapshotView.getTop()); 1008 } else { 1009 setPivotX((right - left) * 0.5f); 1010 setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f); 1011 } 1012 if (Utilities.ATLEAST_Q) { 1013 SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight()); 1014 setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT); 1015 } 1016 } 1017 1018 /** 1019 * How much to scale down pages near the edge of the screen. 1020 */ getEdgeScaleDownFactor(DeviceProfile deviceProfile)1021 public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) { 1022 return deviceProfile.overviewShowAsGrid ? EDGE_SCALE_DOWN_FACTOR_GRID 1023 : EDGE_SCALE_DOWN_FACTOR_CAROUSEL; 1024 } 1025 setNonGridScale(float nonGridScale)1026 private void setNonGridScale(float nonGridScale) { 1027 mNonGridScale = nonGridScale; 1028 applyScale(); 1029 } 1030 getNonGridScale()1031 public float getNonGridScale() { 1032 return mNonGridScale; 1033 } 1034 setSnapshotScale(float dismissScale)1035 private void setSnapshotScale(float dismissScale) { 1036 mDismissScale = dismissScale; 1037 applyScale(); 1038 } 1039 1040 /** 1041 * Moves TaskView between carousel and 2 row grid. 1042 * 1043 * @param gridProgress 0 = carousel; 1 = 2 row grid. 1044 */ setGridProgress(float gridProgress)1045 public void setGridProgress(float gridProgress) { 1046 mGridProgress = gridProgress; 1047 applyTranslationX(); 1048 applyTranslationY(); 1049 applyScale(); 1050 } 1051 applyScale()1052 private void applyScale() { 1053 float scale = 1; 1054 scale *= getPersistentScale(); 1055 scale *= mDismissScale; 1056 setScaleX(scale); 1057 setScaleY(scale); 1058 updateSnapshotRadius(); 1059 } 1060 1061 /** 1062 * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not 1063 * change according to a temporary state. 1064 */ getPersistentScale()1065 public float getPersistentScale() { 1066 float scale = 1; 1067 float gridProgress = GRID_INTERPOLATOR.getInterpolation(mGridProgress); 1068 scale *= Utilities.mapRange(gridProgress, mNonGridScale, 1f); 1069 return scale; 1070 } 1071 setSplitSelectTranslationX(float x)1072 private void setSplitSelectTranslationX(float x) { 1073 mSplitSelectTranslationX = x; 1074 applyTranslationX(); 1075 } 1076 setSplitSelectTranslationY(float y)1077 private void setSplitSelectTranslationY(float y) { 1078 mSplitSelectTranslationY = y; 1079 applyTranslationY(); 1080 } 1081 setSplitScrollOffsetPrimary(float splitSelectScrollOffsetPrimary)1082 public void setSplitScrollOffsetPrimary(float splitSelectScrollOffsetPrimary) { 1083 mSplitSelectScrollOffsetPrimary = splitSelectScrollOffsetPrimary; 1084 } 1085 setDismissTranslationX(float x)1086 private void setDismissTranslationX(float x) { 1087 mDismissTranslationX = x; 1088 applyTranslationX(); 1089 } 1090 setDismissTranslationY(float y)1091 private void setDismissTranslationY(float y) { 1092 mDismissTranslationY = y; 1093 applyTranslationY(); 1094 } 1095 setTaskOffsetTranslationX(float x)1096 private void setTaskOffsetTranslationX(float x) { 1097 mTaskOffsetTranslationX = x; 1098 applyTranslationX(); 1099 } 1100 setTaskOffsetTranslationY(float y)1101 private void setTaskOffsetTranslationY(float y) { 1102 mTaskOffsetTranslationY = y; 1103 applyTranslationY(); 1104 } 1105 setTaskResistanceTranslationX(float x)1106 private void setTaskResistanceTranslationX(float x) { 1107 mTaskResistanceTranslationX = x; 1108 applyTranslationX(); 1109 } 1110 setTaskResistanceTranslationY(float y)1111 private void setTaskResistanceTranslationY(float y) { 1112 mTaskResistanceTranslationY = y; 1113 applyTranslationY(); 1114 } 1115 setNonGridTranslationX(float nonGridTranslationX)1116 private void setNonGridTranslationX(float nonGridTranslationX) { 1117 mNonGridTranslationX = nonGridTranslationX; 1118 applyTranslationX(); 1119 } 1120 setNonGridTranslationY(float nonGridTranslationY)1121 private void setNonGridTranslationY(float nonGridTranslationY) { 1122 mNonGridTranslationY = nonGridTranslationY; 1123 applyTranslationY(); 1124 } 1125 setGridTranslationX(float gridTranslationX)1126 public void setGridTranslationX(float gridTranslationX) { 1127 mGridTranslationX = gridTranslationX; 1128 applyTranslationX(); 1129 } 1130 getGridTranslationX()1131 public float getGridTranslationX() { 1132 return mGridTranslationX; 1133 } 1134 setGridTranslationY(float gridTranslationY)1135 public void setGridTranslationY(float gridTranslationY) { 1136 mGridTranslationY = gridTranslationY; 1137 applyTranslationY(); 1138 } 1139 getGridTranslationY()1140 public float getGridTranslationY() { 1141 return mGridTranslationY; 1142 } 1143 setGridEndTranslationX(float gridEndTranslationX)1144 private void setGridEndTranslationX(float gridEndTranslationX) { 1145 mGridEndTranslationX = gridEndTranslationX; 1146 applyTranslationX(); 1147 } 1148 getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled)1149 public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { 1150 float scrollAdjustment = 0; 1151 if (gridEnabled) { 1152 scrollAdjustment += mGridTranslationX; 1153 } else { 1154 scrollAdjustment += getPrimaryNonGridTranslationProperty().get(this); 1155 } 1156 scrollAdjustment += mSplitSelectScrollOffsetPrimary; 1157 return scrollAdjustment; 1158 } 1159 getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled)1160 public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { 1161 return getScrollAdjustment(fullscreenEnabled, gridEnabled); 1162 } 1163 getSizeAdjustment(boolean fullscreenEnabled)1164 public float getSizeAdjustment(boolean fullscreenEnabled) { 1165 float sizeAdjustment = 1; 1166 if (fullscreenEnabled) { 1167 sizeAdjustment *= mNonGridScale; 1168 } 1169 return sizeAdjustment; 1170 } 1171 setBoxTranslationY(float boxTranslationY)1172 private void setBoxTranslationY(float boxTranslationY) { 1173 mBoxTranslationY = boxTranslationY; 1174 applyTranslationY(); 1175 } 1176 applyTranslationX()1177 private void applyTranslationX() { 1178 setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX 1179 + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX()); 1180 } 1181 applyTranslationY()1182 private void applyTranslationY() { 1183 setTranslationY(mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY 1184 + mSplitSelectTranslationY + getPersistentTranslationY()); 1185 } 1186 1187 /** 1188 * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does not 1189 * change according to a temporary state (e.g. task offset). 1190 */ getPersistentTranslationX()1191 public float getPersistentTranslationX() { 1192 return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX); 1193 } 1194 1195 /** 1196 * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does not 1197 * change according to a temporary state (e.g. task offset). 1198 */ getPersistentTranslationY()1199 public float getPersistentTranslationY() { 1200 return mBoxTranslationY 1201 + getNonGridTrans(mNonGridTranslationY) 1202 + getGridTrans(mGridTranslationY); 1203 } 1204 getPrimarySplitTranslationProperty()1205 public FloatProperty<TaskView> getPrimarySplitTranslationProperty() { 1206 return getPagedOrientationHandler().getPrimaryValue( 1207 SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y); 1208 } 1209 getSecondarySplitTranslationProperty()1210 public FloatProperty<TaskView> getSecondarySplitTranslationProperty() { 1211 return getPagedOrientationHandler().getSecondaryValue( 1212 SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y); 1213 } 1214 getPrimaryDismissTranslationProperty()1215 public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() { 1216 return getPagedOrientationHandler().getPrimaryValue( 1217 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); 1218 } 1219 getSecondaryDissmissTranslationProperty()1220 public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() { 1221 return getPagedOrientationHandler().getSecondaryValue( 1222 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); 1223 } 1224 getPrimaryTaskOffsetTranslationProperty()1225 public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() { 1226 return getPagedOrientationHandler().getPrimaryValue( 1227 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y); 1228 } 1229 getTaskResistanceTranslationProperty()1230 public FloatProperty<TaskView> getTaskResistanceTranslationProperty() { 1231 return getPagedOrientationHandler().getSecondaryValue( 1232 TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y); 1233 } 1234 getPrimaryNonGridTranslationProperty()1235 public FloatProperty<TaskView> getPrimaryNonGridTranslationProperty() { 1236 return getPagedOrientationHandler().getPrimaryValue( 1237 NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y); 1238 } 1239 getSecondaryNonGridTranslationProperty()1240 public FloatProperty<TaskView> getSecondaryNonGridTranslationProperty() { 1241 return getPagedOrientationHandler().getSecondaryValue( 1242 NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y); 1243 } 1244 1245 @Override hasOverlappingRendering()1246 public boolean hasOverlappingRendering() { 1247 // TODO: Clip-out the icon region from the thumbnail, since they are overlapping. 1248 return false; 1249 } 1250 isEndQuickswitchCuj()1251 public boolean isEndQuickswitchCuj() { 1252 return mEndQuickswitchCuj; 1253 } 1254 setEndQuickswitchCuj(boolean endQuickswitchCuj)1255 public void setEndQuickswitchCuj(boolean endQuickswitchCuj) { 1256 mEndQuickswitchCuj = endQuickswitchCuj; 1257 } 1258 1259 private static final class TaskOutlineProvider extends ViewOutlineProvider { 1260 1261 private int mMarginTop; 1262 private FullscreenDrawParams mFullscreenParams; 1263 TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin)1264 TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) { 1265 mMarginTop = topMargin; 1266 mFullscreenParams = fullscreenParams; 1267 } 1268 updateParams(FullscreenDrawParams params, int topMargin)1269 public void updateParams(FullscreenDrawParams params, int topMargin) { 1270 mFullscreenParams = params; 1271 mMarginTop = topMargin; 1272 } 1273 1274 @Override getOutline(View view, Outline outline)1275 public void getOutline(View view, Outline outline) { 1276 RectF insets = mFullscreenParams.mCurrentDrawnInsets; 1277 float scale = mFullscreenParams.mScale; 1278 outline.setRoundRect(0, 1279 (int) (mMarginTop * scale), 1280 (int) ((insets.left + view.getWidth() + insets.right) * scale), 1281 (int) ((insets.top + view.getHeight() + insets.bottom) * scale), 1282 mFullscreenParams.mCurrentDrawnCornerRadius); 1283 } 1284 } 1285 getExpectedViewHeight(View view)1286 private int getExpectedViewHeight(View view) { 1287 int expectedHeight; 1288 int h = view.getLayoutParams().height; 1289 if (h > 0) { 1290 expectedHeight = h; 1291 } else { 1292 int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST); 1293 view.measure(m, m); 1294 expectedHeight = view.getMeasuredHeight(); 1295 } 1296 return expectedHeight; 1297 } 1298 1299 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1300 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1301 super.onInitializeAccessibilityNodeInfo(info); 1302 1303 info.addAction( 1304 new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close, 1305 getContext().getText(R.string.accessibility_close))); 1306 1307 final Context context = getContext(); 1308 for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { 1309 if (taskContainer == null) { 1310 continue; 1311 } 1312 for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, 1313 mActivity.getDeviceProfile(), taskContainer)) { 1314 info.addAction(s.createAccessibilityAction(context)); 1315 } 1316 } 1317 1318 if (mDigitalWellBeingToast.hasLimit()) { 1319 info.addAction( 1320 new AccessibilityNodeInfo.AccessibilityAction( 1321 R.string.accessibility_app_usage_settings, 1322 getContext().getText(R.string.accessibility_app_usage_settings))); 1323 } 1324 1325 final RecentsView recentsView = getRecentsView(); 1326 final AccessibilityNodeInfo.CollectionItemInfo itemInfo = 1327 AccessibilityNodeInfo.CollectionItemInfo.obtain( 1328 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1, 1329 1, false); 1330 info.setCollectionItemInfo(itemInfo); 1331 } 1332 1333 @Override performAccessibilityAction(int action, Bundle arguments)1334 public boolean performAccessibilityAction(int action, Bundle arguments) { 1335 if (action == R.string.accessibility_close) { 1336 getRecentsView().dismissTask(this, true /*animateTaskView*/, 1337 true /*removeTask*/); 1338 return true; 1339 } 1340 1341 if (action == R.string.accessibility_app_usage_settings) { 1342 mDigitalWellBeingToast.openAppUsageSettings(this); 1343 return true; 1344 } 1345 1346 for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { 1347 if (taskContainer == null) { 1348 continue; 1349 } 1350 for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, 1351 mActivity.getDeviceProfile(), taskContainer)) { 1352 if (s.hasHandlerForAction(action)) { 1353 s.onClick(this); 1354 return true; 1355 } 1356 } 1357 } 1358 1359 return super.performAccessibilityAction(action, arguments); 1360 } 1361 getRecentsView()1362 public RecentsView getRecentsView() { 1363 return (RecentsView) getParent(); 1364 } 1365 getPagedOrientationHandler()1366 PagedOrientationHandler getPagedOrientationHandler() { 1367 return getRecentsView().mOrientationState.getOrientationHandler(); 1368 } 1369 notifyTaskLaunchFailed(String tag)1370 private void notifyTaskLaunchFailed(String tag) { 1371 String msg = "Failed to launch task"; 1372 if (mTask != null) { 1373 msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")"; 1374 } 1375 Log.w(tag, msg); 1376 Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show(); 1377 } 1378 1379 /** 1380 * Hides the icon and shows insets when this TaskView is about to be shown fullscreen. 1381 * 1382 * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets. 1383 */ setFullscreenProgress(float progress)1384 public void setFullscreenProgress(float progress) { 1385 progress = Utilities.boundToRange(progress, 0, 1); 1386 mFullscreenProgress = progress; 1387 mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); 1388 mSnapshotView.getTaskOverlay().setFullscreenProgress(progress); 1389 1390 updateSnapshotRadius(); 1391 1392 mOutlineProvider.updateParams( 1393 mCurrentFullscreenParams, 1394 mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx); 1395 invalidateOutline(); 1396 } 1397 1398 protected void updateSnapshotRadius() { 1399 updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper()); 1400 mSnapshotView.setFullscreenParams(mCurrentFullscreenParams); 1401 } 1402 1403 void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) { 1404 if (getRecentsView() == null) { 1405 return; 1406 } 1407 mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(), 1408 getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper); 1409 } 1410 1411 /** 1412 * Updates TaskView scaling and translation required to support variable width if enabled, while 1413 * ensuring TaskView fits into screen in fullscreen. 1414 */ 1415 void updateTaskSize() { 1416 ViewGroup.LayoutParams params = getLayoutParams(); 1417 float nonGridScale; 1418 float boxTranslationY; 1419 int expectedWidth; 1420 int expectedHeight; 1421 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 1422 if (deviceProfile.overviewShowAsGrid) { 1423 final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx; 1424 final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize(); 1425 final int taskWidth = lastComputedTaskSize.width(); 1426 final int taskHeight = lastComputedTaskSize.height(); 1427 1428 int boxWidth; 1429 int boxHeight; 1430 boolean isFocusedTask = isFocusedTask(); 1431 if (isFocusedTask) { 1432 // Task will be focused and should use focused task size. Use focusTaskRatio 1433 // that is associated with the original orientation of the focused task. 1434 boxWidth = taskWidth; 1435 boxHeight = taskHeight; 1436 } else { 1437 // Otherwise task is in grid, and should use lastComputedGridTaskSize. 1438 Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize(); 1439 boxWidth = lastComputedGridTaskSize.width(); 1440 boxHeight = lastComputedGridTaskSize.height(); 1441 } 1442 1443 // Bound width/height to the box size. 1444 expectedWidth = boxWidth; 1445 expectedHeight = boxHeight + thumbnailPadding; 1446 1447 // Scale to to fit task Rect. 1448 nonGridScale = taskWidth / (float) boxWidth; 1449 1450 // Align to top of task Rect. 1451 boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f; 1452 } else { 1453 nonGridScale = 1f; 1454 boxTranslationY = 0f; 1455 expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT; 1456 expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT; 1457 } 1458 1459 setNonGridScale(nonGridScale); 1460 setBoxTranslationY(boxTranslationY); 1461 if (params.width != expectedWidth || params.height != expectedHeight) { 1462 params.width = expectedWidth; 1463 params.height = expectedHeight; 1464 setLayoutParams(params); 1465 } 1466 } 1467 1468 private float getGridTrans(float endTranslation) { 1469 float progress = GRID_INTERPOLATOR.getInterpolation(mGridProgress); 1470 return Utilities.mapRange(progress, 0, endTranslation); 1471 } 1472 1473 private float getNonGridTrans(float endTranslation) { 1474 return endTranslation - getGridTrans(endTranslation); 1475 } 1476 1477 public boolean isRunningTask() { 1478 if (getRecentsView() == null) { 1479 return false; 1480 } 1481 return this == getRecentsView().getRunningTaskView(); 1482 } 1483 1484 public boolean isFocusedTask() { 1485 if (getRecentsView() == null) { 1486 return false; 1487 } 1488 return this == getRecentsView().getFocusedTaskView(); 1489 } 1490 1491 public void setShowScreenshot(boolean showScreenshot) { 1492 mShowScreenshot = showScreenshot; 1493 } 1494 1495 public boolean showScreenshot() { 1496 if (!isRunningTask()) { 1497 return true; 1498 } 1499 return mShowScreenshot; 1500 } 1501 1502 public void setOverlayEnabled(boolean overlayEnabled) { 1503 mSnapshotView.setOverlayEnabled(overlayEnabled); 1504 } 1505 1506 public void initiateSplitSelect(SplitPositionOption splitPositionOption) { 1507 AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU); 1508 getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition); 1509 } 1510 1511 /** 1512 * Set a color tint on the snapshot and supporting views. 1513 */ 1514 public void setColorTint(float amount, int tintColor) { 1515 mSnapshotView.setDimAlpha(amount); 1516 mIconView.setIconColorTint(tintColor, amount); 1517 mDigitalWellBeingToast.setBannerColorTint(tintColor, amount); 1518 } 1519 1520 1521 private int getRootViewDisplayId() { 1522 return getRootView().getDisplay().getDisplayId(); 1523 } 1524 1525 /** 1526 * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. 1527 */ 1528 public static class FullscreenDrawParams { 1529 1530 private final float mCornerRadius; 1531 private final float mWindowCornerRadius; 1532 1533 public float mFullscreenProgress; 1534 public RectF mCurrentDrawnInsets = new RectF(); 1535 public float mCurrentDrawnCornerRadius; 1536 /** The current scale we apply to the thumbnail to adjust for new left/right insets. */ 1537 public float mScale = 1; 1538 1539 public FullscreenDrawParams(Context context) { 1540 mCornerRadius = TaskCornerRadius.get(context); 1541 mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context); 1542 1543 mCurrentDrawnCornerRadius = mCornerRadius; 1544 } 1545 1546 /** 1547 * Sets the progress in range [0, 1] 1548 */ 1549 public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale, 1550 int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) { 1551 mFullscreenProgress = fullscreenProgress; 1552 1553 RectF insets = pph.getInsetsToDrawInFullscreen(dp); 1554 1555 float currentInsetsLeft = insets.left * fullscreenProgress; 1556 float currentInsetsRight = insets.right * fullscreenProgress; 1557 float insetsBottom = insets.bottom; 1558 if (dp.isTaskbarPresentInApps) { 1559 insetsBottom = Math.max(0, insetsBottom - dp.taskbarSize); 1560 } 1561 mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress, 1562 currentInsetsRight, insetsBottom * fullscreenProgress); 1563 float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius; 1564 1565 mCurrentDrawnCornerRadius = 1566 Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius) 1567 / parentScale / taskViewScale; 1568 1569 // We scaled the thumbnail to fit the content (excluding insets) within task view width. 1570 // Now that we are drawing left/right insets again, we need to scale down to fit them. 1571 if (previewWidth > 0) { 1572 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight); 1573 } 1574 } 1575 } 1576 1577 public class TaskIdAttributeContainer { 1578 private final TaskThumbnailView mThumbnailView; 1579 private final Task mTask; 1580 private final IconView mIconView; 1581 /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */ 1582 private @SplitConfigurationOptions.StagePosition int mStagePosition; 1583 @IdRes 1584 private final int mA11yNodeId; 1585 TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView, IconView iconView, int stagePosition)1586 public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView, 1587 IconView iconView, int stagePosition) { 1588 this.mTask = task; 1589 this.mThumbnailView = thumbnailView; 1590 this.mIconView = iconView; 1591 this.mStagePosition = stagePosition; 1592 this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ? 1593 R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo; 1594 } 1595 getThumbnailView()1596 public TaskThumbnailView getThumbnailView() { 1597 return mThumbnailView; 1598 } 1599 getTask()1600 public Task getTask() { 1601 return mTask; 1602 } 1603 getItemInfo()1604 public WorkspaceItemInfo getItemInfo() { 1605 return TaskView.this.getItemInfo(mTask); 1606 } 1607 getTaskView()1608 public TaskView getTaskView() { 1609 return TaskView.this; 1610 } 1611 getIconView()1612 public IconView getIconView() { 1613 return mIconView; 1614 } 1615 getStagePosition()1616 public int getStagePosition() { 1617 return mStagePosition; 1618 } 1619 setStagePosition(@plitConfigurationOptions.StagePosition int stagePosition)1620 void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) { 1621 this.mStagePosition = stagePosition; 1622 } 1623 getA11yNodeId()1624 public int getA11yNodeId() { 1625 return mA11yNodeId; 1626 } 1627 } 1628 } 1629