package de.theknut.xposedgelsettings.hooks.gestures; import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.TabHost; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; import de.theknut.xposedgelsettings.hooks.Common; import de.theknut.xposedgelsettings.hooks.ObfuscationHelper; import de.theknut.xposedgelsettings.hooks.ObfuscationHelper.Classes; import de.theknut.xposedgelsettings.hooks.ObfuscationHelper.Fields; import de.theknut.xposedgelsettings.hooks.ObfuscationHelper.Methods; import de.theknut.xposedgelsettings.hooks.PreferencesHelper; import de.theknut.xposedgelsettings.hooks.appdrawer.tabsandfolders.TabHelperL; import de.theknut.xposedgelsettings.hooks.appdrawer.tabsandfolders.TabHelperM; import de.theknut.xposedgelsettings.hooks.general.ContextMenu; import static de.robv.android.xposed.XposedHelpers.callMethod; import static de.robv.android.xposed.XposedHelpers.callStaticMethod; 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 GestureHooks extends GestureHelper { static boolean autoHideAppDock = PreferencesHelper.hideAppDock && PreferencesHelper.autoHideAppDock; static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); static ScheduledFuture<?> delayedTask = null; static long lastTouchTime = 0; static long currTouchTime = 0; static Object lastTouchView = null; static boolean isScheduledOrRunning = false; public static void initAllHooks(LoadPackageParam lpparam) { if (PreferencesHelper.appdockSettingsSwitch && autoHideAppDock) { XposedBridge.hookAllMethods(Classes.Launcher, "showHotseat", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.setResult(null); } }); XposedBridge.hookAllMethods(Classes.Launcher, "onTransitionPrepare", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (DEBUG) log("GestureHooks: onTransitionPrepare"); hideAppdock(0); } }); XposedBridge.hookAllMethods(Classes.Launcher, "onResume", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (DEBUG) log("GestureHooks: onResume"); hideAppdock(FORCEHIDE); } }); XC_MethodHook hideAppsCustomizeHelper = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (DEBUG) log("GestureHooks: lHideAppsCustomizeHelper"); hideAppdock(FORCEHIDE); } }; if (Common.PACKAGE_OBFUSCATED) { if (Common.GNL_VERSION >= ObfuscationHelper.GNL_5_3_23) { findAndHookMethod(Classes.Launcher, Methods.lShowWorkspace, Integer.TYPE, boolean.class, Runnable.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (DEBUG) log("GestureHooks: lHideAppsCustomizeHelper"); if ((Integer) param.args[0] == -1) { hideAppdock(FORCEHIDE); } } }); } else if (Common.GNL_VERSION >= ObfuscationHelper.GNL_4_2_16) { findAndHookMethod(Classes.Launcher, Methods.lHideAppsCustomizeHelper, Classes.WorkspaceState, boolean.class, boolean.class, Runnable.class, hideAppsCustomizeHelper); } else { findAndHookMethod(Classes.Launcher, Methods.lHideAppsCustomizeHelper, Classes.WorkspaceState, boolean.class, Runnable.class, hideAppsCustomizeHelper); } } else { XposedBridge.hookAllMethods(Classes.Launcher, Methods.lHideAppsCustomizeHelper, hideAppsCustomizeHelper); } XposedBridge.hookAllMethods(Classes.Workspace, "onWindowVisibilityChanged", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (DEBUG) log("GestureHooks: onWindowVisibilityChanged"); try { if (mHotseat.getAlpha() != 1.0f) { if (autoHideAppDock) { if (DEBUG) log("GestureHooks: onWindowVisibilityChanged autoHideAppDock"); hideAppdock(0); } else { if (DEBUG) log("GestureHooks: onWindowVisibilityChanged !autoHideAppDock"); hideAppdock(0); showAppdock(0); } } } catch (Exception ex) { log(ex.getMessage()); } } }); XposedBridge.hookAllMethods(Classes.Workspace, "onRequestFocusInDescendants", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (DEBUG) log("GestureHooks: onRequestFocusInDescendants"); try { if (mHotseat.getAlpha() == 1.0f) { if (autoHideAppDock) { if (DEBUG) log("GestureHooks: onRequestFocusInDescendants autoHideAppDock"); hideAppdock(animateDuration); } else { if (DEBUG) log("GestureHooks: onRequestFocusInDescendants !autoHideAppDock"); hideAppdock(0); showAppdock(0); } } } catch (Exception ex) { log(ex.getMessage()); } } }); } XposedBridge.hookAllMethods(Classes.DragLayer, "onInterceptTouchEvent", new XC_MethodHook() { boolean gnow = true; float downY = 0, downX = 0; @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (Common.FOLDER_GESTURE_ACTIVE || ((Boolean) callMethod(Common.LAUNCHER_INSTANCE, Methods.lIsAllAppsVisible)) || !getObjectField(Common.WORKSPACE_INSTANCE, Fields.wState).toString().equals("NORMAL")) { return; } if (wm == null) { init(); gnow = (Boolean) callMethod(Common.LAUNCHER_INSTANCE, Methods.lHasCustomContentToLeft); } final int currentPage = getIntField(Common.WORKSPACE_INSTANCE, Fields.pvCurrentPage); if (currentPage == 0 && gnow) return; MotionEvent ev = (MotionEvent) param.args[0]; int rotation = Common.LAUNCHER_CONTEXT.getResources().getConfiguration().orientation; switch (rotation) { case Configuration.ORIENTATION_PORTRAIT: if (isLandscape) { if (PreferencesHelper.appdockSettingsSwitch && PreferencesHelper.hideAppDock) { hideAppdock(0); } init(); } isLandscape = false; break; case Configuration.ORIENTATION_LANDSCAPE: if (!isLandscape) { if (PreferencesHelper.appdockSettingsSwitch && PreferencesHelper.hideAppDock) { hideAppdock(0); } init(); } isLandscape = true; break; default: break; } switch (ev.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: downY = ev.getRawY(); downX = ev.getRawX(); break; case MotionEvent.ACTION_UP: mHotseat = (View) getObjectField(Common.LAUNCHER_INSTANCE, Fields.lHotseat); if (PreferencesHelper.appdockSettingsSwitch && PreferencesHelper.hideAppDock) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHotseat.getLayoutParams(); if (mHotseat.getAlpha() == 1.0f && (lp.width == 0 || lp.height == 0)) { mHotseat.setAlpha(0.0f); lp.width = lp.height = 0; mHotseat.setLayoutParams(lp); } else if (autoHideAppDock) { hideAppdock(animateDuration); } } // user probably switched pages if (getBooleanField(Common.WORKSPACE_INSTANCE, Fields.pvIsPageMoving)) { return; } switch (identifyGesture(ev.getRawX(), ev.getRawY(), downX, downY)) { case DOWN_LEFT: handleGesture(getGestureKey(Gestures.DOWN_LEFT), PreferencesHelper.gesture_one_down_left); break; case DOWN_MIDDLE: handleGesture(getGestureKey(Gestures.DOWN_MIDDLE), PreferencesHelper.gesture_one_down_middle); break; case DOWN_RIGHT: handleGesture(getGestureKey(Gestures.DOWN_RIGHT), PreferencesHelper.gesture_one_down_right); break; case UP_LEFT: handleGesture(getGestureKey(Gestures.UP_LEFT), PreferencesHelper.gesture_one_up_left); break; case UP_MIDDLE: handleGesture(getGestureKey(Gestures.UP_MIDDLE), PreferencesHelper.gesture_one_up_middle); break; case UP_RIGHT: handleGesture(getGestureKey(Gestures.UP_RIGHT), PreferencesHelper.gesture_one_up_right); break; default: break; } break; case MotionEvent.ACTION_MOVE: break; default: break; } } }); XC_MethodHook gestureHook = new XC_MethodHook() { void init() throws IOException { wm = (WindowManager) Common.LAUNCHER_CONTEXT.getSystemService(Context.WINDOW_SERVICE); display = wm.getDefaultDisplay(); size = new Point(); display.getSize(size); width = size.x; height = size.y; } float downY, downX; boolean isDown = false; @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (!(Boolean) callMethod(Common.LAUNCHER_INSTANCE, Methods.lIsAllAppsVisible)) return; if (wm == null) init(); MotionEvent ev = (MotionEvent) param.args[0]; int rotation = Common.LAUNCHER_CONTEXT.getResources().getConfiguration().orientation; switch (rotation) { case Configuration.ORIENTATION_PORTRAIT: if (isLandscape) { init(); } isLandscape = false; break; case Configuration.ORIENTATION_LANDSCAPE: if (!isLandscape) { init(); } isLandscape = true; break; default: break; } switch (ev.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: if (DEBUG) log("DOWN: " + ev.getRawX()); downY = ev.getRawY(); downX = ev.getRawX(); isDown = true; Common.APP_DRAWER_PAGE_SWITCHED = false; if (ContextMenu.isOpen()) { ContextMenu.closeAndRemove(); } break; case MotionEvent.ACTION_MOVE: if (DEBUG) log("MOVE: " + ev.getRawX()); if (!isDown) { downY = ev.getRawY(); downX = ev.getRawX(); } if (Common.IS_M_GNL) { if (ev.getRawX() != downX && Math.abs(ev.getRawY() - downY) < gestureDistance) { if (ev.getRawX() - downX > gestureDistance) { TabHelperM.getInstance().setPreviousTab(); isDown = false; } else if ((ev.getRawX() - downX) * -1 > gestureDistance) { TabHelperM.getInstance().setNextTab(); isDown = false; } } return; } break; case MotionEvent.ACTION_UP: if (DEBUG) log("UP: " + ev.getRawX()); if (!isDown) return; isDown = false; if (Common.APP_DRAWER_PAGE_SWITCHED) { callMethod(param.thisObject, Methods.wSnapToPage, 0); param.setResult(false); } if (!PreferencesHelper.gesture_appdrawer) return; if (System.currentTimeMillis() - lastTouchTime < 400) return; lastTouchTime = System.currentTimeMillis(); if (Common.IS_M_GNL) { if (ev.getRawX() != downX && Math.abs(ev.getRawY() - downY) < gestureDistance) { if (ev.getRawX() - downX > gestureDistance) { TabHelperM.getInstance().setNextTab(); } else if ((ev.getRawX() - downX) * -1 > gestureDistance) { TabHelperM.getInstance().setPreviousTab(); } } return; } else { // user probably switched pages if (getBooleanField(getObjectField(Common.LAUNCHER_INSTANCE, Fields.lAppsCustomizeTabHost), Fields.acthInTransition) || Math.abs(downX - ev.getRawX()) > GestureHelper.gestureDistance) { return; } } if ((ev.getRawY() - downY) > gestureDistance) { callMethod(Common.LAUNCHER_INSTANCE, Methods.lShowWorkspace, true, null); } else if ((ev.getRawY() - downY) < -gestureDistance) { if (Common.IS_KK_TREBUCHET) { Toast.makeText(Common.LAUNCHER_CONTEXT, "XGELS: Unfortunately swipe up to toggle apps/widgets doesn't work on Trebuchet", Toast.LENGTH_LONG).show(); return; } if (!getBooleanField(getObjectField(Common.LAUNCHER_INSTANCE, Fields.lAppsCustomizeTabHost), Fields.acthInTransition)) { if (Common.IS_PRE_GNL_4) { TabHost tabhost = (TabHost) getObjectField(Common.LAUNCHER_INSTANCE, Fields.lAppsCustomizeTabHost); int tabIdx = tabhost.getCurrentTab() + 1; tabhost.setCurrentTab(tabIdx == tabhost.getTabWidget().getTabCount() ? 0 : tabIdx); } else { if (PreferencesHelper.enableAppDrawerTabs) { TabHelperL.getInstance().setNextTab(); } else { Object contentType; if (getObjectField(Common.APP_DRAWER_INSTANCE, Fields.acpvContentType).toString().equals("Widgets")) { contentType = callStaticMethod(Classes.AppsCustomizeTabHost, Methods.acthGetContentTypeForTabTag, "APPS"); } else { contentType = callStaticMethod(Classes.AppsCustomizeTabHost, Methods.acthGetContentTypeForTabTag, "WIDGETS"); } callMethod(Common.APP_DRAWER_INSTANCE, Methods.acpvSetContentType, contentType); callMethod(Common.APP_DRAWER_INSTANCE, Methods.acpvSyncPages); callMethod(Common.APP_DRAWER_INSTANCE, Methods.acpvInvalidatePageData, 0, false); } } } } break; default: break; } } }; if (Common.PACKAGE_OBFUSCATED && Common.GNL_VERSION >= ObfuscationHelper.GNL_4_1_21 && Common.GNL_VERSION < ObfuscationHelper.GNL_5_3_23) { method = XposedHelpers.findMethodBestMatch(Classes.Launcher, "a", boolean.class, Classes.AppsCustomizeContentType, boolean.class); } XposedBridge.hookAllMethods(Classes.PagedView, "onTouchEvent", gestureHook); XposedBridge.hookAllMethods(Classes.PagedView, "onInterceptTouchEvent", gestureHook); if (Common.IS_M_GNL) { XposedBridge.hookAllMethods(Classes.DragLayer, "onInterceptTouchEvent", gestureHook); } if (!PreferencesHelper.gesture_double_tap.equals("NONE")) { XposedBridge.hookAllMethods(Classes.Launcher, "onClick", new XC_MethodHook() { // http://androidxref.com/4.4.2_r1/xref/packages/apps/Launcher3/src/com/android/launcher3/LauncherSettings.java // https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-11.0/src/com/android/launcher3/LauncherSettings.java final int ITEM_TYPE_ALLAPPS = 5; final int ITEM_TYPE_FOLDER = 2; Runnable r = new Runnable() { @Override public void run() { if (DEBUG) log("Doubletap: " + currTouchTime + " " + lastTouchTime + " " + (currTouchTime == lastTouchTime) + " " + (currTouchTime - lastTouchTime)); if (currTouchTime == lastTouchTime) { callMethod(Common.LAUNCHER_INSTANCE, "onClick", lastTouchView); } else if ((currTouchTime - lastTouchTime) < 400) { handleGesture(getGestureKey(Gestures.DOUBLE_TAP), PreferencesHelper.gesture_double_tap); } } }; @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { currTouchTime = System.currentTimeMillis(); if (getObjectField(Common.WORKSPACE_INSTANCE, Fields.wState).toString().equals("NORMAL")) { View view = (View) param.args[0]; Object tag = view.getTag(); if (PreferencesHelper.gesture_double_tap_only_on_wallpaper && !view.getClass().equals(Classes.CellLayout)) { return; } else { if (view.getClass().equals(Classes.FolderIcon)) { return; } else if (!view.getClass().equals(Classes.CellLayout) && tag != null) { int itemType = getIntField(tag, Fields.iiItemType); if (itemType == ITEM_TYPE_ALLAPPS || itemType == ITEM_TYPE_FOLDER) { return; } } else if (view instanceof TextView) { // thats the all apps button // we don't want to do anthing when pressing this button return; } } if (isScheduledOrRunning) { isScheduledOrRunning = false; if (DEBUG) log("Doubletap: isScheduledOrRunning"); if (delayedTask.getDelay(TimeUnit.MILLISECONDS) > 0) { if (DEBUG) log("Doubletap: ignore tap"); param.setResult(null); } } else { if (DEBUG) log("!isScheduledOrRunning"); if (executor.getQueue().size() == 0) { if (DEBUG) log("Doubletap: Schedule double tap action"); lastTouchTime = currTouchTime; lastTouchView = view; delayedTask = executor.schedule(r, 400, TimeUnit.MILLISECONDS); isScheduledOrRunning = true; param.setResult(null); } } } } }); } } }