/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.quickstep.views; import static android.widget.Toast.LENGTH_SHORT; import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; import static com.android.launcher3.Utilities.comp; import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.IdRes; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; import android.view.MotionEvent; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.Toast; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.TransformingTouchDelegate; import com.android.launcher3.util.ViewPool.Reusable; import com.android.quickstep.RecentsModel; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.util.CancellableTask; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.TaskCornerRadius; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.lang.annotation.Retention; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; /** * A task in the Recents view. */ public class TaskView extends FrameLayout implements Reusable { private static final String TAG = TaskView.class.getSimpleName(); private static final boolean DEBUG = false; public static final int FLAG_UPDATE_ICON = 1; public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1; public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL; /** * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more * granularity on which components of this task require an update */ @Retention(SOURCE) @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL}) public @interface TaskDataChanges {} /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */ public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f; /** * Should the TaskView display clip off the left inset in RecentsView. */ public static boolean clipLeft(DeviceProfile deviceProfile) { return false; } /** * Should the TaskView display clip off the top inset in RecentsView. */ public static boolean clipTop(DeviceProfile deviceProfile) { return false; } /** * Should the TaskView display clip off the right inset in RecentsView. */ public static boolean clipRight(DeviceProfile deviceProfile) { return false; } /** * Should the TaskView display clip off the bottom inset in RecentsView. */ public static boolean clipBottom(DeviceProfile deviceProfile) { return deviceProfile.isTablet; } /** * Should the TaskView scale down to fit whole thumbnail in fullscreen. */ public static boolean useFullThumbnail(DeviceProfile deviceProfile) { return deviceProfile.isTablet && !deviceProfile.isTaskbarPresentInApps; } private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f; private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f; public static final long SCALE_ICON_DURATION = 120; private static final long DIM_ANIM_DURATION = 700; private static final Interpolator GRID_INTERPOLATOR = ACCEL_DEACCEL; /** * This technically can be a vanilla {@link TouchDelegate} class, however that class requires * setting the touch bounds at construction, so we'd repeatedly be created many instances * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch * delegated bounds only to be updated. */ private TransformingTouchDelegate mIconTouchDelegate; private static final List SYSTEM_GESTURE_EXCLUSION_RECT = Collections.singletonList(new Rect()); public static final FloatProperty FOCUS_TRANSITION = new FloatProperty("focusTransition") { @Override public void setValue(TaskView taskView, float v) { taskView.setIconAndDimTransitionProgress(v, false /* invert */); } @Override public Float get(TaskView taskView) { return taskView.mFocusTransitionProgress; } }; private static final FloatProperty SPLIT_SELECT_TRANSLATION_X = new FloatProperty("splitSelectTranslationX") { @Override public void setValue(TaskView taskView, float v) { taskView.setSplitSelectTranslationX(v); } @Override public Float get(TaskView taskView) { return taskView.mSplitSelectTranslationX; } }; private static final FloatProperty SPLIT_SELECT_TRANSLATION_Y = new FloatProperty("splitSelectTranslationY") { @Override public void setValue(TaskView taskView, float v) { taskView.setSplitSelectTranslationY(v); } @Override public Float get(TaskView taskView) { return taskView.mSplitSelectTranslationY; } }; private static final FloatProperty DISMISS_TRANSLATION_X = new FloatProperty("dismissTranslationX") { @Override public void setValue(TaskView taskView, float v) { taskView.setDismissTranslationX(v); } @Override public Float get(TaskView taskView) { return taskView.mDismissTranslationX; } }; private static final FloatProperty DISMISS_TRANSLATION_Y = new FloatProperty("dismissTranslationY") { @Override public void setValue(TaskView taskView, float v) { taskView.setDismissTranslationY(v); } @Override public Float get(TaskView taskView) { return taskView.mDismissTranslationY; } }; private static final FloatProperty TASK_OFFSET_TRANSLATION_X = new FloatProperty("taskOffsetTranslationX") { @Override public void setValue(TaskView taskView, float v) { taskView.setTaskOffsetTranslationX(v); } @Override public Float get(TaskView taskView) { return taskView.mTaskOffsetTranslationX; } }; private static final FloatProperty TASK_OFFSET_TRANSLATION_Y = new FloatProperty("taskOffsetTranslationY") { @Override public void setValue(TaskView taskView, float v) { taskView.setTaskOffsetTranslationY(v); } @Override public Float get(TaskView taskView) { return taskView.mTaskOffsetTranslationY; } }; private static final FloatProperty TASK_RESISTANCE_TRANSLATION_X = new FloatProperty("taskResistanceTranslationX") { @Override public void setValue(TaskView taskView, float v) { taskView.setTaskResistanceTranslationX(v); } @Override public Float get(TaskView taskView) { return taskView.mTaskResistanceTranslationX; } }; private static final FloatProperty TASK_RESISTANCE_TRANSLATION_Y = new FloatProperty("taskResistanceTranslationY") { @Override public void setValue(TaskView taskView, float v) { taskView.setTaskResistanceTranslationY(v); } @Override public Float get(TaskView taskView) { return taskView.mTaskResistanceTranslationY; } }; private static final FloatProperty NON_GRID_TRANSLATION_X = new FloatProperty("nonGridTranslationX") { @Override public void setValue(TaskView taskView, float v) { taskView.setNonGridTranslationX(v); } @Override public Float get(TaskView taskView) { return taskView.mNonGridTranslationX; } }; private static final FloatProperty NON_GRID_TRANSLATION_Y = new FloatProperty("nonGridTranslationY") { @Override public void setValue(TaskView taskView, float v) { taskView.setNonGridTranslationY(v); } @Override public Float get(TaskView taskView) { return taskView.mNonGridTranslationY; } }; public static final FloatProperty GRID_END_TRANSLATION_X = new FloatProperty("gridEndTranslationX") { @Override public void setValue(TaskView taskView, float v) { taskView.setGridEndTranslationX(v); } @Override public Float get(TaskView taskView) { return taskView.mGridEndTranslationX; } }; public static final FloatProperty SNAPSHOT_SCALE = new FloatProperty("snapshotScale") { @Override public void setValue(TaskView taskView, float v) { taskView.setSnapshotScale(v); } @Override public Float get(TaskView taskView) { return taskView.mSnapshotView.getScaleX(); } }; private final TaskOutlineProvider mOutlineProvider; @Nullable protected Task mTask; protected TaskThumbnailView mSnapshotView; protected IconView mIconView; private final DigitalWellBeingToast mDigitalWellBeingToast; private float mFullscreenProgress; private float mGridProgress; private float mNonGridScale = 1; private float mDismissScale = 1; protected final FullscreenDrawParams mCurrentFullscreenParams; protected final StatefulActivity mActivity; // Various causes of changing primary translation, which we aggregate to setTranslationX/Y(). private float mDismissTranslationX; private float mDismissTranslationY; private float mTaskOffsetTranslationX; private float mTaskOffsetTranslationY; private float mTaskResistanceTranslationX; private float mTaskResistanceTranslationY; // The following translation variables should only be used in the same orientation as Launcher. private float mBoxTranslationY; // The following grid translations scales with mGridProgress. private float mGridTranslationX; private float mGridTranslationY; // The following grid translation is used to animate closing the gap between grid and clear all. private float mGridEndTranslationX; // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick // switch. private float mNonGridTranslationX; private float mNonGridTranslationY; // Used when in SplitScreenSelectState private float mSplitSelectTranslationY; private float mSplitSelectTranslationX; private float mSplitSelectScrollOffsetPrimary; @Nullable private ObjectAnimator mIconAndDimAnimator; private float mIconScaleAnimStartProgress = 0; private float mFocusTransitionProgress = 1; private float mModalness = 0; private float mStableAlpha = 1; private int mTaskViewId = -1; /** * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right */ protected final int[] mTaskIdContainer = new int[]{-1, -1}; protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer = new TaskIdAttributeContainer[2]; private boolean mShowScreenshot; // The current background requests to load the task thumbnail and icon @Nullable private CancellableTask mThumbnailLoadRequest; @Nullable private CancellableTask mIconLoadRequest; private boolean mEndQuickswitchCuj; private final float[] mIconCenterCoords = new float[2]; private boolean mIsClickableAsLiveTile = true; public TaskView(Context context) { this(context, null); } public TaskView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mActivity = StatefulActivity.fromContext(context); setOnClickListener(this::onClick); mCurrentFullscreenParams = new FullscreenDrawParams(context); mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this); mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams, mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx); setOutlineProvider(mOutlineProvider); } public void setTaskViewId(int id) { this.mTaskViewId = id; } public int getTaskViewId() { return mTaskViewId; } /** * Builds proto for logging */ public WorkspaceItemInfo getItemInfo() { return getItemInfo(mTask); } protected WorkspaceItemInfo getItemInfo(Task task) { ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key); WorkspaceItemInfo stubInfo = new WorkspaceItemInfo(); stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK; stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER; stubInfo.user = componentKey.user; stubInfo.intent = new Intent().setComponent(componentKey.componentName); stubInfo.title = task.title; stubInfo.screenId = getRecentsView().indexOfChild(this); return stubInfo; } @Override protected void onFinishInflate() { super.onFinishInflate(); mSnapshotView = findViewById(R.id.snapshot); mIconView = findViewById(R.id.icon); mIconTouchDelegate = new TransformingTouchDelegate(mIconView); } /** * Whether the taskview should take the touch event from parent. Events passed to children * that might require special handling. */ public boolean offerTouchToChildren(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate); } if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) { return true; } return false; } protected void computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords, TransformingTouchDelegate transformingTouchDelegate) { float iconHalfSize = iconView.getWidth() / 2f; tempCenterCoords[0] = tempCenterCoords[1] = iconHalfSize; getDescendantCoordRelativeToAncestor(iconView, mActivity.getDragLayer(), tempCenterCoords, false); transformingTouchDelegate.setBounds( (int) (tempCenterCoords[0] - iconHalfSize), (int) (tempCenterCoords[1] - iconHalfSize), (int) (tempCenterCoords[0] + iconHalfSize), (int) (tempCenterCoords[1] + iconHalfSize)); } /** * The modalness of this view is how it should be displayed when it is shown on its own in the * modal state of overview. * * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own. */ public void setModalness(float modalness) { if (mModalness == modalness) { return; } mModalness = modalness; mIconView.setAlpha(comp(modalness)); mDigitalWellBeingToast.updateBannerOffset(modalness, mCurrentFullscreenParams.mCurrentDrawnInsets.top + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom); } public DigitalWellBeingToast getDigitalWellBeingToast() { return mDigitalWellBeingToast; } /** * Updates this task view to the given {@param task}. * * TODO(b/142282126) Re-evaluate if we need to pass in isMultiWindowMode after * that issue is fixed */ public void bind(Task task, RecentsOrientedState orientedState) { cancelPendingLoadTasks(); mTask = task; mTaskIdContainer[0] = mTask.key.id; mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, mIconView, STAGE_POSITION_UNDEFINED); mSnapshotView.bind(task); setOrientationState(orientedState); } public TaskIdAttributeContainer[] getTaskIdAttributeContainers() { return mTaskIdAttributeContainer; } @Nullable public Task getTask() { return mTask; } /** * @return integer array of two elements to be size consistent with max number of tasks possible * index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value */ public int[] getTaskIds() { return mTaskIdContainer; } public boolean containsMultipleTasks() { return mTaskIdContainer[1] != -1; } public TaskThumbnailView getThumbnail() { return mSnapshotView; } void refreshThumbnails(@Nullable HashMap thumbnailDatas) { if (mTask != null && thumbnailDatas != null) { final ThumbnailData thumbnailData = thumbnailDatas.get(mTask.key.id); if (thumbnailData != null) { mSnapshotView.setThumbnail(mTask, thumbnailData); return; } } mSnapshotView.refresh(); } /** TODO(b/197033698) Remove all usages of above method and migrate to this one */ public TaskThumbnailView[] getThumbnails() { return new TaskThumbnailView[]{mSnapshotView}; } public IconView getIconView() { return mIconView; } private void onClick(View view) { if (getTask() == null) { return; } if (confirmSecondSplitSelectApp()) { return; } RecentsView recentsView = getRecentsView(); RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles; if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) { if (!mIsClickableAsLiveTile) { return; } // Reset the minimized state since we force-toggled the minimized state when entering // overview, but never actually finished the recents animation SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate(); if (p != null) { p.setSplitScreenMinimized(false); } mIsClickableAsLiveTile = false; RemoteAnimationTargets targets; if (remoteTargetHandles.length == 1) { targets = remoteTargetHandles[0].getTransformParams().getTargetSet(); } else { TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams(); TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams(); RemoteAnimationTargetCompat[] apps = Stream.concat( Arrays.stream(topLeftParams.getTargetSet().apps), Arrays.stream(rightBottomParams.getTargetSet().apps)) .toArray(RemoteAnimationTargetCompat[]::new); RemoteAnimationTargetCompat[] wallpapers = Stream.concat( Arrays.stream(topLeftParams.getTargetSet().wallpapers), Arrays.stream(rightBottomParams.getTargetSet().wallpapers)) .toArray(RemoteAnimationTargetCompat[]::new); RemoteAnimationTargetCompat[] nonApps = Stream.concat( Arrays.stream(topLeftParams.getTargetSet().nonApps), Arrays.stream(rightBottomParams.getTargetSet().nonApps)) .toArray(RemoteAnimationTargetCompat[]::new); targets = new RemoteAnimationTargets(apps, wallpapers, nonApps, topLeftParams.getTargetSet().targetMode); } if (targets == null) { // If the recents animation is cancelled somehow between the parent if block and // here, try to launch the task as a non live tile task. launchTaskAnimated(); return; } AnimatorSet anim = new AnimatorSet(); TaskViewUtils.composeRecentsLaunchAnimator( anim, this, targets.apps, targets.wallpapers, targets.nonApps, true /* launcherClosing */, mActivity.getStateManager(), recentsView, recentsView.getDepthController()); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { recentsView.runActionOnRemoteHandles( (Consumer) remoteTargetHandle -> remoteTargetHandle .getTaskViewSimulator() .setDrawsBelowRecents(false)); } @Override public void onAnimationEnd(Animator animator) { mIsClickableAsLiveTile = true; } }); anim.start(); recentsView.onTaskLaunchedInLiveTileMode(); } else { launchTaskAnimated(); } mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo()) .log(LAUNCHER_TASK_LAUNCH_TAP); } /** * @return {@code true} if user is already in split select mode and this tap was to choose the * second app. {@code false} otherwise */ private boolean confirmSecondSplitSelectApp() { boolean isSelectingSecondSplitApp = mActivity.isInState(OVERVIEW_SPLIT_SELECT); if (isSelectingSecondSplitApp) { getRecentsView().confirmSplitSelect(this); } return isSelectingSecondSplitApp; } /** * Starts the task associated with this view and animates the startup. * @return CompletionStage to indicate the animation completion or null if the launch failed. */ @Nullable public RunnableList launchTaskAnimated() { if (mTask != null) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null); opts.options.setLaunchDisplayId(getRootViewDisplayId()); if (ActivityManagerWrapper.getInstance() .startActivityFromRecents(mTask.key, opts.options)) { RecentsView recentsView = getRecentsView(); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) { recentsView.onTaskLaunchedInLiveTileMode(); // Return a fresh callback in the live tile case, so that it's not accidentally // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner. RunnableList callbackList = new RunnableList(); recentsView.addSideTaskLaunchCallback(callbackList); return callbackList; } return opts.onEndCallback; } else { notifyTaskLaunchFailed(TAG); return null; } } else { return null; } } /** * Starts the task associated with this view without any animation */ public void launchTask(@NonNull Consumer callback) { launchTask(callback, false /* freezeTaskList */); } /** * Starts the task associated with this view without any animation */ public void launchTask(@NonNull Consumer callback, boolean freezeTaskList) { if (mTask != null) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); // Indicate success once the system has indicated that the transition has started ActivityOptions opts = ActivityOptionsCompat.makeCustomAnimation( getContext(), 0, 0, () -> callback.accept(true), MAIN_EXECUTOR.getHandler()); opts.setLaunchDisplayId(getRootViewDisplayId()); if (freezeTaskList) { ActivityOptionsCompat.setFreezeRecentTasksList(opts); } Task.TaskKey key = mTask.key; UI_HELPER_EXECUTOR.execute(() -> { if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) { // If the call to start activity failed, then post the result immediately, // otherwise, wait for the animation start callback from the activity options // above MAIN_EXECUTOR.post(() -> { notifyTaskLaunchFailed(TAG); callback.accept(false); }); } }); } else { callback.accept(false); } } /** * See {@link TaskDataChanges} * @param visible If this task view will be visible to the user in overview or hidden */ public void onTaskListVisibilityChanged(boolean visible) { onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL); } /** * See {@link TaskDataChanges} * @param visible If this task view will be visible to the user in overview or hidden */ public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) { if (mTask == null) { return; } cancelPendingLoadTasks(); if (visible) { // These calls are no-ops if the data is already loaded, try and load the high // resolution thumbnail if the state permits RecentsModel model = RecentsModel.INSTANCE.get(getContext()); TaskThumbnailCache thumbnailCache = model.getThumbnailCache(); TaskIconCache iconCache = model.getIconCache(); if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground( mTask, thumbnail -> { mSnapshotView.setThumbnail(mTask, thumbnail); }); } if (needsUpdate(changes, FLAG_UPDATE_ICON)) { mIconLoadRequest = iconCache.updateIconInBackground(mTask, (task) -> { setIcon(mIconView, task.icon); mDigitalWellBeingToast.initialize(mTask); }); } } else { if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) { mSnapshotView.setThumbnail(null, null); // Reset the task thumbnail reference as well (it will be fetched from the cache or // reloaded next time we need it) mTask.thumbnail = null; } if (needsUpdate(changes, FLAG_UPDATE_ICON)) { setIcon(mIconView, null); } } } protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) { return (dataChange & flag) == flag; } protected void cancelPendingLoadTasks() { if (mThumbnailLoadRequest != null) { mThumbnailLoadRequest.cancel(); mThumbnailLoadRequest = null; } if (mIconLoadRequest != null) { mIconLoadRequest.cancel(); mIconLoadRequest = null; } } private boolean showTaskMenu(IconView iconView) { if (getRecentsView().mActivity.isInState(OVERVIEW_SPLIT_SELECT)) { // Don't show menu when selecting second split screen app return true; } if (!mActivity.getDeviceProfile().overviewShowAsGrid && !getRecentsView().isClearAllHidden()) { getRecentsView().snapToPage(getRecentsView().indexOfChild(this)); return false; } else { mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo()) .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS); return showTaskMenuWithContainer(iconView); } } protected boolean showTaskMenuWithContainer(IconView iconView) { TaskIdAttributeContainer menuContainer = mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1]; if (mActivity.getDeviceProfile().overviewShowAsGrid) { boolean alignSecondRow = getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && mActivity.getDeviceProfile().isLandscape; return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignSecondRow); } else { return TaskMenuView.showForTask(menuContainer); } } protected void setIcon(IconView iconView, @Nullable Drawable icon) { if (icon != null) { iconView.setDrawable(icon); iconView.setOnClickListener(v -> { if (confirmSecondSplitSelectApp()) { return; } if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) { RecentsView recentsView = getRecentsView(); recentsView.switchToScreenshot( () -> recentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> showTaskMenu(iconView))); } else { showTaskMenu(iconView); } }); iconView.setOnLongClickListener(v -> { requestDisallowInterceptTouchEvent(true); return showTaskMenu(iconView); }); } else { iconView.setDrawable(null); iconView.setOnClickListener(null); iconView.setOnLongClickListener(null); } } public void setOrientationState(RecentsOrientedState orientationState) { PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler(); boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams(); DeviceProfile deviceProfile = mActivity.getDeviceProfile(); snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; boolean isGridTask = isGridTask(); int taskIconHeight = deviceProfile.overviewTaskIconSizePx; int taskMargin = isGridTask ? deviceProfile.overviewTaskMarginGridPx : deviceProfile.overviewTaskMarginPx; int taskIconMargin = snapshotParams.topMargin - taskIconHeight - taskMargin; LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); orientationHandler.setIconAndSnapshotParams(mIconView, taskIconMargin, taskIconHeight, snapshotParams, isRtl); mSnapshotView.setLayoutParams(snapshotParams); iconParams.width = iconParams.height = taskIconHeight; mIconView.setLayoutParams(iconParams); mIconView.setRotation(orientationHandler.getDegreesRotated()); int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx : deviceProfile.overviewTaskIconDrawableSizePx; mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize); snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; mSnapshotView.setLayoutParams(snapshotParams); mSnapshotView.getTaskOverlay().updateOrientationState(orientationState); } /** * Returns whether the task is part of overview grid and not being focused. */ public boolean isGridTask() { DeviceProfile deviceProfile = mActivity.getDeviceProfile(); return deviceProfile.overviewShowAsGrid && !isFocusedTask(); } private void setIconAndDimTransitionProgress(float progress, boolean invert) { if (invert) { progress = 1 - progress; } mFocusTransitionProgress = progress; float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION; float lowerClamp = invert ? 1f - iconScalePercentage : 0; float upperClamp = invert ? 1 : iconScalePercentage; float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp) .getInterpolation(progress); mIconView.setAlpha(scale); mDigitalWellBeingToast.updateBannerOffset(1f - scale, mCurrentFullscreenParams.mCurrentDrawnInsets.top + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom); } public void setIconScaleAnimStartProgress(float startProgress) { mIconScaleAnimStartProgress = startProgress; } public void animateIconScaleAndDimIntoView() { if (mIconAndDimAnimator != null) { mIconAndDimAnimator.cancel(); } mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1); mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress); mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR); mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mIconAndDimAnimator = null; } }); mIconAndDimAnimator.start(); } protected void setIconScaleAndDim(float iconScale) { setIconScaleAndDim(iconScale, false); } private void setIconScaleAndDim(float iconScale, boolean invert) { if (mIconAndDimAnimator != null) { mIconAndDimAnimator.cancel(); } setIconAndDimTransitionProgress(iconScale, invert); } protected void resetPersistentViewTransforms() { mNonGridTranslationX = mNonGridTranslationY = mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f; resetViewTransforms(); } protected void resetViewTransforms() { // fullscreenTranslation and accumulatedTranslation should not be reset, as // resetViewTransforms is called during Quickswitch scrolling. mDismissTranslationX = mTaskOffsetTranslationX = mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f; mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = mSplitSelectTranslationY = 0f; setSnapshotScale(1f); applyTranslationX(); applyTranslationY(); setTranslationZ(0); setAlpha(mStableAlpha); setIconScaleAndDim(1); setColorTint(0, 0); } public void setStableAlpha(float parentAlpha) { mStableAlpha = parentAlpha; setAlpha(mStableAlpha); } @Override public void onRecycle() { resetPersistentViewTransforms(); // Clear any references to the thumbnail (it will be re-read either from the cache or the // system on next bind) mSnapshotView.setThumbnail(mTask, null); setOverlayEnabled(false); onTaskListVisibilityChanged(false); } public float getTaskCornerRadius() { return TaskCornerRadius.get(mActivity); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mActivity.getDeviceProfile().overviewShowAsGrid) { setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left); setPivotY(mSnapshotView.getTop()); } else { setPivotX((right - left) * 0.5f); setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f); } if (Utilities.ATLEAST_Q) { SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight()); setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT); } } /** * How much to scale down pages near the edge of the screen. */ public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) { return deviceProfile.overviewShowAsGrid ? EDGE_SCALE_DOWN_FACTOR_GRID : EDGE_SCALE_DOWN_FACTOR_CAROUSEL; } private void setNonGridScale(float nonGridScale) { mNonGridScale = nonGridScale; applyScale(); } public float getNonGridScale() { return mNonGridScale; } private void setSnapshotScale(float dismissScale) { mDismissScale = dismissScale; applyScale(); } /** * Moves TaskView between carousel and 2 row grid. * * @param gridProgress 0 = carousel; 1 = 2 row grid. */ public void setGridProgress(float gridProgress) { mGridProgress = gridProgress; applyTranslationX(); applyTranslationY(); applyScale(); } private void applyScale() { float scale = 1; scale *= getPersistentScale(); scale *= mDismissScale; setScaleX(scale); setScaleY(scale); updateSnapshotRadius(); } /** * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not * change according to a temporary state. */ public float getPersistentScale() { float scale = 1; float gridProgress = GRID_INTERPOLATOR.getInterpolation(mGridProgress); scale *= Utilities.mapRange(gridProgress, mNonGridScale, 1f); return scale; } private void setSplitSelectTranslationX(float x) { mSplitSelectTranslationX = x; applyTranslationX(); } private void setSplitSelectTranslationY(float y) { mSplitSelectTranslationY = y; applyTranslationY(); } public void setSplitScrollOffsetPrimary(float splitSelectScrollOffsetPrimary) { mSplitSelectScrollOffsetPrimary = splitSelectScrollOffsetPrimary; } private void setDismissTranslationX(float x) { mDismissTranslationX = x; applyTranslationX(); } private void setDismissTranslationY(float y) { mDismissTranslationY = y; applyTranslationY(); } private void setTaskOffsetTranslationX(float x) { mTaskOffsetTranslationX = x; applyTranslationX(); } private void setTaskOffsetTranslationY(float y) { mTaskOffsetTranslationY = y; applyTranslationY(); } private void setTaskResistanceTranslationX(float x) { mTaskResistanceTranslationX = x; applyTranslationX(); } private void setTaskResistanceTranslationY(float y) { mTaskResistanceTranslationY = y; applyTranslationY(); } private void setNonGridTranslationX(float nonGridTranslationX) { mNonGridTranslationX = nonGridTranslationX; applyTranslationX(); } private void setNonGridTranslationY(float nonGridTranslationY) { mNonGridTranslationY = nonGridTranslationY; applyTranslationY(); } public void setGridTranslationX(float gridTranslationX) { mGridTranslationX = gridTranslationX; applyTranslationX(); } public float getGridTranslationX() { return mGridTranslationX; } public void setGridTranslationY(float gridTranslationY) { mGridTranslationY = gridTranslationY; applyTranslationY(); } public float getGridTranslationY() { return mGridTranslationY; } private void setGridEndTranslationX(float gridEndTranslationX) { mGridEndTranslationX = gridEndTranslationX; applyTranslationX(); } public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { float scrollAdjustment = 0; if (gridEnabled) { scrollAdjustment += mGridTranslationX; } else { scrollAdjustment += getPrimaryNonGridTranslationProperty().get(this); } scrollAdjustment += mSplitSelectScrollOffsetPrimary; return scrollAdjustment; } public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) { return getScrollAdjustment(fullscreenEnabled, gridEnabled); } public float getSizeAdjustment(boolean fullscreenEnabled) { float sizeAdjustment = 1; if (fullscreenEnabled) { sizeAdjustment *= mNonGridScale; } return sizeAdjustment; } private void setBoxTranslationY(float boxTranslationY) { mBoxTranslationY = boxTranslationY; applyTranslationY(); } private void applyTranslationX() { setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX()); } private void applyTranslationY() { setTranslationY(mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY + mSplitSelectTranslationY + getPersistentTranslationY()); } /** * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does not * change according to a temporary state (e.g. task offset). */ public float getPersistentTranslationX() { return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX); } /** * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does not * change according to a temporary state (e.g. task offset). */ public float getPersistentTranslationY() { return mBoxTranslationY + getNonGridTrans(mNonGridTranslationY) + getGridTrans(mGridTranslationY); } public FloatProperty getPrimarySplitTranslationProperty() { return getPagedOrientationHandler().getPrimaryValue( SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y); } public FloatProperty getSecondarySplitTranslationProperty() { return getPagedOrientationHandler().getSecondaryValue( SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y); } public FloatProperty getPrimaryDismissTranslationProperty() { return getPagedOrientationHandler().getPrimaryValue( DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); } public FloatProperty getSecondaryDissmissTranslationProperty() { return getPagedOrientationHandler().getSecondaryValue( DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y); } public FloatProperty getPrimaryTaskOffsetTranslationProperty() { return getPagedOrientationHandler().getPrimaryValue( TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y); } public FloatProperty getTaskResistanceTranslationProperty() { return getPagedOrientationHandler().getSecondaryValue( TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y); } public FloatProperty getPrimaryNonGridTranslationProperty() { return getPagedOrientationHandler().getPrimaryValue( NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y); } public FloatProperty getSecondaryNonGridTranslationProperty() { return getPagedOrientationHandler().getSecondaryValue( NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y); } @Override public boolean hasOverlappingRendering() { // TODO: Clip-out the icon region from the thumbnail, since they are overlapping. return false; } public boolean isEndQuickswitchCuj() { return mEndQuickswitchCuj; } public void setEndQuickswitchCuj(boolean endQuickswitchCuj) { mEndQuickswitchCuj = endQuickswitchCuj; } private static final class TaskOutlineProvider extends ViewOutlineProvider { private int mMarginTop; private FullscreenDrawParams mFullscreenParams; TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) { mMarginTop = topMargin; mFullscreenParams = fullscreenParams; } public void updateParams(FullscreenDrawParams params, int topMargin) { mFullscreenParams = params; mMarginTop = topMargin; } @Override public void getOutline(View view, Outline outline) { RectF insets = mFullscreenParams.mCurrentDrawnInsets; float scale = mFullscreenParams.mScale; outline.setRoundRect(0, (int) (mMarginTop * scale), (int) ((insets.left + view.getWidth() + insets.right) * scale), (int) ((insets.top + view.getHeight() + insets.bottom) * scale), mFullscreenParams.mCurrentDrawnCornerRadius); } } private int getExpectedViewHeight(View view) { int expectedHeight; int h = view.getLayoutParams().height; if (h > 0) { expectedHeight = h; } else { int m = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY - 1, MeasureSpec.AT_MOST); view.measure(m, m); expectedHeight = view.getMeasuredHeight(); } return expectedHeight; } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.addAction( new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close, getContext().getText(R.string.accessibility_close))); final Context context = getContext(); for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { if (taskContainer == null) { continue; } for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, mActivity.getDeviceProfile(), taskContainer)) { info.addAction(s.createAccessibilityAction(context)); } } if (mDigitalWellBeingToast.hasLimit()) { info.addAction( new AccessibilityNodeInfo.AccessibilityAction( R.string.accessibility_app_usage_settings, getContext().getText(R.string.accessibility_app_usage_settings))); } final RecentsView recentsView = getRecentsView(); final AccessibilityNodeInfo.CollectionItemInfo itemInfo = AccessibilityNodeInfo.CollectionItemInfo.obtain( 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1, 1, false); info.setCollectionItemInfo(itemInfo); } @Override public boolean performAccessibilityAction(int action, Bundle arguments) { if (action == R.string.accessibility_close) { getRecentsView().dismissTask(this, true /*animateTaskView*/, true /*removeTask*/); return true; } if (action == R.string.accessibility_app_usage_settings) { mDigitalWellBeingToast.openAppUsageSettings(this); return true; } for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { if (taskContainer == null) { continue; } for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this, mActivity.getDeviceProfile(), taskContainer)) { if (s.hasHandlerForAction(action)) { s.onClick(this); return true; } } } return super.performAccessibilityAction(action, arguments); } public RecentsView getRecentsView() { return (RecentsView) getParent(); } PagedOrientationHandler getPagedOrientationHandler() { return getRecentsView().mOrientationState.getOrientationHandler(); } private void notifyTaskLaunchFailed(String tag) { String msg = "Failed to launch task"; if (mTask != null) { msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")"; } Log.w(tag, msg); Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show(); } /** * Hides the icon and shows insets when this TaskView is about to be shown fullscreen. * * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets. */ public void setFullscreenProgress(float progress) { progress = Utilities.boundToRange(progress, 0, 1); mFullscreenProgress = progress; mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); mSnapshotView.getTaskOverlay().setFullscreenProgress(progress); updateSnapshotRadius(); mOutlineProvider.updateParams( mCurrentFullscreenParams, mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx); invalidateOutline(); } protected void updateSnapshotRadius() { updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper()); mSnapshotView.setFullscreenParams(mCurrentFullscreenParams); } void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) { if (getRecentsView() == null) { return; } mCurrentFullscreenParams.setProgress(mFullscreenProgress, getRecentsView().getScaleX(), getScaleX(), getWidth(), mActivity.getDeviceProfile(), previewPositionHelper); } /** * Updates TaskView scaling and translation required to support variable width if enabled, while * ensuring TaskView fits into screen in fullscreen. */ void updateTaskSize() { ViewGroup.LayoutParams params = getLayoutParams(); float nonGridScale; float boxTranslationY; int expectedWidth; int expectedHeight; DeviceProfile deviceProfile = mActivity.getDeviceProfile(); if (deviceProfile.overviewShowAsGrid) { final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx; final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize(); final int taskWidth = lastComputedTaskSize.width(); final int taskHeight = lastComputedTaskSize.height(); int boxWidth; int boxHeight; boolean isFocusedTask = isFocusedTask(); if (isFocusedTask) { // Task will be focused and should use focused task size. Use focusTaskRatio // that is associated with the original orientation of the focused task. boxWidth = taskWidth; boxHeight = taskHeight; } else { // Otherwise task is in grid, and should use lastComputedGridTaskSize. Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize(); boxWidth = lastComputedGridTaskSize.width(); boxHeight = lastComputedGridTaskSize.height(); } // Bound width/height to the box size. expectedWidth = boxWidth; expectedHeight = boxHeight + thumbnailPadding; // Scale to to fit task Rect. nonGridScale = taskWidth / (float) boxWidth; // Align to top of task Rect. boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f; } else { nonGridScale = 1f; boxTranslationY = 0f; expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT; expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT; } setNonGridScale(nonGridScale); setBoxTranslationY(boxTranslationY); if (params.width != expectedWidth || params.height != expectedHeight) { params.width = expectedWidth; params.height = expectedHeight; setLayoutParams(params); } } private float getGridTrans(float endTranslation) { float progress = GRID_INTERPOLATOR.getInterpolation(mGridProgress); return Utilities.mapRange(progress, 0, endTranslation); } private float getNonGridTrans(float endTranslation) { return endTranslation - getGridTrans(endTranslation); } public boolean isRunningTask() { if (getRecentsView() == null) { return false; } return this == getRecentsView().getRunningTaskView(); } public boolean isFocusedTask() { if (getRecentsView() == null) { return false; } return this == getRecentsView().getFocusedTaskView(); } public void setShowScreenshot(boolean showScreenshot) { mShowScreenshot = showScreenshot; } public boolean showScreenshot() { if (!isRunningTask()) { return true; } return mShowScreenshot; } public void setOverlayEnabled(boolean overlayEnabled) { mSnapshotView.setOverlayEnabled(overlayEnabled); } public void initiateSplitSelect(SplitPositionOption splitPositionOption) { AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU); getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition); } /** * Set a color tint on the snapshot and supporting views. */ public void setColorTint(float amount, int tintColor) { mSnapshotView.setDimAlpha(amount); mIconView.setIconColorTint(tintColor, amount); mDigitalWellBeingToast.setBannerColorTint(tintColor, amount); } private int getRootViewDisplayId() { return getRootView().getDisplay().getDisplayId(); } /** * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. */ public static class FullscreenDrawParams { private final float mCornerRadius; private final float mWindowCornerRadius; public float mFullscreenProgress; public RectF mCurrentDrawnInsets = new RectF(); public float mCurrentDrawnCornerRadius; /** The current scale we apply to the thumbnail to adjust for new left/right insets. */ public float mScale = 1; public FullscreenDrawParams(Context context) { mCornerRadius = TaskCornerRadius.get(context); mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context); mCurrentDrawnCornerRadius = mCornerRadius; } /** * Sets the progress in range [0, 1] */ public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale, int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) { mFullscreenProgress = fullscreenProgress; RectF insets = pph.getInsetsToDrawInFullscreen(dp); float currentInsetsLeft = insets.left * fullscreenProgress; float currentInsetsRight = insets.right * fullscreenProgress; float insetsBottom = insets.bottom; if (dp.isTaskbarPresentInApps) { insetsBottom = Math.max(0, insetsBottom - dp.taskbarSize); } mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress, currentInsetsRight, insetsBottom * fullscreenProgress); float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius; mCurrentDrawnCornerRadius = Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius) / parentScale / taskViewScale; // We scaled the thumbnail to fit the content (excluding insets) within task view width. // Now that we are drawing left/right insets again, we need to scale down to fit them. if (previewWidth > 0) { mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight); } } } public class TaskIdAttributeContainer { private final TaskThumbnailView mThumbnailView; private final Task mTask; private final IconView mIconView; /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */ private @SplitConfigurationOptions.StagePosition int mStagePosition; @IdRes private final int mA11yNodeId; public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView, IconView iconView, int stagePosition) { this.mTask = task; this.mThumbnailView = thumbnailView; this.mIconView = iconView; this.mStagePosition = stagePosition; this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ? R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo; } public TaskThumbnailView getThumbnailView() { return mThumbnailView; } public Task getTask() { return mTask; } public WorkspaceItemInfo getItemInfo() { return TaskView.this.getItemInfo(mTask); } public TaskView getTaskView() { return TaskView.this; } public IconView getIconView() { return mIconView; } public int getStagePosition() { return mStagePosition; } void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) { this.mStagePosition = stagePosition; } public int getA11yNodeId() { return mA11yNodeId; } } }