Merge "Add initial split from GroupedTaskView animation" into tm-qpr-dev am: 4b0a5ed8fa

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/21613692

Change-Id: Ie67a493178748b7d44075e672a4a1ac3f66e7029
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Vinit Nayak
2023-03-09 21:43:25 +00:00
committed by Automerger Merge Worker
8 changed files with 427 additions and 49 deletions

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 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.util
import android.animation.ObjectAnimator
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.View
import com.android.launcher3.DeviceProfile
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.quickstep.views.TaskThumbnailView
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
import java.util.function.Supplier
/**
* Utils class to help run animations for initiating split screen from launcher.
* Will be expanded with future refactors. Works in conjunction with the state stored in
* [SplitSelectStateController]
*/
class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
companion object {
// Break this out into maybe enums? Abstractions into its own classes? Tbd.
data class SplitAnimInitProps(
val originalView: View,
val originalBitmap: Bitmap?,
val iconDrawable: Drawable,
val fadeWithThumbnail: Boolean,
val isStagedTask: Boolean,
val iconView: View?
)
}
/**
* Returns different elements to animate for the initial split selection animation
* depending on the state of the surface from which the split was initiated
*/
fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
splitSelectSourceSupplier: Supplier<SplitSelectSource>)
: SplitAnimInitProps {
if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
// Initiating from home
val splitSelectSource = splitSelectSourceSupplier.get()
return SplitAnimInitProps(splitSelectSource.view, originalBitmap = null,
splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
iconView = null)
} else if (splitSelectStateController.isDismissingFromSplitPair) {
// Initiating split from overview, but on a split pair
val taskView = taskViewSupplier.get()
for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
if (container.task.key.id == splitSelectStateController.initialTaskId) {
return SplitAnimInitProps(container.thumbnailView,
container.thumbnailView.thumbnail, container.iconView.drawable!!,
fadeWithThumbnail = true, isStagedTask = true,
iconView = container.iconView
)
}
}
throw IllegalStateException("Attempting to init split from existing split pair " +
"without a valid taskIdAttributeContainer")
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
taskView.iconView.drawable!!, fadeWithThumbnail = true, isStagedTask = true,
taskView.iconView
)
}
}
/**
* When selecting first app from split pair, second app's thumbnail remains. This animates
* the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
* it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
* Note: The app that **was not** selected as the first split app should be the container that's
* passed through.
*
* @param builder Adds animation to this
* @param taskIdAttributeContainer container of the app that **was not** selected
* @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
* (opposite of that representing [taskIdAttributeContainer])
*/
fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
builder: PendingAnimation, deviceProfile: DeviceProfile,
taskViewWidth: Int, taskViewHeight: Int,
isPrimaryTaskSplitting: Boolean) {
val thumbnail = taskIdAttributeContainer.thumbnailView
val iconView: View = taskIdAttributeContainer.iconView
builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
thumbnail.setShowSplashForSplitSelection(true)
if (deviceProfile.isLandscape) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
builder.add(ObjectAnimator.ofFloat(thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
-centerIconTranslationX))
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
// Reset other dimensions
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
thumbnail.scaleY = 1f
val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
builder.add(ObjectAnimator.ofFloat(thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
translateYResetVal))
} else {
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
// primary thumbnail has layout margin above it, so secondary thumbnail needs to take
// that into account. We should migrate to only using translations otherwise this
// asymmetry causes problems..
// Icon defaults to center | horizontal, we add additional translation for split
val centerIconTranslationX = 0f
var centerThumbnailTranslationY: Float
// TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
// thumbnail needs to take that into account. We should migrate to only using
// translations otherwise this asymmetry causes problems..
if (isPrimaryTaskSplitting) {
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
.toFloat()
} else {
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
}
val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
builder.add(ObjectAnimator.ofFloat(thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
centerIconTranslationX))
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
// Reset other dimensions
thumbnail.scaleX = 1f
builder.add(ObjectAnimator.ofFloat(thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
}
}
}

View File

@@ -82,6 +82,7 @@ public class SplitSelectStateController {
private final Context mContext;
private final Handler mHandler;
private final RecentsModel mRecentTasksModel;
private final SplitAnimationController mSplitAnimationController;
private StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
@@ -96,6 +97,11 @@ public class SplitSelectStateController {
private boolean mRecentsAnimationRunning;
/** If {@code true}, animates the existing task view split placeholder view */
private boolean mAnimateCurrentTaskDismissal;
/**
* Acts as a subset of {@link #mAnimateCurrentTaskDismissal}, we can't be dismissing from a
* split pair task view without wanting to animate current task dismissal overall
*/
private boolean mDismissingFromSplitPair;
@Nullable
private UserHandle mUser;
/** If not null, this is the TaskView we want to launch from */
@@ -116,6 +122,7 @@ public class SplitSelectStateController {
mStateManager = stateManager;
mDepthController = depthController;
mRecentTasksModel = recentsModel;
mSplitAnimationController = new SplitAnimationController(this);
}
/**
@@ -399,6 +406,18 @@ public class SplitSelectStateController {
mAnimateCurrentTaskDismissal = animateCurrentTaskDismissal;
}
public boolean isDismissingFromSplitPair() {
return mDismissingFromSplitPair;
}
public void setDismissingFromSplitPair(boolean dismissingFromSplitPair) {
mDismissingFromSplitPair = dismissingFromSplitPair;
}
public SplitAnimationController getSplitAnimationController() {
return mSplitAnimationController;
}
/**
* Requires Shell Transitions
*/
@@ -506,6 +525,7 @@ public class SplitSelectStateController {
mItemInfo = null;
mSplitEvent = null;
mAnimateCurrentTaskDismissal = false;
mDismissingFromSplitPair = false;
}
/**
@@ -532,6 +552,10 @@ public class SplitSelectStateController {
return mInitialTaskId;
}
public int getSecondTaskId() {
return mSecondTaskId;
}
private boolean isSecondTaskIntentSet() {
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
}

View File

@@ -496,7 +496,7 @@ public class DesktopTaskView extends TaskView {
}
@Override
void setThumbnailVisibility(int visibility) {
void setThumbnailVisibility(int visibility, int taskId) {
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
mSnapshotViewMap.valueAt(i).setVisibility(visibility);
}

View File

@@ -1,5 +1,7 @@
package com.android.quickstep.views;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -25,6 +27,7 @@ import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -267,6 +270,19 @@ public class GroupedTaskView extends TaskView {
@Override
protected int getLastSelectedChildTaskIndex() {
SplitSelectStateController splitSelectController =
getRecentsView().getSplitSelectController();
if (splitSelectController.isDismissingFromSplitPair()) {
// return the container index of the task that wasn't initially selected to split with
// because that is the only remaining app that can be selected. The coordinate checks
// below aren't reliable since both of those views may be gone/transformed
int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
if (initSplitTaskId != INVALID_TASK_ID) {
return initSplitTaskId == mTask.key.id ? 1 : 0;
}
}
// Check which of the two apps was selected
if (isCoordInView(mIconView2, mLastTouchDownPosition)
|| isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
return 1;
@@ -296,9 +312,30 @@ public class GroupedTaskView extends TaskView {
if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
return;
}
getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
int initSplitTaskId = getThisTaskCurrentlyInSplitSelection();
if (initSplitTaskId == INVALID_TASK_ID) {
getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
// Should we be having a separate translation step apart from the measuring above?
// The following only applies to large screen for now, but for future reference
// we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
// translation directions
mSnapshotView.applySplitSelectTranslateX(mSnapshotView.getTranslationX());
mSnapshotView.applySplitSelectTranslateY(mSnapshotView.getTranslationY());
mSnapshotView2.applySplitSelectTranslateX(mSnapshotView2.getTranslationX());
mSnapshotView2.applySplitSelectTranslateY(mSnapshotView2.getTranslationY());
} else {
// Currently being split with this taskView, let the non-split selected thumbnail
// take up full thumbnail area
TaskIdAttributeContainer container =
mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
container.getThumbnailView().measure(widthMeasureSpec,
View.MeasureSpec.makeMeasureSpec(
heightSize -
mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
MeasureSpec.EXACTLY));
}
updateIconPlacement();
}
@@ -379,21 +416,27 @@ public class GroupedTaskView extends TaskView {
mSnapshotView2.refreshSplashView();
}
@Override
protected void resetViewTransforms() {
super.resetViewTransforms();
mSnapshotView2.resetViewTransforms();
}
/**
* Sets visibility for thumbnails and associated elements (DWB banners).
* IconView is unaffected.
* Sets visibility for thumbnails and associated elements (DWB banners).
* IconView is unaffected.
*
* When setting INVISIBLE, sets the visibility for the last selected child task.
* When setting VISIBLE (as a reset), sets the visibility for both tasks.
* When setting INVISIBLE, sets the visibility for the last selected child task.
* When setting VISIBLE (as a reset), sets the visibility for both tasks.
*/
@Override
void setThumbnailVisibility(int visibility) {
void setThumbnailVisibility(int visibility, int taskId) {
if (visibility == VISIBLE) {
mSnapshotView.setVisibility(visibility);
mDigitalWellBeingToast.setBannerVisibility(visibility);
mSnapshotView2.setVisibility(visibility);
mDigitalWellBeingToast2.setBannerVisibility(visibility);
} else if (getLastSelectedChildTaskIndex() == 0) {
} else if (taskId == getTaskIds()[0]) {
mSnapshotView.setVisibility(visibility);
mDigitalWellBeingToast.setBannerVisibility(visibility);
} else {

View File

@@ -184,6 +184,7 @@ import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SurfaceTransaction;
@@ -192,6 +193,7 @@ import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.VibrationConstants;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -935,7 +937,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
if (mHandleTaskStackChanges) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
for (TaskView.TaskIdAttributeContainer container :
for (TaskIdAttributeContainer container :
taskView.getTaskIdAttributeContainers()) {
if (container == null || taskId != container.getTask().key.id) {
continue;
@@ -3099,29 +3101,26 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
RectF startingTaskRect = new RectF();
safeRemoveDragLayerView(mFirstFloatingTaskView);
SplitAnimInitProps splitAnimInitProps =
mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews(
() -> mSplitHiddenTaskView, () -> mSplitSelectSource);
if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
// Create the split select animation from Overview
mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE);
anim.setViewAlpha(mSplitHiddenTaskView.getIconView(), 0, clampToProgress(LINEAR,
mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE,
mSplitSelectStateController.getInitialTaskId());
anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR,
timings.getIconFadeStartOffset(),
timings.getIconFadeEndOffset()));
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
mSplitHiddenTaskView.getThumbnail(),
mSplitHiddenTaskView.getThumbnail().getThumbnail(),
mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
true /* fadeWithThumbnail */, true /* isStagedTask */);
} else {
// Create the split select animation from Home
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
mSplitSelectSource.view, null /* thumbnail */,
mSplitSelectSource.drawable, startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
}
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
splitAnimInitProps.getOriginalView(),
splitAnimInitProps.getOriginalBitmap(),
splitAnimInitProps.getIconDrawable(), startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
mFirstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask());
// Allow user to click staged app to launch into fullscreen
if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
@@ -4450,7 +4449,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mTaskViewsSecondarySplitTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = requireTaskViewAt(i);
if (taskView == mSplitHiddenTaskView) {
if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) {
continue;
}
taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
@@ -4501,6 +4500,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView);
mSplitSelectStateController
.setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal);
// Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
&& mSplitHiddenTaskView.containsMultipleTasks());
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
@@ -4519,8 +4522,32 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
* Modifies a PendingAnimation with the animations for entering split staging
*/
public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) {
if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) {
// Splitting from Overview
boolean isInitiatingSplitFromTaskView =
mSplitSelectStateController.isAnimateCurrentTaskDismissal();
boolean isInitiatingTaskViewSplitPair =
mSplitSelectStateController.isDismissingFromSplitPair();
if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair) {
// Splitting from Overview for split pair task
createInitialSplitSelectAnimation(builder);
// Animate pair thumbnail into full thumbnail
boolean primaryTaskSelected =
mSplitHiddenTaskView.getTaskIdAttributeContainers()[0].getTask().key.id ==
mSplitSelectStateController.getInitialTaskId();
TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
.getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
TaskThumbnailView thumbnail = taskIdAttributeContainer.getThumbnailView();
mSplitSelectStateController.getSplitAnimationController()
.addInitialSplitFromPair(taskIdAttributeContainer, builder,
mActivity.getDeviceProfile(),
mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
primaryTaskSelected);
builder.addOnFrameCallback(() ->{
thumbnail.refreshSplashView();
mSplitHiddenTaskView.updateSnapshotRadius();
});
} else if (isInitiatingSplitFromTaskView) {
// Splitting from Overview for fullscreen task
createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
true /* dismissingForSplitSelection*/);
} else {
@@ -4608,7 +4635,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSecondSplitHiddenView = containerTaskView;
if (mSecondSplitHiddenView != null) {
mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
mSplitSelectStateController.getSecondTaskId());
}
InteractionJankMonitorWrapper.begin(this,
@@ -4634,7 +4662,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
if (mSecondSplitHiddenView != null) {
mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE);
mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSecondSplitHiddenView = null;
}
@@ -4660,7 +4688,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
resetTaskVisuals();
mSplitHiddenTaskViewIndex = -1;
if (mSplitHiddenTaskView != null) {
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
@@ -5530,7 +5558,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
taskView.setShowScreenshot(true);
for (TaskView.TaskIdAttributeContainer container :
for (TaskIdAttributeContainer container :
taskView.getTaskIdAttributeContainers()) {
if (container == null) {
continue;

View File

@@ -81,6 +81,47 @@ public class TaskThumbnailView extends View {
}
};
public static final Property<TaskThumbnailView, Float> SPLASH_ALPHA =
new FloatProperty<TaskThumbnailView>("splashAlpha") {
@Override
public void setValue(TaskThumbnailView thumbnail, float splashAlpha) {
thumbnail.setSplashAlpha(splashAlpha);
}
@Override
public Float get(TaskThumbnailView thumbnailView) {
return thumbnailView.mSplashAlpha / 255f;
}
};
/** Use to animate thumbnail translationX while first app in split selection is initiated */
public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_X =
new FloatProperty<TaskThumbnailView>("splitSelectTranslateX") {
@Override
public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateX) {
thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
}
@Override
public Float get(TaskThumbnailView thumbnailView) {
return thumbnailView.mSplitSelectTranslateX;
}
};
/** Use to animate thumbnail translationY while first app in split selection is initiated */
public static final Property<TaskThumbnailView, Float> SPLIT_SELECT_TRANSLATE_Y =
new FloatProperty<TaskThumbnailView>("splitSelectTranslateY") {
@Override
public void setValue(TaskThumbnailView thumbnail, float splitSelectTranslateY) {
thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
}
@Override
public Float get(TaskThumbnailView thumbnailView) {
return thumbnailView.mSplitSelectTranslateY;
}
};
private final BaseActivity mActivity;
@Nullable
private TaskOverlay mOverlay;
@@ -111,6 +152,10 @@ public class TaskThumbnailView extends View {
private int mSplashAlpha = 0;
private boolean mOverlayEnabled;
/** Used as a placeholder when the original thumbnail animates out to. */
private boolean mShowSplashForSplitSelection;
private float mSplitSelectTranslateX;
private float mSplitSelectTranslateY;
public TaskThumbnailView(Context context) {
this(context, null);
@@ -342,10 +387,17 @@ public class TaskThumbnailView extends View {
// Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
if (shouldShowSplashView()) {
float cornerRadiusX = cornerRadius;
float cornerRadiusY = cornerRadius;
if (mShowSplashForSplitSelection) {
cornerRadiusX = cornerRadius / getScaleX();
cornerRadiusY = cornerRadius / getScaleY();
}
// Always draw background for hiding inconsistencies, even if splash view is not yet
// loaded (which can happen as task icons are loaded asynchronously in the background)
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadius,
cornerRadius, mSplashBackgroundPaint);
canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX,
cornerRadiusY, mSplashBackgroundPaint);
if (mSplashView != null) {
mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
mSplashView.draw(canvas);
@@ -353,6 +405,31 @@ public class TaskThumbnailView extends View {
}
}
/** See {@link #SPLIT_SELECT_TRANSLATE_X} */
protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
mSplitSelectTranslateX = splitSelectTranslateX;
applyTranslateX();
}
/** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
mSplitSelectTranslateY = splitSelectTranslateY;
applyTranslateY();
}
private void applyTranslateX() {
setTranslationX(mSplitSelectTranslateX);
}
private void applyTranslateY() {
setTranslationY(mSplitSelectTranslateY);
}
protected void resetViewTransforms() {
mSplitSelectTranslateX = 0;
mSplitSelectTranslateY = 0;
}
public TaskView getTaskView() {
return (TaskView) getParent();
}
@@ -373,7 +450,12 @@ public class TaskThumbnailView extends View {
*/
public boolean shouldShowSplashView() {
return isThumbnailAspectRatioDifferentFromThumbnailData()
|| isThumbnailRotationDifferentFromTask();
|| isThumbnailRotationDifferentFromTask()
|| mShowSplashForSplitSelection;
}
public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) {
mShowSplashForSplitSelection = showSplashForSplitSelection;
}
protected void refreshSplashView() {
@@ -396,7 +478,6 @@ public class TaskThumbnailView extends View {
imageView.setScaleType(ImageView.ScaleType.MATRIX);
Matrix matrix = new Matrix();
float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
float viewWidth = getMeasuredWidth();
@@ -408,12 +489,13 @@ public class TaskThumbnailView extends View {
float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
float scale = nonGridScale * recentsMaxScale;
float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX());
float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY());
// Center the image in the view.
matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
// Apply scale transformation after translation, pivoting around center of view.
matrix.postScale(scale, scale, viewCenterX, viewCenterY);
matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY);
imageView.setImageMatrix(matrix);
mSplashView = imageView;

View File

@@ -16,6 +16,7 @@
package com.android.quickstep.views;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.widget.Toast.LENGTH_SHORT;
@@ -41,6 +42,7 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.IdRes;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
@@ -706,9 +708,12 @@ public class TaskView extends FrameLayout implements Reusable {
}
SplitSelectStateController splitSelectStateController =
recentsView.getSplitSelectController();
if (splitSelectStateController.isSplitSelectActive() &&
splitSelectStateController.getInitialTaskId() == getTask().key.id) {
// Prevent taps on the this taskview if it's being animated into split select state
// Disable taps for split selection animation unless we have multiple tasks
boolean disableTapsForSplitSelect =
splitSelectStateController.isSplitSelectActive()
&& splitSelectStateController.getInitialTaskId() == getTask().key.id
&& !containsMultipleTasks();
if (disableTapsForSplitSelect) {
return false;
}
@@ -718,6 +723,25 @@ public class TaskView extends FrameLayout implements Reusable {
return super.dispatchTouchEvent(ev);
}
/**
* @return taskId that split selection was initiated with,
* {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
* split selection
*/
protected int getThisTaskCurrentlyInSplitSelection() {
SplitSelectStateController splitSelectController =
getRecentsView().getSplitSelectController();
int initSplitTaskId = INVALID_TASK_ID;
for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
int taskId = container.getTask().key.id;
if (taskId == splitSelectController.getInitialTaskId()) {
initSplitTaskId = taskId;
break;
}
}
return initSplitTaskId;
}
private void onClick(View view) {
if (getTask() == null) {
return;
@@ -747,6 +771,8 @@ public class TaskView extends FrameLayout implements Reusable {
/**
* Returns the task index of the last selected child task (0 or 1).
* If we contain multiple tasks and this TaskView is used as part of split selection, the
* selected child task index will be that of the remaining task.
*/
protected int getLastSelectedChildTaskIndex() {
return 0;
@@ -1084,6 +1110,8 @@ public class TaskView extends FrameLayout implements Reusable {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
// TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
// of a hybrid of both margins and translations
LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
snapshotParams.topMargin = thumbnailTopMargin;
mSnapshotView.setLayoutParams(snapshotParams);
@@ -1179,6 +1207,7 @@ public class TaskView extends FrameLayout implements Reusable {
setAlpha(mStableAlpha);
setIconScaleAndDim(1);
setColorTint(0, 0);
mSnapshotView.resetViewTransforms();
}
public void setStableAlpha(float parentAlpha) {
@@ -1720,10 +1749,12 @@ public class TaskView extends FrameLayout implements Reusable {
}
/**
* Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
* IconView is unaffected.
* Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
* IconView is unaffected.
*
* @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
*/
void setThumbnailVisibility(int visibility) {
void setThumbnailVisibility(int visibility, int taskId) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child != mIconView) {

View File

@@ -635,6 +635,9 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
primarySnapshot.setTranslationX(0);
}
secondarySnapshot.setTranslationY(spaceAboveSnapshot);
// Reset unused translations
primarySnapshot.setTranslationY(0);
} else {
int deviceHeightWithoutTaskbar = dp.availableHeightPx - dp.taskbarSize;
float scale = (float) totalThumbnailHeight / deviceHeightWithoutTaskbar;
@@ -669,6 +672,10 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
View.MeasureSpec.EXACTLY));
primarySnapshot.setScaleX(1);
secondarySnapshot.setScaleX(1);
primarySnapshot.setScaleY(1);
secondarySnapshot.setScaleY(1);
}
@Override
@@ -699,13 +706,13 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
: deviceProfile.getInsets().left;
int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
- fullscreenInsetThickness) / 2);
float midpointFromBottomPct = (float) fullscreenMidpointFromBottom
float midpointFromEndPct = (float) fullscreenMidpointFromBottom
/ deviceProfile.widthPx;
float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
int spaceAboveSnapshots = 0;
int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
* midpointFromBottomPct);
* midpointFromEndPct);
int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
if (deviceProfile.isSeascape()) {