From 3f38c67f1cfd1c842e4c73eb463a4538dc972f1e Mon Sep 17 00:00:00 2001 From: minaripenguin Date: Thu, 10 Oct 2024 11:00:37 +0800 Subject: [PATCH] services: Introduce Shake Gestures [1/2] Co-authored-by: AmeChanRain Change-Id: If2a3c094c2f30e3eb7bf1df811edb482554749bb Signed-off-by: Alvin Francis Signed-off-by: minaripenguin Signed-off-by: MOVZX --- .../server/policy/PhoneWindowManager.java | 27 ++++ .../rising/server/ShakeGestureService.java | 106 +++++++++++++++ .../org/rising/server/ShakeGestureUtils.java | 128 ++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 services/core/java/org/rising/server/ShakeGestureService.java create mode 100644 services/core/java/org/rising/server/ShakeGestureUtils.java diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1ec75c139e925..3c60df30d648f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -284,6 +284,8 @@ import lineageos.providers.LineageSettings; import org.lineageos.internal.buttons.LineageButtons; import org.lineageos.internal.util.ActionUtils; +import org.rising.server.ShakeGestureService; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; @@ -731,6 +733,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mPendingMetaAction; boolean mPendingCapsLockToggle; + private ShakeGestureService mShakeGestures; + // Tracks user-customisable behavior for certain key events private Action mBackLongPressAction; private Action mHomeLongPressAction; @@ -743,6 +747,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private Action mAppSwitchLongPressAction; private Action mEdgeLongSwipeAction; private Action mThreeFingersSwipeAction; + private Action mShakeGestureAction; // support for activating the lock screen while the screen is on private HashSet mAllowLockscreenWhenOnDisplays = new HashSet<>(); @@ -1181,6 +1186,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.Secure.getUriFor( "nothing_three_finger_screenshot"), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(LineageSettings.System.getUriFor( + LineageSettings.System.KEY_SHAKE_GESTURE_ACTION), false, this, + UserHandle.USER_ALL); updateSettings(); } @@ -3505,6 +3513,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver, "nothing_three_finger_screenshot", 0, UserHandle.USER_CURRENT)); + mShakeGestureAction = Action.fromSettings(resolver, + LineageSettings.System.KEY_SHAKE_GESTURE_ACTION, + Action.NOTHING); + mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING; if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE; @@ -7663,6 +7675,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mVrManagerInternal != null) { mVrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); } + + mShakeGestures = ShakeGestureService.getInstance(mContext, new ShakeGestureService.ShakeGesturesCallbacks() { + @Override + public void onShake() { + if (mShakeGestureAction == Action.NOTHING) + return; + long now = SystemClock.uptimeMillis(); + KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_SYSRQ, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_TOUCHSCREEN); + performKeyAction(mShakeGestureAction, event); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Shake Gesture"); + } + }); + mShakeGestures.onStart(); mDockObserverInternal = LocalServices.getService(DockObserverInternal.class); if (mDockObserverInternal != null) { diff --git a/services/core/java/org/rising/server/ShakeGestureService.java b/services/core/java/org/rising/server/ShakeGestureService.java new file mode 100644 index 0000000000000..6dce9663f1ec5 --- /dev/null +++ b/services/core/java/org/rising/server/ShakeGestureService.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023-2024 The RisingOS Android Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rising.server; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; + +public final class ShakeGestureService { + + private static final String TAG = "ShakeGestureService"; + + private static final String SHAKE_GESTURES_ENABLED = "shake_gestures_enabled"; + private static final String SHAKE_GESTURES_ACTION = "shake_gestures_action"; + private static final int USER_ALL = UserHandle.USER_ALL; + + private final Context mContext; + private ShakeGestureUtils mShakeGestureUtils; + private static volatile ShakeGestureService instance; + private final ShakeGesturesCallbacks mShakeCallbacks; + + private final SettingsObserver mSettingsObserver; + private boolean mShakeServiceEnabled = false; + + private ShakeGestureUtils.OnShakeListener mShakeListener; + + public interface ShakeGesturesCallbacks { + void onShake(); + } + + private ShakeGestureService(Context context, ShakeGesturesCallbacks callback) { + mContext = context; + mShakeCallbacks = callback; + mShakeListener = () -> { + if (mShakeServiceEnabled && mShakeCallbacks != null) { + mShakeCallbacks.onShake(); + } + }; + mSettingsObserver = new SettingsObserver(null); + } + + public static synchronized ShakeGestureService getInstance(Context context, ShakeGesturesCallbacks callback) { + if (instance == null) { + synchronized (ShakeGestureService.class) { + if (instance == null) { + instance = new ShakeGestureService(context, callback); + } + } + } + return instance; + } + + public void onStart() { + if (mShakeGestureUtils == null) { + mShakeGestureUtils = new ShakeGestureUtils(mContext); + } + updateSettings(); + mSettingsObserver.observe(); + if (mShakeServiceEnabled) { + mShakeGestureUtils.registerListener(mShakeListener); + } + } + + private void updateSettings() { + boolean wasShakeServiceEnabled = mShakeServiceEnabled; + mShakeServiceEnabled = Settings.System.getInt(mContext.getContentResolver(), + SHAKE_GESTURES_ENABLED, 0) == 1; + if (mShakeServiceEnabled && !wasShakeServiceEnabled) { + mShakeGestureUtils.registerListener(mShakeListener); + } else if (!mShakeServiceEnabled && wasShakeServiceEnabled) { + mShakeGestureUtils.unregisterListener(mShakeListener); + } + } + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + void observe() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(SHAKE_GESTURES_ENABLED), false, this, USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(SHAKE_GESTURES_ACTION), false, this, USER_ALL); + } + @Override + public void onChange(boolean selfChange) { + updateSettings(); + } + } +} diff --git a/services/core/java/org/rising/server/ShakeGestureUtils.java b/services/core/java/org/rising/server/ShakeGestureUtils.java new file mode 100644 index 0000000000000..76e9e02047d7e --- /dev/null +++ b/services/core/java/org/rising/server/ShakeGestureUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023-2024 The RisingOS Android Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.rising.server; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.SystemClock; +import android.provider.Settings; + +import java.util.ArrayList; + +public class ShakeGestureUtils implements SensorEventListener { + + private static final String TAG = "ShakeGestureUtils"; + + private static final String SHAKE_GESTURES_SHAKE_INTENSITY = "shake_gestures_intensity"; + + private Context mContext; + private SensorManager mSensorManager; + private Sensor mAccelerometer; + private ArrayList mListeners = new ArrayList<>(); + private long mLastShakeTime = 0L; + private long mLastUpdateTime = 0L; + private int mShakeCount = 0; + private float mLastX = 0f; + private float mLastY = 0f; + private float mLastZ = 0f; + + public ShakeGestureUtils(Context context) { + mContext = context; + mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if (mSensorManager != null) { + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + } + } + + public interface OnShakeListener { + void onShake(); + } + + public void registerListener(OnShakeListener listener) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + startListening(); + } + } + + public void unregisterListener(OnShakeListener listener) { + mListeners.remove(listener); + if (mListeners.isEmpty()) { + stopListening(); + } + } + + private void startListening() { + if (mAccelerometer != null) { + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI); + } + } + + private void stopListening() { + mSensorManager.unregisterListener(this); + } + + private int getShakeIntensity() { + return Settings.System.getInt(mContext.getContentResolver(), + SHAKE_GESTURES_SHAKE_INTENSITY, 3); + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event == null) { + return; + } + long curUpdateTime = System.currentTimeMillis(); + long timeInterval = curUpdateTime - mLastUpdateTime; + if (timeInterval < (getShakeIntensity() * 14f)) { + return; + } + if (event.values.length < 3) { + return; + } + mLastUpdateTime = curUpdateTime; + float x = event.values[0]; + float y = event.values[1]; + float z = event.values[2]; + float deltaX = x - mLastX; + float deltaY = y - mLastY; + float deltaZ = z - mLastZ; + mLastX = x; + mLastY = y; + mLastZ = z; + double speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 1000.0 / timeInterval; + if (speed >= getShakeIntensity() * 100f) { + notifyShakeListeners(); + } + } + + private void notifyShakeListeners() { + if (SystemClock.elapsedRealtime() - mLastShakeTime < 1000) { + return; + } + for (OnShakeListener listener : mListeners) { + listener.onShake(); + } + mLastShakeTime = SystemClock.elapsedRealtime(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) {} +}