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.app.animation.Interpolators.ACCELERATE; 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.graphics.PointF; 25 import android.os.Handler; 26 27 import androidx.annotation.ColorInt; 28 import androidx.core.graphics.ColorUtils; 29 30 import com.android.launcher3.R; 31 import com.android.launcher3.Utilities; 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 import com.android.quickstep.util.LottieAnimationColorUtils; 38 39 import java.util.ArrayList; 40 import java.util.Map; 41 42 /** A {@link TutorialController} for the Overview tutorial. */ 43 final class OverviewGestureTutorialController extends SwipeUpGestureTutorialController { 44 45 private static final float LAUNCHER_COLOR_BLENDING_RATIO = 0.4f; 46 OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, TutorialType tutorialType)47 OverviewGestureTutorialController(OverviewGestureTutorialFragment fragment, 48 TutorialType tutorialType) { 49 super(fragment, tutorialType); 50 51 // Set the Lottie animation colors specifically for the Overview gesture 52 if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) { 53 LottieAnimationColorUtils.updateToArgbColors( 54 mAnimatedGestureDemonstration, 55 Map.of(".onSurfaceOverview", fragment.mRootView.mColorOnSurfaceOverview, 56 ".surfaceOverview", fragment.mRootView.mColorSurfaceOverview, 57 ".secondaryOverview", fragment.mRootView.mColorSecondaryOverview)); 58 59 LottieAnimationColorUtils.updateToArgbColors( 60 mCheckmarkAnimation, 61 Map.of(".checkmark", 62 Utilities.isDarkTheme(mContext) 63 ? fragment.mRootView.mColorOnSurfaceOverview 64 : fragment.mRootView.mColorSecondaryOverview, 65 ".checkmarkBackground", fragment.mRootView.mColorSurfaceOverview)); 66 } 67 } 68 @Override getIntroductionTitle()69 public int getIntroductionTitle() { 70 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 71 ? R.string.overview_gesture_tutorial_title 72 : R.string.overview_gesture_intro_title; 73 } 74 75 @Override getIntroductionSubtitle()76 public int getIntroductionSubtitle() { 77 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 78 ? R.string.overview_gesture_tutorial_subtitle 79 : R.string.overview_gesture_intro_subtitle; 80 } 81 82 @Override getSpokenIntroductionSubtitle()83 public int getSpokenIntroductionSubtitle() { 84 return R.string.overview_gesture_spoken_intro_subtitle; 85 } 86 87 @Override getSuccessFeedbackTitle()88 public int getSuccessFeedbackTitle() { 89 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 90 ? R.string.overview_gesture_tutorial_success 91 : R.string.gesture_tutorial_nice; 92 } 93 94 @Override getSuccessFeedbackSubtitle()95 public int getSuccessFeedbackSubtitle() { 96 return mTutorialFragment.getNumSteps() > 1 && mTutorialFragment.isAtFinalStep() 97 ? R.string.overview_gesture_feedback_complete_with_follow_up 98 : R.string.overview_gesture_feedback_complete_without_follow_up; 99 } 100 101 @Override getTitleTextAppearance()102 public int getTitleTextAppearance() { 103 return R.style.TextAppearance_GestureTutorial_MainTitle_Overview; 104 } 105 106 @Override getSuccessTitleTextAppearance()107 public int getSuccessTitleTextAppearance() { 108 return R.style.TextAppearance_GestureTutorial_MainTitle_Success_Overview; 109 } 110 111 @Override getDoneButtonTextAppearance()112 public int getDoneButtonTextAppearance() { 113 return R.style.TextAppearance_GestureTutorial_ButtonLabel_Overview; 114 } 115 116 @Override getDoneButtonColor()117 public int getDoneButtonColor() { 118 return Utilities.isDarkTheme(mContext) 119 ? mTutorialFragment.mRootView.mColorOnSurfaceOverview 120 : mTutorialFragment.mRootView.mColorSecondaryOverview; 121 } 122 123 @Override getMockAppTaskLayoutResId()124 protected int getMockAppTaskLayoutResId() { 125 return mTutorialFragment.isLargeScreen() 126 ? R.layout.gesture_tutorial_tablet_mock_conversation_list 127 : R.layout.gesture_tutorial_mock_conversation_list; 128 } 129 130 @Override getGestureLottieAnimationId()131 protected int getGestureLottieAnimationId() { 132 return mTutorialFragment.isLargeScreen() 133 ? mTutorialFragment.isFoldable() 134 ? R.raw.overview_gesture_tutorial_open_foldable_animation 135 : R.raw.overview_gesture_tutorial_tablet_animation 136 : R.raw.overview_gesture_tutorial_animation; 137 } 138 139 @ColorInt getFakeTaskViewStartColor()140 private int getFakeTaskViewStartColor() { 141 return mTutorialFragment.mRootView.mColorSurfaceOverview; 142 } 143 144 @ColorInt getFakeTaskViewEndColor()145 private int getFakeTaskViewEndColor() { 146 return getMockPreviousAppTaskThumbnailColor(); 147 } 148 149 @Override getFakeTaskViewColor()150 protected int getFakeTaskViewColor() { 151 return isGestureCompleted() 152 ? getFakeTaskViewEndColor() 153 : getFakeTaskViewStartColor(); 154 } 155 156 @Override getFakeLauncherColor()157 protected int getFakeLauncherColor() { 158 return ColorUtils.blendARGB( 159 mTutorialFragment.mRootView.mColorSurfaceContainer, 160 mTutorialFragment.mRootView.mColorOnSurfaceOverview, 161 LAUNCHER_COLOR_BLENDING_RATIO); 162 } 163 164 @Override getHotseatIconColor()165 protected int getHotseatIconColor() { 166 return mTutorialFragment.mRootView.mColorOnSurfaceOverview; 167 } 168 169 @Override getMockPreviousAppTaskThumbnailColor()170 protected int getMockPreviousAppTaskThumbnailColor() { 171 return ENABLE_NEW_GESTURE_NAV_TUTORIAL.get() 172 ? mTutorialFragment.mRootView.mColorSurfaceContainer 173 : mContext.getResources().getColor( 174 R.color.gesture_tutorial_fake_previous_task_view_color); 175 } 176 177 @Override onBackGestureAttempted(BackGestureResult result)178 public void onBackGestureAttempted(BackGestureResult result) { 179 if (isGestureCompleted()) { 180 return; 181 } 182 switch (mTutorialType) { 183 case OVERVIEW_NAVIGATION: 184 switch (result) { 185 case BACK_COMPLETED_FROM_LEFT: 186 case BACK_COMPLETED_FROM_RIGHT: 187 case BACK_CANCELLED_FROM_LEFT: 188 case BACK_CANCELLED_FROM_RIGHT: 189 case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE: 190 resetTaskViews(); 191 showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); 192 break; 193 } 194 break; 195 case OVERVIEW_NAVIGATION_COMPLETE: 196 if (result == BackGestureResult.BACK_COMPLETED_FROM_LEFT 197 || result == BackGestureResult.BACK_COMPLETED_FROM_RIGHT) { 198 mTutorialFragment.close(); 199 } 200 break; 201 } 202 } 203 204 @Override onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity)205 public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) { 206 if (isGestureCompleted()) { 207 return; 208 } 209 switch (mTutorialType) { 210 case OVERVIEW_NAVIGATION: 211 switch (result) { 212 case HOME_GESTURE_COMPLETED: { 213 animateFakeTaskViewHome(finalVelocity, () -> { 214 showFeedback(R.string.overview_gesture_feedback_home_detected); 215 resetFakeTaskView(true); 216 }); 217 break; 218 } 219 case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE: 220 case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE: 221 resetTaskViews(); 222 showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge); 223 break; 224 case OVERVIEW_GESTURE_COMPLETED: 225 setGestureCompleted(); 226 mTutorialFragment.releaseFeedbackAnimation(); 227 animateTaskViewToOverview(ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()); 228 onMotionPaused(true /*arbitrary value*/); 229 if (!ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) { 230 showSuccessFeedback(); 231 } 232 break; 233 case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION: 234 case HOME_OR_OVERVIEW_CANCELLED: 235 fadeOutFakeTaskView(false, null); 236 showFeedback(R.string.overview_gesture_feedback_wrong_swipe_direction); 237 break; 238 } 239 break; 240 case OVERVIEW_NAVIGATION_COMPLETE: 241 if (result == NavBarGestureResult.HOME_GESTURE_COMPLETED) { 242 mTutorialFragment.close(); 243 } 244 break; 245 } 246 } 247 248 /** 249 * runnable executed with slight delay to ease the swipe animation after landing on overview 250 */ animateTaskViewToOverview(boolean animateDelayedSuccessFeedback)251 public void animateTaskViewToOverview(boolean animateDelayedSuccessFeedback) { 252 PendingAnimation anim = new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS); 253 anim.setFloat(mTaskViewSwipeUpAnimation 254 .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCELERATE); 255 256 if (animateDelayedSuccessFeedback) { 257 anim.addListener(new AnimatorListenerAdapter() { 258 @Override 259 public void onAnimationEnd(Animator animator) { 260 new Handler().postDelayed( 261 () -> fadeOutFakeTaskView( 262 /* toOverviewFirst= */ true, 263 /* animatePreviousTask= */ false, 264 /* resetViews= */ false, 265 /* updateListener= */ v -> mFakeTaskView.setBackgroundColor( 266 ColorUtils.blendARGB( 267 getFakeTaskViewStartColor(), 268 getFakeTaskViewEndColor(), 269 v.getAnimatedFraction())), 270 /* onEndRunnable= */ () -> { 271 showSuccessFeedback(); 272 resetTaskViews(); 273 }), 274 TASK_VIEW_FILL_SCREEN_ANIMATION_DELAY_MILLIS); 275 } 276 }); 277 } 278 279 ArrayList<Animator> animators = new ArrayList<>(); 280 281 if (mTutorialFragment.isLargeScreen()) { 282 Animator multiRowAnimation = mFakePreviousTaskView.createAnimationToMultiRowLayout(); 283 284 if (multiRowAnimation != null) { 285 multiRowAnimation.setDuration(TASK_VIEW_END_ANIMATION_DURATION_MILLIS); 286 animators.add(multiRowAnimation); 287 } 288 } 289 animators.add(anim.buildAnim()); 290 291 AnimatorSet animset = new AnimatorSet(); 292 animset.playTogether(animators); 293 animset.start(); 294 mRunningWindowAnim = SwipeUpAnimationLogic.RunningWindowAnim.wrap(animset); 295 } 296 } 297