From b1ef5e5c55ee4e86124afb554fc48a1cd8791ea4 Mon Sep 17 00:00:00 2001 From: Lais Andrade Date: Fri, 21 May 2021 13:50:46 +0100 Subject: [PATCH] Add haptic feedback to Launcher recent apps scrolling Add light LOW_TICK effects when the scrolling on RecentsView snaps to a page, and a stronger TICK effect when the edges absorb the scroll (by scrolling fast towards one of the edges). Fix: 182382085 Test: manual Change-Id: Ifb917ae499f73607707773f9870eb39cd6fe16ef --- .../android/quickstep/views/RecentsView.java | 22 ++++++++++++++ src/com/android/launcher3/PagedView.java | 9 ++++++ .../launcher3/util/VibratorWrapper.java | 29 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 376cb31a90..197fc9c168 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -76,6 +76,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.UserHandle; +import android.os.VibrationEffect; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -132,6 +133,7 @@ import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.Themes; import com.android.launcher3.util.TranslateEdgeEffect; +import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.util.ViewPool; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.BaseActivityInterface; @@ -342,6 +344,17 @@ public abstract class RecentsView mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; @@ -960,6 +973,8 @@ public abstract class RecentsView extends ViewGrou if (newPos < mMinScroll && oldPos >= mMinScroll) { mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); mScroller.abortAnimation(); + onEdgeAbsorbingScroll(); } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) { mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); mScroller.abortAnimation(); + onEdgeAbsorbingScroll(); } } @@ -1332,6 +1334,13 @@ public abstract class PagedView extends ViewGrou protected void onNotSnappingToPageInFreeScroll() { } + /** + * Called when the view edges absorb part of the scroll. Subclasses can override this + * to provide custom behavior during animation. + */ + protected void onEdgeAbsorbingScroll() { + } + protected boolean shouldFlingForVelocity(int velocity) { float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity; return Math.abs(velocity) > threshold; diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java index b0defd445a..14bf6c2035 100644 --- a/src/com/android/launcher3/util/VibratorWrapper.java +++ b/src/com/android/launcher3/util/VibratorWrapper.java @@ -21,15 +21,19 @@ import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.media.AudioAttributes; import android.os.Build; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; +import com.android.launcher3.Utilities; + /** * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary. */ @@ -39,6 +43,11 @@ public class VibratorWrapper { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(VibratorWrapper::new); + public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(); + public static final VibrationEffect EFFECT_CLICK = createPredefined(VibrationEffect.EFFECT_CLICK); @@ -81,4 +90,24 @@ public class VibratorWrapper { UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect)); } } + + /** + * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only + * vibrates if haptic feedback is available and enabled. + */ + @SuppressLint("NewApi") + public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) { + if (mHasVibrator && mIsHapticFeedbackEnabled) { + UI_HELPER_EXECUTOR.execute(() -> { + if (Utilities.ATLEAST_R && primitiveId >= 0 + && mVibrator.areAllPrimitivesSupported(primitiveId)) { + mVibrator.vibrate(VibrationEffect.startComposition() + .addPrimitive(primitiveId, primitiveScale) + .compose(), VIBRATION_ATTRS); + } else { + mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS); + } + }); + } + } }