From 1059c30f3a049ee2fcbd6f3c2bda2a01bfa66e68 Mon Sep 17 00:00:00 2001 From: Stevie Kideckel Date: Thu, 10 Jun 2021 15:29:46 +0000 Subject: [PATCH 1/3] Account for header margin depending on which header is selected This removes jitter from incorrectly counting or not the margins for the headers near the top of the widget picker list. Bug: 189956230 Test: verified locally Change-Id: I3eeddbf8a1fb7cca31fe2a12c28b0eb444de631b --- .../widget/picker/WidgetsListAdapter.java | 6 ++++++ .../widget/picker/WidgetsRecyclerView.java | 15 +++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 150bd9902f..e89aea7507 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -298,6 +298,12 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey); } + /** Returns the position of the currently expanded header, or empty if it's not present. */ + public OptionalInt getSelectedHeaderPosition() { + if (mWidgetsContentVisiblePackageUserKey == null) return OptionalInt.empty(); + return getPositionForPackageUserKey(mWidgetsContentVisiblePackageUserKey); + } + /** * Returns the position of {@code key} in {@link #mVisibleEntries}, or empty if it's not * present. diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java index 090362ba33..4f4f1a3566 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java @@ -53,6 +53,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider; private int mLastVisibleWidgetContentTableHeight = 0; private int mWidgetHeaderHeight = 0; + private final int mCollapsedHeaderBottomMarginSize; @Nullable private OnContentChangeListener mOnContentChangeListener; public WidgetsRecyclerView(Context context) { @@ -71,6 +72,10 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch ActivityContext activity = ActivityContext.lookupContext(getContext()); DeviceProfile grid = activity.getDeviceProfile(); + + // The bottom margin used when the header is not expanded. + mCollapsedHeaderBottomMarginSize = + getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin); } @Override @@ -182,10 +187,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch && mLastVisibleWidgetContentTableHeight == 0 && view.getMeasuredHeight() > 0) { // This assumes all header views are of the same height. - RecyclerView.LayoutParams layoutParams = - (RecyclerView.LayoutParams) view.getLayoutParams(); - mWidgetHeaderHeight = view.getMeasuredHeight() + layoutParams.topMargin - + layoutParams.bottomMargin; + mWidgetHeaderHeight = view.getMeasuredHeight(); } } @@ -279,12 +281,17 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch if (untilIndex > mAdapter.getItems().size()) { untilIndex = mAdapter.getItems().size(); } + int expandedHeaderPosition = mAdapter.getSelectedHeaderPosition().orElse(-1); int totalItemsHeight = 0; for (int i = 0; i < untilIndex; i++) { WidgetsListBaseEntry entry = mAdapter.getItems().get(i); if (entry instanceof WidgetsListHeaderEntry || entry instanceof WidgetsListSearchHeaderEntry) { totalItemsHeight += mWidgetHeaderHeight; + if (expandedHeaderPosition != i) { + // If the header is collapsed, include the bottom margin it will use. + totalItemsHeight += mCollapsedHeaderBottomMarginSize; + } } else if (entry instanceof WidgetsListContentEntry) { totalItemsHeight += mLastVisibleWidgetContentTableHeight; } else { From 7fec8fc10c89742bbdb973cfcfad8cedd86a179e Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Wed, 9 Jun 2021 13:44:46 -0700 Subject: [PATCH 2/3] Polish the gesture navigation tutorial sandbox - Updated in-memory tutorial steps to a array so we can better track the tutorial progress after changing the theme. - Added a 1.5 second delay for the gesture video when the user first starts a step, and when the app resumes. - Updated the tutorial step indicator color: previous color was invisible against the background. Bug: 188972959 Test: manual Change-Id: Ia1a67f7cd9e7ed9802dbc0d88d1e3b432acfa127 --- .../interaction/GestureSandboxActivity.java | 34 +++++----- .../interaction/TutorialController.java | 68 ++++++++++++++----- .../interaction/TutorialFragment.java | 5 +- .../interaction/TutorialStepIndicator.java | 10 +-- 4 files changed, 75 insertions(+), 42 deletions(-) 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)); } } } From ebd88cb00bc0691463f91b51e17f1579eddc16d6 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Fri, 11 Jun 2021 15:51:04 -0700 Subject: [PATCH 3/3] Add sdk check to recyclerviewfastscroller Bug: 189982238 Test: builds Change-Id: I2298a67fd957cd2a77c8e5e4c34b828c044f1a61 --- .../launcher3/views/RecyclerViewFastScroller.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index e607ab3f7e..f49d1b5d2a 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -28,6 +28,7 @@ import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Build; import android.util.AttributeSet; import android.util.Property; import android.view.MotionEvent; @@ -37,6 +38,7 @@ import android.view.WindowInsets; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BaseRecyclerView; @@ -342,16 +344,21 @@ public class RecyclerViewFastScroller extends View { // swiping very close to the thumb area (not just within it's bound) // will also prevent back gesture SYSTEM_GESTURE_EXCLUSION_RECT.get(0).offset(mThumbDrawOffset.x, mThumbDrawOffset.y); - SYSTEM_GESTURE_EXCLUSION_RECT.get(0).left = SYSTEM_GESTURE_EXCLUSION_RECT.get(0).right - - mSystemGestureInsets.right; + if (Utilities.ATLEAST_Q) { + SYSTEM_GESTURE_EXCLUSION_RECT.get(0).left = + SYSTEM_GESTURE_EXCLUSION_RECT.get(0).right - mSystemGestureInsets.right; + } setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT); } canvas.restoreToCount(saveCount); } @Override + @RequiresApi(Build.VERSION_CODES.Q) public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mSystemGestureInsets = insets.getSystemGestureInsets(); + if (Utilities.ATLEAST_Q) { + mSystemGestureInsets = insets.getSystemGestureInsets(); + } return super.onApplyWindowInsets(insets); }