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