blob: 7b4bf6251e91b7e17d5b65f100393f1f4c056aa2 [file] [log] [blame]
/*
* 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_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.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 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.LauncherSplitScreenListener;
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.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.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();
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<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
public static final FloatProperty<TaskView> FOCUS_TRANSITION =
new FloatProperty<TaskView>("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<TaskView> SPLIT_SELECT_TRANSLATION_X =
new FloatProperty<TaskView>("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<TaskView> SPLIT_SELECT_TRANSLATION_Y =
new FloatProperty<TaskView>("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<TaskView> DISMISS_TRANSLATION_X =
new FloatProperty<TaskView>("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<TaskView> DISMISS_TRANSLATION_Y =
new FloatProperty<TaskView>("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<TaskView> TASK_OFFSET_TRANSLATION_X =
new FloatProperty<TaskView>("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<TaskView> TASK_OFFSET_TRANSLATION_Y =
new FloatProperty<TaskView>("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<TaskView> TASK_RESISTANCE_TRANSLATION_X =
new FloatProperty<TaskView>("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<TaskView> TASK_RESISTANCE_TRANSLATION_Y =
new FloatProperty<TaskView>("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<TaskView> NON_GRID_TRANSLATION_X =
new FloatProperty<TaskView>("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<TaskView> NON_GRID_TRANSLATION_Y =
new FloatProperty<TaskView>("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<TaskView> GRID_END_TRANSLATION_X =
new FloatProperty<TaskView>("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<TaskView> SNAPSHOT_SCALE =
new FloatProperty<TaskView>("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;
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;
private 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 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
private CancellableTask mThumbnailLoadRequest;
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, AttributeSet attrs) {
this(context, attrs, 0);
}
public TaskView(Context context, 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,
STAGE_POSITION_UNDEFINED);
mSnapshotView.bind(task);
setOrientationState(orientedState);
}
public TaskIdAttributeContainer[] getTaskIdAttributeContainers() {
return mTaskIdAttributeContainer;
}
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 TaskThumbnailView getThumbnail() {
return mSnapshotView;
}
/** 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;
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
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;
RecentsView recentsView = getRecentsView();
RemoteAnimationTargets targets;
RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
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 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.
*/
public RunnableList launchTaskAnimated() {
if (mTask != null) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
ActivityOptionsWrapper opts = mActivity.getActivityLaunchOptions(this, null);
opts.options.setLaunchDisplayId(getRootViewDisplayId());
boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
.getPersistentSplitIds().length > 0;
if (ActivityManagerWrapper.getInstance()
.startActivityFromRecents(mTask.key, opts.options)) {
if (isOldTaskSplit) {
SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id);
}
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<Boolean> callback) {
launchTask(callback, false /* freezeTaskList */);
}
/**
* Starts the task associated with this view without any animation
*/
public void launchTask(@NonNull Consumer<Boolean> 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 (!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) {
return TaskMenuView.showForTask(mTaskIdAttributeContainer[0]);
}
protected void setIcon(IconView iconView, 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 = deviceProfile.overviewShowAsGrid && !isFocusedTask();
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);
}
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();
}
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);
}
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<TaskView> getPrimarySplitTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
}
public FloatProperty<TaskView> getSecondarySplitTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
}
public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
public FloatProperty<TaskView> getSecondaryDissmissTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
}
public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
}
public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
}
public FloatProperty<TaskView> getPrimaryNonGridTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
}
public FloatProperty<TaskView> 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();
// TODO(b/200609838) Determine which task to run A11y action on when in split screen
for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
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;
}
// TODO(b/200609838) Determine which task to run A11y action on when in split screen
for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
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();
}
private 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 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) {
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;
/** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
private @SplitConfigurationOptions.StagePosition int mStagePosition;
public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
int stagePosition) {
this.mTask = task;
this.mThumbnailView = thumbnailView;
this.mStagePosition = stagePosition;
}
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 int getStagePosition() {
return mStagePosition;
}
void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
this.mStagePosition = stagePosition;
}
}
}