Merge "Adds Overview Sandbox tutorial." into ub-launcher3-rvc-dev am: 34413df66d
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/11688637 Change-Id: I6a87092cb8e50fce2770e391898d9c8be8ca0071
This commit is contained in:
commit
66f1e0215c
|
@ -87,7 +87,6 @@
|
|||
<!-- content description for hotseat items -->
|
||||
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
|
||||
|
||||
|
||||
<!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
|
||||
<string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
|
||||
<!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
|
||||
|
@ -111,7 +110,6 @@
|
|||
<!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
|
||||
<string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
|
||||
|
||||
|
||||
<!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
|
||||
<string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
|
||||
<!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
|
||||
|
@ -123,6 +121,17 @@
|
|||
<!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
|
||||
<string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
|
||||
|
||||
<!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
|
||||
<string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
|
||||
<!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
|
||||
<string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
|
||||
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
|
||||
<string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
|
||||
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
|
||||
<string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
|
||||
<!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
|
||||
<string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
|
||||
|
||||
<!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
|
||||
<string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
|
||||
<!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -237,6 +237,15 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
|||
return true;
|
||||
});
|
||||
sandboxCategory.addPreference(launchHomeTutorialPreference);
|
||||
Preference launchOverviewTutorialPreference = new Preference(context);
|
||||
launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
|
||||
launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
|
||||
launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
|
||||
launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION"));
|
||||
return true;
|
||||
});
|
||||
sandboxCategory.addPreference(launchOverviewTutorialPreference);
|
||||
}
|
||||
|
||||
private String toName(String action) {
|
||||
|
|
Loading…
Reference in New Issue