├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── XposedBridgeApi-54.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── hdfg159 │ │ └── qqsendpoke │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── xposed_init │ ├── java │ │ └── hdfg159 │ │ │ └── qqsendpoke │ │ │ ├── hook │ │ │ ├── MyPackageHook.java │ │ │ ├── PokeMsgHook.java │ │ │ └── QQSendPokeHook.java │ │ │ ├── utilities │ │ │ ├── DialogUtil.java │ │ │ ├── ToastUtil.java │ │ │ └── XSharedPreferencesUtil.java │ │ │ └── view │ │ │ ├── Main.java │ │ │ └── Settings.java │ └── res │ │ ├── drawable │ │ └── wechatpay.png │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── sendpoke_setting.xml │ └── test │ └── java │ └── hdfg159 │ └── qqsendpoke │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── screenshot ├── Main.png └── Setting.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xposed模块-QQSendPoke 2 | 3 | ### 主要特性 4 | - 解除发送戳一戳次数和时间的限制 5 | - 支持设置戳一戳小功能连发次数 6 | - 支持设置消息发送间隔(秒) 7 | 8 | ### 使用方法 9 | - 安装Xposed框架 10 | - 安装本程序,并在Xposed框架的模块列表勾选,重启手机 11 | - 若模块依旧失效,在模块列表重新勾选并重启手机 12 | 13 | ### 功能预览 14 | 15 | ![](https://github.com/hdfg159/QQSendPoke/blob/master/screenshot/Setting.png?raw=true) 16 | 17 | ### 注意 18 | 19 | > 本项目源码仅供学习交流,禁止利用源码进行商业用途 20 | > 21 | > 项目目前停止维护 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion '25.0.2' 6 | defaultConfig { 7 | applicationId "hdfg159.qqsendpoke" 8 | minSdkVersion 15 9 | targetSdkVersion 22 10 | versionCode 20170603 11 | versionName '3.0' 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | zipAlignEnabled true 18 | } 19 | } 20 | productFlavors { 21 | } 22 | } 23 | 24 | dependencies { 25 | provided fileTree(include: ['*.jar'], dir: 'libs') 26 | testCompile 'junit:junit:4.12' 27 | provided files('libs/XposedBridgeApi-54.jar') 28 | } 29 | -------------------------------------------------------------------------------- /app/libs/XposedBridgeApi-54.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/app/libs/XposedBridgeApi-54.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Tools\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -keep public class hdfg159.qqsendpoke.hook.PokeMsgHook 19 | -keep public class hdfg159.qqsendpoke.view.Main{ 20 | private boolean isModuleActive(); 21 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/hdfg159/qqsendpoke/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | hdfg159.qqsendpoke.hook.PokeMsgHook -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/hook/MyPackageHook.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.hook; 2 | 3 | import de.robv.android.xposed.XC_MethodHook; 4 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 5 | 6 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; 7 | 8 | /** 9 | * Project:QQSendPoke 10 | * Package:hdfg159.qqsendpoke.hook 11 | * Created by hdfg159 on 2017/2/6 23:14. 12 | */ 13 | class MyPackageHook { 14 | private static final String METHOD_NAME_IS_MODULE_ACTIVE = "isModuleActive"; 15 | private final XC_LoadPackage.LoadPackageParam loadPackageParam; 16 | private static final String QQ_SEND_POKE_PACKAGENAME = "hdfg159.qqsendpoke"; 17 | private static final String CLASSNAME_MAIN = "hdfg159.qqsendpoke.view.Main"; 18 | 19 | public MyPackageHook(XC_LoadPackage.LoadPackageParam loadPackageParam) { 20 | this.loadPackageParam = loadPackageParam; 21 | } 22 | 23 | public void initAndHook() { 24 | if (isQQSendPokePackage(loadPackageParam)) { 25 | findAndHookMethod(CLASSNAME_MAIN, loadPackageParam.classLoader, METHOD_NAME_IS_MODULE_ACTIVE, new XC_MethodHook() { 26 | @Override 27 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 28 | param.setResult(true); 29 | } 30 | }); 31 | } 32 | } 33 | 34 | private boolean isQQSendPokePackage(XC_LoadPackage.LoadPackageParam loadPackageParam) { 35 | return loadPackageParam.packageName.equals(QQ_SEND_POKE_PACKAGENAME); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/hook/PokeMsgHook.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.hook; 2 | 3 | import de.robv.android.xposed.IXposedHookLoadPackage; 4 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; 5 | 6 | /** 7 | * Project:QQSendPoke 8 | * Package:hdfg159.qqsendpoke 9 | * Created by hdfg159 on 2016/7/24 18:32. 10 | */ 11 | public class PokeMsgHook implements IXposedHookLoadPackage { 12 | 13 | @Override 14 | public void handleLoadPackage(LoadPackageParam loadPackageParam) throws Throwable { 15 | new MyPackageHook(loadPackageParam).initAndHook(); 16 | new QQSendPokeHook(loadPackageParam).initAndHook(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/hook/QQSendPokeHook.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.hook; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.text.TextUtils; 7 | 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | 11 | import de.robv.android.xposed.XC_MethodHook; 12 | import de.robv.android.xposed.XC_MethodReplacement; 13 | import de.robv.android.xposed.XposedBridge; 14 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 15 | import hdfg159.qqsendpoke.utilities.XSharedPreferencesUtil; 16 | 17 | import static de.robv.android.xposed.XposedBridge.invokeOriginalMethod; 18 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; 19 | 20 | /** 21 | * Project:QQSendPoke 22 | * Package:hdfg159.qqsendpoke.hook 23 | * Created by hdfg159 on 2017/2/6 23:20. 24 | */ 25 | class QQSendPokeHook { 26 | private final XC_LoadPackage.LoadPackageParam loadPackageParam; 27 | private static final String QQ_PACKAGE_NAME = "com.tencent.mobileqq"; 28 | private static final String CLASSNAME_POKE_ITEM_HELPER = "com.tencent.mobileqq.activity.aio.item.PokeItemHelper"; 29 | private static final String CLASSNAME_QQ_APP_INTERFACE = "com.tencent.mobileqq.app.QQAppInterface"; 30 | private static final String CLASSNAME_SESSION_INFO = "com.tencent.mobileqq.activity.aio.SessionInfo"; 31 | private static final String CLASSNAME_CHAT_ACTIVITY_FACADE = "com.tencent.mobileqq.activity.ChatActivityFacade"; 32 | private static final String METHOD_NAME_IS_ALLOW_POKE = "a"; 33 | // private static final String METHOD_NAME_POKE = "b"; 34 | private static final String METHOD_NAME_POKE = "a"; 35 | 36 | public QQSendPokeHook(XC_LoadPackage.LoadPackageParam loadPackageParam) { 37 | this.loadPackageParam = loadPackageParam; 38 | } 39 | 40 | public void initAndHook() { 41 | if (isQQPackage(loadPackageParam)) { 42 | findAndHookMethod(Application.class, "dispatchActivityResumed", Activity.class, new XC_MethodHook() { 43 | @Override 44 | protected void afterHookedMethod(final MethodHookParam param) throws Throwable { 45 | sendPokeHook(loadPackageParam); 46 | } 47 | }); 48 | } 49 | } 50 | 51 | private void sendPokeHook(final XC_LoadPackage.LoadPackageParam loadPackageParam) { 52 | unlockPokeTimes(loadPackageParam); 53 | pokeMoreTimes(loadPackageParam); 54 | } 55 | 56 | private void unlockPokeTimes(XC_LoadPackage.LoadPackageParam loadPackageParam) { 57 | findAndHookMethod(CLASSNAME_POKE_ITEM_HELPER, loadPackageParam.classLoader, METHOD_NAME_IS_ALLOW_POKE, CLASSNAME_QQ_APP_INTERFACE, CLASSNAME_SESSION_INFO, new XC_MethodHook() { 58 | @Override 59 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 60 | if (isEnableModule()) 61 | param.setResult(true); 62 | } 63 | }); 64 | } 65 | 66 | private void pokeMoreTimes(final XC_LoadPackage.LoadPackageParam loadPackageParam) { 67 | findAndHookMethod(CLASSNAME_CHAT_ACTIVITY_FACADE, loadPackageParam.classLoader, METHOD_NAME_POKE, CLASSNAME_QQ_APP_INTERFACE, Context.class, CLASSNAME_SESSION_INFO, int.class, new XC_MethodReplacement() { 68 | 69 | @Override 70 | protected Object replaceHookedMethod(final MethodHookParam methodHookParam) throws Throwable { 71 | if (isEnableModule()) { 72 | refreshSetting(); 73 | long pokeTimes = getPokeTimes(); 74 | ExecutorService singleThread = Executors.newSingleThreadExecutor(); 75 | for (long i = 0; i < pokeTimes; i++) { 76 | singleThread.execute(new Runnable() { 77 | @Override 78 | public void run() { 79 | try { 80 | invokeOriginalMethod(methodHookParam.method, null, methodHookParam.args); 81 | Thread.sleep(getMessageMillis()); 82 | } catch (Throwable t) { 83 | XposedBridge.log(t); 84 | } 85 | } 86 | }); 87 | } 88 | } else { 89 | invokeOriginalMethod(methodHookParam.method, null, methodHookParam.args); 90 | } 91 | return null; 92 | } 93 | }); 94 | } 95 | 96 | private long getMessageMillis() { 97 | String time = XSharedPreferencesUtil.getString(XSharedPreferencesUtil.KEY_TIME_INTERVAL, "0"); 98 | if (TextUtils.isEmpty(time)) { 99 | time = "0"; 100 | } 101 | Long second = Long.parseLong(time); 102 | return second * 1000; 103 | } 104 | 105 | private long getPokeTimes() { 106 | String time = XSharedPreferencesUtil.getString(XSharedPreferencesUtil.KEY_POKE_TIMES, "1"); 107 | if (TextUtils.isEmpty(time)) { 108 | time = "1"; 109 | } 110 | return Long.parseLong(time); 111 | } 112 | 113 | private boolean isEnableModule() { 114 | refreshSetting(); 115 | return XSharedPreferencesUtil.getBoolean(XSharedPreferencesUtil.KEY_IS_ENABLE, true); 116 | } 117 | 118 | private void refreshSetting() { 119 | XSharedPreferencesUtil.hasFileChangedAndReload(); 120 | } 121 | 122 | private boolean isQQPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) { 123 | return loadPackageParam.packageName.equals(QQ_PACKAGE_NAME); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/utilities/DialogUtil.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.utilities; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.view.WindowManager; 8 | 9 | /** 10 | * 对话框工具 11 | */ 12 | public class DialogUtil { 13 | /** 14 | * 显示一个对话框 15 | * 16 | * @param context 上下文对象,最好给Activity,否则需要android.permission.SYSTEM_ALERT_WINDOW 17 | * @param title 标题 18 | * @param message 消息 19 | * @param confirmButton 确认按钮 20 | * @param confirmButtonClickListener 确认按钮点击监听器 21 | * @param centerButton 中间按钮 22 | * @param centerButtonClickListener 中间按钮点击监听器 23 | * @param cancelButton 取消按钮 24 | * @param cancelButtonClickListener 取消按钮点击监听器 25 | * @param onShowListener 显示监听器,当对话框调用show()方法的时候触发。 26 | * @param cancelable 是否允许通过点击返回按钮或者点击对话框之外的位置关闭对话框 27 | * @param onCancelListener 取消监听器,当对话框调用dismiss()方法的时候触发。 28 | * @param onDismissListener 销毁监听器,当对话框调用cancel()方法的时候触发。 29 | * @return 对话框 30 | */ 31 | public static AlertDialog showAlert(Context context, String title, String message, String confirmButton, DialogInterface.OnClickListener confirmButtonClickListener, String centerButton, DialogInterface.OnClickListener centerButtonClickListener, String cancelButton, DialogInterface.OnClickListener cancelButtonClickListener, DialogInterface.OnShowListener onShowListener, boolean cancelable, DialogInterface.OnCancelListener onCancelListener, DialogInterface.OnDismissListener onDismissListener) { 32 | AlertDialog.Builder promptBuilder = new AlertDialog.Builder(context); 33 | if (title != null) { 34 | promptBuilder.setTitle(title); 35 | } 36 | if (message != null) { 37 | promptBuilder.setMessage(message); 38 | } 39 | if (confirmButton != null) { 40 | promptBuilder.setPositiveButton(confirmButton, 41 | confirmButtonClickListener); 42 | } 43 | if (centerButton != null) { 44 | promptBuilder.setNeutralButton(centerButton, 45 | centerButtonClickListener); 46 | } 47 | if (cancelButton != null) { 48 | promptBuilder.setNegativeButton(cancelButton, 49 | cancelButtonClickListener); 50 | } 51 | promptBuilder.setCancelable(cancelable); 52 | if (cancelable) { 53 | promptBuilder.setOnCancelListener(onCancelListener); 54 | } 55 | AlertDialog alertDialog = promptBuilder.create(); 56 | if (!(context instanceof Activity)) { 57 | alertDialog.getWindow() 58 | .setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 59 | } 60 | alertDialog.setOnDismissListener(null); 61 | alertDialog.setOnShowListener(onShowListener); 62 | alertDialog.show(); 63 | return alertDialog; 64 | } 65 | 66 | 67 | /** 68 | * 显示一个确认和取消按钮的对话框 69 | * 70 | * @param context 上下文对象,最好给Activity,否则需要android.permission.SYSTEM_ALERT_WINDOW 71 | * @param title 标题 72 | * @param message 消息 73 | * @param confirmButton 确认按钮 74 | * @param confirmButtonClickListener 确认按钮点击监听器 75 | * @param cancelButton 取消按钮 76 | * @param cancelButtonClickListener 取消按钮点击监听器 77 | * @return 对话框 78 | */ 79 | public static AlertDialog showAlertTwo(Context context, String title, String message, String confirmButton, DialogInterface.OnClickListener confirmButtonClickListener, String cancelButton, DialogInterface.OnClickListener cancelButtonClickListener) { 80 | return showAlert(context, title, message, confirmButton, 81 | confirmButtonClickListener, null, null, cancelButton, 82 | cancelButtonClickListener, null, true, null, null); 83 | } 84 | 85 | 86 | /** 87 | * 显示只有一个按钮的提示框 88 | * 89 | * @param context 上下文对象,最好给Activity,否则需要android.permission.SYSTEM_ALERT_WINDOW 90 | * @param message 提示的消息 91 | * @param confirmButton 确定按钮的名字 92 | */ 93 | public static AlertDialog showPrompt(Context context, String title, String message, String confirmButton) { 94 | return showAlert(context, title, message, confirmButton, null, null, null, null, null, null, true, null, null); 95 | } 96 | 97 | /** 98 | * 显示有最左边和右边的按钮的提示框 99 | * 100 | * @param context 上下文对象,最好给Activity,否则需要android.permission.SYSTEM_ALERT_WINDOW 101 | * @param message 提示的消息 102 | * @param confirmButton 确定按钮的名字 103 | */ 104 | public static AlertDialog showAlertlr(Context context, String title, String message, String confirmButton, DialogInterface.OnClickListener confirmButtonClickListener, String centerButton, DialogInterface.OnClickListener centerButtonClickListener) { 105 | return showAlert(context, title, message, confirmButton, confirmButtonClickListener, centerButton, centerButtonClickListener, null, null, null, false, null, null); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/utilities/ToastUtil.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.utilities; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | /** 7 | * showToast为单例,连续使用不会出现toast长时间呆在屏幕上的情况(出现新的,旧的立刻取消)。 8 | * makeTextAndShow普通的Toast,将makeText和show连接起来。 9 | */ 10 | public class ToastUtil { 11 | 12 | public static Toast toast; 13 | 14 | public ToastUtil() { 15 | } 16 | 17 | /** 18 | * 单例,连续使用不会出现toast长时间呆在屏幕上的情况,duration为Toast.LENGTH_SHORT 19 | * 20 | * @param context 21 | * @param text 22 | */ 23 | public static void showToast(Context context, String text) { 24 | if (toast != null) { 25 | toast.cancel(); 26 | } 27 | toast = Toast.makeText(context, text, Toast.LENGTH_SHORT); 28 | toast.show(); 29 | } 30 | 31 | /** 32 | * 单例,连续使用不会出现toast长时间呆在屏幕上的情况,使用string资源,duration为Toast.LENGTH_SHORT 33 | * 34 | * @param context 35 | * @param text 36 | */ 37 | public static void showToast(Context context, int resId) { 38 | if (toast != null) { 39 | toast.cancel(); 40 | } 41 | toast = Toast.makeText(context, resId, Toast.LENGTH_SHORT); 42 | toast.show(); 43 | } 44 | 45 | /** 46 | * 单例,连续使用不会出现toast长时间呆在屏幕上的情况,duration为自定义 47 | * 48 | * @param context 49 | * @param text 50 | * @param duration 51 | */ 52 | public static void showToast(Context context, String text, int duration) { 53 | if (toast != null) { 54 | toast.cancel(); 55 | } 56 | toast = Toast.makeText(context, text, duration); 57 | toast.show(); 58 | } 59 | 60 | /** 61 | * 单例,连续使用不会出现toast长时间呆在屏幕上的情况,使用string资源,duration为自定义 62 | * 63 | * @param context 64 | * @param text 65 | * @param duration 66 | */ 67 | public static void showToast(Context context, int resId, int duration) { 68 | if (toast != null) { 69 | toast.cancel(); 70 | } 71 | toast = Toast.makeText(context, resId, duration); 72 | toast.show(); 73 | } 74 | 75 | /** 76 | * 普通的Toast,将makeText和show连接起来,duration为Toast.LENGTH_SHORT 77 | * 78 | * @param context 79 | * @param text 80 | */ 81 | public static void makeShow(Context context, String text) { 82 | Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); 83 | } 84 | 85 | /** 86 | * 普通的Toast,将makeText和show连接起来,使用string资源,duration为Toast.LENGTH_SHORT 87 | * 88 | * @param context 89 | * @param resId 90 | */ 91 | public static void makeShow(Context context, int resId) { 92 | Toast.makeText(context, resId, Toast.LENGTH_SHORT).show(); 93 | } 94 | 95 | /** 96 | * 普通的Toast,将makeText和show连接起来,duration为自定义 97 | * 98 | * @param context 99 | * @param text 100 | * @param duration 101 | */ 102 | public static void makeShow(Context context, String text, int duration) { 103 | Toast.makeText(context, text, duration).show(); 104 | } 105 | 106 | /** 107 | * 普通的Toast,将makeText和show连接起来,使用string资源,duration为自定义 108 | * 109 | * @param context 110 | * @param resId 111 | * @param duration 112 | */ 113 | public static void makeShow(Context context, int resId, int duration) { 114 | Toast.makeText(context, resId, duration).show(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/utilities/XSharedPreferencesUtil.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.utilities; 2 | 3 | import de.robv.android.xposed.XSharedPreferences; 4 | 5 | public class XSharedPreferencesUtil { 6 | 7 | public static final String PACKAGENAME = "hdfg159.qqsendpoke"; 8 | public static final String KEY_IS_ENABLE = "IsEnable"; 9 | public static final String KEY_TIME_INTERVAL = "TimeInterval"; 10 | public static final String KEY_POKE_TIMES = "PokeTimes"; 11 | public static XSharedPreferences xSharedPreferences = new XSharedPreferences(PACKAGENAME); 12 | 13 | public static XSharedPreferences getXSharePreferences() { 14 | return xSharedPreferences; 15 | } 16 | 17 | public static void reload() { 18 | xSharedPreferences.reload(); 19 | } 20 | 21 | public static boolean hasFileChanged() { 22 | return xSharedPreferences.hasFileChanged(); 23 | } 24 | 25 | public static void hasFileChangedAndReload() { 26 | if (xSharedPreferences.hasFileChanged()) { 27 | reload(); 28 | } 29 | } 30 | 31 | public static String getString(String key, String defValue) { 32 | return xSharedPreferences.getString(key, defValue); 33 | } 34 | 35 | public static boolean getBoolean(String key, boolean defValue) { 36 | return xSharedPreferences.getBoolean(key, defValue); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/view/Main.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.view; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.Bitmap; 10 | import android.graphics.BitmapFactory; 11 | import android.media.MediaScannerConnection; 12 | import android.net.Uri; 13 | import android.os.Bundle; 14 | import android.os.Environment; 15 | import android.provider.MediaStore; 16 | import android.text.util.Linkify; 17 | import android.view.View; 18 | import android.widget.Button; 19 | import android.widget.ImageView; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import hdfg159.qqsendpoke.R; 24 | import hdfg159.qqsendpoke.utilities.DialogUtil; 25 | import hdfg159.qqsendpoke.utilities.ToastUtil; 26 | 27 | public class Main extends Activity implements View.OnClickListener { 28 | private Button btnDisplayIcon; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_main); 34 | toggleDisplayIcon(isDisplayIcon()); 35 | initView(); 36 | } 37 | 38 | private void toggleDisplayIcon(boolean displayIcon) { 39 | writeInSP(displayIcon); 40 | PackageManager packageManager = getPackageManager(); 41 | int state = displayIcon ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 42 | : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 43 | packageManager.setComponentEnabledSetting(getIconComponentName(), state, 44 | PackageManager.DONT_KILL_APP); 45 | } 46 | 47 | private void writeInSP(boolean displayIcon) { 48 | getSharedPreferences("SendPokeMain", Context.MODE_PRIVATE).edit().putBoolean("isDisplayIcon", displayIcon).commit(); 49 | } 50 | 51 | private void initView() { 52 | showMoudleState(); 53 | TextView mainContent = (TextView) findViewById(R.id.mainContent); 54 | Linkify.addLinks(mainContent, Linkify.ALL); 55 | btnDisplayIcon = (Button) findViewById(R.id.btndisplayicon); 56 | btnDisplayIcon.setOnClickListener(this); 57 | Button btnAbout = (Button) findViewById(R.id.btnabout); 58 | btnAbout.setOnClickListener(this); 59 | ImageView payPicture = (ImageView) findViewById(R.id.imageView); 60 | payPicture.setOnClickListener(this); 61 | Button btnSetting = (Button) findViewById(R.id.btnsetting); 62 | btnSetting.setOnClickListener(this); 63 | setBtnTitle(); 64 | } 65 | 66 | private void showMoudleState() { 67 | if (!isModuleActive()) 68 | DialogUtil.showAlertlr(this, getString(R.string.tips), getString(R.string.module_active_tips), getString(R.string.ignore), null, getString(R.string.open_xposed), new DialogInterface.OnClickListener() { 69 | @Override 70 | public void onClick(DialogInterface dialogInterface, int i) { 71 | redirectToXposed(); 72 | } 73 | }); 74 | } 75 | 76 | 77 | @Override 78 | public void onClick(View v) { 79 | switch (v.getId()) { 80 | case R.id.btndisplayicon: 81 | toggleDisplayIcon(!isDisplayIcon()); 82 | setBtnTitle(); 83 | break; 84 | case R.id.btnabout: 85 | ToastUtil.showToast(this, R.string.aboutContent, Toast.LENGTH_LONG); 86 | break; 87 | case R.id.imageView: 88 | savePicture(); 89 | break; 90 | case R.id.btnsetting: 91 | openSetting(); 92 | break; 93 | default: 94 | break; 95 | } 96 | } 97 | 98 | private boolean isModuleActive() { 99 | return false; 100 | } 101 | 102 | private void openSetting() { 103 | Intent intent = new Intent(this, Settings.class); 104 | startActivity(intent); 105 | } 106 | 107 | private void savePicture() { 108 | Bitmap alipayPicture = BitmapFactory.decodeResource(getResources(), R.drawable.wechatpay); 109 | MediaStore.Images.Media.insertImage(getContentResolver(), alipayPicture, "time:" + System.currentTimeMillis(), "time:" + System.currentTimeMillis()); 110 | MediaScannerConnection.scanFile(this, new String[]{Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath(), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath()}, null, null); 111 | DialogUtil.showAlertTwo(this, getString(R.string.thanks), getString(R.string.payPictureIsSave), "确定", null, getString(R.string.supportalipay), new DialogInterface.OnClickListener() { 112 | @Override 113 | public void onClick(DialogInterface dialogInterface, int i) { 114 | redirectToAlipay(); 115 | } 116 | }); 117 | } 118 | 119 | private void redirectToAlipay() { 120 | Intent intent = new Intent(Intent.ACTION_VIEW); 121 | intent.setData(Uri.parse("https://QR.ALIPAY.COM/FKX01070B8ASXMQNRFY528")); 122 | startActivity(intent); 123 | } 124 | 125 | private void setBtnTitle() { 126 | if (isDisplayIcon()) { 127 | btnDisplayIcon.setText(R.string.noicon); 128 | } else { 129 | btnDisplayIcon.setText(R.string.displayicon); 130 | } 131 | } 132 | 133 | private void redirectToXposed() { 134 | ComponentName componentName = new ComponentName("de.robv.android.xposed.installer", 135 | "de.robv.android.xposed.installer.WelcomeActivity"); 136 | Intent intent = new Intent(); 137 | intent.setComponent(componentName); 138 | startActivity(intent); 139 | } 140 | 141 | private ComponentName getIconComponentName() { 142 | return new ComponentName(this, "hdfg159.qqsendpoke.view.Main-Alias"); 143 | } 144 | 145 | private boolean isDisplayIcon() { 146 | return getSharedPreferences("SendPokeMain", Context.MODE_PRIVATE).getBoolean("isDisplayIcon", true); 147 | } 148 | 149 | @Override 150 | protected void onDestroy() { 151 | super.onDestroy(); 152 | System.exit(0); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/hdfg159/qqsendpoke/view/Settings.java: -------------------------------------------------------------------------------- 1 | package hdfg159.qqsendpoke.view; 2 | 3 | 4 | import android.app.ActionBar; 5 | import android.content.Context; 6 | import android.content.SharedPreferences; 7 | import android.os.Bundle; 8 | import android.preference.EditTextPreference; 9 | import android.preference.PreferenceActivity; 10 | import android.view.MenuItem; 11 | 12 | import hdfg159.qqsendpoke.R; 13 | import hdfg159.qqsendpoke.utilities.XSharedPreferencesUtil; 14 | 15 | public class Settings extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { 16 | 17 | private EditTextPreference timeInterval, pokeTimes; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | getPreferenceManager().setSharedPreferencesMode(Context.MODE_WORLD_READABLE); 23 | addPreferencesFromResource(R.xml.sendpoke_setting); 24 | initView(); 25 | } 26 | 27 | private void initView() { 28 | setupActionBar(); 29 | pokeTimes = (EditTextPreference) getPreferenceScreen().findPreference(XSharedPreferencesUtil.KEY_POKE_TIMES); 30 | timeInterval = (EditTextPreference) getPreferenceScreen().findPreference(XSharedPreferencesUtil.KEY_TIME_INTERVAL); 31 | pokeTimes.setSummary(getPokeTimesFromPrefences()); 32 | timeInterval.setSummary(getTimeIntervalFromPrefences()); 33 | toggleEnableModulestate(); 34 | } 35 | 36 | 37 | private void setupActionBar() { 38 | ActionBar actionBar = getActionBar(); 39 | if (actionBar != null) { 40 | actionBar.setDisplayHomeAsUpEnabled(true); 41 | } 42 | } 43 | 44 | @Override 45 | public boolean onOptionsItemSelected(MenuItem item) { 46 | switch (item.getItemId()) { 47 | case android.R.id.home: 48 | finish(); 49 | break; 50 | default: 51 | break; 52 | } 53 | return super.onOptionsItemSelected(item); 54 | } 55 | 56 | @Override 57 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 58 | switch (s) { 59 | case XSharedPreferencesUtil.KEY_POKE_TIMES: 60 | pokeTimes.setSummary(getPokeTimesFromPrefences()); 61 | break; 62 | case XSharedPreferencesUtil.KEY_TIME_INTERVAL: 63 | timeInterval.setSummary(getTimeIntervalFromPrefences()); 64 | break; 65 | case XSharedPreferencesUtil.KEY_IS_ENABLE: 66 | toggleEnableModulestate(); 67 | break; 68 | } 69 | } 70 | 71 | private void toggleEnableModulestate() { 72 | if (isEnableModuleFromPrefences()) { 73 | pokeTimes.setEnabled(true); 74 | timeInterval.setEnabled(true); 75 | } else { 76 | pokeTimes.setEnabled(false); 77 | timeInterval.setEnabled(false); 78 | } 79 | } 80 | 81 | private boolean isEnableModuleFromPrefences() { 82 | return getPreferenceManager().getSharedPreferences().getBoolean(XSharedPreferencesUtil.KEY_IS_ENABLE, true); 83 | } 84 | 85 | private String getTimeIntervalFromPrefences() { 86 | return getPreferenceManager().getSharedPreferences().getString(XSharedPreferencesUtil.KEY_TIME_INTERVAL, "0"); 87 | } 88 | 89 | private String getPokeTimesFromPrefences() { 90 | return getPreferenceManager().getSharedPreferences().getString(XSharedPreferencesUtil.KEY_POKE_TIMES, "1"); 91 | } 92 | 93 | @Override 94 | protected void onResume() { 95 | super.onResume(); 96 | getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); 97 | } 98 | 99 | @Override 100 | protected void onPause() { 101 | super.onPause(); 102 | getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/wechatpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/app/src/main/res/drawable/wechatpay.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 29 | 30 | 37 | 38 |