diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java index b797f0cba3..b3f23543d5 100644 --- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java @@ -15,13 +15,21 @@ */ package com.android.quickstep.interaction; +import android.content.SharedPreferences; import android.view.MotionEvent; import android.view.View; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; /** Shows the Home gesture interactive tutorial. */ public class AssistantGestureTutorialFragment extends TutorialFragment { + + protected AssistantGestureTutorialFragment( + SharedPreferences sharedPrefs, StatsLogManager statsLogManager) { + super(sharedPrefs, statsLogManager); + } + @Override TutorialController createController(TutorialType type) { return new AssistantGestureTutorialController(this, type); @@ -39,4 +47,14 @@ public class AssistantGestureTutorialFragment extends TutorialFragment { } return super.onTouch(view, motionEvent); } + + @Override + void logTutorialStepShown() { + // No-Op: tutorial step not currently shown to users + } + + @Override + void logTutorialStepCompleted() { + // No-Op: tutorial step not currently shown to users + } } diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java index f54734d525..e7ed0b4e93 100644 --- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java @@ -19,12 +19,14 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.content.SharedPreferences; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import com.android.launcher3.R; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; import java.util.ArrayList; @@ -32,6 +34,11 @@ import java.util.ArrayList; /** Shows the Back gesture interactive tutorial. */ public class BackGestureTutorialFragment extends TutorialFragment { + protected BackGestureTutorialFragment( + SharedPreferences sharedPrefs, StatsLogManager statsLogManager) { + super(sharedPrefs, statsLogManager); + } + @Nullable @Override Integer getEdgeAnimationResId() { @@ -117,4 +124,16 @@ public class BackGestureTutorialFragment extends TutorialFragment { } return super.onTouch(view, motionEvent); } + + @Override + void logTutorialStepShown() { + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_SHOWN); + } + + @Override + void logTutorialStepCompleted() { + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_COMPLETED); + } } diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java index c2524b1b5a..002e8b7749 100644 --- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java +++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java @@ -15,6 +15,7 @@ */ package com.android.quickstep.interaction; +import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; @@ -28,6 +29,8 @@ import androidx.annotation.NonNull; import androidx.fragment.app.FragmentActivity; import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; import java.util.List; @@ -46,17 +49,26 @@ public class GestureSandboxActivity extends FragmentActivity { private int mCurrentStep; private int mNumSteps; + private SharedPreferences mSharedPrefs; + private StatsLogManager mStatsLogManager; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.gesture_tutorial_activity); + mSharedPrefs = Utilities.getPrefs(this); + mStatsLogManager = StatsLogManager.newInstance(getApplicationContext()); + Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState; mTutorialSteps = getTutorialSteps(args); mCurrentTutorialStep = mTutorialSteps[mCurrentStep - 1]; mFragment = TutorialFragment.newInstance( - mCurrentTutorialStep, args.getBoolean(KEY_GESTURE_COMPLETE, false)); + mCurrentTutorialStep, + args.getBoolean(KEY_GESTURE_COMPLETE, false), + mSharedPrefs, + mStatsLogManager); getSupportFragmentManager().beginTransaction() .add(R.id.gesture_tutorial_fragment_container, mFragment) .commit(); @@ -104,13 +116,6 @@ public class GestureSandboxActivity extends FragmentActivity { return mNumSteps; } - /** - * Closes the tutorial and this activity. - */ - public void closeTutorial() { - mFragment.closeTutorial(); - } - /** * Replaces the current TutorialFragment, continuing to the next tutorial step if there is one. * @@ -122,7 +127,8 @@ public class GestureSandboxActivity extends FragmentActivity { return; } mCurrentTutorialStep = mTutorialSteps[mCurrentStep]; - mFragment = TutorialFragment.newInstance(mCurrentTutorialStep, false); + mFragment = TutorialFragment.newInstance( + mCurrentTutorialStep, /* gestureComplete= */ false, mSharedPrefs, mStatsLogManager); getSupportFragmentManager().beginTransaction() .replace(R.id.gesture_tutorial_fragment_container, mFragment) .runOnCommit(() -> mFragment.onAttachedToWindow()) diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java index 423e66f07d..e987d5a83b 100644 --- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java @@ -18,12 +18,14 @@ package com.android.quickstep.interaction; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.content.SharedPreferences; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import com.android.launcher3.R; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; import java.util.ArrayList; @@ -31,6 +33,11 @@ import java.util.ArrayList; /** Shows the Home gesture interactive tutorial. */ public class HomeGestureTutorialFragment extends TutorialFragment { + protected HomeGestureTutorialFragment( + SharedPreferences sharedPrefs, StatsLogManager statsLogManager) { + super(sharedPrefs, statsLogManager); + } + @Nullable @Override Integer getEdgeAnimationResId() { @@ -99,4 +106,16 @@ public class HomeGestureTutorialFragment extends TutorialFragment { releaseFeedbackAnimation(); return super.onTouch(view, motionEvent); } + + @Override + void logTutorialStepShown() { + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_SHOWN); + } + + @Override + void logTutorialStepCompleted() { + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_COMPLETED); + } } diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java index f63a9459a9..c7e24dbd7c 100644 --- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java @@ -18,12 +18,14 @@ package com.android.quickstep.interaction; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.content.SharedPreferences; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import com.android.launcher3.R; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; import java.util.ArrayList; @@ -31,6 +33,11 @@ import java.util.ArrayList; /** Shows the Overview gesture interactive tutorial. */ public class OverviewGestureTutorialFragment extends TutorialFragment { + protected OverviewGestureTutorialFragment( + SharedPreferences sharedPrefs, StatsLogManager statsLogManager) { + super(sharedPrefs, statsLogManager); + } + @Nullable @Override Integer getEdgeAnimationResId() { @@ -111,4 +118,16 @@ public class OverviewGestureTutorialFragment extends TutorialFragment { releaseFeedbackAnimation(); return super.onTouch(view, motionEvent); } + + @Override + void logTutorialStepShown() { + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_SHOWN); + } + + @Override + void logTutorialStepCompleted() { + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_COMPLETED); + } } diff --git a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java index 955a2f7d2e..92a27314bc 100644 --- a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java @@ -15,14 +15,21 @@ */ package com.android.quickstep.interaction; +import android.content.SharedPreferences; import android.view.MotionEvent; import android.view.View; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; /** Shows the general navigation gesture sandbox environment. */ public class SandboxModeTutorialFragment extends TutorialFragment { + protected SandboxModeTutorialFragment( + SharedPreferences sharedPrefs, StatsLogManager statsLogManager) { + super(sharedPrefs, statsLogManager); + } + @Override TutorialController createController(TutorialType type) { return new SandboxModeTutorialController(this, type); @@ -40,4 +47,14 @@ public class SandboxModeTutorialFragment extends TutorialFragment { } return super.onTouch(view, motionEvent); } + + @Override + void logTutorialStepShown() { + // No-Op: tutorial step not currently shown to users + } + + @Override + void logTutorialStepCompleted() { + // No-Op: tutorial step not currently shown to users + } } diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index 3c88988b7e..6a8894e88f 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -81,7 +81,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, TutorialType mTutorialType; final Context mContext; - final TextView mCloseButton; + final TextView mSkipButton; + final Button mDoneButton; final ViewGroup mFeedbackView; final TextView mFeedbackTitleView; final ImageView mEdgeGestureVideoView; @@ -94,7 +95,6 @@ abstract class TutorialController implements BackGestureAttemptCallback, final AnimatedTaskView mFakePreviousTaskView; final View mRippleView; final RippleDrawable mRippleDrawable; - final Button mActionButton; final TutorialStepIndicator mTutorialStepView; final ImageView mFingerDotView; private final AlertDialog mSkipTutorialDialog; @@ -115,8 +115,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, mContext = mTutorialFragment.getContext(); RootSandboxLayout rootView = tutorialFragment.getRootView(); - mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button); - mCloseButton.setOnClickListener(button -> showSkipTutorialDialog()); + mSkipButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button); + mSkipButton.setOnClickListener(button -> showSkipTutorialDialog()); mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view); mFeedbackTitleView = mFeedbackView.findViewById( R.id.gesture_tutorial_fragment_feedback_title); @@ -130,7 +130,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, rootView.findViewById(R.id.gesture_tutorial_fake_previous_task_view); mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view); mRippleDrawable = (RippleDrawable) mRippleView.getBackground(); - mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button); + mDoneButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button); mTutorialStepView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step); mFingerDotView = rootView.findViewById(R.id.gesture_tutorial_finger_dot); @@ -431,22 +431,22 @@ abstract class TutorialController implements BackGestureAttemptCallback, } void updateCloseButton() { - mCloseButton.setTextAppearance(Utilities.isDarkTheme(mContext) + mSkipButton.setTextAppearance(Utilities.isDarkTheme(mContext) ? R.style.TextAppearance_GestureTutorial_Feedback_Subtext : R.style.TextAppearance_GestureTutorial_Feedback_Subtext_Dark); } void hideActionButton() { - mCloseButton.setVisibility(View.VISIBLE); + mSkipButton.setVisibility(View.VISIBLE); // Invisible to maintain the layout. - mActionButton.setVisibility(View.INVISIBLE); - mActionButton.setOnClickListener(null); + mDoneButton.setVisibility(View.INVISIBLE); + mDoneButton.setOnClickListener(null); } void showActionButton() { - mCloseButton.setVisibility(GONE); - mActionButton.setVisibility(View.VISIBLE); - mActionButton.setOnClickListener(this::onActionButtonClicked); + mSkipButton.setVisibility(GONE); + mDoneButton.setVisibility(View.VISIBLE); + mDoneButton.setOnClickListener(this::onActionButtonClicked); } void hideFakeTaskbar(boolean animateToHotseat) { @@ -609,7 +609,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, R.id.gesture_tutorial_dialog_confirm_button); if (confirmButton != null) { confirmButton.setOnClickListener(v -> { - sandboxActivity.closeTutorial(); + mTutorialFragment.closeTutorial(true); tutorialDialog.dismiss(); }); } else { diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java index 4b836e380e..d79b946146 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java @@ -20,11 +20,13 @@ import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Insets; import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.util.ArraySet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -43,14 +45,24 @@ import androidx.fragment.app.FragmentActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.interaction.TutorialController.TutorialType; +import java.util.Set; + abstract class TutorialFragment extends Fragment implements OnTouchListener { private static final String LOG_TAG = "TutorialFragment"; static final String KEY_TUTORIAL_TYPE = "tutorial_type"; static final String KEY_GESTURE_COMPLETE = "gesture_complete"; + private static final String TUTORIAL_SKIPPED_PREFERENCE_KEY = "pref_gestureTutorialSkipped"; + private static final String COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY = + "pref_completedTutorialSteps"; + + private final SharedPreferences mSharedPrefs; + protected final StatsLogManager mStatsLogManager; + TutorialType mTutorialType; boolean mGestureComplete = false; @Nullable TutorialController mTutorialController = null; @@ -71,10 +83,15 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { private boolean mIsLargeScreen; private boolean mIsFoldable; - public static TutorialFragment newInstance(TutorialType tutorialType, boolean gestureComplete) { - TutorialFragment fragment = getFragmentForTutorialType(tutorialType); + public static TutorialFragment newInstance( + TutorialType tutorialType, + boolean gestureComplete, + SharedPreferences sharedPrefs, + StatsLogManager statsLogManager) { + TutorialFragment fragment = + getFragmentForTutorialType(tutorialType, sharedPrefs, statsLogManager); if (fragment == null) { - fragment = new BackGestureTutorialFragment(); + fragment = new BackGestureTutorialFragment(sharedPrefs, statsLogManager); tutorialType = TutorialType.BACK_NAVIGATION; } @@ -86,28 +103,36 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { } @Nullable - private static TutorialFragment getFragmentForTutorialType(TutorialType tutorialType) { + private static TutorialFragment getFragmentForTutorialType( + TutorialType tutorialType, + SharedPreferences sharedPrefs, + StatsLogManager statsLogManager) { switch (tutorialType) { case BACK_NAVIGATION: case BACK_NAVIGATION_COMPLETE: - return new BackGestureTutorialFragment(); + return new BackGestureTutorialFragment(sharedPrefs, statsLogManager); case HOME_NAVIGATION: case HOME_NAVIGATION_COMPLETE: - return new HomeGestureTutorialFragment(); + return new HomeGestureTutorialFragment(sharedPrefs, statsLogManager); case OVERVIEW_NAVIGATION: case OVERVIEW_NAVIGATION_COMPLETE: - return new OverviewGestureTutorialFragment(); + return new OverviewGestureTutorialFragment(sharedPrefs, statsLogManager); case ASSISTANT: case ASSISTANT_COMPLETE: - return new AssistantGestureTutorialFragment(); + return new AssistantGestureTutorialFragment(sharedPrefs, statsLogManager); case SANDBOX_MODE: - return new SandboxModeTutorialFragment(); + return new SandboxModeTutorialFragment(sharedPrefs, statsLogManager); default: Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name()); } return null; } + protected TutorialFragment(SharedPreferences sharedPrefs, StatsLogManager statsLogManager) { + mSharedPrefs = sharedPrefs; + mStatsLogManager = statsLogManager; + } + @Nullable Integer getEdgeAnimationResId() { return null; } @@ -315,6 +340,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { } void onAttachedToWindow() { + logTutorialStepShown(); mEdgeBackGestureHandler.setViewGroupParent(getRootView()); } @@ -348,8 +374,16 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { } void continueTutorial() { - GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity(); + Set updatedCompletedSteps = new ArraySet<>(mSharedPrefs.getStringSet( + COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY, new ArraySet<>())); + updatedCompletedSteps.add(mTutorialType.toString()); + + mSharedPrefs.edit().putStringSet( + COMPLETED_TUTORIAL_STEPS_PREFERENCE_KEY, updatedCompletedSteps).apply(); + logTutorialStepCompleted(); + + GestureSandboxActivity gestureSandboxActivity = getGestureSandboxActivity(); if (gestureSandboxActivity == null) { closeTutorial(); return; @@ -358,6 +392,15 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { } void closeTutorial() { + closeTutorial(false); + } + + void closeTutorial(boolean tutorialSkipped) { + if (tutorialSkipped) { + mSharedPrefs.edit().putBoolean(TUTORIAL_SKIPPED_PREFERENCE_KEY, true).apply(); + mStatsLogManager.logger().log( + StatsLogManager.LauncherEvent.LAUNCHER_GESTURE_TUTORIAL_SKIPPED); + } FragmentActivity activity = getActivity(); if (activity != null) { activity.setResult(Activity.RESULT_OK); @@ -390,6 +433,10 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener { || (mTutorialController != null && mTutorialController.isGestureCompleted()); } + abstract void logTutorialStepShown(); + + abstract void logTutorialStepCompleted(); + @Nullable private GestureSandboxActivity getGestureSandboxActivity() { Context context = getContext(); diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index f392e95b5f..e6dc21e09c 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -529,6 +529,27 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "User clicks on the search icon on header to launch search in app.") LAUNCHER_ALLAPPS_SEARCHINAPP_LAUNCH(913), + @UiEvent(doc = "User is shown the back gesture navigation tutorial step.") + LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_SHOWN(959), + + @UiEvent(doc = "User is shown the home gesture navigation tutorial step.") + LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_SHOWN(960), + + @UiEvent(doc = "User is shown the overview gesture navigation tutorial step.") + LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_SHOWN(961), + + @UiEvent(doc = "User completed the back gesture navigation tutorial step.") + LAUNCHER_GESTURE_TUTORIAL_BACK_STEP_COMPLETED(962), + + @UiEvent(doc = "User completed the home gesture navigation tutorial step.") + LAUNCHER_GESTURE_TUTORIAL_HOME_STEP_COMPLETED(963), + + @UiEvent(doc = "User completed the overview gesture navigation tutorial step.") + LAUNCHER_GESTURE_TUTORIAL_OVERVIEW_STEP_COMPLETED(964), + + @UiEvent(doc = "User skips the gesture navigation tutorial.") + LAUNCHER_GESTURE_TUTORIAL_SKIPPED(965), + @UiEvent(doc = "User scrolled on one of the all apps surfaces such as A-Z list, search " + "result page etc.") LAUNCHER_ALLAPPS_SCROLLED(985);