diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/go/quickstep/res/drawable/default_thumbnail.xml index 0a2dbf03de..ab22dcf827 100644 --- a/go/quickstep/res/drawable/default_thumbnail.xml +++ b/go/quickstep/res/drawable/default_thumbnail.xml @@ -18,5 +18,5 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - + diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml new file mode 100644 index 0000000000..ee154fcafc --- /dev/null +++ b/go/quickstep/res/values/dimens.xml @@ -0,0 +1,19 @@ + + + + 3dp + \ No newline at end of file diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java index 1b6f2e34d9..9282345532 100644 --- a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java +++ b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java @@ -161,6 +161,13 @@ public final class ContentFillItemAnimator extends SimpleItemAnimator { private void animateChangeImpl(ViewHolder viewHolder, long startDelay) { TaskItemView itemView = (TaskItemView) viewHolder.itemView; + if (itemView.getAlpha() == 0) { + // View is still not visible, so we can finish the change immediately. + CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f); + dispatchChangeFinished(viewHolder, true /* oldItem */); + dispatchFinishedWhenDone(); + return; + } final ObjectAnimator anim = ObjectAnimator.ofFloat(itemView, CONTENT_TRANSITION_PROGRESS, 0.0f, 1.0f); anim.setDuration(ITEM_CHANGE_DURATION).setStartDelay(startDelay); diff --git a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java index 6ef9039f68..a8cc0a1b83 100644 --- a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java +++ b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java @@ -16,17 +16,23 @@ package com.android.quickstep; +import static android.graphics.Shader.TileMode.CLAMP; + +import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; +import com.android.launcher3.R; import com.android.systemui.shared.recents.model.ThumbnailData; /** @@ -39,11 +45,18 @@ public final class ThumbnailDrawable extends Drawable { private final Paint mPaint = new Paint(); private final Matrix mMatrix = new Matrix(); private final ThumbnailData mThumbnailData; + private final BitmapShader mShader; + private final RectF mDestRect = new RectF(); + private final int mCornerRadius; private int mRequestedOrientation; - public ThumbnailDrawable(@NonNull ThumbnailData thumbnailData, int requestedOrientation) { + public ThumbnailDrawable(Resources res, @NonNull ThumbnailData thumbnailData, + int requestedOrientation) { mThumbnailData = thumbnailData; mRequestedOrientation = requestedOrientation; + mCornerRadius = (int) res.getDimension(R.dimen.task_thumbnail_corner_radius); + mShader = new BitmapShader(mThumbnailData.thumbnail, CLAMP, CLAMP); + mPaint.setShader(mShader); updateMatrix(); } @@ -64,12 +77,13 @@ public final class ThumbnailDrawable extends Drawable { if (mThumbnailData.thumbnail == null) { return; } - canvas.drawBitmap(mThumbnailData.thumbnail, mMatrix, mPaint); + canvas.drawRoundRect(mDestRect, mCornerRadius, mCornerRadius, mPaint); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); + mDestRect.set(bounds); updateMatrix(); } @@ -125,5 +139,6 @@ public final class ThumbnailDrawable extends Drawable { } // Scale to fill. mMatrix.postScale(scaleX, scaleY); + mShader.setLocalMatrix(mMatrix); } } diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 0b5ed569c8..90192057be 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -177,7 +177,8 @@ public final class TaskItemView extends LinearLayout { return mDefaultThumbnail; } int orientation = getResources().getConfiguration().orientation; - return new ThumbnailDrawable(thumbnailData, orientation /* requestedOrientation */); + return new ThumbnailDrawable(getResources(), thumbnailData, + orientation /* requestedOrientation */); } private @NonNull String getSafeLabel(@Nullable String label) { diff --git a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java index b1c60dd978..0bad77b607 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java @@ -16,6 +16,9 @@ package com.android.quickstep.views; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.View.MeasureSpec.makeMeasureSpec; + import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; @@ -53,15 +56,20 @@ final class TaskThumbnailIconView extends ViewGroup { int width = height; setMeasuredDimension(width, height); + int subItemSize = (int) (SUBITEM_FRAME_RATIO * height); if (mThumbnailView.getVisibility() != GONE) { - int thumbnailHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - int thumbnailWidthSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY); + boolean isPortrait = + (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT); + int thumbnailHeightSpec = + makeMeasureSpec(isPortrait ? height : subItemSize, MeasureSpec.EXACTLY); + int thumbnailWidthSpec = + makeMeasureSpec(isPortrait ? subItemSize : width, MeasureSpec.EXACTLY); measureChild(mThumbnailView, thumbnailWidthSpec, thumbnailHeightSpec); } if (mIconView.getVisibility() != GONE) { - int iconHeightSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY); - int iconWidthSpec = MeasureSpec.makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY); + int iconHeightSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY); + int iconWidthSpec = makeMeasureSpec(subItemSize, MeasureSpec.EXACTLY); measureChild(mIconView, iconWidthSpec, iconHeightSpec); } } diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 4974dcbdc9..02c6b0f419 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -71,6 +71,7 @@ enum ItemType { NOTIFICATION = 8; TASK = 9; // Each page of Recents UI (QuickStep) WEB_APP = 10; + TASK_ICON = 11; } // Used to define what type of container a Target would represent. @@ -115,6 +116,7 @@ enum ControlType { REMOTE_ACTION_SHORTCUT = 17; APP_USAGE_SETTINGS = 18; BACK_GESTURE = 19; + UNDO = 20; } enum TipType { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java index a76ecd53b1..5e7faf7274 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java @@ -45,7 +45,7 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.InputMonitorCompat; -import com.android.systemui.shared.system.NavigationBarCompat; +import com.android.systemui.shared.system.QuickStepContract; /** * Touch consumer for handling events to launch assistant from launcher @@ -103,7 +103,7 @@ public class AssistantTouchConsumer implements InputConsumer { mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold); mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold); mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold); - mSlop = NavigationBarCompat.getQuickStepDragSlopPx(); + mSlop = QuickStepContract.getQuickStepDragSlopPx(); mInputMonitorCompat = inputMonitorCompat; mActivityControlHelper = activityControlHelper; mState = STATE_INACTIVE; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 35783b58a1..a033402ffe 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -16,14 +16,15 @@ package com.android.quickstep; import static android.view.View.TRANSLATION_Y; - import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO; import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS; +import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -177,6 +178,8 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return new AnimationFactory() { private Animator mShelfAnim; private ShelfAnimState mShelfState; + private Animator mAttachToWindowAnim; + private boolean mIsAttachedToWindow; @Override public void createActivityController(long transitionLength) { @@ -221,6 +224,28 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe mShelfAnim.setDuration(duration); mShelfAnim.start(); } + + @Override + public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { + if (mIsAttachedToWindow == attached && animate) { + return; + } + mIsAttachedToWindow = attached; + if (mAttachToWindowAnim != null) { + mAttachToWindowAnim.cancel(); + } + mAttachToWindowAnim = ObjectAnimator.ofFloat(activity.getOverviewPanel(), + RecentsView.CONTENT_ALPHA, attached ? 1 : 0); + mAttachToWindowAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAttachToWindowAnim = null; + } + }); + mAttachToWindowAnim.setInterpolator(ACCEL_DEACCEL); + mAttachToWindowAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0); + mAttachToWindowAnim.start(); + } }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java index 44ba515d76..5dc641fbeb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java @@ -60,7 +60,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.BackgroundExecutor; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.InputMonitorCompat; -import com.android.systemui.shared.system.NavigationBarCompat; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.WindowManagerWrapper; import java.util.function.Consumer; @@ -152,8 +152,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mDisplayRotation = display.getRotation(); WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); - mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx(); - mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx(); + mDragSlop = QuickStepContract.getQuickStepDragSlopPx(); + mTouchSlop = QuickStepContract.getQuickStepTouchSlopPx(); mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture; } @@ -267,6 +267,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement || isLikelyToStartNewTask); mMotionPauseDetector.addPosition(displacement, ev.getEventTime()); + mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask); } } break; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index e1b38b3336..afc4fcb594 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -43,6 +43,7 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; @@ -111,6 +112,8 @@ public class WindowTransformSwipeHandler implements SwipeAnimationListener, OnApplyWindowInsetsListener { private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); + private static final Rect TEMP_RECT = new Rect(); + private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; private static int getFlagForIndex(int index, String name) { @@ -162,22 +165,23 @@ public class WindowTransformSwipeHandler STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; enum GestureEndTarget { - HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE), + HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false), RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT - | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER), + | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true), - NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP), + NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP, true), - LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP); + LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false); GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, - int containerType) { + int containerType, boolean recentsAttachedToAppWindow) { this.endShift = endShift; this.endState = endState; this.isLauncher = isLauncher; this.canBeContinued = canBeContinued; this.containerType = containerType; + this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; } /** 0 is app, 1 is overview */ @@ -190,6 +194,8 @@ public class WindowTransformSwipeHandler public final boolean canBeContinued; /** Used to log where the user ended up after the gesture ends */ public final int containerType; + /** Whether RecentsView should be attached to the window as we animate to this target */ + public final boolean recentsAttachedToAppWindow; } public static final long MAX_SWIPE_DURATION = 350; @@ -202,6 +208,7 @@ public class WindowTransformSwipeHandler private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; private static final long SHELF_ANIM_DURATION = 120; + public static final long RECENTS_ATTACH_DURATION = 300; /** * Used as the page index for logging when we return to the last task at the end of the gesture. @@ -254,6 +261,7 @@ public class WindowTransformSwipeHandler private int mLogAction = Touch.SWIPE; private int mLogDirection = Direction.UP; private PointF mDownPos; + private boolean mIsLikelyToStartNewTask; private final RecentsAnimationWrapper mRecentsAnimationWrapper; @@ -277,6 +285,9 @@ public class WindowTransformSwipeHandler mMode = SysUINavigationMode.getMode(context); initStateCallbacks(); + + DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); + initTransitionEndpoints(dp); } private void initStateCallbacks() { @@ -434,6 +445,7 @@ public class WindowTransformSwipeHandler mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated); + maybeUpdateRecentsAttachedState(false /* animate */); }; if (mWasLauncherAlreadyVisible) { // Launcher is visible, but might be about to stop. Thus, if we prepare recents @@ -538,10 +550,65 @@ public class WindowTransformSwipeHandler setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION); } + public void maybeUpdateRecentsAttachedState() { + maybeUpdateRecentsAttachedState(true /* animate */); + } + + /** + * Determines whether to show or hide RecentsView. The window is always + * synchronized with its corresponding TaskView in RecentsView, so if + * RecentsView is shown, it will appear to be attached to the window. + * + * Note this method has no effect unless the navigation mode is NO_BUTTON. + */ + private void maybeUpdateRecentsAttachedState(boolean animate) { + if (mMode != Mode.NO_BUTTON || mRecentsView == null) { + return; + } + RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null + ? null + : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId); + final boolean recentsAttachedToAppWindow; + int runningTaskIndex = mRecentsView.getRunningTaskIndex(); + if (mContinuingLastGesture) { + recentsAttachedToAppWindow = true; + animate = false; + } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) { + // The window is going away so make sure recents is always visible in this case. + recentsAttachedToAppWindow = true; + animate = false; + } else { + if (mGestureEndTarget != null) { + recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow; + } else { + recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask; + } + if (animate) { + // Only animate if an adjacent task view is visible on screen. + TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1); + TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1); + animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT)) + || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT)); + } + } + mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate); + } + + public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { + if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) { + mIsLikelyToStartNewTask = isLikelyToStartNewTask; + maybeUpdateRecentsAttachedState(); + } + } + @UiThread public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { mAnimationFactory.setShelfState(shelfState, interpolator, duration); + boolean wasShelfPeeking = mIsShelfPeeking; mIsShelfPeeking = shelfState == PEEK; + if (mIsShelfPeeking != wasShelfPeeking) { + maybeUpdateRecentsAttachedState(); + } if (mRecentsView != null && shelfState.shouldPreformHaptic) { mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); @@ -869,6 +936,8 @@ public class WindowTransformSwipeHandler Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) { mGestureEndTarget = target; + maybeUpdateRecentsAttachedState(); + if (mGestureEndTarget == HOME) { HomeAnimationFactory homeAnimFactory; if (mActivity != null) { @@ -902,8 +971,15 @@ public class WindowTransformSwipeHandler windowAnim.start(velocityPxPerMs); mLauncherTransitionController = null; } else { - Animator windowAnim = mCurrentShift.animateToValue(start, end); + ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end); windowAnim.setDuration(duration).setInterpolator(interpolator); + windowAnim.addUpdateListener(valueAnimator -> { + if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) { + // Views typically don't compute scroll when invisible as an optimization, + // but in our case we need to since the window offset depends on the scroll. + mRecentsView.computeScroll(); + } + }); windowAnim.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { @@ -1177,10 +1253,14 @@ public class WindowTransformSwipeHandler } public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) { - if (!(app.isNotInRecents - || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) { + if (!isNotInRecents(app)) { return 0; } return expectedAlpha; } + + private static boolean isNotInRecents(RemoteAnimationTargetCompat app) { + return app.isNotInRecents + || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index 3ca8b18246..2f739c81fa 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -48,6 +48,8 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.PendingAnimation; @@ -365,9 +367,11 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } - private boolean showTaskMenu() { + private boolean showTaskMenu(int action) { getRecentsView().snapToPage(getRecentsView().indexOfChild(this)); mMenuView = TaskMenuView.showForTask(this); + UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE, + LauncherLogProto.ItemType.TASK_ICON); if (mMenuView != null) { mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener); } @@ -377,10 +381,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private void setIcon(Drawable icon) { if (icon != null) { mIconView.setDrawable(icon); - mIconView.setOnClickListener(v -> showTaskMenu()); + mIconView.setOnClickListener(v -> showTaskMenu(Touch.TAP)); mIconView.setOnLongClickListener(v -> { requestDisallowInterceptTouchEvent(true); - return showTaskMenu(); + return showTaskMenu(Touch.LONGPRESS); }); } else { mIconView.setDrawable(null); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java index 12e6f12bcc..fee18204ea 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java @@ -32,6 +32,8 @@ import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsModel; import com.android.systemui.shared.recents.ISystemUiProxy; +import java.io.PrintWriter; + /** * TouchController for handling touch events that get sent to the StatusBar. Once the * Once the event delta y passes the touch slop, the events start getting forwarded. @@ -45,6 +47,7 @@ public class StatusBarTouchController implements TouchController { protected final TouchEventTranslator mTranslator; private final float mTouchSlop; private ISystemUiProxy mSysUiProxy; + private int mLastAction; /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/ private boolean mCanIntercept; @@ -56,9 +59,18 @@ public class StatusBarTouchController implements TouchController { mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev)); } + @Override + public void dump(String prefix, PrintWriter writer) { + writer.println(prefix + "mCanIntercept:" + mCanIntercept); + writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction)); + writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null)); + + } + private void dispatchTouchEvent(MotionEvent ev) { try { if (mSysUiProxy != null) { + mLastAction = ev.getActionMasked(); mSysUiProxy.onStatusBarMotionEvent(ev); } } catch (RemoteException e) { diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index a71b7bb859..17f88c980e 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -27,6 +27,10 @@ import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; @@ -37,10 +41,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.function.BiPredicate; import java.util.function.Consumer; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; - /** * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. */ @@ -122,6 +122,13 @@ public interface ActivityControlHelper { default void setShelfState(ShelfAnimState animState, Interpolator interpolator, long duration) { } + + /** + * @param attached Whether to show RecentsView alongside the app window. If false, recents + * will be hidden by some property we can animate, e.g. alpha. + * @param animate Whether to animate recents to/from its new attached state. + */ + default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { } } interface HomeAnimationFactory { diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java index 286ddc043e..79d922c240 100644 --- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java +++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java @@ -15,10 +15,6 @@ */ package com.android.quickstep; -import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB; -import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; -import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; - import android.content.Context; import android.os.Handler; import android.os.Message; @@ -34,13 +30,7 @@ import com.android.systemui.shared.recents.ISystemUiProxy; import androidx.annotation.WorkerThread; /** - * Sets overview interaction flags, such as: - * - * - FLAG_DISABLE_QUICK_SCRUB - * - FLAG_DISABLE_SWIPE_UP - * - FLAG_SHOW_OVERVIEW_BUTTON - * - * @see com.android.systemui.shared.system.NavigationBarCompat.InteractionType and associated flags. + * Sets alpha for the back button */ public class OverviewInteractionState { @@ -50,11 +40,10 @@ public class OverviewInteractionState { // We do not need any synchronization for this variable as its only written on UI thread. public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>((c) -> new OverviewInteractionState(c)); + new MainThreadInitializedObject<>(OverviewInteractionState::new); private static final int MSG_SET_PROXY = 200; private static final int MSG_SET_BACK_BUTTON_ALPHA = 201; - private static final int MSG_APPLY_FLAGS = 202; private final Context mContext; private final Handler mUiHandler; @@ -62,7 +51,6 @@ public class OverviewInteractionState { // These are updated on the background thread private ISystemUiProxy mISystemUiProxy; - private boolean mSwipeUpEnabled; private float mBackButtonAlpha = 1; private OverviewInteractionState(Context context) { @@ -83,7 +71,7 @@ public class OverviewInteractionState { } public void setBackButtonAlpha(float alpha, boolean animate) { - if (!mSwipeUpEnabled) { + if (!modeSupportsGestures()) { alpha = 1; } mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA); @@ -111,30 +99,10 @@ public class OverviewInteractionState { case MSG_SET_BACK_BUTTON_ALPHA: applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1); return true; - case MSG_APPLY_FLAGS: - break; } - applyFlags(); return true; } - @WorkerThread - private void applyFlags() { - if (mISystemUiProxy == null) { - return; - } - - int flags = FLAG_DISABLE_QUICK_SCRUB; - if (!mSwipeUpEnabled) { - flags = FLAG_DISABLE_SWIPE_UP | FLAG_DISABLE_QUICK_SCRUB | FLAG_SHOW_OVERVIEW_BUTTON; - } - try { - mISystemUiProxy.setInteractionState(flags); - } catch (RemoteException e) { - Log.w(TAG, "Unable to update overview interaction flags", e); - } - } - @WorkerThread private void applyBackButtonAlpha(float alpha, boolean animate) { if (mISystemUiProxy == null) { @@ -148,18 +116,20 @@ public class OverviewInteractionState { } private void onNavigationModeChanged(SysUINavigationMode.Mode mode) { - mSwipeUpEnabled = mode.hasGestures; resetHomeBounceSeenOnQuickstepEnabledFirstTime(); - mBgHandler.obtainMessage(MSG_APPLY_FLAGS).sendToTarget(); } private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() { - if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean( + if (modeSupportsGestures() && !Utilities.getPrefs(mContext).getBoolean( HAS_ENABLED_QUICKSTEP_ONCE, true)) { Utilities.getPrefs(mContext).edit() - .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true) - .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false) - .apply(); + .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true) + .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false) + .apply(); } } + + private boolean modeSupportsGestures() { + return SysUINavigationMode.getMode(mContext).hasGestures; + } } diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java index 87981571bd..70f9c90dad 100644 --- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java +++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java @@ -12,21 +12,29 @@ import android.app.PendingIntent; import android.app.usage.UsageStatsManager; import android.content.Intent; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.launcher3.Launcher; import com.android.quickstep.views.DigitalWellBeingToast; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import org.junit.Test; +import org.junit.runner.RunWith; import java.time.Duration; +@LargeTest +@RunWith(AndroidJUnit4.class) public class DigitalWellBeingToastTest extends AbstractQuickStepTest { private static final String CALCULATOR_PACKAGE = resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR); @Test public void testToast() throws Exception { + startAppFast(CALCULATOR_PACKAGE); + final UsageStatsManager usageStatsManager = mTargetContext.getSystemService(UsageStatsManager.class); final int observerId = 0; diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index f43683118f..20fdff2c55 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -32,6 +32,7 @@ import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.os.RemoteException; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -65,10 +66,11 @@ public class FallbackRecentsTest { @Rule public final TestRule mSetLauncherCommand; - public FallbackRecentsTest() { + public FallbackRecentsTest() throws RemoteException { Instrumentation instrumentation = getInstrumentation(); Context context = instrumentation.getContext(); mDevice = UiDevice.getInstance(instrumentation); + mDevice.setOrientationNatural(); mLauncher = new LauncherInstrumentation(instrumentation); mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher); diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index a47c8e7a99..8bdb90db79 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -88,7 +88,6 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { } @Test - @Ignore // b/129723135 @NavigationModeSwitch public void testStressSwipeToOverview() { for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { diff --git a/res/values/styles.xml b/res/values/styles.xml index 9b84cc9a34..7932c6d608 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -19,12 +19,13 @@ -