package org.exalm.tabletkat;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XModuleResources;
import android.content.res.XResources;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayInfo;
import org.exalm.tabletkat.launcher.LauncherMod;
import org.exalm.tabletkat.recent.TabletRecentsMod;
import org.exalm.tabletkat.settings.MultiPaneSettingsMod;
import org.exalm.tabletkat.statusbar.tablet.TabletStatusBarMod;
import java.lang.reflect.InvocationTargetException;
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.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
import static de.robv.android.xposed.XposedHelpers.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class TabletKatModule implements IXposedHookZygoteInit, IXposedHookLoadPackage, IXposedHookInitPackageResources {
public static final String TAG = "TabletKatModule";
public static final boolean DEBUG = true;
public static final String SYSTEMUI_PACKAGE = "com.android.systemui";
public static final String SETTINGS_PACKAGE = "com.android.settings";
private static TabletStatusBarMod statusBarMod;
public static TabletRecentsMod recentsMod;
private static IMod settingsMod;
private static LauncherMod launcherMod;
public static Class mActivityManagerNativeClass;
public static Class mBaseStatusBarClass;
public static Class mBaseStatusBarHClass;
public static Class mBatteryControllerClass;
public static Class mBatteryMeterViewClass;
public static Class mBluetoothControllerClass;
public static Class mBrightnessControllerClass;
public static Class mClockClass;
public static Class mComAndroidInternalRDrawableClass;
public static Class mComAndroidInternalRStringClass;
public static Class mComAndroidInternalRStyleClass;
public static Class mDateViewClass;
public static Class mDelegateViewHelperClass;
public static Class mExpandHelperClass;
public static Class mExpandHelperCallbackClass;
public static Class mGlowPadViewClass;
public static Class mKeyButtonViewClass;
public static Class mLocationControllerClass;
public static Class mNetworkControllerClass;
public static Class mNotificationDataEntryClass;
public static Class mNotificationRowLayoutClass;
public static Class mPhoneStatusBarClass;
public static Class mPhoneStatusBarPolicyClass;
public static Class mQuickSettingsClass;
public static Class mRecentTasksLoaderClass;
public static Class mStatusBarIconClass;
public static Class mStatusBarIconViewClass;
public static Class mStatusBarManagerClass;
public static Class mSystemUIClass;
public static Class mToggleSliderClass;
public static Class mTvStatusBarClass;
public static Class mWindowManagerGlobalClass;
public static Class mWindowManagerLayoutParamsClass;
public static final String ACTION_PREFERENCE_CHANGED = "org.exalm.tabletkat.PREFERENCE_CHANGED";
private static String MODULE_PATH = null;
private static XSharedPreferences pref;
private static boolean mIsTabletConfiguration = true;
private static Boolean mHasNavigationBar;
private static Object mSystemUI;
public static TabletKatModule self;
private static BroadcastReceiver mReceiver;
private static Configuration mConfiguration;
@Override
public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
self = this;
MODULE_PATH = startupParam.modulePath;
pref = new XSharedPreferences("org.exalm.tabletkat");
pref.makeWorldReadable();
Class c = findClass("com.android.internal.policy.impl.PhoneWindowManager", null);
final XModuleResources res2 = XModuleResources.createInstance(MODULE_PATH, null);
findAndHookMethod(c, "getNonDecorDisplayWidth", int.class, int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
setNavigationBarProperties(methodHookParam, shouldUseTabletUI(null));
if (shouldUseTabletUI(null)) {
int fullWidth = (Integer) methodHookParam.args[0];
return fullWidth;
}
return invokeOriginalMethod(methodHookParam);
}
});
findAndHookMethod(c, "getNonDecorDisplayHeight", int.class, int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
setNavigationBarProperties(methodHookParam, shouldUseTabletUI(null));
if (shouldUseTabletUI(null)){
int fullHeight = (Integer) methodHookParam.args[1];
int rotation = getIntField(methodHookParam.thisObject, "mSeascapeRotation");
int[] mNavigationBarHeightForRotation = (int[]) getObjectField(methodHookParam.thisObject, "mNavigationBarHeightForRotation");
return fullHeight - mNavigationBarHeightForRotation[rotation];
}
return invokeOriginalMethod(methodHookParam);
}
});
findAndHookMethod(c, "getConfigDisplayHeight", int.class, int.class, int.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
int fullWidth = (Integer) methodHookParam.args[0];
int fullHeight = (Integer) methodHookParam.args[1];
int rotation = (Integer) methodHookParam.args[2];
int base = (Integer) callMethod(methodHookParam.thisObject, "getNonDecorDisplayHeight", fullWidth, fullHeight, rotation);
if (shouldUseTabletUI(null)) {
return base;
}
return base + res2.getDimensionPixelSize(R.dimen.status_bar_height);
}
});
findAndHookMethod(c, "setInitialDisplaySize", Display.class, int.class, int.class, int.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Context mContext = (Context) getObjectField(param.thisObject, "mContext");
Display display = (Display) param.args[0];
if (mContext == null || display.getDisplayId() != Display.DEFAULT_DISPLAY) {
return;
}
setIntField(param.thisObject, "mStatusBarHeight", res2.getDimensionPixelSize(R.dimen.status_bar_height));
}
});
try {
XResources.setSystemWideReplacement("android", "layout", "status_bar_latest_event_ticker", res2.fwd(R.layout.status_bar_latest_event_ticker));
XResources.setSystemWideReplacement("android", "layout", "status_bar_latest_event_ticker_large_icon", res2.fwd(R.layout.status_bar_latest_event_ticker_large_icon));
}catch (Resources.NotFoundException e){}
XResources.setSystemWideReplacement("android", "dimen", "status_bar_height", new CustomDimenReplacement() {
@Override
protected float getValue() {
if (shouldUseTabletUI(null)) {
return 0;
}
return res2.getDimension(R.dimen.status_bar_height);
}
});
}
private void setNavigationBarProperties(XC_MethodHook.MethodHookParam param, boolean b) {
int width = (Integer) param.args[0];
int height = (Integer) param.args[1];
int shortSize = width;
if (width > height){
shortSize = height;
}
Display d = (Display) getObjectField(param.thisObject, "mDisplay");
if (d == null){
return;
}
DisplayInfo info = new DisplayInfo();
d.getDisplayInfo(info);
int density = info.logicalDensityDpi;
int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density;
setBooleanField(param.thisObject, "mNavigationBarCanMove", shortSizeDp < 600 && !b);
if (mHasNavigationBar == null) {
mHasNavigationBar = getBooleanField(param.thisObject, "mHasNavigationBar");
}
setBooleanField(param.thisObject, "mHasNavigationBar", b || mHasNavigationBar);
}
@Override
public void handleLoadPackage(LoadPackageParam loadPackageParam) throws Throwable {
tryHookSystemBarTint(loadPackageParam.classLoader);
if (loadPackageParam.packageName.equals(SETTINGS_PACKAGE)){
if (settingsMod == null){
settingsMod = new MultiPaneSettingsMod();
}
if (isModEnabled("settings")) {
settingsMod.addHooks(loadPackageParam.classLoader);
}
return;
}
if (LauncherMod.isSupported(loadPackageParam.packageName)) {
if (isModEnabled("launcher")) {
if (launcherMod == null || !loadPackageParam.packageName.equals(launcherMod.getPackage())) {
launcherMod = new LauncherMod();
}
launcherMod.addHooks(loadPackageParam.packageName, loadPackageParam.classLoader);
}
return;
}
if (!loadPackageParam.packageName.equals(SYSTEMUI_PACKAGE)){
return;
}
debug("Loaded SystemUI");
if (statusBarMod == null){
statusBarMod = new TabletStatusBarMod();
}
if (recentsMod == null){
recentsMod = new TabletRecentsMod();
}
mActivityManagerNativeClass = findClass("android.app.ActivityManagerNative", loadPackageParam.classLoader);
mBaseStatusBarClass = findClass("com.android.systemui.statusbar.BaseStatusBar", loadPackageParam.classLoader);
mBaseStatusBarHClass = findClass("com.android.systemui.statusbar.BaseStatusBar.H", loadPackageParam.classLoader);
mBatteryControllerClass = findClass("com.android.systemui.statusbar.policy.BatteryController", loadPackageParam.classLoader);
//Xperia custom battery meter
try{
// Class c = findClass("com.sonymobile.systemui.statusbar.BatteryImage", loadPackageParam.classLoader);
// mBatteryMeterViewClass = c;
mBatteryMeterViewClass = findClass("com.android.systemui.BatteryMeterView", loadPackageParam.classLoader);
}catch (Throwable t){
XposedBridge.log(t);
//Ok, it's not Xperia
mBatteryMeterViewClass = findClass("com.android.systemui.BatteryMeterView", loadPackageParam.classLoader);
}
mBluetoothControllerClass = findClass("com.android.systemui.statusbar.policy.BluetoothController", loadPackageParam.classLoader);
mBrightnessControllerClass = findClass("com.android.systemui.settings.BrightnessController", loadPackageParam.classLoader);
mClockClass = findClass("com.android.systemui.statusbar.policy.Clock", loadPackageParam.classLoader);
mComAndroidInternalRDrawableClass = findClass("com.android.internal.R.drawable", loadPackageParam.classLoader);
mComAndroidInternalRStringClass = findClass("com.android.internal.R.string", loadPackageParam.classLoader);
mComAndroidInternalRStyleClass = findClass("com.android.internal.R.style", loadPackageParam.classLoader);
mDateViewClass = findClass("com.android.systemui.statusbar.policy.DateView", loadPackageParam.classLoader);
mDelegateViewHelperClass = findClass("com.android.systemui.statusbar.DelegateViewHelper", loadPackageParam.classLoader);
mExpandHelperClass = findClass("com.android.systemui.ExpandHelper", loadPackageParam.classLoader);
mExpandHelperCallbackClass = findClass("com.android.systemui.ExpandHelper.Callback", loadPackageParam.classLoader);
mGlowPadViewClass = findClass("com.android.internal.widget.multiwaveview.GlowPadView", loadPackageParam.classLoader);
mKeyButtonViewClass = findClass("com.android.systemui.statusbar.policy.KeyButtonView", loadPackageParam.classLoader);
mLocationControllerClass = findClass("com.android.systemui.statusbar.policy.LocationController", loadPackageParam.classLoader);
mNetworkControllerClass = findClass("com.android.systemui.statusbar.policy.NetworkController", loadPackageParam.classLoader);
mNotificationDataEntryClass = findClass("com.android.systemui.statusbar.NotificationData.Entry", loadPackageParam.classLoader);
mNotificationRowLayoutClass = findClass("com.android.systemui.statusbar.policy.NotificationRowLayout", loadPackageParam.classLoader);
mPhoneStatusBarClass = findClass("com.android.systemui.statusbar.phone.PhoneStatusBar", loadPackageParam.classLoader);
mPhoneStatusBarPolicyClass = findClass("com.android.systemui.statusbar.phone.PhoneStatusBarPolicy", loadPackageParam.classLoader);
try {
mQuickSettingsClass = findClass("com.android.systemui.statusbar.phone.QuickSettings", loadPackageParam.classLoader);
} catch (Throwable t){
XposedBridge.log(t);
//CyanogenMod?
}
mRecentTasksLoaderClass = findClass("com.android.systemui.recent.RecentTasksLoader", loadPackageParam.classLoader);
mStatusBarIconClass = findClass("com.android.internal.statusbar.StatusBarIcon", loadPackageParam.classLoader);
mStatusBarIconViewClass = findClass("com.android.systemui.statusbar.StatusBarIconView", loadPackageParam.classLoader);
mStatusBarManagerClass = findClass("android.app.StatusBarManager", loadPackageParam.classLoader);
mSystemUIClass = findClass("com.android.systemui.SystemUI", loadPackageParam.classLoader);
mToggleSliderClass = findClass("com.android.systemui.settings.ToggleSlider", loadPackageParam.classLoader);
mTvStatusBarClass = findClass("com.android.systemui.statusbar.tv.TvStatusBar", loadPackageParam.classLoader);
mWindowManagerGlobalClass = findClass("android.view.WindowManagerGlobal", loadPackageParam.classLoader);
mWindowManagerLayoutParamsClass = findClass("android.view.WindowManager.LayoutParams", loadPackageParam.classLoader);
statusBarMod.addHooks(loadPackageParam.classLoader);
recentsMod.addHooks(loadPackageParam.classLoader);
final Class systemBarsClass = findClass("com.android.systemui.statusbar.SystemBars", loadPackageParam.classLoader);
findAndHookMethod(systemBarsClass, "createStatusBarFromConfig", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
Object self = methodHookParam.thisObject;
Context mContext = (Context) getObjectField(self, "mContext");
Object mComponents = getObjectField(self, "mComponents");
mSystemUI = self;
mConfiguration = mContext.getResources().getConfiguration();
refreshReceiver(mContext);
debug("createStatusBarFromConfig");
String clsName = "com.android.systemui.statusbar.tv.TvStatusBar";
if (!shouldUseTabletUI(mContext.getResources().getConfiguration())) {
clsName = "com.android.systemui.statusbar.phone.PhoneStatusBar";
}
setObjectField(self, "mStatusBar", createStatusBar(clsName, mContext, mComponents));
return null;
}
});
findAndHookMethod(systemBarsClass, "onConfigurationChanged", Configuration.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
mConfiguration = (Configuration) methodHookParam.args[0];
final Object self = methodHookParam.thisObject;
Object mStatusBar = getObjectField(self, "mStatusBar");
if (mStatusBar != null) {
boolean isTabletUI = mTvStatusBarClass.isInstance(mStatusBar);
boolean shouldUseTabletUI = shouldUseTabletUI(mConfiguration);
if (isTabletUI != shouldUseTabletUI){
refreshSystemUI(shouldUseTabletUI);
}else {
callMethod(mStatusBar, "onConfigurationChanged", mConfiguration);
}
}
return null;
}
});
}
//TODO: At least move the ugly hack away from te main module class
private void tryHookSystemBarTint(ClassLoader cl) {
try {
Class c = findClass("com.readystatesoftware.systembartint.SystemBarTintManager$SystemBarConfig", cl);
XposedBridge.hookAllConstructors(c, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (!shouldUseTabletUI(null)) {
return;
}
setBooleanField(param.thisObject, "mTranslucentStatusBar", false);
setBooleanField(param.thisObject, "mHasNavigationBar", true);
setIntField(param.thisObject, "mNavigationBarWidth", 0);
}
});
findAndHookMethod(c, "isNavigationAtBottom", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
if (shouldUseTabletUI(null)) {
return true;
}
return invokeOriginalMethod(methodHookParam);
}
});
}catch (Throwable t){}
}
private boolean shouldUseTabletUI(Configuration conf) {
if (conf != null) {
mIsTabletConfiguration = true;//conf.orientation == Configuration.ORIENTATION_LANDSCAPE;
}
if (!mIsTabletConfiguration) {
return false;
}
pref.reload();
return pref.getBoolean("enable_tablet_ui", true);
}
private static boolean isModEnabled(String id) {
pref.reload();
return pref.getBoolean("enable_mod_" + id, true);
}
private Object createStatusBar(String clsName, Context mContext, Object mComponents) {
Class<?> cls;
try {
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
clsName = "com.android.systemui.statusbar.phone.PhoneStatusBar";
return createStatusBar(clsName, mContext, mComponents);
}
Object mStatusBar;
try {
mStatusBar = cls.newInstance();
} catch (Throwable t) {
clsName = "com.android.systemui.statusbar.phone.PhoneStatusBar";
return createStatusBar(clsName, mContext, mComponents);
}
setObjectField(mStatusBar, "mContext", mContext);
setObjectField(mStatusBar, "mComponents", mComponents);
if (mStatusBar != null) {
statusBarMod.init(mStatusBar);
recentsMod.destroy();
callMethod(mStatusBar, "start");
statusBarMod.onStart();
recentsMod.setBar(mStatusBar, statusBarMod);
recentsMod.registerReceiver(mContext);
recentsMod.createPanel(mStatusBar, statusBarMod);
debug("started " + mStatusBar.getClass().getSimpleName());
}
return mStatusBar;
}
@Override
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam initPackageResourcesParam) throws Throwable {
XResources res = initPackageResourcesParam.res;
MultiPaneSettingsMod.hookBreadcrumbs(res);
if (initPackageResourcesParam.packageName.equals(SETTINGS_PACKAGE)){
if (settingsMod == null){
settingsMod = new MultiPaneSettingsMod();
}
XModuleResources res2 = XModuleResources.createInstance(MODULE_PATH, initPackageResourcesParam.res);
SystemR.init(res, res2);
TkR.init(res, res2);
if (isModEnabled("settings")) {
settingsMod.initResources(res, res2);
}
return;
}
if (LauncherMod.isSupported(initPackageResourcesParam.packageName)) {
if (isModEnabled("launcher")) {
if (launcherMod == null || !initPackageResourcesParam.packageName.equals(launcherMod.getPackage())) {
launcherMod = new LauncherMod();
}
XModuleResources res2 = XModuleResources.createInstance(MODULE_PATH, initPackageResourcesParam.res);
SystemR.init(res, res2);
TkR.init(res, res2);
launcherMod.initResources(res, res2);
}
return;
}
if (!initPackageResourcesParam.packageName.equals(SYSTEMUI_PACKAGE)){
return;
}
if (statusBarMod == null){
statusBarMod = new TabletStatusBarMod();
}
if (recentsMod == null){
recentsMod = new TabletRecentsMod();
}
XModuleResources res2 = XModuleResources.createInstance(MODULE_PATH, initPackageResourcesParam.res);
debug("Replacing SystemUI resources");
SystemR.init(res, res2);
TkR.init(res, res2);
statusBarMod.initResources(res, res2);
recentsMod.initResources(res, res2);
}
public static boolean shouldUseTabletRecents(){
return isModEnabled("recents");
}
public static void debug(String s){
if (!DEBUG){
return;
}
XposedBridge.log(TAG + ": " + s);
}
public static BroadcastReceiver registerReceiver(Context c, final OnPreferenceChangedListener l){
IntentFilter f = new IntentFilter();
f.addAction(ACTION_PREFERENCE_CHANGED);
BroadcastReceiver r = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String key = intent.getStringExtra("key");
if (intent.hasExtra("boolValue")){
boolean boolValue = intent.getBooleanExtra("boolValue", false);
l.onPreferenceChanged(key, boolValue);
}
if (intent.hasExtra("intValue")){
int intValue = intent.getIntExtra("intValue", 0);
l.onPreferenceChanged(key, intValue);
}
}
};
pref.reload();
l.init(pref);
c.registerReceiver(r, f);
return r;
}
private void refreshReceiver(Context c){
if (mReceiver == null){
mReceiver = registerReceiver(c, new OnPreferenceChangedListener(){
@Override
public void onPreferenceChanged(String key, boolean value) {
if (key.equals("enable_tablet_ui")){
refreshSystemUI(value);
}
}
@Override
public void onPreferenceChanged(String key, int value) {
}
@Override
public void init(XSharedPreferences pref) {
}
});
}
}
public static void refreshSystemUI(boolean flag){
if (mSystemUI == null) {
return;
}
try {
long l = (Long) callMethod(mSystemUI, "onServiceStartAttempt");
if (l < 500L) {
l = 500L;
}
Handler h = new Handler() {
};
h.postDelayed(new Runnable() {
@Override
public void run() {
callMethod(mSystemUI, "createStatusBarFromConfig");
}
}, l);
} catch (IllegalArgumentException e) {
}
}
public static Object invokeOriginalMethod(XC_MethodHook.MethodHookParam param) throws IllegalAccessException, InvocationTargetException{
return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
}
public static boolean shouldForceBreadcrumbs() {
return isModEnabled("settings") && pref.getBoolean("force_breadcrumbs", true);
}
public static boolean shouldUseLightTheme() {
return isModEnabled("settings") && pref.getBoolean("settings_light_theme", false);
}
}