diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 910c7b9b1bfea363210d7a1279c2522447ba44aa..e6324abe2123616413cb0718bff0325b91dcf455 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5584,6 +5584,13 @@ public final class Settings { */ public static final String VOLUME_ANSWER_CALL = "volume_answer_call"; + /** + * Action to perform when the screen edge is long-swiped. (Default is 0) + * (See KEY_HOME_LONG_PRESS_ACTION for valid values) + * @hide + */ + public static final String KEY_EDGE_LONG_SWIPE_ACTION = "key_edge_long_swipe_action"; + /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java index 12372593b62f19504dd477a9862818dd67838ab4..008cfa557581857a262e11ab72f240260586b99b 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java @@ -45,6 +45,9 @@ public interface NavigationEdgeBackPlugin extends Plugin { /** Sets the callback that should be invoked when a Back gesture is detected. */ void setBackCallback(BackCallback callback); + /** Specifies if the long swipe should be enabled or not. */ + void setLongSwipeEnabled(boolean enabled); + /** Sets the base LayoutParams for the UI. */ void setLayoutParams(WindowManager.LayoutParams layoutParams); @@ -57,7 +60,7 @@ public interface NavigationEdgeBackPlugin extends Plugin { /** Callback to let the system react to the detected back gestures. */ interface BackCallback { /** Indicates that a Back gesture was recognized and the system should go back. */ - void triggerBack(); + void triggerBack(boolean isLongPress); /** Indicates that the gesture was cancelled and the system should not go back. */ void cancelBack(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 621075cba785fa70f3f8553a3df59f1dd14e8832..5dfa88583d8f8e0ac03250a00acf0944a70b9fee 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -19,6 +19,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_ import static com.android.systemui.classifier.Classifier.BACK_GESTURE; +import static com.android.internal.util.libremobileos.DeviceKeysConstants.Action; + import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; @@ -37,8 +39,10 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.DeviceConfig; +import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; +import android.util.MathUtils; import android.util.TypedValue; import android.view.Choreographer; import android.view.Display; @@ -57,6 +61,7 @@ import android.view.WindowMetrics; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.policy.GestureNavigationSettingsObserver; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -80,6 +85,7 @@ import com.android.systemui.shared.tracing.ProtoTraceable; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.systemui.tuner.TunerService; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -94,12 +100,16 @@ import javax.inject.Inject; * Utility class to handle edge swipes for back gesture */ public class EdgeBackGestureHandler extends CurrentUserTracker - implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { + implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto>, + TunerService.Tunable { private static final String TAG = "EdgeBackGestureHandler"; private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( "gestures.back_timeout", 250); + private static final String KEY_EDGE_LONG_SWIPE_ACTION = + "customsystem:" + Settings.System.KEY_EDGE_LONG_SWIPE_ACTION; + private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; private static final int MAX_NUM_LOGGED_GESTURES = 10; @@ -228,6 +238,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; + private boolean mIsLongSwipeEnabled; private boolean mGestureBlockingActivityRunning; private boolean mIsInPipMode; @@ -238,6 +249,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private int mLeftInset; private int mRightInset; private int mSysUiFlags; + private float mLongSwipeWidth; // For Tf-Lite model. private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider; @@ -259,12 +271,14 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final NavigationEdgeBackPlugin.BackCallback mBackCallback = new NavigationEdgeBackPlugin.BackCallback() { @Override - public void triggerBack() { + public void triggerBack(boolean isLongPress) { // Notify FalsingManager that an intentional gesture has occurred. // TODO(b/186519446): use a different method than isFalseTouch mFalsingManager.isFalseTouch(BACK_GESTURE); - boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, + isLongPress ? KeyEvent.FLAG_LONG_PRESS : 0); + boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, + isLongPress ? KeyEvent.FLAG_LONG_PRESS : 0); if (DEBUG_MISSING_GESTURE) { Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown + ", up=" + sendUp); @@ -370,6 +384,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight; if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft; + final TunerService tunerService = Dependency.get(TunerService.class); + tunerService.addTunable(this, KEY_EDGE_LONG_SWIPE_ACTION); + // Reduce the default touch slop to ensure that we can intercept the gesture // before the app starts to react to it. // TODO(b/130352502) Tune this value and extract into a constant @@ -501,6 +518,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); mPluginManager.addPluginListener( this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); + updateLongSwipeWidth(); } // Update the ML model resources. updateMLModelState(); @@ -511,6 +529,12 @@ public class EdgeBackGestureHandler extends CurrentUserTracker setEdgeBackPlugin(plugin); } + private void updateLongSwipeWidth() { + if (mIsEnabled && mEdgeBackPlugin != null) { + mEdgeBackPlugin.setLongSwipeEnabled(mIsLongSwipeEnabled); + } + } + @Override public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); @@ -867,13 +891,23 @@ public class EdgeBackGestureHandler extends CurrentUserTracker if (mEdgeBackPlugin != null) { mEdgeBackPlugin.setDisplaySize(mDisplaySize); } + updateLongSwipeWidth(); + } + + @Override + public void onTuningChanged(String key, String newValue) { + if (KEY_EDGE_LONG_SWIPE_ACTION.equals(key)) { + mIsLongSwipeEnabled = Action.fromIntSafe(TunerService.parseInteger( + newValue, 0)) != Action.NOTHING; + updateLongSwipeWidth(); + } } - private boolean sendEvent(int action, int code) { + private boolean sendEvent(int action, int code, int flags) { long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, - KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); ev.setDisplayId(mContext.getDisplay().getDisplayId()); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index 8d1dfc842fba517023c2c6fe62cec7591e6ab6fb..32f8253ec2ccb4d25d1883ca7327ffaee9b17ff8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -210,7 +210,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl private boolean mDragSlopPassed; private boolean mArrowsPointLeft; private float mMaxTranslation; + private float mLongSwipeThreshold; private boolean mTriggerBack; + private boolean mTriggerLongSwipe; private float mPreviousTouchTranslation; private float mTotalTouchDelta; private float mVerticalTranslation; @@ -226,6 +228,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl private final Handler mHandler = new Handler(); private final Runnable mFailsafeRunnable = this::onFailsafe; + private boolean mIsLongSwipeEnabled; + private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener = new DynamicAnimation.OnAnimationEndListener() { @Override @@ -423,6 +427,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl mWindowManager.addView(this, mLayoutParams); } + @Override + public void setLongSwipeEnabled(boolean enabled) { + mLongSwipeThreshold = enabled ? MathUtils.min( + mDisplaySize.x * 0.5f, mLayoutParams.width * 2.5f) : 0.0f; + mIsLongSwipeEnabled = mLongSwipeThreshold > 0; + setTriggerLongSwipe(mIsLongSwipeEnabled && mTriggerLongSwipe, false /* animated */); + } + /** * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow. */ @@ -482,8 +494,10 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl Log.d(DEBUG_MISSING_GESTURE_TAG, "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack); } - if (mTriggerBack) { - triggerBack(); + if (mTriggerLongSwipe) { + triggerBack(true); + } else if (mTriggerBack) { + triggerBack(false); } else { cancelBack(); } @@ -522,6 +536,11 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl float x = (polarToCartX(mCurrentAngle) * mArrowLength); float y = (polarToCartY(mCurrentAngle) * mArrowLength); Path arrowPath = calculatePath(x,y); + if (mTriggerLongSwipe) { + arrowPath.addPath(calculatePath(x,y), + mArrowThickness * 2.0f * (mIsLeftPanel ? 1 : -1), 0.0f); + } + if (mShowProtection) { canvas.drawPath(arrowPath, mProtectionPaint); } @@ -616,8 +635,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl return mCurrentTranslation; } - private void triggerBack() { - mBackCallback.triggerBack(); + private void triggerBack(boolean triggerLongSwipe) { + mBackCallback.triggerBack(triggerLongSwipe); if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -695,6 +714,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false"); } setTriggerBack(false /* triggerBack */, false /* animated */); + setTriggerLongSwipe(false /* triggerLongSwipe */, false /* animated */); setDesiredTranslation(0, false /* animated */); setCurrentTranslation(0); updateAngle(false /* animate */); @@ -719,6 +739,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl } } mPreviousTouchTranslation = touchTranslation; + boolean isLongSwipe = touchTranslation > mLongSwipeThreshold; // Apply a haptic on drag slop passed if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) { @@ -781,6 +802,12 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl + ", x=" + x + ", mStartX=" + mStartX); } + + if (mIsLongSwipeEnabled) { + boolean triggerLongSwipe = triggerBack && isLongSwipe; + setTriggerLongSwipe(triggerLongSwipe, true /* animated */); + } + setTriggerBack(triggerBack, true /* animated */); if (!mTriggerBack) { @@ -869,6 +896,18 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl } } + private void setTriggerLongSwipe(boolean triggerLongSwipe, boolean animated) { + if (mTriggerLongSwipe != triggerLongSwipe) { + mTriggerLongSwipe = triggerLongSwipe; + mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK); + mAngleAnimation.cancel(); + updateAngle(animated); + // Whenever the trigger back state changes the existing translation animation should be + // cancelled + mTranslationAnimation.cancel(); + } + } + private void updateAngle(boolean animated) { float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90; if (newAngle != mDesiredAngle) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index cf5cbc72e3b8ad2d6b3f33c0628debb73ef6fe50..0a2666e62bc3c71f4c5e30fe1af21490f550cf16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -35,6 +35,7 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import com.android.internal.policy.SystemBarUtils; +import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d4073b0555724934ad689429aebe30393c825b08..113f8b2f6e04d3109e9bc4b52bc35eacee6b2c38 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -564,6 +564,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private Action mAssistLongPressAction; private Action mAppSwitchPressAction; private Action mAppSwitchLongPressAction; + private Action mEdgeLongSwipeAction; // support for activating the lock screen while the screen is on private HashSet<Integer> mAllowLockscreenWhenOnDisplays = new HashSet<>(); @@ -688,6 +689,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private LineageHardwareManager mLineageHardware; + private boolean mLongSwipeDown; + private static final int LONG_SWIPE_FLAGS = KeyEvent.FLAG_LONG_PRESS + | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY; + private class PolicyHandler extends Handler { @Override public void handleMessage(Message msg) { @@ -848,6 +853,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.KEY_EDGE_LONG_SWIPE_ACTION), false, this, + UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.HOME_WAKE_SCREEN), false, this, UserHandle.USER_ALL); @@ -2344,6 +2352,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mAppSwitchLongPressAction = Action.fromIntSafe(res.getInteger( com.android.internal.R.integer.config_longPressOnAppSwitchBehavior)); + mEdgeLongSwipeAction = Action.NOTHING; + mHomeLongPressAction = Action.fromIntSafe(res.getInteger( com.android.internal.R.integer.config_longPressOnHomeBehavior)); if (mHomeLongPressAction.ordinal() > Action.SLEEP.ordinal()) { @@ -2388,6 +2398,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION, mAppSwitchLongPressAction); + mEdgeLongSwipeAction = Action.fromSettings(resolver, + Settings.System.KEY_EDGE_LONG_SWIPE_ACTION, + mEdgeLongSwipeAction); + mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING; if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE; @@ -4005,6 +4019,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { + boolean isLongSwipe = (event.getFlags() & LONG_SWIPE_FLAGS) == LONG_SWIPE_FLAGS; + if (mLongSwipeDown && isLongSwipe && !down) { + // Trigger long swipe action + performKeyAction(mEdgeLongSwipeAction, event); + // Reset long swipe state + mLongSwipeDown = false; + // Don't pass back press to app + result &= ~ACTION_PASS_TO_USER; + break; + } + mLongSwipeDown = isLongSwipe && down; + if (mLongSwipeDown) { + // Don't pass back press to app + result &= ~ACTION_PASS_TO_USER; + break; + } + if (down) { mBackKeyHandled = false; } else {