/* * 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.Modul; 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.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.PowerManager; 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.XposedBridge; import de.robv.android.xposed.XposedHelpers; public class CopyOfModPowerMenu { 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 mBootloaderIcon; private static Drawable mScreenshotIcon; private static List<IIconListAdapterItem> mRebootItemList; private static String mRebootConfirmStr; private static String mRebootConfirmRecoveryStr; private static String mRebootConfirmBootloaderStr; private static String mScreenshotStr; private static Unhook mRebootActionHook; private static Object mRebootActionItem; private static boolean mRebootActionItemStockExists; private static Object mScreenshotAction; private static boolean mRebootConfirmRequired; private static boolean mRebootAllowOnLockscreen; private static void log(String message) { XposedBridge.log(TAG + ": " + message); } public static void init(final ClassLoader classLoader) { try { 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( Modul.class.getPackage().getName(), 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 = "Recovery"; mBootloaderStr = "Droid Boot"; mRebootIcon = gbRes .getDrawable(R.drawable.ic_launcher); mRebootSoftIcon = gbRes .getDrawable(R.drawable.ic_launcher); gbRes.getDrawable(R.drawable.ic_launcher); mBootloaderIcon = gbRes .getDrawable(R.drawable.ic_launcher); mScreenshotIcon = gbRes .getDrawable(R.drawable.ic_launcher); mRebootItemList = new ArrayList<IIconListAdapterItem>(); mRebootItemList.add(new BasicIconListItem( mRebootStr, null, null, null)); mRebootItemList.add(new BasicIconListItem( mRebootSoftStr, null, null, null)); mRebootItemList.add(new BasicIconListItem( mRecoveryStr, null, null, null)); if (!Utils.isMtkDevice()) { mRebootItemList.add(new BasicIconListItem( mBootloaderStr, null, null, null)); } mRebootConfirmStr = String.format("Reboot?"); mRebootConfirmRecoveryStr = String .format("Reboot Recovery?"); mRebootConfirmBootloaderStr = String .format("Reboot Bootloader?"); 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; } } @SuppressWarnings("unused") @Override protected void afterHookedMethod( final MethodHookParam param) throws Throwable { if (mContext == null) return; mRebootConfirmRequired = true; mRebootAllowOnLockscreen = false; @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; } } 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++; // Add screenshot action if enabled if (false) { 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 { } }); } 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 ScreenshotAction implements InvocationHandler { private Context mContext; private void takeScreenshot() { Intent intent = new Intent(""); mContext.sendBroadcast(intent); } @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; } } } }