• 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.content.SharedPreferences;
19 import android.graphics.Color;
20 import android.graphics.Rect;
21 import android.os.Bundle;
22 import android.text.TextUtils;
23 import android.util.DisplayMetrics;
24 import android.view.Display;
25 import android.view.View;
26 import android.view.Window;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.fragment.app.FragmentActivity;
31 
32 import com.android.launcher3.LauncherPrefs;
33 import com.android.launcher3.R;
34 import com.android.launcher3.config.FeatureFlags;
35 import com.android.launcher3.logging.StatsLogManager;
36 import com.android.quickstep.TouchInteractionService.TISBinder;
37 import com.android.quickstep.interaction.TutorialController.TutorialType;
38 import com.android.quickstep.util.TISBindHelper;
39 
40 import java.util.Arrays;
41 
42 /** Shows the gesture interactive sandbox in full screen mode. */
43 public class GestureSandboxActivity extends FragmentActivity {
44 
45     private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
46     private static final String KEY_CURRENT_STEP = "current_step";
47     static final String KEY_TUTORIAL_TYPE = "tutorial_type";
48     static final String KEY_GESTURE_COMPLETE = "gesture_complete";
49     static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
50 
51     @Nullable private TutorialType[] mTutorialSteps;
52     private GestureSandboxFragment mFragment;
53 
54     private int mCurrentStep;
55     private int mNumSteps;
56 
57     private SharedPreferences mSharedPrefs;
58     private StatsLogManager mStatsLogManager;
59 
60     private TISBindHelper mTISBindHelper;
61     private TISBinder mBinder;
62 
63     @Override
onCreate(Bundle savedInstanceState)64     protected void onCreate(Bundle savedInstanceState) {
65         super.onCreate(savedInstanceState);
66         requestWindowFeature(Window.FEATURE_NO_TITLE);
67         setContentView(R.layout.gesture_tutorial_activity);
68 
69         mSharedPrefs = LauncherPrefs.getPrefs(this);
70         mStatsLogManager = StatsLogManager.newInstance(getApplicationContext());
71 
72         Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
73 
74         boolean gestureComplete = args != null && args.getBoolean(KEY_GESTURE_COMPLETE, false);
75         if (FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
76                 && args != null
77                 && args.getBoolean(KEY_USE_TUTORIAL_MENU, false)) {
78             mTutorialSteps = null;
79             TutorialType tutorialTypeOverride = (TutorialType) args.get(KEY_TUTORIAL_TYPE);
80             mFragment = tutorialTypeOverride == null
81                     ? new MenuFragment()
82                     : makeTutorialFragment(
83                             tutorialTypeOverride,
84                             gestureComplete,
85                             /* fromMenu= */ true);
86         } else {
87             mTutorialSteps = getTutorialSteps(args);
88             mFragment = makeTutorialFragment(
89                     mTutorialSteps[mCurrentStep - 1],
90                     gestureComplete,
91                     /* fromMenu= */ false);
92         }
93         getSupportFragmentManager().beginTransaction()
94                 .add(R.id.gesture_tutorial_fragment_container, mFragment)
95                 .commit();
96 
97         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
98     }
99 
100     @Override
onAttachedToWindow()101     public void onAttachedToWindow() {
102         super.onAttachedToWindow();
103         if (mFragment.shouldDisableSystemGestures()) {
104             disableSystemGestures();
105         }
106         mFragment.onAttachedToWindow();
107     }
108 
109     @Override
onDetachedFromWindow()110     public void onDetachedFromWindow() {
111         super.onDetachedFromWindow();
112         mFragment.onDetachedFromWindow();
113     }
114 
115     @Override
onWindowFocusChanged(boolean hasFocus)116     public void onWindowFocusChanged(boolean hasFocus) {
117         super.onWindowFocusChanged(hasFocus);
118         if (hasFocus) {
119             hideSystemUI();
120         }
121     }
122 
123     @Override
onSaveInstanceState(@onNull Bundle savedInstanceState)124     protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
125         savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
126         savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep);
127         mFragment.onSaveInstanceState(savedInstanceState);
128         super.onSaveInstanceState(savedInstanceState);
129     }
130 
getSharedPrefs()131     protected SharedPreferences getSharedPrefs() {
132         return mSharedPrefs;
133     }
134 
getStatsLogManager()135     protected StatsLogManager getStatsLogManager() {
136         return mStatsLogManager;
137     }
138 
139     /** Returns true iff there aren't anymore tutorial types to display to the user. */
isTutorialComplete()140     public boolean isTutorialComplete() {
141         return mCurrentStep >= mNumSteps;
142     }
143 
getCurrentStep()144     public int getCurrentStep() {
145         return mCurrentStep;
146     }
147 
getNumSteps()148     public int getNumSteps() {
149         return mNumSteps;
150     }
151 
152     /**
153      * Replaces the current TutorialFragment, continuing to the next tutorial step if there is one.
154      *
155      * If there is no following step, the tutorial is closed.
156      */
continueTutorial()157     public void continueTutorial() {
158         if (isTutorialComplete() || mTutorialSteps == null) {
159             mFragment.close();
160             return;
161         }
162         launchTutorialStep(mTutorialSteps[mCurrentStep], false);
163         mCurrentStep++;
164     }
165 
makeTutorialFragment( @onNull TutorialType tutorialType, boolean gestureComplete, boolean fromMenu)166     private TutorialFragment makeTutorialFragment(
167             @NonNull TutorialType tutorialType, boolean gestureComplete, boolean fromMenu) {
168         return TutorialFragment.newInstance(tutorialType, gestureComplete, fromMenu);
169     }
170 
171     /**
172      * Launches the given gesture nav tutorial step.
173      *
174      * If the step is being launched from the gesture nav tutorial menu, then that step will launch
175      * the menu when complete.
176      */
launchTutorialStep(@onNull TutorialType tutorialType, boolean fromMenu)177     public void launchTutorialStep(@NonNull TutorialType tutorialType, boolean fromMenu) {
178         mFragment = makeTutorialFragment(tutorialType, false, fromMenu);
179         getSupportFragmentManager().beginTransaction()
180                 .replace(R.id.gesture_tutorial_fragment_container, mFragment)
181                 .runOnCommit(() -> mFragment.onAttachedToWindow())
182                 .commit();
183     }
184 
185     /** Launches the gesture nav tutorial menu page */
launchTutorialMenu()186     public void launchTutorialMenu() {
187         mFragment = new MenuFragment();
188         getSupportFragmentManager().beginTransaction()
189                 .add(R.id.gesture_tutorial_fragment_container, mFragment)
190                 .commit();
191     }
192 
getTutorialStepNames()193     private String[] getTutorialStepNames() {
194         if (mTutorialSteps == null) {
195             return new String[0];
196         }
197         String[] tutorialStepNames = new String[mTutorialSteps.length];
198 
199         int i = 0;
200         for (TutorialType tutorialStep : mTutorialSteps) {
201             tutorialStepNames[i++] = tutorialStep.name();
202         }
203 
204         return tutorialStepNames;
205     }
206 
getTutorialSteps(Bundle extras)207     private TutorialType[] getTutorialSteps(Bundle extras) {
208         TutorialType[] defaultSteps = new TutorialType[] {
209                 TutorialType.HOME_NAVIGATION,
210                 TutorialType.BACK_NAVIGATION,
211                 TutorialType.OVERVIEW_NAVIGATION};
212         mCurrentStep = 1;
213         mNumSteps = defaultSteps.length;
214 
215         if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
216             return defaultSteps;
217         }
218 
219         String[] savedStepsNames;
220         Object savedSteps = extras.get(KEY_TUTORIAL_STEPS);
221         if (savedSteps instanceof String) {
222             savedStepsNames = TextUtils.isEmpty((String) savedSteps)
223                     ? null : ((String) savedSteps).split(",");
224         } else if (savedSteps instanceof String[]) {
225             savedStepsNames = (String[]) savedSteps;
226         } else {
227             return defaultSteps;
228         }
229 
230         if (savedStepsNames == null || savedStepsNames.length == 0) {
231             return defaultSteps;
232         }
233 
234         TutorialType[] tutorialSteps = new TutorialType[savedStepsNames.length];
235         for (int i = 0; i < savedStepsNames.length; i++) {
236             tutorialSteps[i] = TutorialType.valueOf(savedStepsNames[i]);
237         }
238 
239         mCurrentStep = Math.max(extras.getInt(KEY_CURRENT_STEP, -1), 1);
240         mNumSteps = tutorialSteps.length;
241 
242         return tutorialSteps;
243     }
244 
hideSystemUI()245     private void hideSystemUI() {
246         getWindow().getDecorView().setSystemUiVisibility(
247                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
248                         | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
249                         | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
250                         | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
251                         | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
252                         | View.SYSTEM_UI_FLAG_FULLSCREEN);
253         getWindow().setNavigationBarColor(Color.TRANSPARENT);
254     }
255 
disableSystemGestures()256     private void disableSystemGestures() {
257         Display display = getDisplay();
258         if (display != null) {
259             DisplayMetrics metrics = new DisplayMetrics();
260             display.getMetrics(metrics);
261             getWindow().setSystemGestureExclusionRects(
262                     Arrays.asList(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
263         }
264     }
265 
266     @Override
onResume()267     protected void onResume() {
268         super.onResume();
269         updateServiceState(true);
270     }
271 
onTISConnected(TISBinder binder)272     private void onTISConnected(TISBinder binder) {
273         mBinder = binder;
274         updateServiceState(isResumed());
275     }
276 
277     @Override
onPause()278     protected void onPause() {
279         super.onPause();
280         updateServiceState(false);
281     }
282 
updateServiceState(boolean isEnabled)283     private void updateServiceState(boolean isEnabled) {
284         if (mBinder != null) {
285             mBinder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
286         }
287     }
288 
289     @Override
onDestroy()290     protected void onDestroy() {
291         super.onDestroy();
292         mTISBindHelper.onDestroy();
293         updateServiceState(false);
294     }
295 }
296