Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fad8a877 authored by Andy Wickham's avatar Andy Wickham
Browse files

Adds Overview Sandbox tutorial.

Extracts fake task view animation logic to
SwipeUpGestureTutorialController, which
Home and Overview controllers extend.

Fixes: 157945497
Change-Id: Ibd1c129c57e9bee8db62baae455ca1bd9df114c7
parent 210bb434
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -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] -->
+0 −8
Original line number Diff line number Diff line
@@ -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) {
+11 −221
Original line number Diff line number Diff line
@@ -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();
        }
    }
}
+124 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+37 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading