• 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 android.app.Activity;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.graphics.Insets;
22 import android.graphics.drawable.Animatable2;
23 import android.graphics.drawable.AnimatedVectorDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.os.Bundle;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.View.OnTouchListener;
31 import android.view.ViewGroup;
32 import android.view.WindowInsets;
33 import android.widget.ImageView;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 import androidx.fragment.app.Fragment;
38 import androidx.fragment.app.FragmentActivity;
39 
40 import com.android.launcher3.R;
41 import com.android.launcher3.Utilities;
42 import com.android.quickstep.interaction.TutorialController.TutorialType;
43 
44 abstract class TutorialFragment extends Fragment implements OnTouchListener {
45 
46     private static final String LOG_TAG = "TutorialFragment";
47     static final String KEY_TUTORIAL_TYPE = "tutorial_type";
48 
49     TutorialType mTutorialType;
50     @Nullable TutorialController mTutorialController = null;
51     RootSandboxLayout mRootView;
52     EdgeBackGestureHandler mEdgeBackGestureHandler;
53     NavBarGestureHandler mNavBarGestureHandler;
54     private ImageView mFeedbackVideoView;
55     private ImageView mGestureVideoView;
56 
57     @Nullable private AnimatedVectorDrawable mTutorialAnimation = null;
58     @Nullable private AnimatedVectorDrawable mGestureAnimation = null;
59     private boolean mIntroductionShown = false;
60 
61     private boolean mFragmentStopped = false;
62 
newInstance(TutorialType tutorialType)63     public static TutorialFragment newInstance(TutorialType tutorialType) {
64         TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
65         if (fragment == null) {
66             fragment = new BackGestureTutorialFragment();
67             tutorialType = TutorialType.BACK_NAVIGATION;
68         }
69 
70         Bundle args = new Bundle();
71         args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
72         fragment.setArguments(args);
73         return fragment;
74     }
75 
76     @Nullable
getFragmentForTutorialType(TutorialType tutorialType)77     private static TutorialFragment getFragmentForTutorialType(TutorialType tutorialType) {
78         switch (tutorialType) {
79             case BACK_NAVIGATION:
80             case BACK_NAVIGATION_COMPLETE:
81                 return new BackGestureTutorialFragment();
82             case HOME_NAVIGATION:
83             case HOME_NAVIGATION_COMPLETE:
84                 return new HomeGestureTutorialFragment();
85             case OVERVIEW_NAVIGATION:
86             case OVERVIEW_NAVIGATION_COMPLETE:
87                 return new OverviewGestureTutorialFragment();
88             case ASSISTANT:
89             case ASSISTANT_COMPLETE:
90                 return new AssistantGestureTutorialFragment();
91             case SANDBOX_MODE:
92                 return new SandboxModeTutorialFragment();
93             default:
94                 Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
95         }
96         return null;
97     }
98 
getFeedbackVideoResId(boolean forDarkMode)99     @Nullable Integer getFeedbackVideoResId(boolean forDarkMode) {
100         return null;
101     }
102 
getGestureVideoResId()103     @Nullable Integer getGestureVideoResId() {
104         return null;
105     }
106 
107     @Nullable
getTutorialAnimation()108     AnimatedVectorDrawable getTutorialAnimation() {
109         return mTutorialAnimation;
110     }
111 
112     @Nullable
getGestureAnimation()113     AnimatedVectorDrawable getGestureAnimation() {
114         return mGestureAnimation;
115     }
116 
createController(TutorialType type)117     abstract TutorialController createController(TutorialType type);
118 
getControllerClass()119     abstract Class<? extends TutorialController> getControllerClass();
120 
121     @Override
onCreate(Bundle savedInstanceState)122     public void onCreate(Bundle savedInstanceState) {
123         super.onCreate(savedInstanceState);
124         Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
125         mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
126         mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
127         mNavBarGestureHandler = new NavBarGestureHandler(getContext());
128     }
129 
130     @Override
onDestroy()131     public void onDestroy() {
132         super.onDestroy();
133         mEdgeBackGestureHandler.unregisterBackGestureAttemptCallback();
134         mNavBarGestureHandler.unregisterNavBarGestureAttemptCallback();
135     }
136 
137     @Override
onCreateView( @onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)138     public View onCreateView(
139             @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
140         super.onCreateView(inflater, container, savedInstanceState);
141 
142         mRootView = (RootSandboxLayout) inflater.inflate(
143                 R.layout.gesture_tutorial_fragment, container, false);
144         mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
145             Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
146             mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
147             return insets;
148         });
149         mRootView.setOnTouchListener(this);
150         mFeedbackVideoView = mRootView.findViewById(R.id.gesture_tutorial_feedback_video);
151         mGestureVideoView = mRootView.findViewById(R.id.gesture_tutorial_gesture_video);
152         return mRootView;
153     }
154 
155     @Override
onStop()156     public void onStop() {
157         super.onStop();
158         releaseFeedbackVideoView();
159         releaseGestureVideoView();
160         mFragmentStopped = true;
161     }
162 
initializeFeedbackVideoView()163     void initializeFeedbackVideoView() {
164         if (!updateFeedbackVideo()) {
165             return;
166         }
167 
168         if (!mIntroductionShown && mTutorialController != null) {
169             Integer introTileStringResId = mTutorialController.getIntroductionTitle();
170             Integer introSubtitleResId = mTutorialController.getIntroductionSubtitle();
171             if (introTileStringResId != null && introSubtitleResId != null) {
172                 mTutorialController.showFeedback(
173                         introTileStringResId, introSubtitleResId, false, true);
174                 mIntroductionShown = true;
175             }
176         }
177     }
178 
updateFeedbackVideo()179     boolean updateFeedbackVideo() {
180         if (getContext() == null) {
181             return false;
182         }
183         Integer feedbackVideoResId = getFeedbackVideoResId(Utilities.isDarkTheme(getContext()));
184 
185         if (feedbackVideoResId == null || !updateGestureVideo()) {
186             return false;
187         }
188         mTutorialAnimation = (AnimatedVectorDrawable) getContext().getDrawable(feedbackVideoResId);
189 
190         if (mTutorialAnimation != null) {
191             mTutorialAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() {
192 
193                 @Override
194                 public void onAnimationStart(Drawable drawable) {
195                     super.onAnimationStart(drawable);
196 
197                     mFeedbackVideoView.setVisibility(View.VISIBLE);
198                 }
199 
200                 @Override
201                 public void onAnimationEnd(Drawable drawable) {
202                     super.onAnimationEnd(drawable);
203 
204                     releaseFeedbackVideoView();
205                 }
206             });
207         }
208         mFeedbackVideoView.setImageDrawable(mTutorialAnimation);
209 
210         return true;
211     }
212 
updateGestureVideo()213     boolean updateGestureVideo() {
214         Integer gestureVideoResId = getGestureVideoResId();
215         if (gestureVideoResId == null || getContext() == null) {
216             return false;
217         }
218         mGestureAnimation = (AnimatedVectorDrawable) getContext().getDrawable(gestureVideoResId);
219 
220         if (mGestureAnimation != null) {
221             mGestureAnimation.registerAnimationCallback(new Animatable2.AnimationCallback() {
222 
223                 @Override
224                 public void onAnimationEnd(Drawable drawable) {
225                     super.onAnimationEnd(drawable);
226 
227                     mGestureAnimation.start();
228                 }
229             });
230         }
231         mGestureVideoView.setImageDrawable(mGestureAnimation);
232 
233         return true;
234     }
235 
releaseFeedbackVideoView()236     void releaseFeedbackVideoView() {
237         if (mTutorialAnimation != null && mTutorialAnimation.isRunning()) {
238             mTutorialAnimation.stop();
239         }
240 
241         mFeedbackVideoView.setVisibility(View.GONE);
242     }
243 
releaseGestureVideoView()244     void releaseGestureVideoView() {
245         if (mGestureAnimation != null && mGestureAnimation.isRunning()) {
246             mGestureAnimation.stop();
247         }
248 
249         mGestureVideoView.setVisibility(View.GONE);
250     }
251 
252     @Override
onResume()253     public void onResume() {
254         super.onResume();
255         if (mFragmentStopped && mTutorialController != null) {
256             mTutorialController.showFeedback();
257             mFragmentStopped = false;
258         } else {
259             changeController(mTutorialType);
260         }
261     }
262 
263     @Override
onTouch(View view, MotionEvent motionEvent)264     public boolean onTouch(View view, MotionEvent motionEvent) {
265         // Note: Using logical-or to ensure both functions get called.
266         return mEdgeBackGestureHandler.onTouch(view, motionEvent)
267                 | mNavBarGestureHandler.onTouch(view, motionEvent);
268     }
269 
onInterceptTouch(MotionEvent motionEvent)270     boolean onInterceptTouch(MotionEvent motionEvent) {
271         // Note: Using logical-or to ensure both functions get called.
272         return mEdgeBackGestureHandler.onInterceptTouch(motionEvent)
273                 | mNavBarGestureHandler.onInterceptTouch(motionEvent);
274     }
275 
onAttachedToWindow()276     void onAttachedToWindow() {
277         mEdgeBackGestureHandler.setViewGroupParent(getRootView());
278     }
279 
onDetachedFromWindow()280     void onDetachedFromWindow() {
281         mEdgeBackGestureHandler.setViewGroupParent(null);
282     }
283 
changeController(TutorialType tutorialType)284     void changeController(TutorialType tutorialType) {
285         if (getControllerClass().isInstance(mTutorialController)) {
286             mTutorialController.setTutorialType(tutorialType);
287             mTutorialController.fadeTaskViewAndRun(mTutorialController::transitToController);
288         } else {
289             mTutorialController = createController(tutorialType);
290             mTutorialController.transitToController();
291         }
292         mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
293         mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
294         mTutorialType = tutorialType;
295         initializeFeedbackVideoView();
296     }
297 
298     @Override
onSaveInstanceState(Bundle savedInstanceState)299     public void onSaveInstanceState(Bundle savedInstanceState) {
300         savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
301         super.onSaveInstanceState(savedInstanceState);
302     }
303 
getRootView()304     RootSandboxLayout getRootView() {
305         return mRootView;
306     }
307 
continueTutorial()308     void continueTutorial() {
309         GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity();
310 
311         if (gestureSandboxActivity == null) {
312             closeTutorial();
313             return;
314         }
315         gestureSandboxActivity.continueTutorial();
316     }
317 
closeTutorial()318     void closeTutorial() {
319         FragmentActivity activity = getActivity();
320         if (activity != null) {
321             activity.setResult(Activity.RESULT_OK);
322             activity.finish();
323         }
324     }
325 
startSystemNavigationSetting()326     void startSystemNavigationSetting() {
327         startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
328     }
329 
getCurrentStep()330     int getCurrentStep() {
331         GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity();
332 
333         return gestureSandboxActivity == null ? -1 : gestureSandboxActivity.getCurrentStep();
334     }
335 
getNumSteps()336     int getNumSteps() {
337         GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity();
338 
339         return gestureSandboxActivity == null ? -1 : gestureSandboxActivity.getNumSteps();
340     }
341 
isAtFinalStep()342     boolean isAtFinalStep() {
343         return getCurrentStep() == getNumSteps();
344     }
345 
346     @Nullable
getGestureSandboxActivity()347     private GestureSandboxActivity getGestureSandboxActivity() {
348         Context context = getContext();
349 
350         return context instanceof GestureSandboxActivity ? (GestureSandboxActivity) context : null;
351     }
352 }
353