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