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