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