/*
* Copyright (C) 2013 Peter Gregus for GravityBox Project (C3C076@xda)
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hello.dcsms.omzen.PowerMenu;
import hello.dcsms.omzen.R;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
import android.os.RemoteException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodHook.Unhook;
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;
public class ModPowerMenu {
private static final String TAG = "GB:ModPowerMenu";
public static final String PACKAGE_NAME = "android";
public static final String CLASS_GLOBAL_ACTIONS = "com.android.internal.policy.impl.GlobalActions";
public static final String CLASS_ACTION = "com.android.internal.policy.impl.GlobalActions.Action";
private static final boolean DEBUG = false;
private static Context mContext;
private static String mRebootStr;
private static String mRebootSoftStr;
private static String mRecoveryStr;
private static String mBootloaderStr;
private static Drawable mRebootIcon;
private static Drawable mRebootSoftIcon;
private static Drawable mRecoveryIcon;
private static Drawable mBootloaderIcon;
private static Drawable mExpandedDesktopIcon;
private static Drawable mScreenshotIcon;
private static List<IIconListAdapterItem> mRebootItemList;
private static String mRebootConfirmStr;
private static String mRebootConfirmRecoveryStr;
private static String mRebootConfirmBootloaderStr;
private static String mExpandedDesktopStr;
private static String mExpandedDesktopOnStr;
private static String mExpandedDesktopOffStr;
private static String mScreenshotStr;
private static Unhook mRebootActionHook;
private static Object mRebootActionItem;
private static boolean mRebootActionItemStockExists;
private static Object mScreenshotAction;
private static Object mExpandedDesktopAction;
private static boolean mRebootConfirmRequired;
private static boolean mRebootAllowOnLockscreen;
private static void log(String message) {
XposedBridge.log(TAG + ": " + message);
}
public static void init(final XSharedPreferences prefs,
final ClassLoader classLoader) {
try {
String CLASS_PHONE_WINDOW_MANAGER = "com.android.internal.policy.impl.PhoneWindowManager";
String CLASS_WINDOW_MANAGER_FUNCS = "android.view.WindowManagerPolicy.WindowManagerFuncs";
String CLASS_IWINDOW_MANAGER = "android.view.IWindowManager";
final Class<?> classPhoneWindowManager = XposedHelpers.findClass(
CLASS_PHONE_WINDOW_MANAGER, null);
XposedHelpers.findAndHookMethod(classPhoneWindowManager, "init",
Context.class, CLASS_IWINDOW_MANAGER,
CLASS_WINDOW_MANAGER_FUNCS, phoneWindowManagerInitHook);
final Class<?> globalActionsClass = XposedHelpers.findClass(
CLASS_GLOBAL_ACTIONS, classLoader);
final Class<?> actionClass = XposedHelpers.findClass(CLASS_ACTION,
classLoader);
XposedBridge.hookAllConstructors(globalActionsClass,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(
final MethodHookParam param) throws Throwable {
mContext = (Context) param.args[0];
Context gbContext = mContext.createPackageContext(
"hello.dcsms.omzen",
Context.CONTEXT_IGNORE_SECURITY);
Resources res = mContext.getResources();
Resources gbRes = gbContext.getResources();
int rebootStrId = res.getIdentifier(
"factorytest_reboot", "string",
PACKAGE_NAME);
mRebootStr = (rebootStrId == 0) ? "Reboot" : res
.getString(rebootStrId);
mRebootSoftStr = "Soft Reboot";
mRecoveryStr = "Reboot Recovery";
mBootloaderStr = "Reboot DroidBoot";
mExpandedDesktopStr = "Expanded Desktop";
mExpandedDesktopOnStr = "Expanded Desktop ON";
mExpandedDesktopOffStr = "Expanded Desktop OFF";
mScreenshotStr = "ScreenShot";
mRebootIcon = gbRes
.getDrawable(R.drawable.ic_lock_reboot);
mRebootSoftIcon = gbRes
.getDrawable(R.drawable.ic_lock_reboot_soft);
mRecoveryIcon = gbRes
.getDrawable(R.drawable.ic_lock_recovery);
mBootloaderIcon = gbRes
.getDrawable(R.drawable.ic_lock_bootloader);
mExpandedDesktopIcon = gbRes.getDrawable(Utils
.hasLenovoVibeUI() ? R.drawable.ic_lock_expanded_desktop_vibeui
: R.drawable.ic_lock_expanded_desktop);
mScreenshotIcon = gbRes.getDrawable(Utils
.hasLenovoVibeUI() ? R.drawable.ic_lock_screenshot_vibeui
: R.drawable.ic_lock_screenshot);
// colorize icons to make them look good on light
// backgrounds
int airplaneIconId = res.getIdentifier(
"ic_lock_airplane_mode_off", "drawable",
PACKAGE_NAME);
if (airplaneIconId != 0) {
Drawable airplaneIcon = res
.getDrawable(airplaneIconId);
Bitmap bitmap = Utils
.drawableToBitmap(airplaneIcon);
int airplaneIconColor = Utils
.getBitmapPredominantColor(bitmap);
if (airplaneIconColor != Color.WHITE) {
int iconColor = Utils.hasLenovoVibeUI() ? Color
.parseColor("#4b4b4b") : Color.GRAY;
mRebootIcon.setColorFilter(iconColor,
PorterDuff.Mode.SRC_ATOP);
mRebootSoftIcon.setColorFilter(iconColor,
PorterDuff.Mode.SRC_ATOP);
mRecoveryIcon.setColorFilter(iconColor,
PorterDuff.Mode.SRC_ATOP);
mBootloaderIcon.setColorFilter(iconColor,
PorterDuff.Mode.SRC_ATOP);
mExpandedDesktopIcon
.setColorFilter(iconColor,
PorterDuff.Mode.SRC_ATOP);
mScreenshotIcon.setColorFilter(iconColor,
PorterDuff.Mode.SRC_ATOP);
}
}
mRebootItemList = new ArrayList<IIconListAdapterItem>();
mRebootItemList.add(new BasicIconListItem(
mRebootStr, null, mRebootIcon, null));
mRebootItemList
.add(new BasicIconListItem(mRebootSoftStr,
null, mRebootSoftIcon, null));
mRebootItemList.add(new BasicIconListItem(
mRecoveryStr, null, mRecoveryIcon, null));
if (!Utils.isMtkDevice()) {
mRebootItemList.add(new BasicIconListItem(
mBootloaderStr, null, mBootloaderIcon,
null));
}
mRebootConfirmStr = "Reboot?";
mRebootConfirmRecoveryStr = "Reboot Recovery?";
mRebootConfirmBootloaderStr = "Reboot Droidboot?";
if (DEBUG)
log("GlobalActions constructed, resources set.");
}
});
XposedHelpers.findAndHookMethod(globalActionsClass, "createDialog",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(
final MethodHookParam param) throws Throwable {
if (mRebootActionHook != null) {
if (DEBUG)
log("Unhooking previous hook of reboot action item");
mRebootActionHook.unhook();
mRebootActionHook = null;
}
}
@Override
protected void afterHookedMethod(
final MethodHookParam param) throws Throwable {
if (mContext == null)
return;
mRebootConfirmRequired = true;
mRebootAllowOnLockscreen = true;
@SuppressWarnings("unchecked")
List<Object> mItems = (List<Object>) XposedHelpers
.getObjectField(param.thisObject, "mItems");
BaseAdapter mAdapter = (BaseAdapter) XposedHelpers
.getObjectField(param.thisObject,
"mAdapter");
int index = 1;
// try to find out if reboot action item already
// exists in the list of GlobalActions items
// strategy:
// 1) check if Action has mIconResId field or
// mMessageResId field
// 2) check if the name of the corresponding
// resource contains "reboot" or "restart" substring
if (mRebootActionItem == null) {
if (DEBUG)
log("Searching for existing reboot action item...");
Resources res = mContext.getResources();
for (Object o : mItems) {
// search for drawable
try {
Field f = XposedHelpers.findField(
o.getClass(), "mIconResId");
String resName = res
.getResourceEntryName(
(Integer) f.get(o))
.toLowerCase(Locale.US);
if (DEBUG)
log("Drawable resName = " + resName);
if (resName.contains("reboot")
|| resName.contains("restart")) {
mRebootActionItem = o;
break;
}
} catch (NoSuchFieldError nfe) {
// continue
} catch (Resources.NotFoundException resnfe) {
// continue
} catch (IllegalArgumentException iae) {
// continue
}
if (mRebootActionItem == null) {
// search for text
try {
Field f = XposedHelpers.findField(
o.getClass(),
"mMessageResId");
String resName = res
.getResourceEntryName(
(Integer) f.get(o))
.toLowerCase(Locale.US);
if (DEBUG)
log("Text resName = " + resName);
if (resName.contains("reboot")
|| resName
.contains("restart")) {
mRebootActionItem = o;
break;
}
} catch (NoSuchFieldError nfe) {
// continue
} catch (Resources.NotFoundException resnfe) {
// continue
} catch (IllegalArgumentException iae) {
// continue
}
}
}
if (mRebootActionItem == null) {
if (DEBUG)
log("Existing Reboot action item NOT found! Creating new RebootAction item");
mRebootActionItemStockExists = false;
mRebootActionItem = Proxy.newProxyInstance(
classLoader,
new Class<?>[] { actionClass },
new RebootAction());
} else {
if (DEBUG)
log("Existing Reboot action item found!");
mRebootActionItemStockExists = true;
}
}
// Add/hook reboot action if enabled
if (prefs.getBoolean("MOD_POWER_MENU", false)) {
if (mRebootActionItemStockExists) {
mRebootActionHook = XposedHelpers
.findAndHookMethod(
mRebootActionItem
.getClass(),
"onPress",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(
MethodHookParam param)
throws Throwable {
RebootAction
.showRebootDialog(mContext);
return null;
}
});
} else {
// add to the second position
mItems.add(index, mRebootActionItem);
}
index++;
} else if (mRebootActionItemStockExists) {
index++;
}
// Add screenshot action if enabled
if (true) {
if (mScreenshotAction == null) {
mScreenshotAction = Proxy.newProxyInstance(
classLoader,
new Class<?>[] { actionClass },
new ScreenshotAction());
if (DEBUG)
log("mScreenshotAction created");
}
mItems.add(index++, mScreenshotAction);
}
mAdapter.notifyDataSetChanged();
}
});
XposedHelpers.findAndHookMethod(globalActionsClass, "showDialog",
boolean.class, boolean.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(
final MethodHookParam param) throws Throwable {
prefs.reload();
if (true) {
boolean locked = (Boolean) param.args[0];
if (!locked) {
// double-check using keyguard manager
try {
Context context = (Context) XposedHelpers
.getObjectField(
param.thisObject,
"mContext");
KeyguardManager km = (KeyguardManager) context
.getSystemService(Context.KEYGUARD_SERVICE);
locked = km.isKeyguardLocked();
} catch (Throwable t) {
}
}
if (locked) {
Dialog d = (Dialog) XposedHelpers
.getObjectField(param.thisObject,
"mDialog");
if (d == null) {
XposedHelpers.callMethod(
param.thisObject,
"createDialog");
}
param.setResult(null);
}
}
}
});
} catch (Throwable t) {
XposedBridge.log(t);
}
}
private static class RebootAction implements InvocationHandler {
private Context mContext;
public RebootAction() {
}
public static void showRebootDialog(final Context context) {
if (context == null) {
if (DEBUG)
log("Context is null - aborting");
return;
}
try {
if (DEBUG)
log("about to build reboot dialog");
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setTitle(mRebootStr)
.setAdapter(
new IconListAdapter(context, mRebootItemList),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
if (DEBUG)
log("onClick() item = " + which);
handleReboot(context, mRebootStr, which);
}
})
.setNegativeButton(android.R.string.no,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.show();
} catch (Throwable t) {
XposedBridge.log(t);
}
}
private static void doReboot(Context context, int mode) {
final PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
if (mode == 0) {
pm.reboot(null);
} else if (mode == 1) {
Utils.performSoftReboot();
} else if (mode == 2) {
pm.reboot("recovery");
} else if (mode == 3) {
pm.reboot("bootloader");
}
}
private static void handleReboot(final Context context, String caption,
final int mode) {
try {
if (!mRebootConfirmRequired) {
doReboot(context, mode);
} else {
String message = mRebootConfirmStr;
if (mode == 2) {
message = mRebootConfirmRecoveryStr;
} else if (mode == 3) {
message = mRebootConfirmBootloaderStr;
}
AlertDialog.Builder builder = new AlertDialog.Builder(
context)
.setTitle(caption)
.setMessage(message)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
dialog.dismiss();
doReboot(context, mode);
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.show();
}
} catch (Throwable t) {
XposedBridge.log(t);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if (methodName.equals("create")) {
mContext = (Context) args[0];
Resources res = mContext.getResources();
LayoutInflater li = (LayoutInflater) args[3];
int layoutId = res.getIdentifier("global_actions_item",
"layout", "android");
View v = li.inflate(layoutId, (ViewGroup) args[2], false);
ImageView icon = (ImageView) v.findViewById(res.getIdentifier(
"icon", "id", "android"));
icon.setImageDrawable(mRebootIcon);
TextView messageView = (TextView) v.findViewById(res
.getIdentifier("message", "id", "android"));
messageView.setText(mRebootStr);
TextView statusView = (TextView) v.findViewById(res
.getIdentifier("status", "id", "android"));
statusView.setVisibility(View.GONE);
return v;
} else if (methodName.equals("onPress")) {
showRebootDialog(mContext);
return null;
} else if (methodName.equals("onLongPress")) {
handleReboot(mContext, mRebootStr, 0);
return true;
} else if (methodName.equals("showDuringKeyguard")) {
return mRebootAllowOnLockscreen;
} else if (methodName.equals("showBeforeProvisioning")) {
return true;
} else if (methodName.equals("isEnabled")) {
return true;
} else if (methodName.equals("showConditional")) {
return true;
} else {
log("RebootAction: Unhandled invocation method: " + methodName);
return null;
}
}
}
private static class ExpandedDesktopAction implements InvocationHandler {
private Context mContext;
private TextView mStatus;
private Handler mHandler;
public ExpandedDesktopAction() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if (methodName.equals("create")) {
mContext = (Context) args[0];
mHandler = new Handler();
Resources res = mContext.getResources();
LayoutInflater li = (LayoutInflater) args[3];
int layoutId = res.getIdentifier("global_actions_item",
"layout", "android");
View v = li.inflate(layoutId, (ViewGroup) args[2], false);
ImageView icon = (ImageView) v.findViewById(res.getIdentifier(
"icon", "id", "android"));
icon.setImageDrawable(mExpandedDesktopIcon);
TextView messageView = (TextView) v.findViewById(res
.getIdentifier("message", "id", "android"));
messageView.setText(mExpandedDesktopStr);
mStatus = (TextView) v.findViewById(res.getIdentifier("status",
"id", "android"));
mStatus.setVisibility(View.VISIBLE);
return v;
} else if (methodName.equals("onPress")) {
return null;
} else if (methodName.equals("onLongPress")) {
return false;
} else if (methodName.equals("showDuringKeyguard")) {
return true;
} else if (methodName.equals("showBeforeProvisioning")) {
return true;
} else if (methodName.equals("isEnabled")) {
return true;
} else if (methodName.equals("showConditional")) {
return true;
} else {
log("ExpandedDesktopAction: Unhandled invocation method: "
+ methodName);
return null;
}
}
}
private static XC_MethodHook phoneWindowManagerInitHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param)
throws Throwable {
mPhoneWindowManager = param.thisObject;
}
};
private static Object mPhoneWindowManager;
private static class ScreenshotAction implements InvocationHandler {
private static Context mContext;
private static final Object mScreenshotLock = new Object();
private static ServiceConnection mScreenshotConnection = null;
private static final Runnable mScreenshotTimeout = new Runnable() {
@Override
public void run() {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
}
}
}
};
private static void takeScreenshot() {
final Handler handler = (Handler) XposedHelpers.getObjectField(
mPhoneWindowManager, "mHandler");
if (handler == null)
return;
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
}
ComponentName cn = new ComponentName("com.android.systemui",
"com.android.systemui.screenshot.TakeScreenshotService");
Intent intent = new Intent();
intent.setComponent(cn);
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name,
IBinder service) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != this) {
return;
}
final Messenger messenger = new Messenger(service);
final Message msg = Message.obtain(null, 1);
final ServiceConnection myConn = this;
Handler h = new Handler(handler.getLooper()) {
@Override
public void handleMessage(Message msg) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection == myConn) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
handler.removeCallbacks(mScreenshotTimeout);
}
}
}
};
msg.replyTo = new Messenger(h);
msg.arg1 = msg.arg2 = 0;
h.postDelayed(new Runnable() {
@Override
public void run() {
try {
messenger.send(msg);
} catch (RemoteException e) {
XposedBridge.log(e);
}
}
}, 1000);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
if (mContext
.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
mScreenshotConnection = conn;
handler.postDelayed(mScreenshotTimeout, 10000);
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
if (methodName.equals("create")) {
mContext = (Context) args[0];
Resources res = mContext.getResources();
LayoutInflater li = (LayoutInflater) args[3];
int layoutId = res.getIdentifier("global_actions_item",
"layout", "android");
View v = li.inflate(layoutId, (ViewGroup) args[2], false);
ImageView icon = (ImageView) v.findViewById(res.getIdentifier(
"icon", "id", "android"));
icon.setImageDrawable(mScreenshotIcon);
TextView messageView = (TextView) v.findViewById(res
.getIdentifier("message", "id", "android"));
messageView.setText(mScreenshotStr);
TextView statusView = (TextView) v.findViewById(res
.getIdentifier("status", "id", "android"));
statusView.setVisibility(View.GONE);
return v;
} else if (methodName.equals("onPress")) {
takeScreenshot();
return null;
} else if (methodName.equals("onLongPress")) {
return true;
} else if (methodName.equals("showDuringKeyguard")) {
return true;
} else if (methodName.equals("showBeforeProvisioning")) {
return true;
} else if (methodName.equals("isEnabled")) {
return true;
} else if (methodName.equals("showConditional")) {
return true;
} else {
log("ScreenshotAction: Unhandled invocation method: "
+ methodName);
return null;
}
}
}
}