package tk.wasdennnoch.androidn_ify; import android.os.Build; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.util.Slog; import com.android.internal.os.BatteryStatsImpl; import de.robv.android.xposed.IXposedHookInitPackageResources; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.IXposedHookZygoteInit; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XSharedPreferences; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_LoadPackage; import tk.wasdennnoch.androidn_ify.android.AndroidHooks; import tk.wasdennnoch.androidn_ify.google.AssistantHooks; import tk.wasdennnoch.androidn_ify.phone.emergency.EmergencyHooks; import tk.wasdennnoch.androidn_ify.settings.SettingsHooks; import tk.wasdennnoch.androidn_ify.systemui.SystemUIHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.NotificationHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.NotificationPanelHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.StatusBarHeaderHooks; import tk.wasdennnoch.androidn_ify.systemui.notifications.stack.StackScrollAlgorithmHooks; import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.misc.LiveDisplayObserver; import tk.wasdennnoch.androidn_ify.systemui.recents.doubletap.DoubleTapHwKeys; import tk.wasdennnoch.androidn_ify.systemui.recents.doubletap.DoubleTapSwKeys; import tk.wasdennnoch.androidn_ify.systemui.recents.navigate.RecentsNavigation; import tk.wasdennnoch.androidn_ify.systemui.recents.stack.RecentsStackHooks; import tk.wasdennnoch.androidn_ify.systemui.screenshot.ScreenshotHooks; import tk.wasdennnoch.androidn_ify.utils.ConfigUtils; import tk.wasdennnoch.androidn_ify.utils.PermissionGranter; import tk.wasdennnoch.androidn_ify.utils.RomUtils; @SuppressWarnings("WeakerAccess") public class XposedHook implements IXposedHookLoadPackage, IXposedHookZygoteInit, IXposedHookInitPackageResources { private static final String TAG = "XposedHook"; private static String LOG_FORMAT = "[Android N-ify] %1$s %2$s: %3$s"; public static final String PACKAGE_ANDROID = "android"; public static final String PACKAGE_SYSTEMUI = "com.android.systemui"; public static final String PACKAGE_SETTINGS = "com.android.settings"; public static final String PACKAGE_PHONE = "com.android.phone"; public static final String PACKAGE_GOOGLE = "com.google.android.googlequicksearchbox"; public static final String PACKAGE_OWN = "tk.wasdennnoch.androidn_ify"; public static final String SETTINGS_OWN = PACKAGE_OWN + ".ui.SettingsActivity"; public static final String ACTION_MARK_UNSTABLE = "tk.wasdennnoch.androidn_ify.action.ACTION_MARK_UNSTABLE"; public static boolean debug = false; private static String sModulePath; private static XSharedPreferences sPrefs; private static boolean pro = false; public static void markUnstable() { LOG_FORMAT = "[Android N-ify] [UNSTABLE] %1$s %2$s: %3$s"; logE(TAG, "MARK_UNSTABLE", null); } public static void logE(String tag, String msg, Throwable t) { XposedBridge.log(String.format(LOG_FORMAT, "[ERROR]", tag, msg)); if (t != null) XposedBridge.log(t); } public static void logW(String tag, String msg) { XposedBridge.log(String.format(LOG_FORMAT, "[WARNING]", tag, msg)); } public static void logI(String tag, String msg) { XposedBridge.log(String.format(LOG_FORMAT, "[INFO]", tag, msg)); } public static void logD(String tag, String msg) { if (debug) XposedBridge.log(String.format(LOG_FORMAT, "[DEBUG]", tag, msg)); } @Override public void initZygote(StartupParam startupParam) throws Throwable { sModulePath = startupParam.modulePath; sPrefs = new XSharedPreferences(PACKAGE_OWN); if (sPrefs.getBoolean("pro", false)) { pro = true; logW(TAG, "Pro version detected. Aborting."); return; } RomUtils.init(sPrefs); logI(TAG, "Version " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")"); //noinspection PointlessBooleanExpression if (BuildConfig.OFFICIAL_BUILD) { logI(TAG, "Official Build; Release: " + !BuildConfig.DEBUG + " (" + BuildConfig.BUILD_TYPE + ")"); if (BuildConfig.DEBUG) logI(TAG, "Build Time: " + BuildConfig.BUILD_TIME); } else if (BuildConfig.AUTOMATED_BUILD){ logI(TAG, "Automated Build; Version: " + BuildConfig.BUILD_NUMBER); logI(TAG, "Build Time: " + BuildConfig.BUILD_TIME); logI(TAG, "Git SHA: " + BuildConfig.GIT_COMMIT); logI(TAG, "Git info: \n | " + BuildConfig.GIT_INFO); } else { logI(TAG, "3rd Party Build; Version: " + BuildConfig.BUILD_NUMBER); logI(TAG, "Git SHA: " + BuildConfig.GIT_COMMIT); logI(TAG, "Git info: \n | " + BuildConfig.GIT_INFO); } if (ConfigUtils.isExperimental(sPrefs)) { logI(TAG, "Experimental features enabled"); } logI(TAG, "---- Device info ----"); logI(TAG, "SDK Version: " + Build.VERSION.SDK_INT); logI(TAG, "Build ID: " + Build.DISPLAY); logI(TAG, "Manufacturer: " + Build.MANUFACTURER); logI(TAG, "Brand: " + Build.BRAND); logI(TAG, "Model: " + Build.MODEL); if (!sPrefs.getBoolean("can_read_prefs", false)) { // With SELinux enforcing, it might happen that we don't have access // to the prefs file. Test this by reading a test key that should be // set to true. If it is false, we either can't read the file or the // user has never opened the preference screen before. logW(TAG, "Can't read prefs file, default values will be applied in hooks!"); } debug = sPrefs.getBoolean("debug_log", false); } @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (pro || sPrefs.getBoolean("pro", false)) { return; } switch (lpparam.packageName) { case PACKAGE_SETTINGS: SettingsHooks.hook(lpparam.classLoader); break; case PACKAGE_SYSTEMUI: SystemUIHooks.hookSystemUI(lpparam.classLoader); ScreenshotHooks.hook(lpparam.classLoader); StatusBarHeaderHooks.hook(lpparam.classLoader); NotificationPanelHooks.hook(lpparam.classLoader); StackScrollAlgorithmHooks.hook(lpparam.classLoader); NotificationHooks.hookSystemUI(lpparam.classLoader); RecentsStackHooks.hookSystemUI(lpparam.classLoader); RecentsNavigation.hookSystemUI(lpparam.classLoader); DoubleTapSwKeys.hook(lpparam.classLoader); break; case PACKAGE_ANDROID: AndroidHooks.hook(lpparam.classLoader); DoubleTapHwKeys.hook(lpparam.classLoader); LiveDisplayObserver.hook(lpparam.classLoader); PermissionGranter.initAndroid(lpparam.classLoader); if (!ConfigUtils.M) hookBatteryStats(lpparam.classLoader); break; case PACKAGE_PHONE: EmergencyHooks.hook(lpparam.classLoader); break; case PACKAGE_OWN: XposedHelpers.findAndHookMethod(SETTINGS_OWN, lpparam.classLoader, "isActivated", XC_MethodReplacement.returnConstant(true)); if (!sPrefs.getBoolean("can_read_prefs", false)) XposedHelpers.findAndHookMethod(SETTINGS_OWN, lpparam.classLoader, "isPrefsFileReadable", XC_MethodReplacement.returnConstant(false)); break; case PACKAGE_GOOGLE: if (ConfigUtils.M && ConfigUtils.assistant().enable_assistant) { AssistantHooks.hook(lpparam.classLoader); } break; } // Has to be hooked in every app as every app creates own instances of the Notification.Builder NotificationHooks.hook(lpparam.classLoader); // CM Custom Tile API; May implement it later, disabled for now if (ConfigUtils.qs().enable_qs_editor) { try { Class<?> classCMStatusBarManager = XposedHelpers.findClass("cyanogenmod.app.CMStatusBarManager", lpparam.classLoader); XposedBridge.hookAllMethods(classCMStatusBarManager, "publishTile", XC_MethodReplacement.DO_NOTHING); XposedBridge.hookAllMethods(classCMStatusBarManager, "publishTileAsUser", XC_MethodReplacement.DO_NOTHING); } catch (Throwable ignore) { } } } private static void hookBatteryStats(ClassLoader classLoader) { XposedHelpers.findAndHookMethod("com.android.server.am.BatteryStatsService", classLoader, "getStatisticsStream", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Parcel out = Parcel.obtain(); BatteryStatsImpl mStats = (BatteryStatsImpl) XposedHelpers.getObjectField(param.thisObject, "mStats"); mStats.writeToParcel(out, 0); byte[] data = out.marshall(); out.recycle(); try { param.setResult(XposedHelpers.callStaticMethod(ParcelFileDescriptor.class, "fromData", data, "stats")); } catch (Exception e) { Slog.w(TAG, "Unable to create shared memory", e); param.setResult(null); } } }); } @Override public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) throws Throwable { if (pro || sPrefs.getBoolean("pro", false)) { return; } switch (resparam.packageName) { case PACKAGE_SETTINGS: SettingsHooks.hookRes(resparam, sModulePath); break; case PACKAGE_SYSTEMUI: NotificationHooks.hookResSystemui(resparam, sModulePath); StatusBarHeaderHooks.hookResSystemui(resparam, sModulePath); RecentsStackHooks.hookResSystemui(resparam, sModulePath); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { RecentsNavigation.hookResSystemui(resparam); } SystemUIHooks.hookResSystemUI(resparam, sModulePath); break; } // Has to be hooked in every app because every hook only applies to the current process ConfigUtils.notifications().loadBlacklistedApps(); if (!ConfigUtils.notifications().blacklistedApps.contains(resparam.packageName)) { NotificationHooks.hookResAndroid(resparam); } } public static String getModulePath() { return sModulePath; } }