package tk.wasdennnoch.androidn_ify.systemui.statusbar; import android.content.Context; import android.content.res.XModuleResources; import android.os.Build; import android.view.View; import java.lang.reflect.Array; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import tk.wasdennnoch.androidn_ify.XposedHook; import tk.wasdennnoch.androidn_ify.systemui.SystemUIHooks; import tk.wasdennnoch.androidn_ify.utils.ConfigUtils; import static tk.wasdennnoch.androidn_ify.XposedHook.PACKAGE_SYSTEMUI; public class StatusBarHooks { private static final String TAG = "StatusBarHooks"; private static final String CLASS_SIGNAL_CONTROLLER = "com.android.systemui.statusbar.policy.SignalController"; private static final String CLASS_SIGNAL_CLUSTER_VIEW = "com.android.systemui.statusbar.SignalClusterView"; private static final String CLASS_MOBILE_SIGNAL_CONTROLLER = "com.android.systemui.statusbar.policy.MobileSignalController"; private static final String CLASS_MOBILE_DATA_CONTROLLER_51 = "com.android.systemui.statusbar.policy.MobileDataControllerImpl"; private static final String CLASS_MOBILE_DATA_CONTROLLER_51_MOTO = "com.android.systemui.statusbar.policy.MotorolaMobileDataControllerImpl"; // yeah thx Motorola private static final String CLASS_MOBILE_DATA_CONTROLLER_50 = "com.android.systemui.statusbar.policy.MobileDataController"; private static final String CLASS_NAVIGATION_BAR_TRANSITIONS = "com.android.systemui.statusbar.phone.NavigationBarTransitions"; public static final int LIGHTS_IN_DURATION = 250; public static final int LIGHTS_OUT_DURATION = 750; ClassLoader mClassLoader; Class<?> mSignalClusterClass; private Class<?> mMobileDataControllerClass; private boolean mLastDataDisabled = false; boolean mDataDisabled = false; private Object mPhone; public static StatusBarHooks create(ClassLoader classLoader) { if (ConfigUtils.M) { return new StatusBarHooks(classLoader); } else { return new LollipopStatusBarHooks(classLoader); } } StatusBarHooks(ClassLoader classLoader) { try { mClassLoader = classLoader; mSignalClusterClass = XposedHelpers.findClass(CLASS_SIGNAL_CLUSTER_VIEW, mClassLoader); try { mMobileDataControllerClass = XposedHelpers.findClass(getMobileDataControllerClass(), mClassLoader); } catch (Throwable t) { // Motorola try { mMobileDataControllerClass = XposedHelpers.findClass(CLASS_MOBILE_DATA_CONTROLLER_51_MOTO, mClassLoader); } catch (Throwable t2) { // Xperia 5.1.1 mMobileDataControllerClass = XposedHelpers.findClass(CLASS_MOBILE_DATA_CONTROLLER_50, mClassLoader); } } if (ConfigUtils.M) hookIsDirty(); hookSetMobileDataIndicators(); hookUpdateTelephony(); hookSetMobileDataEnabled(); hookApplyLightsOut(); } catch (Throwable t) { XposedHook.logE(TAG, "Error in <init>", t); } } private void hookApplyLightsOut() { try { Class classNavigationBarTransitions = XposedHelpers.findClass(CLASS_NAVIGATION_BAR_TRANSITIONS, mClassLoader); XposedHelpers.findAndHookMethod(classNavigationBarTransitions, "applyLightsOut", boolean.class, boolean.class, boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { boolean lightsOut = (boolean) param.args[0], animate = (boolean) param.args[1], force = (boolean) param.args[2]; if (!force && lightsOut == XposedHelpers.getBooleanField(param.thisObject, "mLightsOut")) return null; XposedHelpers.setBooleanField(param.thisObject, "mLightsOut", lightsOut); View layout = (View) XposedHelpers.callMethod(XposedHelpers.getObjectField(param.thisObject, "mView"), "getCurrentView"); Context context = layout.getContext(); final View navButtons = layout.findViewById(context.getResources().getIdentifier("nav_buttons", "id", PACKAGE_SYSTEMUI)); // ok, everyone, stop it right there navButtons.animate().cancel(); final float navButtonsAlpha = lightsOut ? 0.5f : 1f; if (!animate) { navButtons.setAlpha(navButtonsAlpha); } else { final int duration = lightsOut ? LIGHTS_OUT_DURATION : LIGHTS_IN_DURATION; navButtons.animate() .alpha(navButtonsAlpha) .setDuration(duration) .start(); } return null; } }); } catch (Throwable t) { XposedHook.logE(TAG, "Can't hook applyLightsOut", t); } } private void hookUpdateTelephony() { // Hook if possible, to reflect changes faster when toggling mobile data on try { Class classMobileSignalController = XposedHelpers.findClass(CLASS_MOBILE_SIGNAL_CONTROLLER, mClassLoader); XposedHelpers.findAndHookMethod(classMobileSignalController, "updateTelephony", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { mDataDisabled = isDataDisabled(param.thisObject); } }); } catch (Throwable ignore) { hookMobileDataController(); } } private boolean isDataDisabled(Object mobileSignalController) { try { if (mPhone == null) mPhone = XposedHelpers.getObjectField(mobileSignalController, "mPhone"); Object mSubscriptionInfo = XposedHelpers.getObjectField(mobileSignalController, "mSubscriptionInfo"); int subscriptionId = (int) XposedHelpers.callMethod(mSubscriptionInfo, "getSubscriptionId"); return !((boolean) XposedHelpers.callMethod(mPhone, "getDataEnabled", subscriptionId)); } catch (Throwable t) { return false; } } private void hookMobileDataController() { XposedHelpers.findAndHookConstructor(mMobileDataControllerClass, Context.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { boolean isDataEnabled; try { isDataEnabled = (boolean) XposedHelpers.callMethod(param.thisObject, "isMobileDataEnabled"); } catch (Throwable t) { // TODO implement better dual-sim handling? isDataEnabled = (boolean) XposedHelpers.callMethod(param.thisObject, "isMobileDataEnabled", XposedHelpers.callMethod( XposedHelpers.getObjectField(param.thisObject, "mTelephonyManager"), "getSubId")); } mDataDisabled = !isDataEnabled; } }); } private void hookIsDirty() { Class<?> classSignalController = XposedHelpers.findClass(CLASS_SIGNAL_CONTROLLER, mClassLoader); XposedHelpers.findAndHookMethod(classSignalController, "isDirty", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (mLastDataDisabled != mDataDisabled) { mLastDataDisabled = mDataDisabled; param.setResult(true); } } }); } private XC_MethodHook setMobileDataEnabledHook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { mDataDisabled = !(boolean) param.args[0]; } }; private XC_MethodHook setMobileDataEnabledHook2 = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { mDataDisabled = !(boolean) param.args[1]; } }; private void hookSetMobileDataEnabled() { try { XposedHelpers.findAndHookMethod(mMobileDataControllerClass, "setMobileDataEnabled", boolean.class, setMobileDataEnabledHook); } catch (Throwable t) { // multi-sim // TODO implement better dual-sim handling XposedHelpers.findAndHookMethod(mMobileDataControllerClass, "setMobileDataEnabled", int.class, boolean.class, setMobileDataEnabledHook2); } } protected void hookSetMobileDataIndicators() { Class<?> classCallbackHandler = XposedHelpers.findClass("com.android.systemui.statusbar.policy.CallbackHandler", mClassLoader); // QS cellular tile isn't a SignalClusterView XposedBridge.hookAllMethods(classCallbackHandler, "setMobileDataIndicators", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (mDataDisabled && ConfigUtils.notifications().enable_data_disabled_indicator) { int typeIcon = 2; int qsTypeIcon = 3; int isWide = 8; Class mediaTekIconIdWrapperClass = null; try { mediaTekIconIdWrapperClass = XposedHelpers.findClass("com.mediatek.systemui.ext.IconIdWrapper", mClassLoader); XposedHook.logI(TAG, "Identified MediaTek device"); } catch (XposedHelpers.ClassNotFoundError ignore) { } if (mediaTekIconIdWrapperClass != null && mediaTekIconIdWrapperClass.isInstance(param.args[typeIcon])) { // MediaTek devices wrap the id Object iconIdWrapperArray = Array.newInstance(mediaTekIconIdWrapperClass, 1); Array.set(iconIdWrapperArray, 0, XposedHelpers.newInstance(mediaTekIconIdWrapperClass, XModuleResources.createInstance(XposedHook.getModulePath(), null), SystemUIHooks.R_drawable_stat_sys_data_disabled)); param.args[typeIcon] = iconIdWrapperArray; Object iconIdWrapperArray2 = Array.newInstance(mediaTekIconIdWrapperClass, 1); Array.set(iconIdWrapperArray2, 0, XposedHelpers.newInstance(mediaTekIconIdWrapperClass, XModuleResources.createInstance(XposedHook.getModulePath(), null), SystemUIHooks.R_drawable_ic_qs_data_disabled)); param.args[qsTypeIcon] = iconIdWrapperArray2; } else { param.args[typeIcon] = SystemUIHooks.R_drawable_stat_sys_data_disabled; param.args[qsTypeIcon] = SystemUIHooks.R_drawable_ic_qs_data_disabled; } if (param.args[isWide] instanceof Boolean) { param.args[isWide] = false; } else if (param.args[isWide] instanceof Boolean) { // Xperia put a load of ints in between param.args[12] = false; } else { XposedHook.logD(TAG, "setMobileDataIndicators: Didn't find isWide param"); } } } }); } private String getMobileDataControllerClass() { return Build.VERSION.SDK_INT >= 22 ? CLASS_MOBILE_DATA_CONTROLLER_51 : CLASS_MOBILE_DATA_CONTROLLER_50; } }