Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1ef5e5c authored by Lais Andrade's avatar Lais Andrade
Browse files

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
parent 9f194ee2
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
    private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
    private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;

    private static final int SNAP_TO_PAGE_VIBRATION_PRIMITIVE =
            Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
    private static final float SNAP_TO_PAGE_VIBRATION_PRIMITIVE_SCALE = 0.4f;
    private static final VibrationEffect SNAP_TO_PAGE_VIBRATION_FALLBACK =
            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
    private static final int EDGE_IMPACT_VIBRATION_PRIMITIVE =
            Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
    private static final float EDGE_IMPACT_VIBRATION_PRIMITIVE_SCALE = 0.8f;
    private static final VibrationEffect EDGE_IMPACT_VIBRATION_FALLBACK =
            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK);

    protected final RecentsOrientedState mOrientationState;
    protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
    protected RecentsAnimationController mRecentsAnimationController;
@@ -960,6 +973,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
    @Override
    protected void onPageEndTransition() {
        super.onPageEndTransition();
        VibratorWrapper.INSTANCE.get(mContext).vibrate(SNAP_TO_PAGE_VIBRATION_PRIMITIVE,
                SNAP_TO_PAGE_VIBRATION_PRIMITIVE_SCALE, SNAP_TO_PAGE_VIBRATION_FALLBACK);
        if (isClearAllHidden()) {
            mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
        }
@@ -1062,6 +1077,13 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        }
    }

    @Override
    protected void onEdgeAbsorbingScroll() {
        super.onEdgeAbsorbingScroll();
        VibratorWrapper.INSTANCE.get(mContext).vibrate(EDGE_IMPACT_VIBRATION_PRIMITIVE,
                EDGE_IMPACT_VIBRATION_PRIMITIVE_SCALE, EDGE_IMPACT_VIBRATION_FALLBACK);
    }

    @Override
    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
        // Enables swiping to the left or right only if the task overlay is not modal.
+9 −0
Original line number Diff line number Diff line
@@ -479,9 +479,11 @@ public abstract class PagedView<T extends View & PageIndicator> 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<T extends View & PageIndicator> 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;
+29 −0
Original line number Diff line number Diff line
@@ -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<VibratorWrapper> 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);
                }
            });
        }
    }
}