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.content.Intent;
19 import android.graphics.Insets;
20 import android.os.Bundle;
21 import android.util.Log;
22 import android.view.LayoutInflater;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.view.View.OnTouchListener;
26 import android.view.ViewGroup;
27 import android.view.WindowInsets;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 import androidx.fragment.app.Fragment;
32 import androidx.fragment.app.FragmentActivity;
33 
34 import com.android.launcher3.R;
35 import com.android.quickstep.interaction.TutorialController.TutorialType;
36 
37 abstract class TutorialFragment extends Fragment implements OnTouchListener {
38 
39     private static final String LOG_TAG = "TutorialFragment";
40     static final String KEY_TUTORIAL_TYPE = "tutorial_type";
41 
42     TutorialType mTutorialType;
43     @Nullable TutorialController mTutorialController = null;
44     View mRootView;
45     TutorialHandAnimation mHandCoachingAnimation;
46     EdgeBackGestureHandler mEdgeBackGestureHandler;
47     NavBarGestureHandler mNavBarGestureHandler;
48 
newInstance(TutorialType tutorialType)49     public static TutorialFragment newInstance(TutorialType tutorialType) {
50         TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
51         if (fragment == null) {
52             fragment = new BackGestureTutorialFragment();
53             tutorialType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
54         }
55         Bundle args = new Bundle();
56         args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
57         fragment.setArguments(args);
58         return fragment;
59     }
60 
61     @Nullable
getFragmentForTutorialType(TutorialType tutorialType)62     private static TutorialFragment getFragmentForTutorialType(TutorialType tutorialType) {
63         switch (tutorialType) {
64             case RIGHT_EDGE_BACK_NAVIGATION:
65             case LEFT_EDGE_BACK_NAVIGATION:
66             case BACK_NAVIGATION_COMPLETE:
67                 return new BackGestureTutorialFragment();
68             case HOME_NAVIGATION:
69             case HOME_NAVIGATION_COMPLETE:
70                 return new HomeGestureTutorialFragment();
71             case OVERVIEW_NAVIGATION:
72             case OVERVIEW_NAVIGATION_COMPLETE:
73                 return new OverviewGestureTutorialFragment();
74             case ASSISTANT:
75             case ASSISTANT_COMPLETE:
76                 return new AssistantGestureTutorialFragment();
77             default:
78                 Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
79         }
80         return null;
81     }
82 
getHandAnimationResId()83     abstract int getHandAnimationResId();
84 
createController(TutorialType type)85     abstract TutorialController createController(TutorialType type);
86 
getControllerClass()87     abstract Class<? extends TutorialController> getControllerClass();
88 
89     @Override
onCreate(Bundle savedInstanceState)90     public void onCreate(Bundle savedInstanceState) {
91         super.onCreate(savedInstanceState);
92         Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
93         mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
94         mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
95         mNavBarGestureHandler = new NavBarGestureHandler(getContext());
96     }
97 
98     @Override
onDestroy()99     public void onDestroy() {
100         super.onDestroy();
101         mEdgeBackGestureHandler.unregisterBackGestureAttemptCallback();
102         mNavBarGestureHandler.unregisterNavBarGestureAttemptCallback();
103     }
104 
105     @Override
onCreateView( @onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)106     public View onCreateView(
107             @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
108         super.onCreateView(inflater, container, savedInstanceState);
109 
110         mRootView = inflater.inflate(R.layout.gesture_tutorial_fragment, container, false);
111         mRootView.setOnApplyWindowInsetsListener((view, insets) -> {
112             Insets systemInsets = insets.getInsets(WindowInsets.Type.systemBars());
113             mEdgeBackGestureHandler.setInsets(systemInsets.left, systemInsets.right);
114             return insets;
115         });
116         mRootView.setOnTouchListener(this);
117         mHandCoachingAnimation = new TutorialHandAnimation(getContext(), mRootView,
118                 getHandAnimationResId());
119         return mRootView;
120     }
121 
122     @Override
onResume()123     public void onResume() {
124         super.onResume();
125         changeController(mTutorialType);
126     }
127 
128     @Override
onPause()129     public void onPause() {
130         super.onPause();
131         mHandCoachingAnimation.stop();
132     }
133 
134     @Override
onTouch(View view, MotionEvent motionEvent)135     public boolean onTouch(View view, MotionEvent motionEvent) {
136         // Note: Using logical or to ensure both functions get called.
137         return mEdgeBackGestureHandler.onTouch(view, motionEvent)
138                 | mNavBarGestureHandler.onTouch(view, motionEvent);
139     }
140 
onAttachedToWindow()141     void onAttachedToWindow() {
142         mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView());
143     }
144 
onDetachedFromWindow()145     void onDetachedFromWindow() {
146         mEdgeBackGestureHandler.setViewGroupParent(null);
147     }
148 
changeController(TutorialType tutorialType)149     void changeController(TutorialType tutorialType) {
150         if (getControllerClass().isInstance(mTutorialController)) {
151             mTutorialController.setTutorialType(tutorialType);
152         } else {
153             mTutorialController = createController(tutorialType);
154         }
155         mTutorialController.transitToController();
156         mEdgeBackGestureHandler.registerBackGestureAttemptCallback(mTutorialController);
157         mNavBarGestureHandler.registerNavBarGestureAttemptCallback(mTutorialController);
158         mTutorialType = tutorialType;
159     }
160 
161     @Override
onSaveInstanceState(Bundle savedInstanceState)162     public void onSaveInstanceState(Bundle savedInstanceState) {
163         savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType);
164         super.onSaveInstanceState(savedInstanceState);
165     }
166 
getRootView()167     View getRootView() {
168         return mRootView;
169     }
170 
getHandAnimation()171     TutorialHandAnimation getHandAnimation() {
172         return mHandCoachingAnimation;
173     }
174 
closeTutorial()175     void closeTutorial() {
176         FragmentActivity activity = getActivity();
177         if (activity != null) {
178             activity.finish();
179         }
180     }
181 
startSystemNavigationSetting()182     void startSystemNavigationSetting() {
183         startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
184     }
185 }
186