mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-20 11:18:21 +00:00
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:
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user