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