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.graphics.Color;
19 import android.graphics.Rect;
20 import android.os.Bundle;
21 import android.text.TextUtils;
22 import android.util.DisplayMetrics;
23 import android.view.Display;
24 import android.view.View;
25 import android.view.Window;
26 
27 import androidx.annotation.NonNull;
28 import androidx.fragment.app.FragmentActivity;
29 
30 import com.android.launcher3.R;
31 import com.android.quickstep.interaction.TutorialController.TutorialType;
32 
33 import java.util.List;
34 
35 /** Shows the gesture interactive sandbox in full screen mode. */
36 public class GestureSandboxActivity extends FragmentActivity {
37 
38     private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
39     private static final String KEY_CURRENT_STEP = "current_step";
40     private static final String KEY_GESTURE_COMPLETE = "gesture_complete";
41 
42     private TutorialType[] mTutorialSteps;
43     private TutorialType mCurrentTutorialStep;
44     private TutorialFragment mFragment;
45 
46     private int mCurrentStep;
47     private int mNumSteps;
48 
49     @Override
onCreate(Bundle savedInstanceState)50     protected void onCreate(Bundle savedInstanceState) {
51         super.onCreate(savedInstanceState);
52         requestWindowFeature(Window.FEATURE_NO_TITLE);
53         setContentView(R.layout.gesture_tutorial_activity);
54 
55         Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
56         mTutorialSteps = getTutorialSteps(args);
57         mCurrentTutorialStep = mTutorialSteps[mCurrentStep - 1];
58         mFragment = TutorialFragment.newInstance(
59                 mCurrentTutorialStep, args.getBoolean(KEY_GESTURE_COMPLETE, false));
60         getSupportFragmentManager().beginTransaction()
61                 .add(R.id.gesture_tutorial_fragment_container, mFragment)
62                 .commit();
63     }
64 
65     @Override
onAttachedToWindow()66     public void onAttachedToWindow() {
67         super.onAttachedToWindow();
68         disableSystemGestures();
69         mFragment.onAttachedToWindow();
70     }
71 
72     @Override
onDetachedFromWindow()73     public void onDetachedFromWindow() {
74         super.onDetachedFromWindow();
75         mFragment.onDetachedFromWindow();
76     }
77 
78     @Override
onWindowFocusChanged(boolean hasFocus)79     public void onWindowFocusChanged(boolean hasFocus) {
80         super.onWindowFocusChanged(hasFocus);
81         if (hasFocus) {
82             hideSystemUI();
83         }
84     }
85 
86     @Override
onSaveInstanceState(@onNull Bundle savedInstanceState)87     protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
88         savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
89         savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep);
90         savedInstanceState.putBoolean(KEY_GESTURE_COMPLETE, mFragment.isGestureComplete());
91         super.onSaveInstanceState(savedInstanceState);
92     }
93 
94     /** Returns true iff there aren't anymore tutorial types to display to the user. */
isTutorialComplete()95     public boolean isTutorialComplete() {
96         return mCurrentStep >= mNumSteps;
97     }
98 
getCurrentStep()99     public int getCurrentStep() {
100         return mCurrentStep;
101     }
102 
getNumSteps()103     public int getNumSteps() {
104         return mNumSteps;
105     }
106 
107     /**
108      * Closes the tutorial and this activity.
109      */
closeTutorial()110     public void closeTutorial() {
111         mFragment.closeTutorial();
112     }
113 
114     /**
115      * Replaces the current TutorialFragment, continuing to the next tutorial step if there is one.
116      *
117      * If there is no following step, the tutorial is closed.
118      */
continueTutorial()119     public void continueTutorial() {
120         if (isTutorialComplete()) {
121             mFragment.closeTutorial();
122             return;
123         }
124         mCurrentTutorialStep = mTutorialSteps[mCurrentStep];
125         mFragment = TutorialFragment.newInstance(mCurrentTutorialStep, false);
126         getSupportFragmentManager().beginTransaction()
127                 .replace(R.id.gesture_tutorial_fragment_container, mFragment)
128                 .runOnCommit(() -> mFragment.onAttachedToWindow())
129                 .commit();
130         mCurrentStep++;
131     }
132 
getTutorialStepNames()133     private String[] getTutorialStepNames() {
134         String[] tutorialStepNames = new String[mTutorialSteps.length];
135 
136         int i = 0;
137         for (TutorialType tutorialStep : mTutorialSteps) {
138             tutorialStepNames[i++] = tutorialStep.name();
139         }
140 
141         return tutorialStepNames;
142     }
143 
getTutorialSteps(Bundle extras)144     private TutorialType[] getTutorialSteps(Bundle extras) {
145         TutorialType[] defaultSteps = new TutorialType[] {TutorialType.BACK_NAVIGATION};
146         mCurrentStep = 1;
147         mNumSteps = 1;
148 
149         if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
150             return defaultSteps;
151         }
152 
153         Object savedSteps = extras.get(KEY_TUTORIAL_STEPS);
154         int currentStep = extras.getInt(KEY_CURRENT_STEP, -1);
155         String[] savedStepsNames;
156 
157         if (savedSteps instanceof String) {
158             savedStepsNames = TextUtils.isEmpty((String) savedSteps)
159                     ? null : ((String) savedSteps).split(",");
160         } else if (savedSteps instanceof String[]) {
161             savedStepsNames = (String[]) savedSteps;
162         } else {
163             return defaultSteps;
164         }
165 
166         if (savedStepsNames == null) {
167             return defaultSteps;
168         }
169 
170         TutorialType[] tutorialSteps = new TutorialType[savedStepsNames.length];
171         for (int i = 0; i < savedStepsNames.length; i++) {
172             tutorialSteps[i] = TutorialType.valueOf(savedStepsNames[i]);
173         }
174 
175         mCurrentStep = Math.max(currentStep, 1);
176         mNumSteps = tutorialSteps.length;
177 
178         return tutorialSteps;
179     }
180 
hideSystemUI()181     private void hideSystemUI() {
182         getWindow().getDecorView().setSystemUiVisibility(
183                 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
184                         | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
185                         | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
186                         | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
187                         | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
188                         | View.SYSTEM_UI_FLAG_FULLSCREEN);
189         getWindow().setNavigationBarColor(Color.TRANSPARENT);
190     }
191 
disableSystemGestures()192     private void disableSystemGestures() {
193         Display display = getDisplay();
194         if (display != null) {
195             DisplayMetrics metrics = new DisplayMetrics();
196             display.getMetrics(metrics);
197             getWindow().setSystemGestureExclusionRects(
198                     List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
199         }
200     }
201 }
202