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.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 import static android.widget.Toast.LENGTH_SHORT; 22 23 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; 24 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN; 25 import static com.android.app.animation.Interpolators.LINEAR; 26 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 27 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; 28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS; 29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; 30 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 31 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 32 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 33 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; 34 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition; 35 import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts; 36 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR; 37 38 import static java.lang.annotation.RetentionPolicy.SOURCE; 39 40 import android.animation.Animator; 41 import android.animation.AnimatorListenerAdapter; 42 import android.animation.AnimatorSet; 43 import android.animation.ObjectAnimator; 44 import android.annotation.IdRes; 45 import android.app.ActivityOptions; 46 import android.app.ActivityTaskManager; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.res.TypedArray; 50 import android.graphics.Canvas; 51 import android.graphics.PointF; 52 import android.graphics.Rect; 53 import android.graphics.RectF; 54 import android.graphics.drawable.Drawable; 55 import android.os.Bundle; 56 import android.os.Handler; 57 import android.util.AttributeSet; 58 import android.util.FloatProperty; 59 import android.util.Log; 60 import android.view.Display; 61 import android.view.MotionEvent; 62 import android.view.RemoteAnimationTarget; 63 import android.view.TouchDelegate; 64 import android.view.View; 65 import android.view.ViewGroup; 66 import android.view.accessibility.AccessibilityNodeInfo; 67 import android.view.animation.Interpolator; 68 import android.widget.FrameLayout; 69 import android.widget.Toast; 70 71 import androidx.annotation.IntDef; 72 import androidx.annotation.NonNull; 73 import androidx.annotation.Nullable; 74 import androidx.annotation.VisibleForTesting; 75 76 import com.android.app.animation.Interpolators; 77 import com.android.launcher3.DeviceProfile; 78 import com.android.launcher3.LauncherSettings; 79 import com.android.launcher3.R; 80 import com.android.launcher3.Utilities; 81 import com.android.launcher3.config.FeatureFlags; 82 import com.android.launcher3.model.data.WorkspaceItemInfo; 83 import com.android.launcher3.popup.SystemShortcut; 84 import com.android.launcher3.statemanager.StatefulActivity; 85 import com.android.launcher3.testing.TestLogging; 86 import com.android.launcher3.testing.shared.TestProtocol; 87 import com.android.launcher3.touch.PagedOrientationHandler; 88 import com.android.launcher3.util.ActivityOptionsWrapper; 89 import com.android.launcher3.util.ComponentKey; 90 import com.android.launcher3.util.RunnableList; 91 import com.android.launcher3.util.SplitConfigurationOptions; 92 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 93 import com.android.launcher3.util.TraceHelper; 94 import com.android.launcher3.util.TransformingTouchDelegate; 95 import com.android.launcher3.util.ViewPool.Reusable; 96 import com.android.quickstep.RecentsModel; 97 import com.android.quickstep.RemoteAnimationTargets; 98 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; 99 import com.android.quickstep.TaskAnimationManager; 100 import com.android.quickstep.TaskIconCache; 101 import com.android.quickstep.TaskThumbnailCache; 102 import com.android.quickstep.TaskUtils; 103 import com.android.quickstep.TaskViewUtils; 104 import com.android.quickstep.util.BorderAnimator; 105 import com.android.quickstep.util.CancellableTask; 106 import com.android.quickstep.util.RecentsOrientedState; 107 import com.android.quickstep.util.SplitSelectStateController; 108 import com.android.quickstep.util.TaskCornerRadius; 109 import com.android.quickstep.util.TaskRemovedDuringLaunchListener; 110 import com.android.quickstep.util.TransformParams; 111 import com.android.systemui.shared.recents.model.Task; 112 import com.android.systemui.shared.recents.model.ThumbnailData; 113 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper; 114 import com.android.systemui.shared.system.ActivityManagerWrapper; 115 import com.android.systemui.shared.system.QuickStepContract; 116 117 import java.lang.annotation.Retention; 118 import java.util.Arrays; 119 import java.util.Collections; 120 import java.util.HashMap; 121 import java.util.List; 122 import java.util.function.Consumer; 123 import java.util.stream.Stream; 124 125 /** 126 * A task in the Recents view. 127 */ 128 public class TaskView extends FrameLayout implements Reusable { 129 130 private static final String TAG = TaskView.class.getSimpleName(); 131 private static final boolean DEBUG = false; 132 133 private static final RectF EMPTY_RECT_F = new RectF(); 134 135 public static final int FLAG_UPDATE_ICON = 1; 136 public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1; 137 public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1; 138 139 public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL 140 | FLAG_UPDATE_CORNER_RADIUS; 141 142 /** 143 * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more 144 * granularity on which components of this task require an update 145 */ 146 @Retention(SOURCE) 147 @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS}) 148 public @interface TaskDataChanges {} 149 150 /** 151 * Type of task view 152 */ 153 @Retention(SOURCE) 154 @IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP}) 155 public @interface Type { 156 int SINGLE = 1; 157 int GROUPED = 2; 158 int DESKTOP = 3; 159 } 160 161 /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */ 162 public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f; 163 164 private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f; 165 private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f; 166 167 public static final long SCALE_ICON_DURATION = 120; 168 private static final long DIM_ANIM_DURATION = 700; 169 170 private static final Interpolator GRID_INTERPOLATOR = ACCELERATE_DECELERATE; 171 172 /** 173 * This technically can be a vanilla {@link TouchDelegate} class, however that class requires 174 * setting the touch bounds at construction, so we'd repeatedly be created many instances 175 * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch 176 * delegated bounds only to be updated. 177 */ 178 private TransformingTouchDelegate mIconTouchDelegate; 179 180 private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT = 181 Collections.singletonList(new Rect()); 182 183 public static final FloatProperty<TaskView> FOCUS_TRANSITION = 184 new FloatProperty<TaskView>("focusTransition") { 185 @Override 186 public void setValue(TaskView taskView, float v) { 187 taskView.setIconsAndBannersTransitionProgress(v, false /* invert */); 188 } 189 190 @Override 191 public Float get(TaskView taskView) { 192 return taskView.mFocusTransitionProgress; 193 } 194 }; 195 196 private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_X = 197 new FloatProperty<TaskView>("splitSelectTranslationX") { 198 @Override 199 public void setValue(TaskView taskView, float v) { 200 taskView.setSplitSelectTranslationX(v); 201 } 202 203 @Override 204 public Float get(TaskView taskView) { 205 return taskView.mSplitSelectTranslationX; 206 } 207 }; 208 209 private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_Y = 210 new FloatProperty<TaskView>("splitSelectTranslationY") { 211 @Override 212 public void setValue(TaskView taskView, float v) { 213 taskView.setSplitSelectTranslationY(v); 214 } 215 216 @Override 217 public Float get(TaskView taskView) { 218 return taskView.mSplitSelectTranslationY; 219 } 220 }; 221 222 private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X = 223 new FloatProperty<TaskView>("dismissTranslationX") { 224 @Override 225 public void setValue(TaskView taskView, float v) { 226 taskView.setDismissTranslationX(v); 227 } 228 229 @Override 230 public Float get(TaskView taskView) { 231 return taskView.mDismissTranslationX; 232 } 233 }; 234 235 private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y = 236 new FloatProperty<TaskView>("dismissTranslationY") { 237 @Override 238 public void setValue(TaskView taskView, float v) { 239 taskView.setDismissTranslationY(v); 240 } 241 242 @Override 243 public Float get(TaskView taskView) { 244 return taskView.mDismissTranslationY; 245 } 246 }; 247 248 private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X = 249 new FloatProperty<TaskView>("taskOffsetTranslationX") { 250 @Override 251 public void setValue(TaskView taskView, float v) { 252 taskView.setTaskOffsetTranslationX(v); 253 } 254 255 @Override 256 public Float get(TaskView taskView) { 257 return taskView.mTaskOffsetTranslationX; 258 } 259 }; 260 261 private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y = 262 new FloatProperty<TaskView>("taskOffsetTranslationY") { 263 @Override 264 public void setValue(TaskView taskView, float v) { 265 taskView.setTaskOffsetTranslationY(v); 266 } 267 268 @Override 269 public Float get(TaskView taskView) { 270 return taskView.mTaskOffsetTranslationY; 271 } 272 }; 273 274 private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X = 275 new FloatProperty<TaskView>("taskResistanceTranslationX") { 276 @Override 277 public void setValue(TaskView taskView, float v) { 278 taskView.setTaskResistanceTranslationX(v); 279 } 280 281 @Override 282 public Float get(TaskView taskView) { 283 return taskView.mTaskResistanceTranslationX; 284 } 285 }; 286 287 private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y = 288 new FloatProperty<TaskView>("taskResistanceTranslationY") { 289 @Override 290 public void setValue(TaskView taskView, float v) { 291 taskView.setTaskResistanceTranslationY(v); 292 } 293 294 @Override 295 public Float get(TaskView taskView) { 296 return taskView.mTaskResistanceTranslationY; 297 } 298 }; 299 300 private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_X = 301 new FloatProperty<TaskView>("nonGridTranslationX") { 302 @Override 303 public void setValue(TaskView taskView, float v) { 304 taskView.setNonGridTranslationX(v); 305 } 306 307 @Override 308 public Float get(TaskView taskView) { 309 return taskView.mNonGridTranslationX; 310 } 311 }; 312 313 private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_Y = 314 new FloatProperty<TaskView>("nonGridTranslationY") { 315 @Override 316 public void setValue(TaskView taskView, float v) { 317 taskView.setNonGridTranslationY(v); 318 } 319 320 @Override 321 public Float get(TaskView taskView) { 322 return taskView.mNonGridTranslationY; 323 } 324 }; 325 326 public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X = 327 new FloatProperty<TaskView>("gridEndTranslationX") { 328 @Override 329 public void setValue(TaskView taskView, float v) { 330 taskView.setGridEndTranslationX(v); 331 } 332 333 @Override 334 public Float get(TaskView taskView) { 335 return taskView.mGridEndTranslationX; 336 } 337 }; 338 339 public static final FloatProperty<TaskView> SNAPSHOT_SCALE = 340 new FloatProperty<TaskView>("snapshotScale") { 341 @Override 342 public void setValue(TaskView taskView, float v) { 343 taskView.setSnapshotScale(v); 344 } 345 346 @Override 347 public Float get(TaskView taskView) { 348 return taskView.mSnapshotView.getScaleX(); 349 } 350 }; 351 352 @Nullable 353 protected Task mTask; 354 protected TaskThumbnailView mSnapshotView; 355 protected IconView mIconView; 356 protected final DigitalWellBeingToast mDigitalWellBeingToast; 357 protected float mFullscreenProgress; 358 private float mGridProgress; 359 protected float mTaskThumbnailSplashAlpha; 360 private float mNonGridScale = 1; 361 private float mDismissScale = 1; 362 protected final FullscreenDrawParams mCurrentFullscreenParams; 363 protected final StatefulActivity mActivity; 364 365 // Various causes of changing primary translation, which we aggregate to setTranslationX/Y(). 366 private float mDismissTranslationX; 367 private float mDismissTranslationY; 368 private float mTaskOffsetTranslationX; 369 private float mTaskOffsetTranslationY; 370 private float mTaskResistanceTranslationX; 371 private float mTaskResistanceTranslationY; 372 // The following translation variables should only be used in the same orientation as Launcher. 373 private float mBoxTranslationY; 374 // The following grid translations scales with mGridProgress. 375 private float mGridTranslationX; 376 private float mGridTranslationY; 377 // The following grid translation is used to animate closing the gap between grid and clear all. 378 private float mGridEndTranslationX; 379 // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick 380 // switch. 381 private float mNonGridTranslationX; 382 private float mNonGridTranslationY; 383 // Used when in SplitScreenSelectState 384 private float mSplitSelectTranslationY; 385 private float mSplitSelectTranslationX; 386 387 @Nullable 388 private ObjectAnimator mIconAndDimAnimator; 389 private float mIconScaleAnimStartProgress = 0; 390 private float mFocusTransitionProgress = 1; 391 private float mModalness = 0; 392 private float mStableAlpha = 1; 393 394 private int mTaskViewId = -1; 395 /** 396 * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right 397 */ 398 protected int[] mTaskIdContainer = new int[]{-1, -1}; 399 protected TaskIdAttributeContainer[] mTaskIdAttributeContainer = 400 new TaskIdAttributeContainer[2]; 401 402 private boolean mShowScreenshot; 403 404 // The current background requests to load the task thumbnail and icon 405 @Nullable 406 private CancellableTask mThumbnailLoadRequest; 407 @Nullable 408 private CancellableTask mIconLoadRequest; 409 410 private boolean mEndQuickswitchCuj; 411 412 private final float[] mIconCenterCoords = new float[2]; 413 414 protected final PointF mLastTouchDownPosition = new PointF(); 415 416 private boolean mIsClickableAsLiveTile = true; 417 418 @Nullable private final BorderAnimator mFocusBorderAnimator; 419 420 @Nullable private final BorderAnimator mHoverBorderAnimator; 421 TaskView(Context context)422 public TaskView(Context context) { 423 this(context, null); 424 } 425 TaskView(Context context, @Nullable AttributeSet attrs)426 public TaskView(Context context, @Nullable AttributeSet attrs) { 427 this(context, attrs, 0); 428 } 429 TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)430 public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 431 this(context, attrs, defStyleAttr, 0); 432 } 433 TaskView( Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)434 public TaskView( 435 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 436 super(context, attrs, defStyleAttr, defStyleRes); 437 mActivity = StatefulActivity.fromContext(context); 438 setOnClickListener(this::onClick); 439 440 mCurrentFullscreenParams = new FullscreenDrawParams(context); 441 mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this); 442 443 boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() 444 || DesktopTaskView.DESKTOP_MODE_SUPPORTED; 445 446 boolean willDrawBorder = 447 keyboardFocusHighlightEnabled || FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get(); 448 setWillNotDraw(!willDrawBorder); 449 450 if (willDrawBorder) { 451 TypedArray styledAttrs = context.obtainStyledAttributes( 452 attrs, R.styleable.TaskView, defStyleAttr, defStyleRes); 453 454 mFocusBorderAnimator = keyboardFocusHighlightEnabled ? new BorderAnimator( 455 /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius, 456 /* borderColor= */ styledAttrs.getColor( 457 R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR), 458 /* borderAnimationParams= */ new BorderAnimator.SimpleParams( 459 /* borderWidthPx= */ context.getResources().getDimensionPixelSize( 460 R.dimen.keyboard_quick_switch_border_width), 461 /* boundsBuilder= */ this::updateBorderBounds, 462 /* targetView= */ this)) : null; 463 464 mHoverBorderAnimator = 465 FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get() ? new BorderAnimator( 466 /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius, 467 /* borderColor= */ styledAttrs.getColor( 468 R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR), 469 /* borderAnimationParams= */ new BorderAnimator.SimpleParams( 470 /* borderWidthPx= */ context.getResources() 471 .getDimensionPixelSize(R.dimen.task_hover_border_width), 472 /* boundsBuilder= */ this::updateBorderBounds, 473 /* targetView= */ this)) : null; 474 475 styledAttrs.recycle(); 476 } else { 477 mFocusBorderAnimator = null; 478 mHoverBorderAnimator = null; 479 } 480 } 481 updateBorderBounds(Rect bounds)482 protected void updateBorderBounds(Rect bounds) { 483 bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()), 484 mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()), 485 mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()), 486 mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY())); 487 } 488 setTaskViewId(int id)489 public void setTaskViewId(int id) { 490 this.mTaskViewId = id; 491 } 492 getTaskViewId()493 public int getTaskViewId() { 494 return mTaskViewId; 495 } 496 497 /** 498 * Builds proto for logging 499 */ getItemInfo()500 public WorkspaceItemInfo getItemInfo() { 501 return getItemInfo(mTask); 502 } 503 getItemInfo(@ullable Task task)504 protected WorkspaceItemInfo getItemInfo(@Nullable Task task) { 505 WorkspaceItemInfo stubInfo = new WorkspaceItemInfo(); 506 stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK; 507 stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER; 508 if (task == null) { 509 return stubInfo; 510 } 511 512 ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key); 513 stubInfo.user = componentKey.user; 514 stubInfo.intent = new Intent().setComponent(componentKey.componentName); 515 stubInfo.title = task.title; 516 if (getRecentsView() != null) { 517 stubInfo.screenId = getRecentsView().indexOfChild(this); 518 } 519 return stubInfo; 520 } 521 522 @Override onFinishInflate()523 protected void onFinishInflate() { 524 super.onFinishInflate(); 525 mSnapshotView = findViewById(R.id.snapshot); 526 mIconView = findViewById(R.id.icon); 527 mIconTouchDelegate = new TransformingTouchDelegate(mIconView); 528 } 529 530 @Override onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)531 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 532 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 533 if (mFocusBorderAnimator != null) { 534 mFocusBorderAnimator.buildAnimator(gainFocus).start(); 535 } 536 } 537 538 @Override onHoverEvent(MotionEvent event)539 public boolean onHoverEvent(MotionEvent event) { 540 if (FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get()) { 541 switch (event.getAction()) { 542 case MotionEvent.ACTION_HOVER_ENTER: 543 mHoverBorderAnimator.buildAnimator(/* isAppearing= */ true).start(); 544 break; 545 case MotionEvent.ACTION_HOVER_EXIT: 546 mHoverBorderAnimator.buildAnimator(/* isAppearing= */ false).start(); 547 break; 548 default: 549 break; 550 } 551 } 552 return super.onHoverEvent(event); 553 } 554 555 @Override onInterceptHoverEvent(MotionEvent event)556 public boolean onInterceptHoverEvent(MotionEvent event) { 557 if (FeatureFlags.ENABLE_CURSOR_HOVER_STATES.get()) { 558 // avoid triggering hover event on child elements which would cause HOVER_EXIT for this 559 // task view 560 return true; 561 } else { 562 return super.onInterceptHoverEvent(event); 563 } 564 } 565 566 @Override draw(Canvas canvas)567 public void draw(Canvas canvas) { 568 super.draw(canvas); 569 if (mFocusBorderAnimator != null) { 570 mFocusBorderAnimator.drawBorder(canvas); 571 } 572 573 if (mHoverBorderAnimator != null) { 574 mHoverBorderAnimator.drawBorder(canvas); 575 } 576 } 577 578 /** 579 * Whether the taskview should take the touch event from parent. Events passed to children 580 * that might require special handling. 581 */ offerTouchToChildren(MotionEvent event)582 public boolean offerTouchToChildren(MotionEvent event) { 583 if (event.getAction() == MotionEvent.ACTION_DOWN) { 584 computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate); 585 } 586 if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) { 587 return true; 588 } 589 return false; 590 } 591 computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords, TransformingTouchDelegate transformingTouchDelegate)592 protected void computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords, 593 TransformingTouchDelegate transformingTouchDelegate) { 594 float iconHalfSize = iconView.getWidth() / 2f; 595 tempCenterCoords[0] = tempCenterCoords[1] = iconHalfSize; 596 getDescendantCoordRelativeToAncestor(iconView, mActivity.getDragLayer(), tempCenterCoords, 597 false); 598 transformingTouchDelegate.setBounds( 599 (int) (tempCenterCoords[0] - iconHalfSize), 600 (int) (tempCenterCoords[1] - iconHalfSize), 601 (int) (tempCenterCoords[0] + iconHalfSize), 602 (int) (tempCenterCoords[1] + iconHalfSize)); 603 } 604 605 /** 606 * The modalness of this view is how it should be displayed when it is shown on its own in the 607 * modal state of overview. 608 * 609 * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own. 610 */ setModalness(float modalness)611 public void setModalness(float modalness) { 612 if (mModalness == modalness) { 613 return; 614 } 615 mModalness = modalness; 616 mIconView.setAlpha(1 - modalness); 617 mDigitalWellBeingToast.updateBannerOffset(modalness); 618 } 619 getDigitalWellBeingToast()620 public DigitalWellBeingToast getDigitalWellBeingToast() { 621 return mDigitalWellBeingToast; 622 } 623 624 /** 625 * Updates this task view to the given {@param task}. 626 * 627 * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after 628 * that issue is fixed 629 */ bind(Task task, RecentsOrientedState orientedState)630 public void bind(Task task, RecentsOrientedState orientedState) { 631 cancelPendingLoadTasks(); 632 mTask = task; 633 mTaskIdContainer[0] = mTask.key.id; 634 mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, 635 mIconView, STAGE_POSITION_UNDEFINED); 636 mSnapshotView.bind(task); 637 setOrientationState(orientedState); 638 } 639 640 /** 641 * Sets up an on-click listener and the visibility for show_windows icon on top of the task. 642 */ setUpShowAllInstancesListener()643 public void setUpShowAllInstancesListener() { 644 String taskPackageName = mTaskIdAttributeContainer[0].mTask.key.getPackageName(); 645 646 // icon of the top/left task 647 View showWindowsView = findViewById(R.id.show_windows); 648 updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName)); 649 } 650 651 /** 652 * Returns a callback that updates the state of the filter and the recents overview 653 * 654 * @param taskPackageName package name of the task to filter by 655 */ 656 @Nullable getFilterUpdateCallback(String taskPackageName)657 protected View.OnClickListener getFilterUpdateCallback(String taskPackageName) { 658 View.OnClickListener cb = (view) -> { 659 // update and apply a new filter 660 getRecentsView().setAndApplyFilter(taskPackageName); 661 }; 662 663 if (!getRecentsView().getFilterState().shouldShowFilterUI(taskPackageName)) { 664 cb = null; 665 } 666 return cb; 667 } 668 669 /** 670 * Sets the correct visibility and callback on the provided filterView based on whether 671 * the callback is null or not 672 */ updateFilterCallback(@onNull View filterView, @Nullable View.OnClickListener callback)673 protected void updateFilterCallback(@NonNull View filterView, 674 @Nullable View.OnClickListener callback) { 675 // Filtering changes alpha instead of the visibility since visibility 676 // can be altered separately through RecentsView#resetFromSplitSelectionState() 677 if (callback == null) { 678 filterView.setAlpha(0); 679 } else { 680 filterView.setAlpha(1); 681 } 682 683 filterView.setOnClickListener(callback); 684 } 685 getTaskIdAttributeContainers()686 public TaskIdAttributeContainer[] getTaskIdAttributeContainers() { 687 return mTaskIdAttributeContainer; 688 } 689 690 @Nullable getTask()691 public Task getTask() { 692 return mTask; 693 } 694 695 /** 696 * Check if given {@code taskId} is tracked in this view 697 */ containsTaskId(int taskId)698 public boolean containsTaskId(int taskId) { 699 return mTask != null && mTask.key.id == taskId; 700 } 701 702 /** 703 * @return integer array of two elements to be size consistent with max number of tasks possible 704 * index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value 705 */ getTaskIds()706 public int[] getTaskIds() { 707 return Arrays.copyOf(mTaskIdContainer, mTaskIdContainer.length); 708 } 709 containsMultipleTasks()710 public boolean containsMultipleTasks() { 711 return mTaskIdContainer[1] != -1; 712 } 713 714 /** 715 * Returns the TaskIdAttributeContainer corresponding to a given taskId, or null if the TaskView 716 * does not contain a Task with that ID. 717 */ 718 @Nullable getTaskAttributesById(int taskId)719 public TaskIdAttributeContainer getTaskAttributesById(int taskId) { 720 for (TaskIdAttributeContainer attributes : mTaskIdAttributeContainer) { 721 if (attributes.getTask().key.id == taskId) { 722 return attributes; 723 } 724 } 725 726 return null; 727 } 728 getThumbnail()729 public TaskThumbnailView getThumbnail() { 730 return mSnapshotView; 731 } 732 refreshThumbnails(@ullable HashMap<Integer, ThumbnailData> thumbnailDatas)733 void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) { 734 if (mTask != null && thumbnailDatas != null) { 735 final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id); 736 if (thumbnailData != null) { 737 mSnapshotView.setThumbnail(mTask, thumbnailData); 738 return; 739 } 740 } 741 742 mSnapshotView.refresh(); 743 } 744 745 /** TODO(b/197033698) Remove all usages of above method and migrate to this one */ getThumbnails()746 public TaskThumbnailView[] getThumbnails() { 747 return new TaskThumbnailView[]{mSnapshotView}; 748 } 749 getIconView()750 public IconView getIconView() { 751 return mIconView; 752 } 753 754 @Override dispatchTouchEvent(MotionEvent ev)755 public boolean dispatchTouchEvent(MotionEvent ev) { 756 RecentsView recentsView = getRecentsView(); 757 if (recentsView == null || getTask() == null) { 758 return false; 759 } 760 SplitSelectStateController splitSelectStateController = 761 recentsView.getSplitSelectController(); 762 // Disable taps for split selection animation unless we have multiple tasks 763 boolean disableTapsForSplitSelect = 764 splitSelectStateController.isSplitSelectActive() 765 && splitSelectStateController.getInitialTaskId() == getTask().key.id 766 && !containsMultipleTasks(); 767 if (disableTapsForSplitSelect) { 768 return false; 769 } 770 771 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 772 mLastTouchDownPosition.set(ev.getX(), ev.getY()); 773 } 774 return super.dispatchTouchEvent(ev); 775 } 776 777 /** 778 * @return taskId that split selection was initiated with, 779 * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of 780 * split selection 781 */ getThisTaskCurrentlyInSplitSelection()782 protected int getThisTaskCurrentlyInSplitSelection() { 783 SplitSelectStateController splitSelectController = 784 getRecentsView().getSplitSelectController(); 785 int initSplitTaskId = INVALID_TASK_ID; 786 for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) { 787 int taskId = container.getTask().key.id; 788 if (taskId == splitSelectController.getInitialTaskId()) { 789 initSplitTaskId = taskId; 790 break; 791 } 792 } 793 return initSplitTaskId; 794 } 795 onClick(View view)796 private void onClick(View view) { 797 if (getTask() == null) { 798 return; 799 } 800 if (confirmSecondSplitSelectApp()) { 801 return; 802 } 803 launchTasks(); 804 mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo()) 805 .log(LAUNCHER_TASK_LAUNCH_TAP); 806 } 807 808 /** 809 * @return {@code true} if user is already in split select mode and this tap was to choose the 810 * second app. {@code false} otherwise 811 */ confirmSecondSplitSelectApp()812 protected boolean confirmSecondSplitSelectApp() { 813 int index = getLastSelectedChildTaskIndex(); 814 TaskIdAttributeContainer container = mTaskIdAttributeContainer[index]; 815 if (container != null) { 816 return getRecentsView().confirmSplitSelect(this, container.getTask(), 817 container.getIconView().getDrawable(), container.getThumbnailView(), 818 container.getThumbnailView().getThumbnail(), /* intent */ null, 819 /* user */ null); 820 } 821 return false; 822 } 823 824 /** 825 * Returns the task index of the last selected child task (0 or 1). 826 * If we contain multiple tasks and this TaskView is used as part of split selection, the 827 * selected child task index will be that of the remaining task. 828 */ getLastSelectedChildTaskIndex()829 protected int getLastSelectedChildTaskIndex() { 830 return 0; 831 } 832 833 /** 834 * Starts the task associated with this view and animates the startup. 835 * @return CompletionStage to indicate the animation completion or null if the launch failed. 836 */ 837 @Nullable launchTaskAnimated()838 public RunnableList launchTaskAnimated() { 839 if (mTask != null) { 840 TestLogging.recordEvent( 841 TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); 842 ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null); 843 opts.options.setLaunchDisplayId( 844 getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); 845 if (ActivityManagerWrapper.getInstance() 846 .startActivityFromRecents(mTask.key, opts.options)) { 847 RecentsView recentsView = getRecentsView(); 848 if (recentsView.getRunningTaskViewId() != -1) { 849 recentsView.onTaskLaunchedInLiveTileMode(); 850 851 // Return a fresh callback in the live tile case, so that it's not accidentally 852 // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner. 853 RunnableList callbackList = new RunnableList(); 854 recentsView.addSideTaskLaunchCallback(callbackList); 855 return callbackList; 856 } 857 if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { 858 // If the recents transition is running (ie. in live tile mode), then the start 859 // of a new task will merge into the existing transition and it currently will 860 // not be run independently, so we need to rely on the onTaskAppeared() call 861 // for the new task to trigger the side launch callback to flush this runnable 862 // list (which is usually flushed when the app launch animation finishes) 863 recentsView.addSideTaskLaunchCallback(opts.onEndCallback); 864 } 865 return opts.onEndCallback; 866 } else { 867 notifyTaskLaunchFailed(TAG); 868 return null; 869 } 870 } else { 871 return null; 872 } 873 } 874 875 /** 876 * Starts the task associated with this view without any animation 877 */ launchTask(@onNull Consumer<Boolean> callback)878 public void launchTask(@NonNull Consumer<Boolean> callback) { 879 launchTask(callback, false /* isQuickswitch */); 880 } 881 882 /** 883 * Starts the task associated with this view without any animation 884 */ launchTask(@onNull Consumer<Boolean> callback, boolean isQuickswitch)885 public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) { 886 if (mTask != null) { 887 TestLogging.recordEvent( 888 TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); 889 890 final TaskRemovedDuringLaunchListener 891 failureListener = new TaskRemovedDuringLaunchListener(); 892 if (isQuickswitch) { 893 // We only listen for failures to launch in quickswitch because the during this 894 // gesture launcher is in the background state, vs other launches which are in 895 // the actual overview state 896 failureListener.register(mActivity, mTask.key.id, () -> { 897 notifyTaskLaunchFailed(TAG); 898 RecentsView rv = getRecentsView(); 899 if (rv != null) { 900 // Disable animations for now, as it is an edge case and the app usually 901 // covers launcher and also any state transition animation also gets 902 // clobbered by QuickstepTransitionManager.createWallpaperOpenAnimations 903 // when launcher shows again 904 rv.startHome(false /* animated */); 905 if (rv.mSizeStrategy.getTaskbarController() != null) { 906 // LauncherTaskbarUIController depends on the launcher state when 907 // checking whether to handle resume, but that can come in before 908 // startHome() changes the state, so force-refresh here to ensure the 909 // taskbar is updated 910 rv.mSizeStrategy.getTaskbarController().refreshResumedState(); 911 } 912 } 913 }); 914 } 915 // Indicate success once the system has indicated that the transition has started 916 ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(getContext(), 0, 0, 917 MAIN_EXECUTOR.getHandler(), 918 elapsedRealTime -> { 919 callback.accept(true); 920 }, 921 elapsedRealTime -> { 922 failureListener.onTransitionFinished(); 923 }); 924 opts.setLaunchDisplayId( 925 getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); 926 if (isQuickswitch) { 927 opts.setFreezeRecentTasksReordering(); 928 } 929 opts.setDisableStartingWindow(mSnapshotView.shouldShowSplashView()); 930 Task.TaskKey key = mTask.key; 931 UI_HELPER_EXECUTOR.execute(() -> { 932 if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) { 933 // If the call to start activity failed, then post the result immediately, 934 // otherwise, wait for the animation start callback from the activity options 935 // above 936 MAIN_EXECUTOR.post(() -> { 937 notifyTaskLaunchFailed(TAG); 938 callback.accept(false); 939 }); 940 } 941 }); 942 } else { 943 callback.accept(false); 944 } 945 } 946 947 /** 948 * Returns ActivityOptions for overriding task transition animation. 949 */ makeCustomAnimation(Context context, int enterResId, int exitResId, final Runnable callback, final Handler callbackHandler)950 private ActivityOptions makeCustomAnimation(Context context, int enterResId, 951 int exitResId, final Runnable callback, final Handler callbackHandler) { 952 return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId, 953 callbackHandler, 954 elapsedRealTime -> { 955 if (callback != null) { 956 callbackHandler.post(callback); 957 } 958 }, null /* finishedListener */); 959 } 960 961 /** 962 * Launch of the current task (both live and inactive tasks) with an animation. 963 */ 964 @Nullable 965 public RunnableList launchTasks() { 966 RecentsView recentsView = getRecentsView(); 967 RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles; 968 if (isRunningTask() && remoteTargetHandles != null) { 969 if (!mIsClickableAsLiveTile) { 970 Log.e(TAG, "TaskView is not clickable as a live tile; returning to home."); 971 return null; 972 } 973 974 mIsClickableAsLiveTile = false; 975 RemoteAnimationTargets targets; 976 if (remoteTargetHandles.length == 1) { 977 targets = remoteTargetHandles[0].getTransformParams().getTargetSet(); 978 } else { 979 TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams(); 980 TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams(); 981 RemoteAnimationTarget[] apps = Stream.concat( 982 Arrays.stream(topLeftParams.getTargetSet().apps), 983 Arrays.stream(rightBottomParams.getTargetSet().apps)) 984 .toArray(RemoteAnimationTarget[]::new); 985 RemoteAnimationTarget[] wallpapers = Stream.concat( 986 Arrays.stream(topLeftParams.getTargetSet().wallpapers), 987 Arrays.stream(rightBottomParams.getTargetSet().wallpapers)) 988 .toArray(RemoteAnimationTarget[]::new); 989 targets = new RemoteAnimationTargets(apps, wallpapers, 990 topLeftParams.getTargetSet().nonApps, 991 topLeftParams.getTargetSet().targetMode); 992 } 993 if (targets == null) { 994 // If the recents animation is cancelled somehow between the parent if block and 995 // here, try to launch the task as a non live tile task. 996 RunnableList runnableList = launchTaskAnimated(); 997 if (runnableList == null) { 998 Log.e(TAG, "Recents animation cancelled and cannot launch task as non-live tile" 999 + "; returning to home"); 1000 } 1001 mIsClickableAsLiveTile = true; 1002 return runnableList; 1003 } 1004 1005 RunnableList runnableList = new RunnableList(); 1006 AnimatorSet anim = new AnimatorSet(); 1007 TaskViewUtils.composeRecentsLaunchAnimator( 1008 anim, this, targets.apps, 1009 targets.wallpapers, targets.nonApps, true /* launcherClosing */, 1010 mActivity.getStateManager(), recentsView, 1011 recentsView.getDepthController()); 1012 anim.addListener(new AnimatorListenerAdapter() { 1013 @Override 1014 public void onAnimationStart(Animator animation) { 1015 if (!recentsView.showAsGrid()) { 1016 return; 1017 } 1018 recentsView.runActionOnRemoteHandles( 1019 (Consumer<RemoteTargetHandle>) remoteTargetHandle -> 1020 remoteTargetHandle 1021 .getTaskViewSimulator() 1022 .setDrawsBelowRecents(false)); 1023 } 1024 1025 @Override 1026 public void onAnimationEnd(Animator animator) { 1027 if (mTask != null && mTask.key.displayId != getRootViewDisplayId()) { 1028 launchTaskAnimated(); 1029 } 1030 mIsClickableAsLiveTile = true; 1031 runEndCallback(); 1032 } 1033 1034 @Override 1035 public void onAnimationCancel(Animator animation) { 1036 runEndCallback(); 1037 } 1038 1039 private void runEndCallback() { 1040 runnableList.executeAllAndDestroy(); 1041 } 1042 }); 1043 anim.start(); 1044 recentsView.onTaskLaunchedInLiveTileMode(); 1045 return runnableList; 1046 } else { 1047 return launchTaskAnimated(); 1048 } 1049 } 1050 1051 /** 1052 * See {@link TaskDataChanges} 1053 * @param visible If this task view will be visible to the user in overview or hidden 1054 */ 1055 public void onTaskListVisibilityChanged(boolean visible) { 1056 onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL); 1057 } 1058 1059 /** 1060 * See {@link TaskDataChanges} 1061 * @param visible If this task view will be visible to the user in overview or hidden 1062 */ 1063 public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) { 1064 if (mTask == null) { 1065 return; 1066 } 1067 cancelPendingLoadTasks(); 1068 if (visible) { 1069 // These calls are no-ops if the data is already loaded, try and load the high 1070 // resolution thumbnail if the state permits 1071 RecentsModel model = RecentsModel.INSTANCE.get(getContext()); 1072 TaskThumbnailCache thumbnailCache = model.getThumbnailCache(); 1073 TaskIconCache iconCache = model.getIconCache(); 1074 1075 if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { 1076 mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground( 1077 mTask, thumbnail -> { 1078 mSnapshotView.setThumbnail(mTask, thumbnail); 1079 }); 1080 } 1081 if (needsUpdate(changes, FLAG_UPDATE_ICON)) { 1082 mIconLoadRequest = iconCache.updateIconInBackground(mTask, 1083 (task) -> { 1084 setIcon(mIconView, task.icon); 1085 mDigitalWellBeingToast.initialize(task); 1086 }); 1087 } 1088 if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) { 1089 mCurrentFullscreenParams.updateCornerRadius(getContext()); 1090 } 1091 } else { 1092 if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { 1093 mSnapshotView.setThumbnail(null, null); 1094 // Reset the task thumbnail reference as well (it will be fetched from the cache or 1095 // reloaded next time we need it) 1096 mTask.thumbnail = null; 1097 } 1098 if (needsUpdate(changes, FLAG_UPDATE_ICON)) { 1099 setIcon(mIconView, null); 1100 } 1101 } 1102 } 1103 1104 protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) { 1105 return (dataChange & flag) == flag; 1106 } 1107 1108 protected void cancelPendingLoadTasks() { 1109 if (mThumbnailLoadRequest != null) { 1110 mThumbnailLoadRequest.cancel(); 1111 mThumbnailLoadRequest = null; 1112 } 1113 if (mIconLoadRequest != null) { 1114 mIconLoadRequest.cancel(); 1115 mIconLoadRequest = null; 1116 } 1117 } 1118 1119 private boolean showTaskMenu(IconView iconView) { 1120 if (!getRecentsView().canLaunchFullscreenTask()) { 1121 // Don't show menu when selecting second split screen app 1122 return true; 1123 } 1124 1125 if (!mActivity.getDeviceProfile().isTablet 1126 && !getRecentsView().isClearAllHidden()) { 1127 getRecentsView().snapToPage(getRecentsView().indexOfChild(this)); 1128 return false; 1129 } else { 1130 mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo()) 1131 .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS); 1132 return showTaskMenuWithContainer(iconView); 1133 } 1134 } 1135 1136 protected boolean showTaskMenuWithContainer(IconView iconView) { 1137 TaskIdAttributeContainer menuContainer = 1138 mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1]; 1139 DeviceProfile dp = mActivity.getDeviceProfile(); 1140 if (dp.isTablet) { 1141 int alignedOptionIndex = 0; 1142 if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) { 1143 if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) { 1144 // With no focused task, there is less available space below the tasks, so align 1145 // the arrow to the third option in the menu. 1146 alignedOptionIndex = 2; 1147 } else { 1148 // Bottom row of landscape grid aligns arrow to second option to avoid clipping 1149 alignedOptionIndex = 1; 1150 } 1151 } 1152 return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignedOptionIndex); 1153 } else { 1154 return TaskMenuView.showForTask(menuContainer); 1155 } 1156 } 1157 1158 protected void setIcon(IconView iconView, @Nullable Drawable icon) { 1159 if (icon != null) { 1160 iconView.setDrawable(icon); 1161 iconView.setOnClickListener(v -> { 1162 if (confirmSecondSplitSelectApp()) { 1163 return; 1164 } 1165 showTaskMenu(iconView); 1166 }); 1167 iconView.setOnLongClickListener(v -> { 1168 requestDisallowInterceptTouchEvent(true); 1169 return showTaskMenu(iconView); 1170 }); 1171 } else { 1172 iconView.setDrawable(null); 1173 iconView.setOnClickListener(null); 1174 iconView.setOnLongClickListener(null); 1175 } 1176 } 1177 1178 public void setOrientationState(RecentsOrientedState orientationState) { 1179 setIconOrientation(orientationState); 1180 setThumbnailOrientation(orientationState); 1181 } 1182 1183 protected void setIconOrientation(RecentsOrientedState orientationState) { 1184 PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler(); 1185 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 1186 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 1187 1188 boolean isGridTask = isGridTask(); 1189 LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); 1190 1191 int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; 1192 int taskIconHeight = deviceProfile.overviewTaskIconSizePx; 1193 int taskMargin = deviceProfile.overviewTaskMarginPx; 1194 1195 orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight, 1196 thumbnailTopMargin, isRtl); 1197 iconParams.width = iconParams.height = taskIconHeight; 1198 mIconView.setLayoutParams(iconParams); 1199 1200 mIconView.setRotation(orientationHandler.getDegreesRotated()); 1201 int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx 1202 : deviceProfile.overviewTaskIconDrawableSizePx; 1203 mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize); 1204 } 1205 1206 protected void setThumbnailOrientation(RecentsOrientedState orientationState) { 1207 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 1208 int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; 1209 1210 // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead 1211 // of a hybrid of both margins and translations 1212 LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); 1213 snapshotParams.topMargin = thumbnailTopMargin; 1214 mSnapshotView.setLayoutParams(snapshotParams); 1215 1216 mSnapshotView.getTaskOverlay().updateOrientationState(orientationState); 1217 mDigitalWellBeingToast.initialize(mTask); 1218 } 1219 1220 /** 1221 * Returns whether the task is part of overview grid and not being focused. 1222 */ 1223 public boolean isGridTask() { 1224 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 1225 return deviceProfile.isTablet && !isFocusedTask(); 1226 } 1227 1228 /** Whether this task view represents the desktop */ 1229 public boolean isDesktopTask() { 1230 return false; 1231 } 1232 1233 /** 1234 * Called to animate a smooth transition when going directly from an app into Overview (and 1235 * vice versa). Icons fade in, and DWB banners slide in with a "shift up" animation. 1236 */ 1237 protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) { 1238 if (invert) { 1239 progress = 1 - progress; 1240 } 1241 mFocusTransitionProgress = progress; 1242 float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION; 1243 float lowerClamp = invert ? 1f - iconScalePercentage : 0; 1244 float upperClamp = invert ? 1 : iconScalePercentage; 1245 float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp) 1246 .getInterpolation(progress); 1247 mIconView.setAlpha(scale); 1248 mDigitalWellBeingToast.updateBannerOffset(1f - scale); 1249 } 1250 1251 public void setIconScaleAnimStartProgress(float startProgress) { 1252 mIconScaleAnimStartProgress = startProgress; 1253 } 1254 1255 public void animateIconScaleAndDimIntoView() { 1256 if (mIconAndDimAnimator != null) { 1257 mIconAndDimAnimator.cancel(); 1258 } 1259 mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1); 1260 mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress); 1261 mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR); 1262 mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() { 1263 @Override 1264 public void onAnimationEnd(Animator animation) { 1265 mIconAndDimAnimator = null; 1266 } 1267 }); 1268 mIconAndDimAnimator.start(); 1269 } 1270 1271 protected void setIconScaleAndDim(float iconScale) { 1272 setIconScaleAndDim(iconScale, false); 1273 } 1274 1275 private void setIconScaleAndDim(float iconScale, boolean invert) { 1276 if (mIconAndDimAnimator != null) { 1277 mIconAndDimAnimator.cancel(); 1278 } 1279 setIconsAndBannersTransitionProgress(iconScale, invert); 1280 } 1281 1282 protected void resetPersistentViewTransforms() { 1283 mNonGridTranslationX = mNonGridTranslationY = 1284 mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f; 1285 resetViewTransforms(); 1286 } 1287 1288 protected void resetViewTransforms() { 1289 // fullscreenTranslation and accumulatedTranslation should not be reset, as 1290 // resetViewTransforms is called during Quickswitch scrolling. 1291 mDismissTranslationX = mTaskOffsetTranslationX = 1292 mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f; 1293 mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = 0f; 1294 if (getRecentsView() == null || !getRecentsView().isSplitSelectionActive()) { 1295 mSplitSelectTranslationY = 0f; 1296 } 1297 1298 setSnapshotScale(1f); 1299 applyTranslationX(); 1300 applyTranslationY(); 1301 setTranslationZ(0); 1302 setAlpha(mStableAlpha); 1303 setIconScaleAndDim(1); 1304 setColorTint(0, 0); 1305 mSnapshotView.resetViewTransforms(); 1306 } 1307 1308 public void setStableAlpha(float parentAlpha) { 1309 mStableAlpha = parentAlpha; 1310 setAlpha(mStableAlpha); 1311 } 1312 1313 @Override 1314 public void onRecycle() { 1315 resetPersistentViewTransforms(); 1316 // Clear any references to the thumbnail (it will be re-read either from the cache or the 1317 // system on next bind) 1318 mSnapshotView.setThumbnail(mTask, null); 1319 setOverlayEnabled(false); 1320 onTaskListVisibilityChanged(false); 1321 } 1322 1323 public float getTaskCornerRadius() { 1324 return mCurrentFullscreenParams.mCornerRadius; 1325 } 1326 1327 @Override 1328 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1329 super.onLayout(changed, left, top, right, bottom); 1330 if (mActivity.getDeviceProfile().isTablet) { 1331 setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left); 1332 setPivotY(mSnapshotView.getTop()); 1333 } else { 1334 setPivotX((right - left) * 0.5f); 1335 setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f); 1336 } 1337 if (Utilities.ATLEAST_Q) { 1338 SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight()); 1339 setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT); 1340 } 1341 } 1342 1343 /** 1344 * How much to scale down pages near the edge of the screen. 1345 */ 1346 public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) { 1347 return deviceProfile.isTablet ? EDGE_SCALE_DOWN_FACTOR_GRID 1348 : EDGE_SCALE_DOWN_FACTOR_CAROUSEL; 1349 } 1350 1351 private void setNonGridScale(float nonGridScale) { 1352 mNonGridScale = nonGridScale; 1353 applyScale(); 1354 } 1355 1356 public float getNonGridScale() { 1357 return mNonGridScale; 1358 } 1359 1360 private void setSnapshotScale(float dismissScale) { 1361 mDismissScale = dismissScale; 1362 applyScale(); 1363 } 1364 1365 /** 1366 * Moves TaskView between carousel and 2 row grid. 1367 * 1368 * @param gridProgress 0 = carousel; 1 = 2 row grid. 1369 */ 1370 public void setGridProgress(float gridProgress) { 1371 mGridProgress = gridProgress; 1372 applyTranslationX(); 1373 applyTranslationY(); 1374 applyScale(); 1375 } 1376 1377 private void applyScale() { 1378 float scale = 1; 1379 scale *= getPersistentScale(); 1380 scale *= mDismissScale; 1381 setScaleX(scale); 1382 setScaleY(scale); 1383 updateSnapshotRadius(); 1384 } 1385 1386 /** 1387 * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not 1388 * change according to a temporary state. 1389 */ 1390 public float getPersistentScale() { 1391 float scale = 1; 1392 float gridProgress = GRID_INTERPOLATOR.getInterpolation(mGridProgress); 1393 scale *= Utilities.mapRange(gridProgress, mNonGridScale, 1f); 1394 return scale; 1395 } 1396 1397 /** 1398 * Updates alpha of task thumbnail splash on swipe up/down. 1399 */ 1400 public void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) { 1401 mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha; 1402 applyThumbnailSplashAlpha(); 1403 } 1404 1405 protected void applyThumbnailSplashAlpha() { 1406 mSnapshotView.setSplashAlpha(mTaskThumbnailSplashAlpha); 1407 } 1408 1409 protected void refreshTaskThumbnailSplash() { 1410 mSnapshotView.refreshSplashView(); 1411 } 1412 1413 private void setSplitSelectTranslationX(float x) { 1414 mSplitSelectTranslationX = x; 1415 applyTranslationX(); 1416 } 1417 1418 private void setSplitSelectTranslationY(float y) { 1419 mSplitSelectTranslationY = y; 1420 applyTranslationY(); 1421 } 1422 1423 private void setDismissTranslationX(float x) { 1424 mDismissTranslationX = x; 1425 applyTranslationX(); 1426 } 1427 1428 private void setDismissTranslationY(float y) { 1429 mDismissTranslationY = y; 1430 applyTranslationY(); 1431 } 1432 1433 private void setTaskOffsetTranslationX(float x) { 1434 mTaskOffsetTranslationX = x; 1435 applyTranslationX(); 1436 } 1437 1438 private void setTaskOffsetTranslationY(float y) { 1439 mTaskOffsetTranslationY = y; 1440 applyTranslationY(); 1441 } 1442 1443 private void setTaskResistanceTranslationX(float x) { 1444 mTaskResistanceTranslationX = x; 1445 applyTranslationX(); 1446 } 1447 1448 private void setTaskResistanceTranslationY(float y) { 1449 mTaskResistanceTranslationY = y; 1450 applyTranslationY(); 1451 } 1452 1453 private void setNonGridTranslationX(float nonGridTranslationX) { 1454 mNonGridTranslationX = nonGridTranslationX; 1455 applyTranslationX(); 1456 } 1457 1458 private void setNonGridTranslationY(float nonGridTranslationY) { 1459 mNonGridTranslationY = nonGridTranslationY; 1460 applyTranslationY(); 1461 } 1462 1463 public void setGridTranslationX(float gridTranslationX) { 1464 mGridTranslationX = gridTranslationX; 1465 applyTranslationX(); 1466 } 1467 1468 public float getGridTranslationX() { 1469 return mGridTranslationX; 1470 } 1471 1472 public void setGridTranslationY(float gridTranslationY) { 1473 mGridTranslationY = gridTranslationY; 1474 applyTranslationY(); 1475 } 1476 1477 public float getGridTranslationY() { 1478 return mGridTranslationY; 1479 } 1480 1481 private void setGridEndTranslationX(float gridEndTranslationX) { 1482 mGridEndTranslationX = gridEndTranslationX; 1483 applyTranslationX(); 1484 } 1485 1486 public float getScrollAdjustment(boolean gridEnabled) { 1487 float scrollAdjustment = 0; 1488 if (gridEnabled) { 1489 scrollAdjustment += mGridTranslationX; 1490 } else { 1491 scrollAdjustment += getPrimaryNonGridTranslationProperty().get(this); 1492 } 1493 return scrollAdjustment; 1494 } 1495 1496 public float getOffsetAdjustment(boolean gridEnabled) { 1497 return getScrollAdjustment(gridEnabled); 1498 } 1499 1500 public float getSizeAdjustment(boolean fullscreenEnabled) { 1501 float sizeAdjustment = 1; 1502 if (fullscreenEnabled) { 1503 sizeAdjustment *= mNonGridScale; 1504 } 1505 return sizeAdjustment; 1506 } 1507 1508 private void setBoxTranslationY(float boxTranslationY) { 1509 mBoxTranslationY = boxTranslationY; 1510 applyTranslationY(); 1511 } 1512 1513 private void applyTranslationX() { 1514 setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX 1515 + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX()); 1516 } 1517 1518 private void applyTranslationY() { 1519 setTranslationY(mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY 1520 + mSplitSelectTranslationY + getPersistentTranslationY()); 1521 } 1522 1523 /** 1524 * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does not 1525 * change according to a temporary state (e.g. task offset). 1526 */ 1527 public float getPersistentTranslationX() { 1528 return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX); 1529 } 1530 1531 /** 1532 * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does not 1533 * change according to a temporary state (e.g. task offset). 1534 */ 1535 public float getPersistentTranslationY() { 1536 return mBoxTranslationY 1537 + getNonGridTrans(mNonGridTranslationY) 1538 + getGridTrans(mGridTranslationY); 1539 } 1540 1541 public FloatProperty<TaskView> getPrimarySplitTranslationProperty() { 1542 return getPagedOrientationHandler().getPrimaryValue( 1543 SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y); 1544 } 1545 1546 public FloatProperty<TaskView> getSecondarySplitTranslationProperty() { 1547 return getPagedOrientationHandler().getSecondaryValue( 1548 SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y); 1549 } 1550 1551 public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() { 1552 return getPagedOrientationHandler().getPrimaryValue( 1553 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); 1554 } 1555 1556 public FloatProperty<TaskView> getSecondaryDismissTranslationProperty() { 1557 return getPagedOrientationHandler().getSecondaryValue( 1558 DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); 1559 } 1560 1561 public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() { 1562 return getPagedOrientationHandler().getPrimaryValue( 1563 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y); 1564 } 1565 1566 public FloatProperty<TaskView> getSecondaryTaskOffsetTranslationProperty() { 1567 return getPagedOrientationHandler().getSecondaryValue( 1568 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y); 1569 } 1570 1571 public FloatProperty<TaskView> getTaskResistanceTranslationProperty() { 1572 return getPagedOrientationHandler().getSecondaryValue( 1573 TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y); 1574 } 1575 1576 public FloatProperty<TaskView> getPrimaryNonGridTranslationProperty() { 1577 return getPagedOrientationHandler().getPrimaryValue( 1578 NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y); 1579 } 1580 1581 public FloatProperty<TaskView> getSecondaryNonGridTranslationProperty() { 1582 return getPagedOrientationHandler().getSecondaryValue( 1583 NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y); 1584 } 1585 1586 @Override 1587 public boolean hasOverlappingRendering() { 1588 // TODO: Clip-out the icon region from the thumbnail, since they are overlapping. 1589 return false; 1590 } 1591 1592 public boolean isEndQuickswitchCuj() { 1593 return mEndQuickswitchCuj; 1594 } 1595 1596 public void setEndQuickswitchCuj(boolean endQuickswitchCuj) { 1597 mEndQuickswitchCuj = endQuickswitchCuj; 1598 } 1599 1600 private int getExpectedViewHeight(View view) { 1601 int expectedHeight; 1602 int h = view.getLayoutParams().height; 1603 if (h > 0) { 1604 expectedHeight = h; 1605 } else { 1606 int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST); 1607 view.measure(m, m); 1608 expectedHeight = view.getMeasuredHeight(); 1609 } 1610 return expectedHeight; 1611 } 1612 1613 @Override 1614 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1615 super.onInitializeAccessibilityNodeInfo(info); 1616 1617 info.addAction( 1618 new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close, 1619 getContext().getText(R.string.accessibility_close))); 1620 1621 final Context context = getContext(); 1622 for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { 1623 if (taskContainer == null) { 1624 continue; 1625 } 1626 for (SystemShortcut s : TraceHelper.allowIpcs( 1627 "TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) { 1628 info.addAction(s.createAccessibilityAction(context)); 1629 } 1630 } 1631 1632 if (mDigitalWellBeingToast.hasLimit()) { 1633 info.addAction( 1634 new AccessibilityNodeInfo.AccessibilityAction( 1635 R.string.accessibility_app_usage_settings, 1636 getContext().getText(R.string.accessibility_app_usage_settings))); 1637 } 1638 1639 final RecentsView recentsView = getRecentsView(); 1640 final AccessibilityNodeInfo.CollectionItemInfo itemInfo = 1641 AccessibilityNodeInfo.CollectionItemInfo.obtain( 1642 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1, 1643 1, false); 1644 info.setCollectionItemInfo(itemInfo); 1645 } 1646 1647 @Override 1648 public boolean performAccessibilityAction(int action, Bundle arguments) { 1649 if (action == R.string.accessibility_close) { 1650 getRecentsView().dismissTask(this, true /*animateTaskView*/, 1651 true /*removeTask*/); 1652 return true; 1653 } 1654 1655 if (action == R.string.accessibility_app_usage_settings) { 1656 mDigitalWellBeingToast.openAppUsageSettings(this); 1657 return true; 1658 } 1659 1660 for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { 1661 if (taskContainer == null) { 1662 continue; 1663 } 1664 for (SystemShortcut s : getEnabledShortcuts(this, 1665 taskContainer)) { 1666 if (s.hasHandlerForAction(action)) { 1667 s.onClick(this); 1668 return true; 1669 } 1670 } 1671 } 1672 1673 return super.performAccessibilityAction(action, arguments); 1674 } 1675 1676 public RecentsView getRecentsView() { 1677 return (RecentsView) getParent(); 1678 } 1679 1680 PagedOrientationHandler getPagedOrientationHandler() { 1681 return getRecentsView().mOrientationState.getOrientationHandler(); 1682 } 1683 1684 private void notifyTaskLaunchFailed(String tag) { 1685 String msg = "Failed to launch task"; 1686 if (mTask != null) { 1687 msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")"; 1688 } 1689 Log.w(tag, msg); 1690 Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show(); 1691 } 1692 1693 /** 1694 * Hides the icon and shows insets when this TaskView is about to be shown fullscreen. 1695 * 1696 * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets. 1697 */ 1698 public void setFullscreenProgress(float progress) { 1699 progress = Utilities.boundToRange(progress, 0, 1); 1700 mFullscreenProgress = progress; 1701 mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); 1702 mSnapshotView.getTaskOverlay().setFullscreenProgress(progress); 1703 1704 // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are 1705 // oversized and banner would look disproportionately large. 1706 if (mActivity.getStateManager().getState() != BACKGROUND_APP) { 1707 setIconsAndBannersTransitionProgress(progress, true); 1708 } 1709 1710 updateSnapshotRadius(); 1711 } 1712 1713 protected void updateSnapshotRadius() { 1714 updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper()); 1715 mSnapshotView.setFullscreenParams(mCurrentFullscreenParams); 1716 } 1717 1718 void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) { 1719 updateFullscreenParams(mCurrentFullscreenParams, previewPositionHelper); 1720 } 1721 1722 protected void updateFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams, 1723 PreviewPositionHelper previewPositionHelper) { 1724 if (getRecentsView() == null) { 1725 return; 1726 } 1727 fullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(), 1728 getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper); 1729 } 1730 1731 /** 1732 * Updates TaskView scaling and translation required to support variable width if enabled, while 1733 * ensuring TaskView fits into screen in fullscreen. 1734 */ 1735 void updateTaskSize() { 1736 ViewGroup.LayoutParams params = getLayoutParams(); 1737 float nonGridScale; 1738 float boxTranslationY; 1739 int expectedWidth; 1740 int expectedHeight; 1741 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 1742 if (deviceProfile.isTablet) { 1743 final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx; 1744 final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize(); 1745 final int taskWidth = lastComputedTaskSize.width(); 1746 final int taskHeight = lastComputedTaskSize.height(); 1747 1748 int boxWidth; 1749 int boxHeight; 1750 boolean isFocusedTask = isFocusedTask(); 1751 if (isDesktopTask()) { 1752 Rect lastComputedDesktopTaskSize = 1753 getRecentsView().getLastComputedDesktopTaskSize(); 1754 boxWidth = lastComputedDesktopTaskSize.width(); 1755 boxHeight = lastComputedDesktopTaskSize.height(); 1756 } else if (isFocusedTask) { 1757 // Task will be focused and should use focused task size. Use focusTaskRatio 1758 // that is associated with the original orientation of the focused task. 1759 boxWidth = taskWidth; 1760 boxHeight = taskHeight; 1761 } else { 1762 // Otherwise task is in grid, and should use lastComputedGridTaskSize. 1763 Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize(); 1764 boxWidth = lastComputedGridTaskSize.width(); 1765 boxHeight = lastComputedGridTaskSize.height(); 1766 } 1767 1768 // Bound width/height to the box size. 1769 expectedWidth = boxWidth; 1770 expectedHeight = boxHeight + thumbnailPadding; 1771 1772 // Scale to to fit task Rect. 1773 nonGridScale = taskWidth / (float) boxWidth; 1774 1775 // Align to top of task Rect. 1776 boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f; 1777 } else { 1778 nonGridScale = 1f; 1779 boxTranslationY = 0f; 1780 expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT; 1781 expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT; 1782 } 1783 1784 setNonGridScale(nonGridScale); 1785 setBoxTranslationY(boxTranslationY); 1786 if (params.width != expectedWidth || params.height != expectedHeight) { 1787 params.width = expectedWidth; 1788 params.height = expectedHeight; 1789 setLayoutParams(params); 1790 } 1791 } 1792 1793 private float getGridTrans(float endTranslation) { 1794 float progress = GRID_INTERPOLATOR.getInterpolation(mGridProgress); 1795 return Utilities.mapRange(progress, 0, endTranslation); 1796 } 1797 1798 private float getNonGridTrans(float endTranslation) { 1799 return endTranslation - getGridTrans(endTranslation); 1800 } 1801 1802 public boolean isRunningTask() { 1803 if (getRecentsView() == null) { 1804 return false; 1805 } 1806 return this == getRecentsView().getRunningTaskView(); 1807 } 1808 1809 public boolean isFocusedTask() { 1810 if (getRecentsView() == null) { 1811 return false; 1812 } 1813 return this == getRecentsView().getFocusedTaskView(); 1814 } 1815 1816 public void setShowScreenshot(boolean showScreenshot) { 1817 mShowScreenshot = showScreenshot; 1818 } 1819 1820 public boolean showScreenshot() { 1821 if (!isRunningTask()) { 1822 return true; 1823 } 1824 return mShowScreenshot; 1825 } 1826 1827 public void setOverlayEnabled(boolean overlayEnabled) { 1828 mSnapshotView.setOverlayEnabled(overlayEnabled); 1829 } 1830 1831 public void initiateSplitSelect(SplitPositionOption splitPositionOption) { 1832 getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition, 1833 getLogEventForPosition(splitPositionOption.stagePosition)); 1834 } 1835 1836 /** 1837 * Set a color tint on the snapshot and supporting views. 1838 */ 1839 public void setColorTint(float amount, int tintColor) { 1840 mSnapshotView.setDimAlpha(amount); 1841 mIconView.setIconColorTint(tintColor, amount); 1842 mDigitalWellBeingToast.setBannerColorTint(tintColor, amount); 1843 } 1844 1845 1846 private int getRootViewDisplayId() { 1847 Display display = getRootView().getDisplay(); 1848 return display != null ? display.getDisplayId() : DEFAULT_DISPLAY; 1849 } 1850 1851 /** 1852 * Sets visibility for the thumbnail and associated elements (DWB banners and action chips). 1853 * IconView is unaffected. 1854 * 1855 * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value 1856 */ 1857 void setThumbnailVisibility(int visibility, int taskId) { 1858 for (int i = 0; i < getChildCount(); i++) { 1859 View child = getChildAt(i); 1860 if (child != mIconView) { 1861 child.setVisibility(visibility); 1862 } 1863 } 1864 } 1865 1866 /** 1867 * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. 1868 */ 1869 public static class FullscreenDrawParams { 1870 1871 private float mCornerRadius; 1872 private float mWindowCornerRadius; 1873 1874 public float mCurrentDrawnCornerRadius; 1875 1876 public FullscreenDrawParams(Context context) { 1877 updateCornerRadius(context); 1878 } 1879 1880 /** Recomputes the start and end corner radius for the given Context. */ 1881 public void updateCornerRadius(Context context) { 1882 mCornerRadius = computeTaskCornerRadius(context); 1883 mWindowCornerRadius = computeWindowCornerRadius(context); 1884 } 1885 1886 @VisibleForTesting 1887 public float computeTaskCornerRadius(Context context) { 1888 return TaskCornerRadius.get(context); 1889 } 1890 1891 @VisibleForTesting 1892 public float computeWindowCornerRadius(Context context) { 1893 return QuickStepContract.getWindowCornerRadius(context); 1894 } 1895 1896 /** 1897 * Sets the progress in range [0, 1] 1898 */ 1899 public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale, 1900 int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) { 1901 mCurrentDrawnCornerRadius = 1902 Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius) 1903 / parentScale / taskViewScale; 1904 } 1905 } 1906 1907 public class TaskIdAttributeContainer { 1908 private final TaskThumbnailView mThumbnailView; 1909 private final Task mTask; 1910 private final IconView mIconView; 1911 /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */ 1912 private @SplitConfigurationOptions.StagePosition int mStagePosition; 1913 @IdRes 1914 private final int mA11yNodeId; 1915 1916 public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView, 1917 IconView iconView, int stagePosition) { 1918 this.mTask = task; 1919 this.mThumbnailView = thumbnailView; 1920 this.mIconView = iconView; 1921 this.mStagePosition = stagePosition; 1922 this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ? 1923 R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo; 1924 } 1925 1926 public TaskThumbnailView getThumbnailView() { 1927 return mThumbnailView; 1928 } 1929 1930 public Task getTask() { 1931 return mTask; 1932 } 1933 1934 public WorkspaceItemInfo getItemInfo() { 1935 return TaskView.this.getItemInfo(mTask); 1936 } 1937 1938 public TaskView getTaskView() { 1939 return TaskView.this; 1940 } 1941 1942 public IconView getIconView() { 1943 return mIconView; 1944 } 1945 1946 public int getStagePosition() { 1947 return mStagePosition; 1948 } 1949 1950 void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) { 1951 this.mStagePosition = stagePosition; 1952 } 1953 1954 public int getA11yNodeId() { 1955 return mA11yNodeId; 1956 } 1957 } 1958 } 1959