• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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