diff --git a/app/src/main/java/com/darkeyes/tricks/FeatureFactory.java b/app/src/main/java/com/darkeyes/tricks/FeatureFactory.java new file mode 100644 index 0000000..b52d489 --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/FeatureFactory.java @@ -0,0 +1,48 @@ +package com.darkeyes.tricks; + +import com.darkeyes.tricks.features.DoubleTapStatusBarOrLockScreenSdk29; +import com.darkeyes.tricks.features.DoubleTapStatusBarOrLockScreenSdk31AndHigher; +import com.darkeyes.tricks.features.Feature; +import com.darkeyes.tricks.features.QuickPullDownFeatureSdk31AndHigher; + +public class FeatureFactory { + + private FeatureFactory() { + } + + /** + * Create instances of features if available. + * + * @param featureName the feature you want to instantiate. See {@link com.darkeyes.tricks.features.FeatureNames} + * @return null if feature is not available other the feature you want. + */ + public static Feature createFeature(final String featureName) { + Feature feature = null; + + if (DoubleTapStatusBarOrLockScreenSdk31AndHigher.isPlatformSupported(featureName)) { + feature = new DoubleTapStatusBarOrLockScreenSdk31AndHigher(); + } else if (DoubleTapStatusBarOrLockScreenSdk29.isPlatformSupported(featureName)) { + feature = new DoubleTapStatusBarOrLockScreenSdk29(); + } else if (QuickPullDownFeatureSdk31AndHigher.isPlatformSupported(featureName)) { + feature = new QuickPullDownFeatureSdk31AndHigher(); + } + return feature; + } + + /** + * Check if a feature is available on this android platform. + * + * @param featureName the feature you want to instantiate. See {@link com.darkeyes.tricks.features.FeatureNames} + * @return true if available otherwise false + */ + public static boolean hasFeature(final String featureName) { + boolean hasFeature = DoubleTapStatusBarOrLockScreenSdk31AndHigher.isPlatformSupported(featureName); + if (!hasFeature) { + hasFeature = DoubleTapStatusBarOrLockScreenSdk29.isPlatformSupported(featureName); + } + if (!hasFeature) { + hasFeature = QuickPullDownFeatureSdk31AndHigher.isPlatformSupported(featureName); + } + return hasFeature; + } +} diff --git a/app/src/main/java/com/darkeyes/tricks/Main.java b/app/src/main/java/com/darkeyes/tricks/Main.java index 8f6b2b2..293aa3b 100644 --- a/app/src/main/java/com/darkeyes/tricks/Main.java +++ b/app/src/main/java/com/darkeyes/tricks/Main.java @@ -30,21 +30,18 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.ArrayMap; -import android.view.GestureDetector; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewGroup; import android.widget.TextView; +import com.darkeyes.tricks.features.Feature; +import com.darkeyes.tricks.features.FeatureNames; + import java.io.File; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.IXposedHookZygoteInit; @@ -99,11 +96,6 @@ public class Main implements IXposedHookZygoteInit, IXposedHookLoadPackage { private ArrayMap mLastTimestamps = new ArrayMap<>(); private long mDownTime = 0L; private boolean mCameraGesture; - private GestureDetector mDoubleTapGesture; - private Object mNotificationPanelViewController; - private int mStatusBarHeight = 0; - private int mStatusBarHeaderHeight = 0; - private long mLastDownEvent = 0L; private Object mLockPatterUtils; private Object mLockCallback; private Object mKeyguardMonitor; @@ -113,8 +105,6 @@ public class Main implements IXposedHookZygoteInit, IXposedHookLoadPackage { private float mBottom; private Object mEdgeObject; private String mOldEntry; - private Date securityPatch; - private Date december; public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) { @@ -170,12 +160,7 @@ protected void beforeHookedMethod(MethodHookParam param) { } public void handleLoadPackage(XC_LoadPackage.LoadPackageParam param) { - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); - try { - securityPatch = format.parse(Build.VERSION.SECURITY_PATCH); - december = format.parse("2022-12-01"); - } catch (ParseException ignored) { - } + final Utils utils = new Utils(); if (param.packageName.equals("com.android.systemui")) { classLoader = param.classLoader; @@ -366,131 +351,17 @@ protected void beforeHookedMethod(MethodHookParam param) { } } - if ((pref.getBoolean("trick_doubleTapStatusBar", false) || (pref.getBoolean("trick_doubleTapLockScreen", false)) - || pref.getBoolean("trick_quickPulldown", true)) && Build.VERSION.SDK_INT >= 31) { - - String notificationPanelViewController = securityPatch.after(december) ? "com.android.systemui.shade.NotificationPanelViewController" : "com.android.systemui.statusbar.phone.NotificationPanelViewController"; - findAndHookMethod(notificationPanelViewController, param.classLoader, "onFinishInflate", new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - mNotificationPanelViewController = param.thisObject; - - if (pref.getBoolean("trick_doubleTapStatusBar", false) || pref.getBoolean("trick_doubleTapLockScreen", false)) { - mStatusBarHeight = getIntField(param.thisObject, "mStatusBarMinHeight"); - mStatusBarHeaderHeight = getIntField(param.thisObject, "mStatusBarHeaderHeightKeyguard"); - View view = (View) getObjectField(param.thisObject, "mView"); - if (mPowerManager == null) - mPowerManager = (PowerManager) getObjectField(param.thisObject, "mPowerManager"); - if (mDoubleTapGesture == null) { - mDoubleTapGesture = new GestureDetector(view.getContext(), - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onDoubleTap(MotionEvent e) { - callMethod(mPowerManager, "goToSleep", e.getEventTime()); - return true; - } - }); - } - } - } - }); - - if (pref.getBoolean("trick_quickPulldown", true)) { - if (Build.VERSION.SDK_INT >= 33) { - findAndHookMethod("com.android.systemui.statusbar.phone.HeadsUpTouchHelper", param.classLoader, "onTouchEvent", MotionEvent.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - MotionEvent event = (MotionEvent) param.args[0]; - if (getBooleanField(mNotificationPanelViewController, "mSplitShadeEnabled") && - (boolean) callMethod(mNotificationPanelViewController, "touchXOutsideOfQs", event.getX())) - return; - ViewGroup view = (ViewGroup) getObjectField(mNotificationPanelViewController, "mView"); - int state = (int) getObjectField(mNotificationPanelViewController, "mBarState"); - int height = getIntField(mNotificationPanelViewController, "mStatusBarMinHeight"); - boolean tracking = getBooleanField(param.thisObject, "mTrackingHeadsUp"); - - float w = view.getMeasuredWidth(); - float x = event.getX(); - float y = event.getY(event.getActionIndex()); - - if (x > 3.f * w / 4.f && state == 0 && !tracking && y < height) { - setBooleanField(mNotificationPanelViewController, "mQsExpandImmediate", true); - callMethod(mNotificationPanelViewController, "setShowShelfOnly", true); - String update = securityPatch.after(december) ? "updateExpandedHeightToMaxHeight" : "requestPanelHeightUpdate"; - callMethod(mNotificationPanelViewController, update); - callMethod(mNotificationPanelViewController, "setListening", true); - } - } - }); - } else { - findAndHookMethod("com.android.systemui.statusbar.phone.NotificationPanelViewController", param.classLoader, "isOpenQsEvent", MotionEvent.class, new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - if ((boolean) param.getResult() == false) { - MotionEvent event = (MotionEvent) param.args[0]; - ViewGroup view = (ViewGroup) getObjectField(param.thisObject, "mView"); - int state = (int) getObjectField(param.thisObject, "mBarState"); - float w = view.getMeasuredWidth(); - float x = event.getX(); - - param.setResult(x > 3.f * w / 4.f && state == 0); - } - } - }); - } + if ((pref.getBoolean(FeatureNames.TRICK_DOUBLE_TAP_STATUSBAR, false) || (pref.getBoolean(FeatureNames.TRICK_DOUBLE_TAP_LOCKSCREEN, false))) ) { + Feature feature = FeatureFactory.createFeature(FeatureNames.TRICK_DOUBLE_TAP_STATUSBAR); + if (feature != null) { + feature.inject(param, pref, utils); } + } - if (pref.getBoolean("trick_doubleTapStatusBar", false) || pref.getBoolean("trick_doubleTapLockScreen", false)) { - String touchHandler = securityPatch.after(december) ? "com.android.systemui.shade.PanelViewController$TouchHandler" : "com.android.systemui.statusbar.phone.PanelViewController$TouchHandler"; - findAndHookMethod(touchHandler, param.classLoader, "onTouch", View.class, MotionEvent.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - String notificationPanelView = securityPatch.after(december) ? "com.android.systemui.shade.NotificationPanelView" : "com.android.systemui.statusbar.phone.NotificationPanelView"; - if (param.args[0].getClass().getName().equals(notificationPanelView) - && mNotificationPanelViewController != null && mDoubleTapGesture != null) { - MotionEvent event = (MotionEvent) param.args[1]; - boolean isExpanded = getBooleanField(mNotificationPanelViewController, "mQsExpanded"); - boolean isPulsing = getBooleanField(mNotificationPanelViewController, "mPulsing"); - boolean isDozing = getBooleanField(mNotificationPanelViewController, "mDozing"); - boolean isKeyguard = getIntField(mNotificationPanelViewController, "mBarState") == 1 - && !isPulsing && !isDozing; - boolean isStatusBar = event.getY() < mStatusBarHeight && !isExpanded; - - if ((isKeyguard && pref.getBoolean("trick_doubleTapLockScreen", false)) - || (isStatusBar && pref.getBoolean("trick_doubleTapStatusBar", false))) - mDoubleTapGesture.onTouchEvent(event); - } - } - }); - - if (pref.getBoolean("trick_doubleTapLockScreen", false)) { - findAndHookMethod("com.android.systemui.statusbar.DragDownHelper", param.classLoader, "onInterceptTouchEvent", MotionEvent.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - MotionEvent event = (MotionEvent) param.args[0]; - long time = event.getEventTime(); - View host = (View) getObjectField(param.thisObject, "host"); - if (mPowerManager == null) - mPowerManager = (PowerManager) host.getContext().getSystemService(Context.POWER_SERVICE); - if (event.getActionMasked() == MotionEvent.ACTION_DOWN - && event.getY() < mStatusBarHeaderHeight) { - if (time - mLastDownEvent < 300) { - callMethod(mPowerManager, "goToSleep", time); - } - mLastDownEvent = event.getEventTime(); - } - } - }); - } - - if (pref.getBoolean("trick_doubleTapStatusBar", false) && Build.VERSION.SDK_INT < 33) { - findAndHookMethod("com.android.systemui.statusbar.phone.PanelViewController", param.classLoader, "startOpening", MotionEvent.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - param.setResult(null); - } - }); - } + if (pref.getBoolean(FeatureNames.TRICK_QUICK_PULLDOWN, true)) { + Feature feature = FeatureFactory.createFeature(FeatureNames.TRICK_QUICK_PULLDOWN); + if (feature != null) { + feature.inject(param, pref, utils); } } @@ -836,7 +707,7 @@ protected void beforeHookedMethod(MethodHookParam param) { if (pref.getBoolean("trick_skipTrack", true) || pref.getBoolean("trick_powerTorch", false)) { if (Build.VERSION.SDK_INT >= 33) { - String init = securityPatch.after(december) ? "initKeyCombinationRules" : "init"; + String init = utils.isSecurityPatchAfterDecember2022() ? "initKeyCombinationRules" : "init"; findAndHookMethod("com.android.server.policy.PhoneWindowManager", param.classLoader, init, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) { diff --git a/app/src/main/java/com/darkeyes/tricks/SettingsActivity.java b/app/src/main/java/com/darkeyes/tricks/SettingsActivity.java index dbef953..d10cc82 100644 --- a/app/src/main/java/com/darkeyes/tricks/SettingsActivity.java +++ b/app/src/main/java/com/darkeyes/tricks/SettingsActivity.java @@ -17,6 +17,10 @@ import java.io.File; +import static com.darkeyes.tricks.features.FeatureNames.TRICK_DOUBLE_TAP_LOCKSCREEN; +import static com.darkeyes.tricks.features.FeatureNames.TRICK_DOUBLE_TAP_STATUSBAR; +import static com.darkeyes.tricks.features.FeatureNames.TRICK_QUICK_PULLDOWN; + public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { private EditTextPreference customCarrierText; @@ -42,8 +46,8 @@ protected void onCreate(Bundle savedInstanceState) { SwitchPreference navbarAlwaysRight = (SwitchPreference) findPreference("trick_navbarAlwaysRight"); SwitchPreference hideBuildVersion = (SwitchPreference) findPreference("trick_hideBuildVersion"); SwitchPreference powerTorch = (SwitchPreference) findPreference("trick_powerTorch"); - SwitchPreference doubleTapStatusBar = (SwitchPreference) findPreference("trick_doubleTapStatusBar"); - SwitchPreference doubleTapLockScreen = (SwitchPreference) findPreference("trick_doubleTapLockScreen"); + SwitchPreference doubleTapStatusBar = (SwitchPreference) findPreference(TRICK_DOUBLE_TAP_STATUSBAR); + SwitchPreference doubleTapLockScreen = (SwitchPreference) findPreference(TRICK_DOUBLE_TAP_LOCKSCREEN); quickUnlock = (SwitchPreference) findPreference("trick_quickUnlock"); SwitchPreference batteryEstimate = (SwitchPreference) findPreference("trick_batteryEstimate"); customCarrierText = (EditTextPreference) findPreference("trick_customCarrierText"); @@ -52,6 +56,7 @@ protected void onCreate(Bundle savedInstanceState) { SwitchPreference smallClock = (SwitchPreference) findPreference("trick_smallClock"); gestureHeight = (ListPreference) findPreference("trick_gestureHeight"); SwitchPreference expandedNotifications = (SwitchPreference) findPreference("trick_expandedNotifications"); + SwitchPreference quickPulldown = (SwitchPreference) findPreference(TRICK_QUICK_PULLDOWN); getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); updateSummary(); @@ -69,9 +74,14 @@ protected void onCreate(Bundle savedInstanceState) { prefScreen.removePreference(hideBuildVersion); prefScreen.removePreference(lessNotifications); } - if (Build.VERSION.SDK_INT < 31) { + + if (!FeatureFactory.hasFeature(TRICK_DOUBLE_TAP_STATUSBAR)) { prefScreen.removePreference(doubleTapStatusBar); + } + if (!FeatureFactory.hasFeature(TRICK_DOUBLE_TAP_LOCKSCREEN)) { prefScreen.removePreference(doubleTapLockScreen); + } + if (Build.VERSION.SDK_INT < 31) { prefScreen.removePreference(quickUnlock); prefScreen.removePreference(batteryEstimate); prefScreen.removePreference(smallClock); @@ -84,6 +94,9 @@ protected void onCreate(Bundle savedInstanceState) { if (!torchAvailable()) { prefScreen.removePreference(powerTorch); } + if (!FeatureFactory.hasFeature(TRICK_QUICK_PULLDOWN)) { + prefScreen.removePreference(quickPulldown); + } } @Override @@ -189,4 +202,4 @@ private static void setBoolean(Bundle extras) { sp.edit().putBoolean(preference, value).commit(); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/darkeyes/tricks/Utils.java b/app/src/main/java/com/darkeyes/tricks/Utils.java new file mode 100644 index 0000000..aa2680a --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/Utils.java @@ -0,0 +1,34 @@ +package com.darkeyes.tricks; + +import android.os.Build; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class Utils { + + private Date securityPatch; + private Date december; + + public Utils() { + initDates(); + } + + private void initDates() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + try { + securityPatch = format.parse(Build.VERSION.SECURITY_PATCH); + december = format.parse("2022-12-01"); + } catch (ParseException ignored) { + } + } + + public String getComAndroidSystemui_NotificationPanelViewControllerClassName() { + return isSecurityPatchAfterDecember2022() ? "com.android.systemui.shade.NotificationPanelViewController" : "com.android.systemui.statusbar.phone.NotificationPanelViewController"; + } + + public boolean isSecurityPatchAfterDecember2022() { + return securityPatch.after(december); + } +} diff --git a/app/src/main/java/com/darkeyes/tricks/features/DoubleTapStatusBarOrLockScreenSdk29.java b/app/src/main/java/com/darkeyes/tricks/features/DoubleTapStatusBarOrLockScreenSdk29.java new file mode 100644 index 0000000..d92265e --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/features/DoubleTapStatusBarOrLockScreenSdk29.java @@ -0,0 +1,48 @@ +package com.darkeyes.tricks.features; + +import android.content.Context; +import android.os.Build; +import android.os.PowerManager; +import android.view.MotionEvent; + +import com.darkeyes.tricks.Utils; + +import java.util.Objects; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XSharedPreferences; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; +import static de.robv.android.xposed.XposedHelpers.getObjectField; + +public class DoubleTapStatusBarOrLockScreenSdk29 extends DoubleTapStatusBarOrLockScreenSdk31AndHigher { + + public static boolean isPlatformSupported(final String featureName) { + return Build.VERSION.SDK_INT == 29 // Android 10 + && (Objects.equals(featureName, FeatureNames.TRICK_DOUBLE_TAP_STATUSBAR) + || Objects.equals(featureName, FeatureNames.TRICK_DOUBLE_TAP_LOCKSCREEN)); + } + + @Override + public void inject(final XC_LoadPackage.LoadPackageParam param, + final XSharedPreferences pref, + final Utils utils) { + findAndHookMethod("com.android.systemui.statusbar.phone.PanelView", param.classLoader, "onFinishInflate", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + final Context context = (Context) getObjectField(param.thisObject, "mContext"); + if (mPowerManager == null) + mPowerManager = context.getSystemService(PowerManager.class); + registerGestureDetectorListener(param, context, mPowerManager); + } + }); + + findAndHookMethod("com.android.systemui.statusbar.phone.NotificationPanelView", param.classLoader, "onTouchEvent", MotionEvent.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + fireOnTouchEventIfPossible((MotionEvent) param.args[0], pref); + } + }); + } +} diff --git a/app/src/main/java/com/darkeyes/tricks/features/DoubleTapStatusBarOrLockScreenSdk31AndHigher.java b/app/src/main/java/com/darkeyes/tricks/features/DoubleTapStatusBarOrLockScreenSdk31AndHigher.java new file mode 100644 index 0000000..5e4bd6b --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/features/DoubleTapStatusBarOrLockScreenSdk31AndHigher.java @@ -0,0 +1,132 @@ +package com.darkeyes.tricks.features; + +import android.content.Context; +import android.os.Build; +import android.os.PowerManager; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import com.darkeyes.tricks.Utils; + +import java.util.Objects; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XSharedPreferences; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.darkeyes.tricks.features.FeatureNames.TRICK_DOUBLE_TAP_LOCKSCREEN; +import static com.darkeyes.tricks.features.FeatureNames.TRICK_DOUBLE_TAP_STATUSBAR; +import static de.robv.android.xposed.XposedHelpers.callMethod; +import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; +import static de.robv.android.xposed.XposedHelpers.getBooleanField; +import static de.robv.android.xposed.XposedHelpers.getIntField; +import static de.robv.android.xposed.XposedHelpers.getObjectField; + +public class DoubleTapStatusBarOrLockScreenSdk31AndHigher implements Feature { + + protected Object mNotificationPanelViewController; + protected GestureDetector mDoubleTapGesture; + protected int mStatusBarHeight = 0; + protected PowerManager mPowerManager; + private int mStatusBarHeaderHeight = 0; + private long mLastDownEvent = 0L; + + public static boolean isPlatformSupported(final String featureName) { + return Build.VERSION.SDK_INT >= 31 + && (Objects.equals(featureName, FeatureNames.TRICK_DOUBLE_TAP_STATUSBAR) + || Objects.equals(featureName, FeatureNames.TRICK_DOUBLE_TAP_LOCKSCREEN)); + } + + @Override + public void inject(final XC_LoadPackage.LoadPackageParam param, + final XSharedPreferences pref, + final Utils utils) { + final String notificationPanelViewController = utils.getComAndroidSystemui_NotificationPanelViewControllerClassName(); + findAndHookMethod(notificationPanelViewController, param.classLoader, "onFinishInflate", new XC_MethodHook() { + + @Override + protected void afterHookedMethod(MethodHookParam param) { + if (mPowerManager == null) + mPowerManager = (PowerManager) getObjectField(param.thisObject, "mPowerManager"); + mStatusBarHeaderHeight = getIntField(param.thisObject, "mStatusBarHeaderHeightKeyguard"); + final View view = (View) getObjectField(param.thisObject, "mView"); + registerGestureDetectorListener(param, view.getContext(), mPowerManager); + } + }); + + String touchHandler = utils.isSecurityPatchAfterDecember2022() ? "com.android.systemui.shade.PanelViewController$TouchHandler" : "com.android.systemui.statusbar.phone.PanelViewController$TouchHandler"; + findAndHookMethod(touchHandler, param.classLoader, "onTouch", View.class, MotionEvent.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + final String notificationPanelView = utils.isSecurityPatchAfterDecember2022() ? "com.android.systemui.shade.NotificationPanelView" : "com.android.systemui.statusbar.phone.NotificationPanelView"; + if (param.args[0].getClass().getName().equals(notificationPanelView)) { + fireOnTouchEventIfPossible((MotionEvent) param.args[1], pref); + } + } + }); + + if (pref.getBoolean(TRICK_DOUBLE_TAP_LOCKSCREEN, false)) { + findAndHookMethod("com.android.systemui.statusbar.DragDownHelper", param.classLoader, "onInterceptTouchEvent", MotionEvent.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + MotionEvent event = (MotionEvent) param.args[0]; + long time = event.getEventTime(); + View host = (View) getObjectField(param.thisObject, "host"); + if (mPowerManager == null) + mPowerManager = (PowerManager) host.getContext().getSystemService(Context.POWER_SERVICE); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN + && event.getY() < mStatusBarHeaderHeight) { + if (time - mLastDownEvent < 300) { + callMethod(mPowerManager, "goToSleep", time); + } + mLastDownEvent = event.getEventTime(); + } + } + }); + } + + if (pref.getBoolean(TRICK_DOUBLE_TAP_STATUSBAR, false) && Build.VERSION.SDK_INT < 33) { + findAndHookMethod("com.android.systemui.statusbar.phone.PanelViewController", param.classLoader, "startOpening", MotionEvent.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + param.setResult(null); + } + }); + } + } + + protected void registerGestureDetectorListener(final XC_MethodHook.MethodHookParam param, + final Context context, + final PowerManager powerManager) { + mNotificationPanelViewController = param.thisObject; + mStatusBarHeight = getIntField(param.thisObject, "mStatusBarMinHeight"); + + if (mDoubleTapGesture == null) { + mDoubleTapGesture = new GestureDetector(context, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + callMethod(powerManager, "goToSleep", e.getEventTime()); + return true; + } + }); + } + } + + protected void fireOnTouchEventIfPossible(final MotionEvent event, + final XSharedPreferences pref) { + if (mNotificationPanelViewController != null && mDoubleTapGesture != null) { + boolean isExpanded = getBooleanField(mNotificationPanelViewController, "mQsExpanded"); + boolean isPulsing = getBooleanField(mNotificationPanelViewController, "mPulsing"); + boolean isDozing = getBooleanField(mNotificationPanelViewController, "mDozing"); + boolean isKeyguard = getIntField(mNotificationPanelViewController, "mBarState") == 1 + && !isPulsing && !isDozing; + boolean isStatusBar = event.getY() < mStatusBarHeight && !isExpanded; + + if ((isKeyguard && pref.getBoolean(TRICK_DOUBLE_TAP_LOCKSCREEN, false)) + || (isStatusBar && pref.getBoolean(TRICK_DOUBLE_TAP_STATUSBAR, false))) + mDoubleTapGesture.onTouchEvent(event); + } + } +} diff --git a/app/src/main/java/com/darkeyes/tricks/features/Feature.java b/app/src/main/java/com/darkeyes/tricks/features/Feature.java new file mode 100644 index 0000000..49b6f5a --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/features/Feature.java @@ -0,0 +1,22 @@ +package com.darkeyes.tricks.features; + +import com.darkeyes.tricks.Utils; + +import de.robv.android.xposed.XSharedPreferences; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +/** + * A feature is the implementation of an modification you want to do with Xposed. + *

+ * Every class that implements {@link Feature} should also implement a static method + * that determines if a feature is supported on which platforms. The static method + * should have a signature like this: + *

+ * static boolean hasFeature(String featureName); + *

+ * This method should be used in the {@link com.darkeyes.tricks.FeatureFactory} to + * actually create the feature if available. + */ +public interface Feature { + void inject(XC_LoadPackage.LoadPackageParam param, XSharedPreferences pref, Utils utils); +} diff --git a/app/src/main/java/com/darkeyes/tricks/features/FeatureNames.java b/app/src/main/java/com/darkeyes/tricks/features/FeatureNames.java new file mode 100644 index 0000000..e4cac65 --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/features/FeatureNames.java @@ -0,0 +1,7 @@ +package com.darkeyes.tricks.features; + +public class FeatureNames { + public static final String TRICK_DOUBLE_TAP_STATUSBAR = "trick_doubleTapStatusBar"; + public static final String TRICK_DOUBLE_TAP_LOCKSCREEN = "trick_doubleTapLockScreen"; + public static final String TRICK_QUICK_PULLDOWN = "trick_quickPulldown"; +} diff --git a/app/src/main/java/com/darkeyes/tricks/features/QuickPullDownFeatureSdk31AndHigher.java b/app/src/main/java/com/darkeyes/tricks/features/QuickPullDownFeatureSdk31AndHigher.java new file mode 100644 index 0000000..8ae5819 --- /dev/null +++ b/app/src/main/java/com/darkeyes/tricks/features/QuickPullDownFeatureSdk31AndHigher.java @@ -0,0 +1,91 @@ +package com.darkeyes.tricks.features; + +import android.os.Build; +import android.view.MotionEvent; +import android.view.ViewGroup; + +import com.darkeyes.tricks.Utils; + +import java.util.Objects; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XSharedPreferences; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +import static com.darkeyes.tricks.features.FeatureNames.TRICK_QUICK_PULLDOWN; +import static de.robv.android.xposed.XposedHelpers.callMethod; +import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; +import static de.robv.android.xposed.XposedHelpers.getBooleanField; +import static de.robv.android.xposed.XposedHelpers.getIntField; +import static de.robv.android.xposed.XposedHelpers.getObjectField; +import static de.robv.android.xposed.XposedHelpers.setBooleanField; + +public class QuickPullDownFeatureSdk31AndHigher implements Feature { + private Object mNotificationPanelViewController; + + public static boolean isPlatformSupported(final String featureName) { + return (Build.VERSION.SDK_INT >= 31) + // && (Build.VERSION.SDK_INT <= 33) + && Objects.equals(featureName, TRICK_QUICK_PULLDOWN); + } + + @Override + public void inject(final XC_LoadPackage.LoadPackageParam param, + final XSharedPreferences pref, + final Utils utils) { + + if (pref.getBoolean(TRICK_QUICK_PULLDOWN, true)) { + + final String notificationPanelViewController = utils.getComAndroidSystemui_NotificationPanelViewControllerClassName(); + findAndHookMethod(notificationPanelViewController, param.classLoader, "onFinishInflate", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + mNotificationPanelViewController = param.thisObject; + } + }); + + if (Build.VERSION.SDK_INT >= 33) { + findAndHookMethod("com.android.systemui.statusbar.phone.HeadsUpTouchHelper", param.classLoader, "onTouchEvent", MotionEvent.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + MotionEvent event = (MotionEvent) param.args[0]; + if (getBooleanField(mNotificationPanelViewController, "mSplitShadeEnabled") && + (boolean) callMethod(mNotificationPanelViewController, "touchXOutsideOfQs", event.getX())) + return; + ViewGroup view = (ViewGroup) getObjectField(mNotificationPanelViewController, "mView"); + int state = (int) getObjectField(mNotificationPanelViewController, "mBarState"); + int height = getIntField(mNotificationPanelViewController, "mStatusBarMinHeight"); + boolean tracking = getBooleanField(param.thisObject, "mTrackingHeadsUp"); + + float w = view.getMeasuredWidth(); + float x = event.getX(); + float y = event.getY(event.getActionIndex()); + + if (x > 3.f * w / 4.f && state == 0 && !tracking && y < height) { + setBooleanField(mNotificationPanelViewController, "mQsExpandImmediate", true); + callMethod(mNotificationPanelViewController, "setShowShelfOnly", true); + String update = utils.isSecurityPatchAfterDecember2022() ? "updateExpandedHeightToMaxHeight" : "requestPanelHeightUpdate"; + callMethod(mNotificationPanelViewController, update); + callMethod(mNotificationPanelViewController, "setListening", true); + } + } + }); + } else { + findAndHookMethod("com.android.systemui.statusbar.phone.NotificationPanelViewController", param.classLoader, "isOpenQsEvent", MotionEvent.class, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + if ((boolean) param.getResult() == false) { + MotionEvent event = (MotionEvent) param.args[0]; + ViewGroup view = (ViewGroup) getObjectField(param.thisObject, "mView"); + int state = (int) getObjectField(param.thisObject, "mBarState"); + float w = view.getMeasuredWidth(); + float x = event.getX(); + + param.setResult(x > 3.f * w / 4.f && state == 0); + } + } + }); + } + } + } +} diff --git a/app/src/main/res/xml/pref_tricks.xml b/app/src/main/res/xml/pref_tricks.xml index dbfe7fc..5eea4f5 100644 --- a/app/src/main/res/xml/pref_tricks.xml +++ b/app/src/main/res/xml/pref_tricks.xml @@ -27,7 +27,7 @@ android:defaultValue="false" />