diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java index c3b342babc..cf523d054d 100644 --- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java @@ -29,8 +29,6 @@ import androidx.fragment.app.FragmentActivity; import com.android.launcher3.R; import com.android.quickstep.interaction.TutorialController.TutorialType; -import java.util.ArrayDeque; -import java.util.Deque; import java.util.List; /** Shows the gesture interactive sandbox in full screen mode. */ @@ -39,8 +37,9 @@ public class GestureSandboxActivity extends FragmentActivity { private static final String LOG_TAG = "GestureSandboxActivity"; private static final String KEY_TUTORIAL_STEPS = "tutorial_steps"; + private static final String KEY_CURRENT_STEP = "current_step"; - private Deque mTutorialSteps; + private TutorialType[] mTutorialSteps; private TutorialType mCurrentTutorialStep; private TutorialFragment mFragment; @@ -55,9 +54,7 @@ public class GestureSandboxActivity extends FragmentActivity { Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState; mTutorialSteps = getTutorialSteps(args); - mCurrentStep = 1; - mNumSteps = mTutorialSteps.size(); - mCurrentTutorialStep = mTutorialSteps.pop(); + mCurrentTutorialStep = mTutorialSteps[mCurrentStep - 1]; mFragment = TutorialFragment.newInstance(mCurrentTutorialStep); getSupportFragmentManager().beginTransaction() .add(R.id.gesture_tutorial_fragment_container, mFragment) @@ -88,12 +85,13 @@ public class GestureSandboxActivity extends FragmentActivity { @Override protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) { savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames()); + savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep); super.onSaveInstanceState(savedInstanceState); } /** Returns true iff there aren't anymore tutorial types to display to the user. */ public boolean isTutorialComplete() { - return mTutorialSteps.isEmpty(); + return mCurrentStep >= mNumSteps; } public int getCurrentStep() { @@ -121,7 +119,7 @@ public class GestureSandboxActivity extends FragmentActivity { mFragment.closeTutorial(); return; } - mCurrentTutorialStep = mTutorialSteps.pop(); + mCurrentTutorialStep = mTutorialSteps[mCurrentStep]; mFragment = TutorialFragment.newInstance(mCurrentTutorialStep); getSupportFragmentManager().beginTransaction() .replace(R.id.gesture_tutorial_fragment_container, mFragment) @@ -131,10 +129,9 @@ public class GestureSandboxActivity extends FragmentActivity { } private String[] getTutorialStepNames() { - String[] tutorialStepNames = new String[mTutorialSteps.size() + 1]; + String[] tutorialStepNames = new String[mTutorialSteps.length]; - int i = 1; - tutorialStepNames[0] = mCurrentTutorialStep.name(); + int i = 0; for (TutorialType tutorialStep : mTutorialSteps) { tutorialStepNames[i++] = tutorialStep.name(); } @@ -142,25 +139,28 @@ public class GestureSandboxActivity extends FragmentActivity { return tutorialStepNames; } - private Deque getTutorialSteps(Bundle extras) { - Deque defaultSteps = new ArrayDeque<>(); - defaultSteps.push(TutorialType.RIGHT_EDGE_BACK_NAVIGATION); + private TutorialType[] getTutorialSteps(Bundle extras) { + TutorialType[] defaultSteps = new TutorialType[] {TutorialType.LEFT_EDGE_BACK_NAVIGATION}; if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) { return defaultSteps; } String[] tutorialStepNames = extras.getStringArray(KEY_TUTORIAL_STEPS); + int currentStep = extras.getInt(KEY_CURRENT_STEP, -1); if (tutorialStepNames == null) { return defaultSteps; } - Deque tutorialSteps = new ArrayDeque<>(); - for (String tutorialStepName : tutorialStepNames) { - tutorialSteps.addLast(TutorialType.valueOf(tutorialStepName)); + TutorialType[] tutorialSteps = new TutorialType[tutorialStepNames.length]; + for (int i = 0; i < tutorialStepNames.length; i++) { + tutorialSteps[i] = TutorialType.valueOf(tutorialStepNames[i]); } + mCurrentStep = Math.max(currentStep, 1); + mNumSteps = tutorialSteps.length; + return tutorialSteps; } diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index c9da609a08..dfe0c7270a 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -58,6 +58,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, private static final int FEEDBACK_ANIMATION_MS = 250; private static final int RIPPLE_VISIBLE_MS = 300; + private static final int GESTURE_ANIMATION_DELAY_MS = 1500; final TutorialFragment mTutorialFragment; TutorialType mTutorialType; @@ -65,6 +66,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, final TextView mCloseButton; final ViewGroup mFeedbackView; + final TextView mFeedbackTitleView; final ImageView mFeedbackVideoView; final ImageView mGestureVideoView; final RelativeLayout mFakeLauncherView; @@ -80,6 +82,12 @@ abstract class TutorialController implements BackGestureAttemptCallback, protected boolean mGestureCompleted = false; + // These runnables should be used when posting callbacks to their views and cleared from their + // views before posting new callbacks. + private final Runnable mTitleViewCallback; + @Nullable private Runnable mFeedbackViewCallback; + @Nullable private Runnable mFeedbackVideoViewCallback; + TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { mTutorialFragment = tutorialFragment; mTutorialType = tutorialType; @@ -89,6 +97,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button); mCloseButton.setOnClickListener(button -> showSkipTutorialDialog()); mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view); + mFeedbackTitleView = mFeedbackView.findViewById( + R.id.gesture_tutorial_fragment_feedback_title); mFeedbackVideoView = rootView.findViewById(R.id.gesture_tutorial_feedback_video); mGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_gesture_video); mFakeLauncherView = rootView.findViewById(R.id.gesture_tutorial_fake_launcher_view); @@ -103,6 +113,9 @@ abstract class TutorialController implements BackGestureAttemptCallback, mTutorialStepView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step); mSkipTutorialDialog = createSkipTutorialDialog(); + + mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent( + AccessibilityEvent.TYPE_VIEW_FOCUSED); } private void showSkipTutorialDialog() { @@ -169,7 +182,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, playFeedbackVideo(tutorialAnimation, gestureAnimation, () -> { mFeedbackView.setTranslationY(0); title.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - }); + }, true); } } @@ -192,15 +205,17 @@ abstract class TutorialController implements BackGestureAttemptCallback, isGestureSuccessful ? R.string.gesture_tutorial_nice : R.string.gesture_tutorial_try_again, subtitleResId, - isGestureSuccessful); + isGestureSuccessful, + false); } void showFeedback( int titleResId, int subtitleResId, - boolean isGestureSuccessful) { - TextView title = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_title); - title.setText(titleResId); + boolean isGestureSuccessful, + boolean useGestureAnimationDelay) { + mFeedbackTitleView.setText(titleResId); + mFeedbackTitleView.removeCallbacks(mTitleViewCallback); TextView subtitle = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle); subtitle.setText(subtitleResId); @@ -221,11 +236,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, .setDuration(FEEDBACK_ANIMATION_MS) .translationY(0) .start(); - title.postDelayed( - () -> title.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_FOCUSED), - FEEDBACK_ANIMATION_MS); - }); + mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS); + }, useGestureAnimationDelay); return; } else { mTutorialFragment.releaseFeedbackVideoView(); @@ -237,10 +249,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, .setDuration(FEEDBACK_ANIMATION_MS) .translationY(0) .start(); - title.postDelayed( - () -> title.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_FOCUSED), - FEEDBACK_ANIMATION_MS); + mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS); } void hideFeedback(boolean releaseFeedbackVideo) { @@ -254,7 +263,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, private void playFeedbackVideo( @NonNull AnimatedVectorDrawable tutorialAnimation, @NonNull AnimatedVectorDrawable gestureAnimation, - @NonNull Runnable onStartRunnable) { + @NonNull Runnable onStartRunnable, + boolean useGestureAnimationDelay) { if (tutorialAnimation.isRunning()) { tutorialAnimation.reset(); @@ -270,7 +280,9 @@ abstract class TutorialController implements BackGestureAttemptCallback, gestureAnimation.stop(); } - onStartRunnable.run(); + if (!useGestureAnimationDelay) { + onStartRunnable.run(); + } } @Override @@ -284,8 +296,28 @@ abstract class TutorialController implements BackGestureAttemptCallback, } }); - tutorialAnimation.start(); - mFeedbackVideoView.setVisibility(View.VISIBLE); + if (mFeedbackViewCallback != null) { + mFeedbackVideoView.removeCallbacks(mFeedbackViewCallback); + mFeedbackViewCallback = null; + } + if (mFeedbackVideoViewCallback != null) { + mFeedbackVideoView.removeCallbacks(mFeedbackVideoViewCallback); + mFeedbackVideoViewCallback = null; + } + if (useGestureAnimationDelay) { + mFeedbackViewCallback = onStartRunnable; + mFeedbackVideoViewCallback = () -> { + mFeedbackVideoView.setVisibility(View.VISIBLE); + tutorialAnimation.start(); + }; + + mFeedbackVideoView.setVisibility(View.GONE); + mFeedbackView.post(mFeedbackViewCallback); + mFeedbackVideoView.postDelayed(mFeedbackVideoViewCallback, GESTURE_ANIMATION_DELAY_MS); + } else { + mFeedbackVideoView.setVisibility(View.VISIBLE); + tutorialAnimation.start(); + } } void setRippleHotspot(float x, float y) { diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index ec26022adf..da0fc668ac 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -170,7 +170,8 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { Integer introTileStringResId = mTutorialController.getIntroductionTitle(); Integer introSubtitleResId = mTutorialController.getIntroductionSubtitle(); if (introTileStringResId != null && introSubtitleResId != null) { - mTutorialController.showFeedback(introTileStringResId, introSubtitleResId, false); + mTutorialController.showFeedback( + introTileStringResId, introSubtitleResId, false, true); mIntroductionShown = true; } } @@ -252,7 +253,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { @Override public void onResume() { super.onResume(); - if (mFragmentStopped) { + if (mFragmentStopped && mTutorialController != null) { mTutorialController.showFeedback(); mFragmentStopped = false; } else { diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java index eda61583fa..d880d74e5d 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java @@ -23,6 +23,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.graphics.ColorUtils; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -86,6 +87,8 @@ public class TutorialStepIndicator extends LinearLayout { for (int i = mTotalSteps; i < getChildCount(); i++) { removeViewAt(i); } + int stepIndicatorColor = GraphicsUtils.getAttrColor( + getContext(), android.R.attr.textColorPrimary); for (int i = 0; i < mTotalSteps; i++) { Drawable pageIndicatorPillDrawable = AppCompatResources.getDrawable( getContext(), R.drawable.tutorial_step_indicator_pill); @@ -104,13 +107,10 @@ public class TutorialStepIndicator extends LinearLayout { } if (pageIndicatorPillDrawable != null) { if (i < mCurrentStep) { - pageIndicatorPillDrawable.setTint( - GraphicsUtils.getAttrColor(getContext(), - android.R.attr.textColorPrimary)); + pageIndicatorPillDrawable.setTint(stepIndicatorColor); } else { pageIndicatorPillDrawable.setTint( - GraphicsUtils.getAttrColor(getContext(), - android.R.attr.textColorPrimaryInverse)); + ColorUtils.setAlphaComponent(stepIndicatorColor, 0x22)); } } }