├── .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 | 
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 |
43 |
44 |
50 |
51 |
52 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | QQSendPoke
3 | 如果喜欢我的程序,可以微信扫一下上面的二维码进行捐赠,那怕是一点点钱也是心意,谢谢......\n\n作者:hdfg159\n邮箱:hdfg159@126.com\nGithub:https://github.com/hdfg159/QQSendPoke
4 | 隐藏图标
5 | 显示图标
6 | 关于程序
7 | 感谢
8 | 设置
9 | 二维码图片已经保存到系统相册,打开微信(wechat)从相册选取并扫描二维码即可捐赠,谢谢捐赠…\n不想用微信支付也可以点击跳转到支付宝捐赠哦\n\n功能虽然不多,但是我也花上时间,希望能得到一点回报。
10 | 本程序仅供学习交流,请勿恶意使用,更不要用于商业用途。
11 | Settings
12 | 支持支付宝捐赠
13 | 提示
14 | 模块尚未激活,请前往Xposed框架的模块列表重新勾选并重启手机,否则模块不生效!
15 | 打开Xposed框架
16 | 忽略
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/sendpoke_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
18 |
25 |
26 |
27 |
30 |
33 |
34 |
37 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/test/java/hdfg159/qqsendpoke/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package hdfg159.qqsendpoke;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.5.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Sun Jul 24 18:22:15 CST 2016
16 |
--------------------------------------------------------------------------------
/screenshot/Main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/screenshot/Main.png
--------------------------------------------------------------------------------
/screenshot/Setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hdfg159/QQSendPoke/f85a958526fbed7657558114e8f574e33fe16365/screenshot/Setting.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------