Merging from ub-launcher3-rvc-dev @ build 6557059

Bug:150504032
Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-rvc-dev_rvc-dev_6557059.html

Change-Id: I9c0912f26445d454f71c4f0f63d45b184726db3d
Merged-In: I280025d9e58626fe725fe24ca60c28e89d0cee74
This commit is contained in:
Adam Cohen
2020-06-04 00:41:09 -04:00
52 changed files with 1013 additions and 351 deletions
@@ -93,7 +93,7 @@ public abstract class BaseQuickstepLauncher extends Launcher
public void onNavigationModeChanged(Mode newMode) {
getDragLayer().recreateControllers();
if (mActionsView != null && isOverviewActionsEnabled()) {
mActionsView.updateVerticalMarginForNavModeChange(newMode);
mActionsView.updateVerticalMargin(newMode);
}
}
@@ -175,7 +175,7 @@ public abstract class BaseQuickstepLauncher extends Launcher
// Overview is above all other launcher elements, including qsb, so move it to the top.
getOverviewPanel().bringToFront();
mActionsView.bringToFront();
mActionsView.updateVerticalMarginForNavModeChange(SysUINavigationMode.getMode(this));
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
}
}
@@ -82,6 +82,7 @@ import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -89,7 +90,6 @@ import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -455,9 +455,9 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
wallpaperTargets, MODE_OPENING);
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(floatingView);
openingTargets.addDependentTransactionApplier(surfaceApplier);
SurfaceTransactionApplier surfaceApplier =
new SurfaceTransactionApplier(floatingView);
openingTargets.addReleaseCheck(surfaceApplier);
// Scale the app icon to take up the entire screen. This simplifies the math when
// animating the app window position / scale.
@@ -714,8 +714,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
*/
private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
@@ -743,8 +742,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets) {
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
Matrix matrix = new Matrix();
Point tmpPos = new Point();
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
@@ -77,6 +77,7 @@ public final class WellbeingModel {
private static final String EXTRA_ACTION = "action";
private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
private static final String EXTRA_PACKAGES = "packages";
private static final String EXTRA_SUCCESS = "success";
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -221,6 +222,7 @@ public final class WellbeingModel {
params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1);
// Perform wellbeing call .
remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params);
if (!remoteActionBundle.getBoolean(EXTRA_SUCCESS, true)) return false;
synchronized (mModelLock) {
// Remove the entries for requested packages, and then update the fist with what we
@@ -281,9 +283,9 @@ public final class WellbeingModel {
// Remove all existing messages
mWorkerHandler.removeCallbacksAndMessages(null);
final String[] packageNames = mContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle()).stream()
.map(li -> li.getApplicationInfo().packageName).distinct()
.toArray(String[]::new);
.getActivityList(null, Process.myUserHandle()).stream()
.map(li -> li.getApplicationInfo().packageName).distinct()
.toArray(String[]::new);
if (!updateActions(packageNames)) {
scheduleRefreshRetry(msg);
}
@@ -17,6 +17,7 @@
package com.android.launcher3.statehandlers;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
import android.os.IBinder;
@@ -191,11 +192,12 @@ public class DepthController implements StateHandler<LauncherState> {
float toDepth = toState.getDepth(mLauncher);
if (Float.compare(mDepth, toDepth) != 0) {
animation.setFloat(this, DEPTH, toDepth, LINEAR);
animation.setFloat(this, DEPTH, toDepth, config.getInterpolator(ANIM_DEPTH, LINEAR));
}
}
private void setDepth(float depth) {
depth = Utilities.boundToRange(depth, 0, 1);
// Round out the depth to dedupe frequent, non-perceptable updates
int depthI = (int) (depth * 256);
float depthF = depthI / 256f;
@@ -36,6 +36,7 @@ import android.view.Surface;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import java.io.PrintWriter;
@@ -246,6 +247,10 @@ class OrientationTouchTransformer {
}
boolean touchInValidSwipeRegions(float x, float y) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "touchInValidSwipeRegions " + x + "," + y + " in "
+ mLastRectTouched);
}
if (mLastRectTouched != null) {
return mLastRectTouched.contains(x, y);
}
@@ -287,10 +292,16 @@ class OrientationTouchTransformer {
for (int i = 0; i < MAX_ORIENTATIONS; i++) {
OrientationRectF rect = mSwipeTouchRegions.get(i);
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "transform:DOWN, rect=" + rect);
}
if (rect == null) {
continue;
}
if (rect.applyTransform(event, false)) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "setting mLastRectTouched");
}
mLastRectTouched = rect;
mLastRectRotation = rect.mRotation;
if (mEnableMultipleRegions && mCurrentDisplayRotation == mLastRectRotation) {
@@ -525,13 +525,15 @@ public class RecentsAnimationDeviceState implements
/**
* @param ev An ACTION_DOWN motion event
* @return whether the given motion event can trigger the assistant.
* @param task Info for the currently running task
* @return whether the given motion event can trigger the assistant over the current task.
*/
public boolean canTriggerAssistantAction(MotionEvent ev) {
public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
return mAssistantAvailable
&& !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
&& mOrientationTouchTransformer.touchInAssistantRegion(ev)
&& !isLockToAppActive();
&& !isLockToAppActive()
&& !isGestureBlockedActivity(task);
}
/**
@@ -16,19 +16,16 @@
package com.android.quickstep;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Holds a collection of RemoteAnimationTargets, filtered by different properties.
*/
public class RemoteAnimationTargets {
private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
new ArrayDeque<>(1);
private final CopyOnWriteArrayList<ReleaseCheck> mReleaseChecks = new CopyOnWriteArrayList<>();
public final RemoteAnimationTargetCompat[] unfilteredApps;
public final RemoteAnimationTargetCompat[] apps;
@@ -36,6 +33,8 @@ public class RemoteAnimationTargets {
public final int targetMode;
public final boolean hasRecents;
private boolean mReleased = false;
public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps,
RemoteAnimationTargetCompat[] wallpapers, int targetMode) {
ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
@@ -76,21 +75,65 @@ public class RemoteAnimationTargets {
return false;
}
public void addDependentTransactionApplier(SyncRtSurfaceTransactionApplierCompat delay) {
mDependentTransactionAppliers.add(delay);
public void addReleaseCheck(ReleaseCheck check) {
mReleaseChecks.add(check);
}
public void release() {
SyncRtSurfaceTransactionApplierCompat applier = mDependentTransactionAppliers.poll();
if (applier == null) {
for (RemoteAnimationTargetCompat target : unfilteredApps) {
target.release();
if (mReleased) {
return;
}
for (ReleaseCheck check : mReleaseChecks) {
if (!check.mCanRelease) {
check.addOnSafeToReleaseCallback(this::release);
return;
}
for (RemoteAnimationTargetCompat target : wallpapers) {
target.release();
}
mReleaseChecks.clear();
mReleased = true;
for (RemoteAnimationTargetCompat target : unfilteredApps) {
target.release();
}
for (RemoteAnimationTargetCompat target : wallpapers) {
target.release();
}
}
/**
* Interface for intercepting surface release method
*/
public static class ReleaseCheck {
boolean mCanRelease = false;
private Runnable mAfterApplyCallback;
protected void setCanRelease(boolean canRelease) {
mCanRelease = canRelease;
if (mCanRelease && mAfterApplyCallback != null) {
Runnable r = mAfterApplyCallback;
mAfterApplyCallback = null;
r.run();
}
}
/**
* Adds a callback to notify when the surface can safely be released
*/
void addOnSafeToReleaseCallback(Runnable callback) {
if (mCanRelease) {
callback.run();
} else {
if (mAfterApplyCallback == null) {
mAfterApplyCallback = callback;
} else {
final Runnable oldCallback = mAfterApplyCallback;
mAfterApplyCallback = () -> {
callback.run();
oldCallback.run();
};
}
}
} else {
applier.addAfterApplyCallback(this::release);
}
}
}
@@ -34,14 +34,6 @@ final class BackGestureTutorialController extends TutorialController {
super(fragment, tutorialType);
}
@Override
void transitToController() {
super.transitToController();
if (mTutorialType != BACK_NAVIGATION_COMPLETE) {
showHandCoachingAnimation();
}
}
@Override
Integer getTitleStringId() {
switch (mTutorialType) {
@@ -280,7 +280,11 @@ public class EdgeBackGesturePanel extends View {
new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mPaint.setColor(context.getColor(R.color.back_arrow_color_dark));
int currentNightMode =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
mPaint.setColor(context.getColor(currentNightMode == Configuration.UI_MODE_NIGHT_YES
? R.color.back_arrow_color_light
: R.color.back_arrow_color_dark));
loadDimens();
updateArrowDirection();
@@ -15,143 +15,23 @@
*/
package com.android.quickstep.interaction;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SwipeUpAnimationLogic;
import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/** A {@link TutorialController} for the Home tutorial. */
@TargetApi(Build.VERSION_CODES.R)
final class HomeGestureTutorialController extends TutorialController {
private float mFakeTaskViewRadius;
private Rect mFakeTaskViewRect = new Rect();
private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
private RunningWindowAnim mRunningWindowAnim;
final class HomeGestureTutorialController extends SwipeUpGestureTutorialController {
HomeGestureTutorialController(HomeGestureTutorialFragment fragment, TutorialType tutorialType) {
super(fragment, tutorialType);
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
new GestureState(observer, -1));
observer.onDestroy();
deviceState.destroy();
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
.getDeviceProfile(mContext)
.copy(mContext);
Insets insets = mContext.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getWindowInsets()
.getInsets(Type.systemBars());
dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
mViewSwipeUpAnimation.initDp(dp);
mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
mFakeTaskView.setClipToOutline(true);
mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
}
});
}
private void cancelRunningAnimation() {
if (mRunningWindowAnim != null) {
mRunningWindowAnim.cancel();
}
mRunningWindowAnim = null;
}
/** Fades the task view, optionally after animating to a fake Overview. */
private void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
cancelRunningAnimation();
PendingAnimation anim = new PendingAnimation(300);
AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakeTaskView.setAlpha(1);
mRunningWindowAnim = null;
}
};
if (toOverviewFirst) {
anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
PendingAnimation fadeAnim = new PendingAnimation(300);
fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
fadeAnim.addListener(resetTaskView);
AnimatorSet animset = fadeAnim.buildAnim();
animset.setStartDelay(100);
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
});
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
anim.addListener(resetTaskView);
}
if (onEndRunnable != null) {
anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
}
AnimatorSet animset = anim.buildAnim();
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
@Override
void transitToController() {
super.transitToController();
if (mTutorialType != HOME_NAVIGATION_COMPLETE) {
showHandCoachingAnimation();
}
}
@Override
@@ -190,6 +70,14 @@ final class HomeGestureTutorialController extends TutorialController {
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
case HOME_NAVIGATION:
switch (result) {
case BACK_COMPLETED_FROM_LEFT:
case BACK_COMPLETED_FROM_RIGHT:
case BACK_CANCELLED_FROM_LEFT:
case BACK_CANCELLED_FROM_RIGHT:
showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
break;
}
break;
case HOME_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
@@ -206,17 +94,8 @@ final class HomeGestureTutorialController extends TutorialController {
case HOME_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
hideFeedback();
cancelRunningAnimation();
hideHandCoachingAnimation();
RectFSpringAnim rectAnim =
mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
// After home animation finishes, fade out and then move to the next screen.
rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
() -> fadeOutFakeTaskView(false,
() -> mTutorialFragment.changeController(
HOME_NAVIGATION_COMPLETE))));
mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
animateFakeTaskViewHome(finalVelocity, () ->
mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE));
break;
}
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
@@ -242,93 +121,4 @@ final class HomeGestureTutorialController extends TutorialController {
}
}
@Override
public void setNavBarGestureProgress(@Nullable Float displacement) {
if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE) {
mFakeTaskView.setVisibility(View.INVISIBLE);
} else {
mFakeTaskView.setVisibility(View.VISIBLE);
if (mRunningWindowAnim == null) {
mViewSwipeUpAnimation.updateDisplacement(displacement);
}
}
}
private class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState) {
super(context, deviceState, gestureState, new FakeTransformParams());
}
void initDp(DeviceProfile dp) {
initTransitionEndpoints(dp);
mTaskViewSimulator.setPreviewBounds(
new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
}
@Override
public void updateFinalShift() {
float progress = mCurrentShift.value / mDragLengthFactor;
mWindowTransitionController.setPlayFraction(progress);
mTaskViewSimulator.apply(mTransformParams);
}
AnimatedFloat getCurrentShift() {
return mCurrentShift;
}
RectFSpringAnim handleSwipeUpToHome(PointF velocity) {
PointF velocityPxPerMs = new PointF(velocity.x, velocity.y);
float currentShift = mCurrentShift.value;
final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
* getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
float distanceToTravel = (1 - currentShift) * mTransitionDragLength;
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
@NonNull
@Override
public RectF getWindowTargetRect() {
int fakeHomeIconSizePx = mDp.allAppsIconSizePx;
int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2;
int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3);
return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
fakeHomeIconLeft + fakeHomeIconSizePx,
fakeHomeIconTop + fakeHomeIconSizePx);
}
};
RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
windowAnim.start(mContext, velocityPxPerMs);
return windowAnim;
}
}
private class FakeTransformParams extends TransformParams {
@Override
public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
proxy.onBuildTargetParams(builder, null, this);
return new SurfaceParams[] {builder.build()};
}
@Override
public void applySurfaceParams(SurfaceParams[] params) {
SurfaceParams p = params[0];
mFakeTaskView.setAnimationMatrix(p.matrix);
mFakeTaskViewRect.set(p.windowCrop);
mFakeTaskViewRadius = p.cornerRadius;
mFakeTaskView.invalidateOutline();
}
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 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.interaction;
import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
import android.annotation.TargetApi;
import android.graphics.PointF;
import android.os.Build;
import android.view.View;
import com.android.launcher3.R;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
/** A {@link TutorialController} for the Overview tutorial. */
@TargetApi(Build.VERSION_CODES.R)
final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController {
OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment,
TutorialType tutorialType) {
super(fragment, tutorialType);
}
@Override
Integer getTitleStringId() {
switch (mTutorialType) {
case OVERVIEW_NAVIGATION:
return R.string.overview_gesture_tutorial_playground_title;
case OVERVIEW_NAVIGATION_COMPLETE:
return R.string.gesture_tutorial_confirm_title;
}
return null;
}
@Override
Integer getSubtitleStringId() {
if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) {
return R.string.overview_gesture_tutorial_playground_subtitle;
}
return null;
}
@Override
Integer getActionButtonStringId() {
if (mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
return R.string.gesture_tutorial_action_button_label_done;
}
return null;
}
@Override
void onActionButtonClicked(View button) {
mTutorialFragment.closeTutorial();
}
@Override
public void onBackGestureAttempted(BackGestureResult result) {
switch (mTutorialType) {
case OVERVIEW_NAVIGATION:
switch (result) {
case BACK_COMPLETED_FROM_LEFT:
case BACK_COMPLETED_FROM_RIGHT:
case BACK_CANCELLED_FROM_LEFT:
case BACK_CANCELLED_FROM_RIGHT:
showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
break;
}
break;
case OVERVIEW_NAVIGATION_COMPLETE:
if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT
|| result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) {
mTutorialFragment.closeTutorial();
}
break;
}
}
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
switch (mTutorialType) {
case OVERVIEW_NAVIGATION:
switch (result) {
case HOME_GESTURE_COMPLETED: {
animateFakeTaskViewHome(finalVelocity, () ->
showFeedback(R.string.overview_gesture_feedback_home_detected));
break;
}
case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
break;
case OVERVIEW_GESTURE_COMPLETED:
fadeOutFakeTaskView(true, () ->
mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE));
break;
case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
case HOME_OR_OVERVIEW_CANCELLED:
fadeOutFakeTaskView(false, null);
showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction);
break;
}
break;
case OVERVIEW_NAVIGATION_COMPLETE:
if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) {
mTutorialFragment.closeTutorial();
}
break;
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 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.interaction;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
/** Shows the Overview gesture interactive tutorial. */
public class OverviewGestureTutorialFragment extends TutorialFragment {
@Override
int getHandAnimationResId() {
return R.drawable.overview_gesture;
}
@Override
TutorialController createController(TutorialType type) {
return new OverviewGestureTutorialController(this, type);
}
@Override
Class<? extends TutorialController> getControllerClass() {
return OverviewGestureTutorialController.class;
}
}
@@ -0,0 +1,246 @@
/*
* Copyright (C) 2020 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.interaction;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.SwipeUpAnimationLogic;
import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@TargetApi(Build.VERSION_CODES.R)
abstract class SwipeUpGestureTutorialController extends TutorialController {
private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
private float mFakeTaskViewRadius;
private Rect mFakeTaskViewRect = new Rect();
private RunningWindowAnim mRunningWindowAnim;
SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
super(tutorialFragment, tutorialType);
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
new GestureState(observer, -1));
observer.onDestroy();
deviceState.destroy();
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
.getDeviceProfile(mContext)
.copy(mContext);
Insets insets = mContext.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getWindowInsets()
.getInsets(WindowInsets.Type.systemBars());
dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
mViewSwipeUpAnimation.initDp(dp);
mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
mFakeTaskView.setClipToOutline(true);
mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
}
});
}
private void cancelRunningAnimation() {
if (mRunningWindowAnim != null) {
mRunningWindowAnim.cancel();
}
mRunningWindowAnim = null;
}
/** Fades the task view, optionally after animating to a fake Overview. */
void fadeOutFakeTaskView(boolean toOverviewFirst, @Nullable Runnable onEndRunnable) {
hideFeedback();
hideHandCoachingAnimation();
cancelRunningAnimation();
PendingAnimation anim = new PendingAnimation(300);
AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakeTaskView.setAlpha(1);
mRunningWindowAnim = null;
}
};
if (toOverviewFirst) {
anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
PendingAnimation fadeAnim = new PendingAnimation(300);
fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
fadeAnim.addListener(resetTaskView);
AnimatorSet animset = fadeAnim.buildAnim();
animset.setStartDelay(100);
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
});
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
anim.addListener(resetTaskView);
}
if (onEndRunnable != null) {
anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
}
AnimatorSet animset = anim.buildAnim();
animset.start();
mRunningWindowAnim = RunningWindowAnim.wrap(animset);
}
void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) {
hideFeedback();
hideHandCoachingAnimation();
cancelRunningAnimation();
RectFSpringAnim rectAnim =
mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
// After home animation finishes, fade out and run onEndRunnable.
rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
() -> fadeOutFakeTaskView(false, onEndRunnable)));
mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
}
@Override
public void setNavBarGestureProgress(@Nullable Float displacement) {
if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE
|| mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
mFakeTaskView.setVisibility(View.INVISIBLE);
} else {
mFakeTaskView.setVisibility(View.VISIBLE);
if (mRunningWindowAnim == null) {
mViewSwipeUpAnimation.updateDisplacement(displacement);
}
}
}
class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState) {
super(context, deviceState, gestureState, new FakeTransformParams());
}
void initDp(DeviceProfile dp) {
initTransitionEndpoints(dp);
mTaskViewSimulator.setPreviewBounds(
new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets());
}
@Override
public void updateFinalShift() {
float progress = mCurrentShift.value / mDragLengthFactor;
mWindowTransitionController.setPlayFraction(progress);
mTaskViewSimulator.apply(mTransformParams);
}
AnimatedFloat getCurrentShift() {
return mCurrentShift;
}
RectFSpringAnim handleSwipeUpToHome(PointF velocity) {
PointF velocityPxPerMs = new PointF(velocity.x, velocity.y);
float currentShift = mCurrentShift.value;
final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
* getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
float distanceToTravel = (1 - currentShift) * mTransitionDragLength;
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
@NonNull
@Override
public RectF getWindowTargetRect() {
int fakeHomeIconSizePx = mDp.allAppsIconSizePx;
int fakeHomeIconLeft = (mDp.widthPx - fakeHomeIconSizePx) / 2;
int fakeHomeIconTop = mDp.heightPx - (mDp.allAppsCellHeightPx * 3);
return new RectF(fakeHomeIconLeft, fakeHomeIconTop,
fakeHomeIconLeft + fakeHomeIconSizePx,
fakeHomeIconTop + fakeHomeIconSizePx);
}
};
RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
windowAnim.start(mContext, velocityPxPerMs);
return windowAnim;
}
}
private class FakeTransformParams extends TransformParams {
@Override
public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
proxy.onBuildTargetParams(builder, null, this);
return new SurfaceParams[] {builder.build()};
}
@Override
public void applySurfaceParams(SurfaceParams[] params) {
SurfaceParams p = params[0];
mFakeTaskView.setAnimationMatrix(p.matrix);
mFakeTaskViewRect.set(p.windowCrop);
mFakeTaskViewRadius = p.cornerRadius;
mFakeTaskView.invalidateOutline();
}
}
}
@@ -140,6 +140,9 @@ abstract class TutorialController implements BackGestureAttemptCallback,
void onActionTextButtonClicked(View button) {}
void showHandCoachingAnimation() {
if (isComplete()) {
return;
}
mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
}
@@ -153,6 +156,12 @@ abstract class TutorialController implements BackGestureAttemptCallback,
hideFeedback();
updateTitles();
updateActionButtons();
if (isComplete()) {
hideHandCoachingAnimation();
} else {
showHandCoachingAnimation();
}
}
private void updateTitles() {
@@ -190,12 +199,20 @@ abstract class TutorialController implements BackGestureAttemptCallback,
button.setOnClickListener(listener);
}
private boolean isComplete() {
return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE
|| mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE
|| mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
}
/** Denotes the type of the tutorial. */
enum TutorialType {
RIGHT_EDGE_BACK_NAVIGATION,
LEFT_EDGE_BACK_NAVIGATION,
BACK_NAVIGATION_COMPLETE,
HOME_NAVIGATION,
HOME_NAVIGATION_COMPLETE
HOME_NAVIGATION_COMPLETE,
OVERVIEW_NAVIGATION,
OVERVIEW_NAVIGATION_COMPLETE
}
}
@@ -68,6 +68,9 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
case HOME_NAVIGATION:
case HOME_NAVIGATION_COMPLETE:
return new HomeGestureTutorialFragment();
case OVERVIEW_NAVIGATION:
case OVERVIEW_NAVIGATION_COMPLETE:
return new OverviewGestureTutorialFragment();
default:
Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
}