package tk.wasdennnoch.androidn_ify.systemui.recents.doubletap; import android.annotation.SuppressLint; import android.content.Context; import android.hardware.input.InputManager; import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.WindowManagerPolicy; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; import tk.wasdennnoch.androidn_ify.XposedHook; import tk.wasdennnoch.androidn_ify.misc.SafeRunnable; import tk.wasdennnoch.androidn_ify.utils.ConfigUtils; @SuppressLint("StaticFieldLeak") public class DoubleTapHwKeys extends DoubleTapBase { private static final String TAG = "DoubleTapHwKeys"; private static final String CLASS_PHONE_WINDOW_MANAGER = ConfigUtils.M ? "com.android.server.policy.PhoneWindowManager" : "com.android.internal.policy.impl.PhoneWindowManager"; private static Object mPhoneWindowManager; private static Context mContext; private static Handler mHandler; private static boolean mWasPressed = false; private static final Runnable resetPressedState = new SafeRunnable() { @Override public void runSafe() { XposedHook.logD(TAG, "Double tap timed out after " + mDoubletapSpeed + "ms, injecting original KeyEvent"); mWasPressed = false; injectKey(KeyEvent.KEYCODE_APP_SWITCH); } }; private static final XC_MethodHook initHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { mPhoneWindowManager = param.thisObject; mContext = (Context) XposedHelpers.getObjectField(mPhoneWindowManager, "mContext"); mHandler = (Handler) XposedHelpers.getObjectField(mPhoneWindowManager, "mHandler"); // No need to unregister this because the system process will last "forever" registerReceiver(mContext, true); } }; private static final XC_MethodHook interceptKeyBeforeDispatchingHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if ((boolean) XposedHelpers.callMethod(mPhoneWindowManager, "keyguardOn")) return; KeyEvent event = (KeyEvent) param.args[1]; int keyCode = event.getKeyCode(); boolean down = event.getAction() == KeyEvent.ACTION_DOWN; boolean isFromSystem = (event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0; if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) { XposedHook.logD(TAG, "interceptKeyBeforeDispatching: keyCode= " + keyCode + "; keyCodeString=" + KeyEvent.keyCodeToString(keyCode) + "; down= " + down + "; repeatCount= " + event.getRepeatCount() + "; isInjected= " + (((Integer) param.args[2] & 0x01000000) != 0) + "; fromSystem= " + isFromSystem); if (isFromSystem && !isTaskLocked(mContext) && down && event.getRepeatCount() == 0) { if (!mWasPressed) { XposedHook.logD(TAG, "HW recents clicked"); mWasPressed = true; mHandler.postDelayed(resetPressedState, mDoubletapSpeed); } else { XposedHook.logD(TAG, "Double tap detected"); mHandler.removeCallbacks(resetPressedState); mWasPressed = false; switchToLastApp(mContext, mHandler); } param.setResult(-1); } } } }; public static void hook(ClassLoader classLoader) { try { if (Build.VERSION.SDK_INT >= 23 && !ConfigUtils.recents().alternative_method) return; Class<?> classPhoneWindowManager = XposedHelpers.findClass(CLASS_PHONE_WINDOW_MANAGER, classLoader); XposedHelpers.findAndHookMethod(classPhoneWindowManager, "init", Context.class, IWindowManager.class, WindowManagerPolicy.WindowManagerFuncs.class, initHook); loadPrefDoubleTapSpeed(); if (ConfigUtils.recents().double_tap) { XposedHelpers.findAndHookMethod(classPhoneWindowManager, "interceptKeyBeforeDispatching", WindowManagerPolicy.WindowState.class, KeyEvent.class, int.class, interceptKeyBeforeDispatchingHook); } } catch (Throwable t) { XposedHook.logE(TAG, "Error in hook", t); } } private static void injectKey(final int keyCode) { mHandler.post(new Runnable() { @Override public void run() { try { final long eventTime = SystemClock.uptimeMillis(); final InputManager inputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE); XposedHelpers.callMethod(inputManager, "injectInputEvent", new KeyEvent(eventTime - 50, eventTime - 50, KeyEvent.ACTION_DOWN, keyCode, 0), 0); XposedHelpers.callMethod(inputManager, "injectInputEvent", new KeyEvent(eventTime - 50, eventTime - 25, KeyEvent.ACTION_UP, keyCode, 0), 0); } catch (Throwable t) { XposedHook.logE(TAG, "injectKey failed", t); } } }); } }