/* * Copyright (C) 2013 75py * * 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 com.nagopy.android.xposed.utilities; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.nagopy.android.common.util.DimenUtil; import com.nagopy.android.common.util.ImageUtil; import com.nagopy.android.common.util.VersionUtil; import com.nagopy.android.common.util.ViewUtil; import com.nagopy.android.xposed.util.XUtil; import com.nagopy.android.xposed.utilities.XposedModules.InitZygote; import com.nagopy.android.xposed.utilities.XposedModules.XposedModule; import com.nagopy.android.xposed.utilities.setting.ModToastSettingsGen; import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam; 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; /** * {@link Toast}をロックスクリーン上よりも上位レイヤーに表示させるモジュール. */ @XposedModule(setting = ModToastSettingsGen.class) public class ModToast { @InitZygote(summary = "トースト アプリアイコン表示") public static void showAppIcon( StartupParam startupParam, final ModToastSettingsGen mToastSettings ) throws Throwable { if (!mToastSettings.toastShowAppIcon) { return; } // show前にViewをごにょごにょ XposedHelpers.findAndHookMethod(Toast.class, "show", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { View mNextView = (View) XposedHelpers.getObjectField(param.thisObject, "mNextView"); Context context = mNextView.getContext(); // LinearLayoutの場合 if (mNextView instanceof LinearLayout) { LinearLayout originalLL = (LinearLayout) mNextView; if (originalLL.getOrientation() == LinearLayout.HORIZONTAL) { // LinearLayout横配置の場合 // たぶん、カスタムレイアウト // TODO どーする?何もしないでおk? } else { // LinearLayout縦配置の場合 // たぶん、デフォルトのレイアウト // ほんとにデフォルトか確認 if (originalLL.getChildCount() == 1) { // 子View一個かを確認 View childAt = originalLL.getChildAt(0); if (childAt.getId() == android.R.id.message && childAt instanceof TextView) { // 子View一個、idがmessage、TextView // ここまで厳密にやつ必要あるか疑問だが…… // アイコン取得 Drawable icon = ImageUtil.getApplicationIcon(context, context.getPackageName()); if (icon != null) { // アイコンサイズをセット int iconSize = ImageUtil.getIconSize(context); icon.setBounds(0, 0, iconSize, iconSize); // アイコンをセット TextView messageTextView = (TextView) childAt; ViewUtil.setCompoundDrawablesRelative(messageTextView, icon, null, null, null); // 余白設定 messageTextView.setCompoundDrawablePadding(DimenUtil .getPixelFromDp(context, 4)); // 文字を中央になるよう調整 messageTextView.setGravity(Gravity.CENTER_VERTICAL); } } else { // 子View一個のLinearLayoutだが、TextViewじゃないらしい // TODO どーする? } } else { // 子Viewが二個以上 // TODO 何かするべき? } } } else { // LinearLayout以外の場合 // まず間違いなくカスタムレイアウト // TODO どうする?何もしない? } } }); } @InitZygote(summary = "トーストレイヤー変更") public static void setToastAboceLockscreen(StartupParam startupParam, final ModToastSettingsGen mToastSettings) throws Throwable { if (!mToastSettings.setToastAboveLockscreen) { return; } // レイヤーとかをごにょごびょ Class<?> clsTN = XposedHelpers.findClass("android.widget.Toast$TN", null); XposedBridge.hookAllConstructors(clsTN, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (mToastSettings.setToastAboveLockscreen) { // トーストのレイヤーを変更 WindowManager.LayoutParams mParams = (LayoutParams) XposedHelpers .getObjectField(param.thisObject, "mParams"); mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; // タイトルをつける(パーミッションチェック回避用) XposedHelpers.setAdditionalInstanceField(mParams, "flg", true); mParams.setTitle("Toast"); } } }); // パーミッションチェック回避用のチェック(気休め程度) XposedHelpers.findAndHookMethod(WindowManager.LayoutParams.class, "setTitle", CharSequence.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Object flg = XposedHelpers.getAdditionalInstanceField(param.thisObject, "flg"); CharSequence title = (CharSequence) param.args[0]; if (flg == null && TextUtils.equals(title, "Toast")) { // フラグがついていないけどsetTitle("Toast")ってしてる場合 // 無効化する param.setResult(null); } } }); // Toastのパーミッションチェックをごにょごにょ Class<?> clsPhoneWindowManager = XposedHelpers.findClass( "com.android.internal.policy.impl.PhoneWindowManager", null); final int OKAY = 0; // TODO WindowManagerGlobal#ADD_OKAY を取得した方が良い XC_MethodReplacement checkAddPermissionReplacement = new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { Object originalReturns = XUtil.invokeOriginalMethod(param); WindowManager.LayoutParams layoutParams = (LayoutParams) param.args[0]; CharSequence title = layoutParams.getTitle(); if (title != null && title.equals("Toast")) { // トーストの場合、無条件でおっけーにしとく return OKAY; } else { return originalReturns; } } }; if (VersionUtil.isJBmr2OrLater()) { XposedHelpers.findAndHookMethod(clsPhoneWindowManager, "checkAddPermission", WindowManager.LayoutParams.class, int[].class, checkAddPermissionReplacement); } else { XposedHelpers.findAndHookMethod(clsPhoneWindowManager, "checkAddPermission", WindowManager.LayoutParams.class, checkAddPermissionReplacement); } } @InitZygote(summary = "トースト表示時間変更") public static void setToastDuration(StartupParam startupParam, final ModToastSettingsGen mToastSettings) throws Throwable { // 表示時間調整 Class<?> clsNotificationManagerService = XposedHelpers.findClass( "com.android.server.NotificationManagerService", null); final int MESSAGE_TIMEOUT = XposedHelpers.getStaticIntField(clsNotificationManagerService, "MESSAGE_TIMEOUT"); Class<?> clsToastRecord = XposedHelpers.findClass( "com.android.server.NotificationManagerService$ToastRecord", null); if (VersionUtil.isKitKatOrLator()) { XposedHelpers .findAndHookMethod(clsNotificationManagerService, "scheduleTimeoutLocked", clsToastRecord, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { Object r = param.args[0]; Handler mHandler = (Handler) XposedHelpers.getObjectField( param.thisObject, "mHandler"); Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); int duration = XposedHelpers.getIntField(r, "duration"); long delay = duration == Toast.LENGTH_LONG ? mToastSettings.toastLongDelay * 100 : mToastSettings.toastShortDelay * 100; mHandler.sendMessageDelayed(m, delay); return null; } }); } else { XposedHelpers .findAndHookMethod(clsNotificationManagerService, "scheduleTimeoutLocked", clsToastRecord, boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { Object r = param.args[0]; boolean immediate = (Boolean) param.args[1]; Handler mHandler = (Handler) XposedHelpers.getObjectField( param.thisObject, "mHandler"); Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); int duration = XposedHelpers.getIntField(r, "duration"); long delay = immediate ? 0 : (duration == Toast.LENGTH_LONG ? mToastSettings.toastLongDelay * 100 : mToastSettings.toastShortDelay * 100); mHandler.sendMessageDelayed(m, delay); return null; } }); } } }