mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-11 14:54:00 +00:00
Merge "Implement diff haptics going into all apps" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f4437f742e
@@ -40,6 +40,7 @@
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<!-- for rotating surface by arbitrary degree -->
|
||||
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
@@ -62,6 +63,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
|
||||
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
|
||||
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
private final RecentsView mRecentsView;
|
||||
private final MotionPauseDetector mMotionPauseDetector;
|
||||
private final float mMotionPauseMinDisplacement;
|
||||
@@ -82,6 +84,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
mRecentsView = l.getOverviewPanel();
|
||||
mMotionPauseDetector = new MotionPauseDetector(l);
|
||||
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
|
||||
mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,6 +191,11 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
// need to manually set the duration to a reasonable value.
|
||||
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
|
||||
}
|
||||
if (FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() &&
|
||||
((mFromState == NORMAL && mToState == ALL_APPS)
|
||||
|| (mFromState == ALL_APPS && mToState == NORMAL)) && isFling) {
|
||||
mVibratorWrapper.vibrateForDragBump();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMotionPauseDetected() {
|
||||
|
||||
@@ -32,6 +32,7 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
|
||||
import android.animation.Animator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
@@ -47,17 +48,21 @@ import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.AllAppsSwipeController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
|
||||
/**
|
||||
@@ -78,6 +83,8 @@ public class AllAppsTransitionController
|
||||
private static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200;
|
||||
|
||||
private static final float NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.1f;
|
||||
private static final float SWIPE_DRAG_COMMIT_THRESHOLD =
|
||||
1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
|
||||
|
||||
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
|
||||
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
|
||||
@@ -181,6 +188,7 @@ public class AllAppsTransitionController
|
||||
private boolean mIsTablet;
|
||||
|
||||
private boolean mHasScaleEffect;
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
|
||||
public AllAppsTransitionController(Launcher l) {
|
||||
mLauncher = l;
|
||||
@@ -193,6 +201,7 @@ public class AllAppsTransitionController
|
||||
|
||||
setShiftRange(dp.allAppsShiftRange);
|
||||
mLauncher.addOnDeviceProfileChangeListener(this);
|
||||
mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext());
|
||||
}
|
||||
|
||||
public float getShiftRange() {
|
||||
@@ -311,6 +320,11 @@ public class AllAppsTransitionController
|
||||
/**
|
||||
* Creates an animation which updates the vertical transition progress and updates all the
|
||||
* dependent UI using various animation events
|
||||
*
|
||||
* This method also dictates where along the progress the haptics should be played. As the user
|
||||
* scrolls up from workspace or down from AllApps, a drag haptic is being played until the
|
||||
* commit point where it plays a commit haptic. Where we play the haptics differs when going
|
||||
* from workspace -> allApps and vice versa.
|
||||
*/
|
||||
@Override
|
||||
public void setStateWithAnimation(LauncherState toState,
|
||||
@@ -339,6 +353,20 @@ public class AllAppsTransitionController
|
||||
});
|
||||
}
|
||||
|
||||
if(FeatureFlags.ENABLE_HAPTICS_ALL_APPS.get() && config.userControlled
|
||||
&& Utilities.ATLEAST_S) {
|
||||
if (toState == ALL_APPS) {
|
||||
builder.addOnFrameListener(
|
||||
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
|
||||
SWIPE_DRAG_COMMIT_THRESHOLD, 1));
|
||||
} else {
|
||||
builder.addOnFrameListener(
|
||||
new VibrationAnimatorUpdateListener(this, mVibratorWrapper,
|
||||
0, SWIPE_DRAG_COMMIT_THRESHOLD));
|
||||
}
|
||||
builder.addEndListener(mVibratorWrapper::cancelVibrate);
|
||||
}
|
||||
|
||||
float targetProgress = toState.getVerticalProgress(mLauncher);
|
||||
if (Float.compare(mProgress, targetProgress) == 0) {
|
||||
setAlphas(toState, config, builder);
|
||||
@@ -356,7 +384,7 @@ public class AllAppsTransitionController
|
||||
|
||||
setAlphas(toState, config, builder);
|
||||
|
||||
if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) {
|
||||
if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL) && !(Utilities.ATLEAST_S)) {
|
||||
mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
|
||||
}
|
||||
@@ -494,4 +522,45 @@ public class AllAppsTransitionController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This VibrationAnimatorUpdateListener class takes in four parameters, a controller, start
|
||||
* threshold, end threshold, and a Vibrator wrapper. We use the progress given by the controller
|
||||
* as it gives an accurate progress that dictates where the vibrator should vibrate.
|
||||
* Note: once the user begins a gesture and does the commit haptic, there should not be anymore
|
||||
* haptics played for that gesture.
|
||||
*/
|
||||
private static class VibrationAnimatorUpdateListener implements
|
||||
ValueAnimator.AnimatorUpdateListener {
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
private final AllAppsTransitionController mController;
|
||||
private final float mStartThreshold;
|
||||
private final float mEndThreshold;
|
||||
private boolean mHasCommitted;
|
||||
|
||||
VibrationAnimatorUpdateListener(AllAppsTransitionController controller,
|
||||
VibratorWrapper vibratorWrapper, float startThreshold,
|
||||
float endThreshold) {
|
||||
mController = controller;
|
||||
mVibratorWrapper = vibratorWrapper;
|
||||
mStartThreshold = startThreshold;
|
||||
mEndThreshold = endThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
if (mHasCommitted) {
|
||||
return;
|
||||
}
|
||||
float currentProgress =
|
||||
AllAppsTransitionController.ALL_APPS_PROGRESS.get(mController);
|
||||
if (currentProgress > mStartThreshold && currentProgress < mEndThreshold) {
|
||||
mVibratorWrapper.vibrateForDragTexture();
|
||||
} else if (!(currentProgress == 0 || currentProgress == 1)) {
|
||||
// This check guards against committing at the location of the start of the gesture
|
||||
mVibratorWrapper.vibrateForDragCommit();
|
||||
mHasCommitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,6 +371,8 @@ public final class FeatureFlags {
|
||||
"ENABLE_LAUNCH_FROM_STAGED_APP", true,
|
||||
"Enable the ability to tap a staged app during split select to launch it in full screen"
|
||||
);
|
||||
public static final BooleanFlag ENABLE_HAPTICS_ALL_APPS = getDebugFlag(
|
||||
"ENABLE_HAPTICS_ALL_APPS", false, "Enables haptics opening/closing All apps");
|
||||
|
||||
public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(
|
||||
"ENABLE_FORCED_MONO_ICON", false,
|
||||
|
||||
@@ -129,10 +129,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController {
|
||||
Interpolators.clampToProgress(
|
||||
Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.4f, 1f),
|
||||
ALL_APPS_STATE_TRANSITION_ATOMIC, 1f);
|
||||
public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL =
|
||||
Interpolators.clampToProgress(
|
||||
Interpolators.mapToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f),
|
||||
ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
|
||||
public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_MANUAL = LINEAR;
|
||||
|
||||
// --------
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.android.launcher3.util;
|
||||
|
||||
import static android.os.VibrationEffect.createPredefined;
|
||||
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;
|
||||
|
||||
@@ -28,12 +27,17 @@ import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.media.AudioAttributes;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
|
||||
@@ -52,6 +56,21 @@ public class VibratorWrapper {
|
||||
public static final VibrationEffect EFFECT_CLICK =
|
||||
createPredefined(VibrationEffect.EFFECT_CLICK);
|
||||
|
||||
private static final float DRAG_TEXTURE_SCALE = 0.03f;
|
||||
private static final float DRAG_COMMIT_SCALE = 0.5f;
|
||||
private static final float DRAG_BUMP_SCALE = 0.4f;
|
||||
private static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
|
||||
|
||||
@Nullable
|
||||
private final VibrationEffect mDragEffect;
|
||||
@Nullable
|
||||
private final VibrationEffect mCommitEffect;
|
||||
@Nullable
|
||||
private final VibrationEffect mBumpEffect;
|
||||
|
||||
private long mLastDragTime;
|
||||
private final int mThresholdUntilNextDragCallMillis;
|
||||
|
||||
/**
|
||||
* Haptic when entering overview.
|
||||
*/
|
||||
@@ -62,7 +81,7 @@ public class VibratorWrapper {
|
||||
|
||||
private boolean mIsHapticFeedbackEnabled;
|
||||
|
||||
public VibratorWrapper(Context context) {
|
||||
private VibratorWrapper(Context context) {
|
||||
mVibrator = context.getSystemService(Vibrator.class);
|
||||
mHasVibrator = mVibrator.hasVibrator();
|
||||
if (mHasVibrator) {
|
||||
@@ -75,12 +94,88 @@ public class VibratorWrapper {
|
||||
}
|
||||
};
|
||||
resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
|
||||
false /* notifyForDescendents */, observer);
|
||||
false /* notifyForDescendants */, observer);
|
||||
} else {
|
||||
mIsHapticFeedbackEnabled = false;
|
||||
}
|
||||
|
||||
if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK)) {
|
||||
|
||||
// Drag texture, Commit, and Bump should only be used for premium phones.
|
||||
// Before using these haptics make sure check if the device can use it
|
||||
VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
|
||||
for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
|
||||
dragEffect.addPrimitive(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
|
||||
}
|
||||
mDragEffect = dragEffect.compose();
|
||||
mCommitEffect = VibrationEffect.startComposition().addPrimitive(
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose();
|
||||
mBumpEffect = VibrationEffect.startComposition().addPrimitive(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK, DRAG_BUMP_SCALE).compose();
|
||||
int primitiveDuration = mVibrator.getPrimitiveDurations(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0];
|
||||
|
||||
mThresholdUntilNextDragCallMillis =
|
||||
DRAG_TEXTURE_EFFECT_SIZE * primitiveDuration + 100;
|
||||
} else {
|
||||
mDragEffect = null;
|
||||
mCommitEffect = null;
|
||||
mBumpEffect = null;
|
||||
mThresholdUntilNextDragCallMillis = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the user swipes to/from all apps. This is meant to be used in between
|
||||
* long animation progresses so that it gives a dragging texture effect. For a better
|
||||
* experience, this should be used in combination with vibrateForDragCommit().
|
||||
*/
|
||||
public void vibrateForDragTexture() {
|
||||
if (mDragEffect == null) {
|
||||
return;
|
||||
}
|
||||
long currentTime = SystemClock.elapsedRealtime();
|
||||
long elapsedTimeSinceDrag = currentTime - mLastDragTime;
|
||||
if (elapsedTimeSinceDrag >= mThresholdUntilNextDragCallMillis) {
|
||||
vibrate(mDragEffect);
|
||||
mLastDragTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used when user reaches the commit threshold when swiping to/from from all apps.
|
||||
*/
|
||||
public void vibrateForDragCommit() {
|
||||
if (mCommitEffect != null) {
|
||||
vibrate(mCommitEffect);
|
||||
}
|
||||
// resetting dragTexture timestamp to be able to play dragTexture again
|
||||
mLastDragTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bump haptic is used to be called at the end of a swipe and only if it the gesture is a
|
||||
* FLING going to/from all apps. Client can just call this method elsewhere just for the
|
||||
* effect.
|
||||
*/
|
||||
public void vibrateForDragBump() {
|
||||
if (mBumpEffect != null) {
|
||||
vibrate(mBumpEffect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)}.
|
||||
*/
|
||||
public void cancelVibrate(boolean unused) {
|
||||
UI_HELPER_EXECUTOR.execute(mVibrator::cancel);
|
||||
// reset dragTexture timestamp to be able to play dragTexture again whenever cancelled
|
||||
mLastDragTime = 0;
|
||||
}
|
||||
private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
|
||||
return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user