Enforce the correct orientation for the gesture navigation tutorial
The lottie animations used for the gesture navigation tutorial are
designed for specific orientations depending on the device. This change
ensures that users are in the correct orientation for their device. For large screen users in portrait mode, we display a prompt to rotate the screen.
Flag: ENABLE_NEW_GESTURE_TUTORIAL
Fix: 277781713
Bug: 276515961
Test: Manually went through the tutorial starting from both
orientations on handheld, foldable, and tablet to ensure that the prompt
was shown and hidden correctly. Also ensured that gestures cannot be
completed in the background while the prompt is being shown.
Change-Id: I86ae566721f240264177ad4ec7fc12e58d1b95cd
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 8e1baba..7c0a5ae 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -104,7 +104,8 @@
android:autoRemoveFromRecents="true"
android:excludeFromRecents="true"
android:theme="@style/GestureTutorialActivity"
- android:exported="true">
+ android:exported="true"
+ android:configChanges="orientation">
<intent-filter>
<action android:name="com.android.quickstep.action.GESTURE_SANDBOX"/>
<category android:name="android.intent.category.DEFAULT"/>
diff --git a/quickstep/res/drawable/rotate_prompt_bg.xml b/quickstep/res/drawable/rotate_prompt_bg.xml
new file mode 100644
index 0000000..528a2bc
--- /dev/null
+++ b/quickstep/res/drawable/rotate_prompt_bg.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/surfaceContainer" />
+ <corners android:radius="@dimen/gesture_tutorial_menu_button_radius" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/drawable/rotate_tutorial_warning.xml b/quickstep/res/drawable/rotate_tutorial_warning.xml
new file mode 100644
index 0000000..d1117a4
--- /dev/null
+++ b/quickstep/res/drawable/rotate_tutorial_warning.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="?androidprv:attr/materialColorOnSurface"
+ android:pathData="M40,840L480,80L920,840L40,840ZM178,760L782,760L480,240L178,760ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM440,600L520,600L520,400L440,400L440,600ZM480,500L480,500L480,500L480,500Z" />
+</vector>
diff --git a/quickstep/res/layout/gesture_tutorial_activity.xml b/quickstep/res/layout/gesture_tutorial_activity.xml
index 4dc8913..0e763ec 100644
--- a/quickstep/res/layout/gesture_tutorial_activity.xml
+++ b/quickstep/res/layout/gesture_tutorial_activity.xml
@@ -13,7 +13,80 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/gesture_tutorial_fragment_container"
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
\ No newline at end of file
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/gesture_tutorial_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <RelativeLayout
+ android:id="@+id/rotation_prompt"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/surfaceHome"
+ android:visibility="gone"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/background"
+ android:layout_width="300dp"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:background="@drawable/rotate_prompt_bg"
+ android:padding="24dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/rotate_tutorial_warning"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:id="@+id/rotate_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:lineSpacingExtra="2sp"
+ android:text="@string/gesture_tutorial_rotation_prompt_title"
+ android:textAppearance="@style/rotate_prompt_title"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:id="@+id/rotate_screen_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:gravity="center"
+ android:text="@string/gesture_tutorial_rotation_prompt"
+ android:textAppearance="@style/rotate_prompt_subtitle"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/rotate_title"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </RelativeLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index bd69f9f..3b82784 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -94,6 +94,10 @@
<!-- content description for hotseat items -->
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
+ <!-- Title of prompt shown before the gesture navigation tutorial to users who need to rotate their screen. [CHAR LIMIT=100] -->
+ <string name="gesture_tutorial_rotation_prompt_title">Rotate your device</string>
+ <!-- Prompt shown before the gesture navigation tutorial to users who need to rotate their screen to begin. [CHAR LIMIT=100] -->
+ <string name="gesture_tutorial_rotation_prompt">Please rotate your device to complete the gesture navigation tutorial</string>
<!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
<string name="back_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe from the far-right or far-left edge.</string>
<!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 612682a..21b7fd5 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -290,4 +290,12 @@
<item name="surfaceOverview">@android:color/system_accent2_300</item>
<item name="secondaryOverview">?androidprv:attr/materialColorOnSecondaryFixedVariant</item>
</style>
+
+ <style name="rotate_prompt_title" parent="TextAppearance.GestureTutorial.Dialog.Title">
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ </style>
+
+ <style name="rotate_prompt_subtitle" parent="TextAppearance.GestureTutorial.Dialog.Subtitle">
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+ </style>
</resources>
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 54c441b..9083d51 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -147,7 +147,7 @@
@Override
public void onBackGestureAttempted(BackGestureResult result) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
switch (mTutorialType) {
@@ -165,7 +165,7 @@
@Override
public void onBackGestureProgress(float diffx, float diffy, boolean isLeftGesture) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
@@ -234,7 +234,7 @@
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index aeac760..62726a0 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -16,6 +16,8 @@
package com.android.quickstep.interaction;
import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
@@ -29,6 +31,8 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
@@ -53,10 +57,12 @@
private int mCurrentStep;
private int mNumSteps;
+ private boolean mShowRotationPrompt;
private SharedPreferences mSharedPrefs;
private StatsLogManager mStatsLogManager;
+ private View mRotationPrompt;
private TISBindHelper mTISBindHelper;
private TISBinder mBinder;
@@ -94,10 +100,50 @@
.add(R.id.gesture_tutorial_fragment_container, mFragment)
.commit();
+ mRotationPrompt = findViewById(R.id.rotation_prompt);
+ if (FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ correctUserOrientation();
+ }
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ // Ensure the prompt to rotate the screen is updated
+ if (FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
+ correctUserOrientation();
+ }
+ }
+
+ /**
+ * Gesture animations are only in landscape for large screens and portrait for mobile. This
+ * method enforces the following flows:
+ * 1) phone / two-panel closed -> lock to portrait
+ * 2) two-panel open / tablet + portrait -> prompt the user to rotate the screen
+ * 3) two-panel open / tablet + landscape -> hide potential rotating prompt
+ */
+ private void correctUserOrientation() {
+ DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get(
+ getApplicationContext()).getDeviceProfile(this);
+ if (deviceProfile.isTablet) {
+ mShowRotationPrompt = getResources().getConfiguration().orientation
+ == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ updateVisibility(mRotationPrompt, mShowRotationPrompt ? View.VISIBLE : View.GONE);
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ }
+
+ void updateVisibility(View view, int visibility) {
+ if (view == null || view.getVisibility() == visibility) {
+ return;
+ }
+ view.setVisibility(visibility);
+ }
+
+ @Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (mFragment.shouldDisableSystemGestures()) {
@@ -128,6 +174,10 @@
super.onSaveInstanceState(savedInstanceState);
}
+ protected boolean isRotationPromptShowing() {
+ return mShowRotationPrompt;
+ }
+
protected SharedPreferences getSharedPrefs() {
return mSharedPrefs;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 891f20e..333ecbb 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -140,7 +140,7 @@
@Override
public void onBackGestureAttempted(BackGestureResult result) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
switch (mTutorialType) {
@@ -167,7 +167,7 @@
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
switch (mTutorialType) {
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 667fe4d..09a6bbe 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -170,7 +170,7 @@
@Override
public void onBackGestureAttempted(BackGestureResult result) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
switch (mTutorialType) {
@@ -197,7 +197,7 @@
@Override
public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
switch (mTutorialType) {
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 0bbf373..f1cbfcc 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -260,7 +260,7 @@
@Override
public void setNavBarGestureProgress(@Nullable Float displacement) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
if (mTutorialType == HOME_NAVIGATION_COMPLETE
@@ -281,7 +281,7 @@
@Override
public void onMotionPaused(boolean unused) {
- if (isGestureCompleted()) {
+ if (skipGestureAttempt()) {
return;
}
if (mShowTasks) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 5ec92a6..a58f453 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -462,6 +462,10 @@
return mGestureCompleted;
}
+ public boolean skipGestureAttempt() {
+ return isGestureCompleted() || mTutorialFragment.isRotationPromptShowing();
+ }
+
void hideFeedback() {
if (mFeedbackView.getVisibility() != View.VISIBLE) {
return;
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index bfad643..84326f5 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -498,6 +498,11 @@
return activity != null ? activity.getStatsLogManager() : null;
}
+ protected boolean isRotationPromptShowing() {
+ GestureSandboxActivity activity = getGestureSandboxActivity();
+ return activity != null && activity.isRotationPromptShowing();
+ }
+
@Nullable
private SharedPreferences getSharedPreferences() {
GestureSandboxActivity activity = getGestureSandboxActivity();