Adds haptic build-up during LPNH behind feature flag
Flag: Omnient flags + ENABLE_SEARCH_HAPTIC_HINT
Test: Manual
Bug: 303023676
Change-Id: Ic43d1f34a05622328add6727f36406ce75d5a115
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 8d19040..34bfdb7 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -64,12 +64,6 @@
public class LauncherAppState implements SafeCloseable {
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
- public static final String KEY_ICON_STATE = "pref_icon_shape_path";
- public static final String KEY_ALL_APPS_OVERVIEW_THRESHOLD = "pref_all_apps_overview_threshold";
- public static final String KEY_LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
- "pref_long_press_nav_handle_slop_multiplier";
- public static final String KEY_LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
- "pref_long_press_nav_handle_timeout_ms";
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 9a0d02a..c6a9283 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -297,31 +297,59 @@
@JvmField
val ICON_STATE =
nonRestorableItem(
- LauncherAppState.KEY_ICON_STATE,
+ "pref_icon_shape_path",
"",
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val ALL_APPS_OVERVIEW_THRESHOLD =
nonRestorableItem(
- LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD,
+ "pref_all_apps_overview_threshold",
180,
EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField
val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
- nonRestorableItem(
- LauncherAppState.KEY_LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE,
- 100,
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ nonRestorableItem(
+ "pref_long_press_nav_handle_slop_multiplier",
+ 100,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
@JvmField
val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
- nonRestorableItem(
- LauncherAppState.KEY_LONG_PRESS_NAV_HANDLE_TIMEOUT_MS,
- ViewConfiguration.getLongPressTimeout(),
- EncryptionType.MOVE_TO_DEVICE_PROTECTED
- )
+ nonRestorableItem(
+ "pref_long_press_nav_handle_timeout_ms",
+ ViewConfiguration.getLongPressTimeout(),
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
+ nonRestorableItem(
+ "pref_long_press_nav_handle_haptic_hint_start_scale_percent",
+ 0,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
+ nonRestorableItem(
+ "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
+ 50,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
+ nonRestorableItem(
+ "pref_long_press_nav_handle_haptic_hint_scale_exponent",
+ 2,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
+ nonRestorableItem(
+ "pref_long_press_nav_handle_haptic_hint_iterations",
+ 40,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
@JvmField
val THEMED_ICONS =
backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index c09a5b9..278e4e5 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -373,7 +373,9 @@
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
0, SWIPE_DRAG_COMMIT_THRESHOLD));
}
- builder.addEndListener(mVibratorWrapper::cancelVibrate);
+ builder.addEndListener((unused) -> {
+ mVibratorWrapper.cancelVibrate();
+ });
}
float targetProgress = toState.getVerticalProgress(mLauncher);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 5b44069..3f3b3ce 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -262,6 +262,10 @@
getReleaseFlag(282993230, "ENABLE_LONG_PRESS_NAV_HANDLE", TEAMFOOD,
"Enables long pressing on the bottom bar nav handle to trigger events.");
+ public static final BooleanFlag ENABLE_SEARCH_HAPTIC_HINT =
+ getReleaseFlag(303023676, "ENABLE_SEARCH_HAPTIC_HINT", TEAMFOOD,
+ "Enables haptic hint when long pressing on the bottom bar nav handle.");
+
// TODO(Block 17): Clean up flags
public static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
"ENABLE_TASKBAR_PINNING", DISABLED,
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 91945ca..80a9bae 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -18,6 +18,10 @@
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -35,10 +39,9 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.PendingAnimation;
-
-import java.util.function.Consumer;
+import com.android.launcher3.config.FeatureFlags;
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
@@ -70,7 +73,7 @@
private final VibrationEffect mBumpEffect;
@Nullable
- private final VibrationEffect mAssistEffect;
+ private final VibrationEffect mSearchEffect;
private long mLastDragTime;
private final int mThresholdUntilNextDragCallMillis;
@@ -80,12 +83,14 @@
*/
public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;
+ private final Context mContext;
private final Vibrator mVibrator;
private final boolean mHasVibrator;
private boolean mIsHapticFeedbackEnabled;
private VibratorWrapper(Context context) {
+ mContext = context;
mVibrator = context.getSystemService(Vibrator.class);
mHasVibrator = mVibrator.hasVibrator();
if (mHasVibrator) {
@@ -133,14 +138,20 @@
if (Utilities.ATLEAST_R && mVibrator.areAllPrimitivesSupported(
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
VibrationEffect.Composition.PRIMITIVE_TICK)) {
- // quiet ramp, short pause, then sharp tick
- mAssistEffect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
- .compose();
+ if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
+ mSearchEffect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f)
+ .compose();
+ } else {
+ // quiet ramp, short pause, then sharp tick
+ mSearchEffect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
+ .compose();
+ }
} else {
// fallback for devices without composition support
- mAssistEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK);
+ mSearchEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK);
}
}
@@ -184,20 +195,10 @@
}
/**
- * The assist haptic is used to be called when an assistant is invoked
- */
- public void vibrateForAssist() {
- if (mAssistEffect != null) {
- vibrate(mAssistEffect);
- }
- }
-
- /**
* This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For
- * example, when no animation is happening but a vibrator happens to be vibrating still. Need
- * boolean parameter for {@link PendingAnimation#addEndListener(Consumer)}.
+ * example, when no animation is happening but a vibrator happens to be vibrating still.
*/
- public void cancelVibrate(boolean unused) {
+ public void cancelVibrate() {
UI_HELPER_EXECUTOR.execute(mVibrator::cancel);
// reset dragTexture timestamp to be able to play dragTexture again whenever cancelled
mLastDragTime = 0;
@@ -233,4 +234,37 @@
});
}
}
+
+ /** Indicates that search has been invoked. */
+ public void vibrateForSearch() {
+ if (mSearchEffect != null) {
+ vibrate(mSearchEffect);
+ }
+ }
+
+ /** Indicates that search will be invoked if the current gesture is maintained. */
+ public void vibrateForSearchHint() {
+ if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get() && Utilities.ATLEAST_S
+ && mVibrator.areAllPrimitivesSupported(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK)) {
+ float startScale = LauncherPrefs.get(mContext).get(
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT) / 100f;
+ float endScale = LauncherPrefs.get(mContext).get(
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT) / 100f;
+ int scaleExponent = LauncherPrefs.get(mContext).get(
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
+ int iterations = LauncherPrefs.get(mContext).get(
+ LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
+
+ VibrationEffect.Composition composition = VibrationEffect.startComposition();
+ for (int i = 0; i < iterations; i++) {
+ float t = i / (iterations - 1f);
+ float scale = (float) Math.pow((1 - t) * startScale + t * endScale,
+ scaleExponent);
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale);
+ }
+
+ vibrate(composition.compose());
+ }
+ }
}