1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep.interaction; 17 18 import static com.android.launcher3.anim.Interpolators.ACCEL; 19 import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.AnimatorSet; 24 import android.annotation.Nullable; 25 import android.annotation.TargetApi; 26 import android.graphics.PointF; 27 import android.os.Build; 28 import android.os.Handler; 29 import android.view.View; 30 31 import com.android.launcher3.R; 32 import com.android.launcher3.anim.AnimatedFloat; 33 import com.android.launcher3.anim.PendingAnimation; 34 import com.android.quickstep.SwipeUpAnimationLogic; 35 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult; 36 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult; 37 38 import java.util.ArrayList; 39 40 /** A {@link TutorialController} for the Overview tutorial. */ 41 @TargetApi(Build.VERSION_CODES.R) 42 final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController { 43 OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, TutorialType tutorialType)44 OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, 45 TutorialType tutorialType) { 46 super(fragment, tutorialType); 47 } 48 @Override getIntroductionTitle()49 public int getIntroductionTitle() { 50 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 51 ? R.string.overview_gesture_tutorial_title 52 : R.string.overview_gesture_intro_title; 53 } 54 55 @Override getIntroductionSubtitle()56 public int getIntroductionSubtitle() { 57 return R.string.overview_gesture_intro_subtitle; 58 } 59 60 @Override getSpokenIntroductionSubtitle()61 public int getSpokenIntroductionSubtitle() { 62 return R.string.overview_gesture_spoken_intro_subtitle; 63 } 64 65 @Override getSuccessFeedbackSubtitle()66 public int getSuccessFeedbackSubtitle() { 67 return mTutorialFragment.getNumSteps() > 1 && mTutorialFragment.isAtFinalStep() 68 ? R.string.overview_gesture_feedback_complete_with_follow_up 69 : R.string.overview_gesture_feedback_complete_without_follow_up; 70 } 71 72 @Override getMockAppTaskLayoutResId()73 protected int getMockAppTaskLayoutResId() { 74 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 75 ? R.layout.gesture_tutorial_mock_task_view 76 : mTutorialFragment.isLargeScreen() 77 ? R.layout.gesture_tutorial_tablet_mock_conversation_list 78 : R.layout.gesture_tutorial_mock_conversation_list; 79 } 80 81 @Override getGestureLottieAnimationId()82 protected int getGestureLottieAnimationId() { 83 return mTutorialFragment.isLargeScreen() 84 ? R.raw.overview_gesture_tutorial_tablet_animation 85 : R.raw.overview_gesture_tutorial_animation; 86 } 87 88 @Override getSwipeActionColorResId()89 protected int getSwipeActionColorResId() { 90 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 91 ? R.color.gesture_overview_background 92 : R.color.gesture_overview_tutorial_swipe_rect; 93 } 94 95 @Override getMockPreviousAppTaskThumbnailColorResId()96 protected int getMockPreviousAppTaskThumbnailColorResId() { 97 return R.color.gesture_overview_tutorial_swipe_rect; 98 } 99 100 @Override onBackGestureAttempted(BackGestureResult result)101 public void onBackGestureAttempted(BackGestureResult result) { 102 if (isGestureCompleted()) { 103 return; 104 } 105 switch (mTutorialType) { 106 case OVERVIEW_NAVIGATION: 107 switch (result) { 108 case BACK_COMPLETED_FROM_LEFT: 109 case BACK_COMPLETED_FROM_RIGHT: 110 case BACK_CANCELLED_FROM_LEFT: 111 case BACK_CANCELLED_FROM_RIGHT: 112 showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); 113 break; 114 } 115 break; 116 case OVERVIEW_NAVIGATION_COMPLETE: 117 if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT 118 || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) { 119 mTutorialFragment.close(); 120 } 121 break; 122 } 123 } 124 125 @Override onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity)126 public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) { 127 if (isGestureCompleted()) { 128 return; 129 } 130 switch (mTutorialType) { 131 case OVERVIEW_NAVIGATION: 132 switch (result) { 133 case HOME_GESTURE_COMPLETED: { 134 animateFakeTaskViewHome(finalVelocity, () -> { 135 showFeedback(R.string.overview_gesture_feedback_home_detected); 136 resetFakeTaskView(true); 137 }); 138 break; 139 } 140 case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: 141 case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: 142 showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); 143 break; 144 case OVERVIEW_GESTURE_COMPLETED: 145 mTutorialFragment.releaseFeedbackAnimation(); 146 if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) { 147 onMotionPaused(true /*arbitrary value*/); 148 animateTaskViewToOverview(() -> { 149 mFakeTaskView.setVisibility(View.INVISIBLE); 150 if(!mTutorialFragment.isLargeScreen()){ 151 mFakePreviousTaskView.animateToFillScreen(() -> { 152 mFakeLauncherView.setBackgroundColor( 153 mContext.getColor( 154 R.color.gesture_overview_tutorial_swipe_rect 155 )); 156 showSuccessFeedback(); 157 }); 158 } else { 159 mFakeLauncherView.setBackgroundColor( 160 mContext.getColor( 161 R.color.gesture_overview_tutorial_swipe_rect 162 )); 163 showSuccessFeedback(); 164 } 165 }); 166 } else { 167 animateTaskViewToOverview(null); 168 onMotionPaused(true /*arbitrary value*/); 169 showSuccessFeedback(); 170 } 171 break; 172 case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: 173 case HOME_OR_OVERVIEW_CANCELLED: 174 fadeOutFakeTaskView(false, true, null); 175 showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction); 176 break; 177 } 178 break; 179 case OVERVIEW_NAVIGATION_COMPLETE: 180 if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { 181 mTutorialFragment.close(); 182 } 183 break; 184 } 185 } 186 187 /** 188 * runnable executed with slight delay to ease the swipe animation after landing on overview 189 * @param runnable 190 */ animateTaskViewToOverview(@ullable Runnable runnable)191 public void animateTaskViewToOverview(@Nullable Runnable runnable) { 192 PendingAnimation anim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS); 193 anim.setFloat(mTaskViewSwipeUpAnimation 194 .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); 195 196 anim.addListener(new AnimatorListenerAdapter() { 197 @Override 198 public void onAnimationEnd(Animator animator) { 199 if (runnable != null) { 200 new Handler().postDelayed(runnable, 300); 201 } 202 } 203 }); 204 205 ArrayList<Animator> animators = new ArrayList<>(); 206 207 if (mTutorialFragment.isLargeScreen()) { 208 Animator multiRowAnimation = mFakePreviousTaskView.createAnimationToMultiRowLayout(); 209 210 if (multiRowAnimation != null) { 211 multiRowAnimation.setDuration(TASK_VIEW_END_ANIMATION_DURATION_MILLIS); 212 animators.add(multiRowAnimation); 213 } 214 } 215 animators.add(anim.buildAnim()); 216 217 AnimatorSet animset = new AnimatorSet(); 218 animset.playTogether(animators); 219 animset.start(); 220 mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset); 221 } 222 } 223