├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── assets
│ │ │ └── xposed_init
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── layout
│ │ │ │ └── activity_main.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── admin
│ │ │ └── hookwxyydemo
│ │ │ ├── MyProvider.java
│ │ │ ├── CommandUtils.java
│ │ │ ├── MyCursor.java
│ │ │ ├── MainActivity.java
│ │ │ └── Module.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── admin
│ │ │ └── hookwxyydemo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── admin
│ │ └── hookwxyydemo
│ │ └── ExampleInstrumentedTest.java
├── release
│ ├── send1.apk
│ ├── simulator.apk
│ └── output.json
├── jar
│ └── XposedBridgeApi-54.jar
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── 微信语音
├── xposed_init
├── 3.pcm
├── 微信语音模块的使用说明.doc
├── MyProvider.java
├── CommandUtils.java
├── MyCursor.java
└── Module.java
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .idea
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
└── misc.xml
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/微信语音/xposed_init:
--------------------------------------------------------------------------------
1 | com.example.admin.hookwxyydemo.Module
--------------------------------------------------------------------------------
/app/src/main/assets/xposed_init:
--------------------------------------------------------------------------------
1 | com.example.admin.hookwxyydemo.Module
--------------------------------------------------------------------------------
/微信语音/3.pcm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/微信语音/3.pcm
--------------------------------------------------------------------------------
/微信语音/微信语音模块的使用说明.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/微信语音/微信语音模块的使用说明.doc
--------------------------------------------------------------------------------
/app/release/send1.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/release/send1.apk
--------------------------------------------------------------------------------
/app/release/simulator.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/release/simulator.apk
--------------------------------------------------------------------------------
/app/jar/XposedBridgeApi-54.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/jar/XposedBridgeApi-54.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HookWxYYDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carrys17/HookWxYYDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/release/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1},"path":"app-release.apk","properties":{"packageId":"com.example.admin.hookwxyydemo","split":"","minSdkVersion":"15"}}]
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jan 11 16:23:49 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/admin/hookwxyydemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/admin/hookwxyydemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.admin.hookwxyydemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "com.example.admin.hookwxyydemo"
7 | minSdkVersion 15
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(include: ['*.jar'], dir: 'libs')
23 | implementation 'com.android.support:appcompat-v7:26.1.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
28 | provided files('jar/XposedBridgeApi-54.jar')
29 | }
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HookWxYYDemo
2 |
3 | hook微信语音,实现替换并发送指定语音
4 |
5 | 文章地址:https://www.jianshu.com/p/562d15ddc61c
6 |
7 | ### 【2018.1.17】初步提交
8 |
9 | 确定需要hook的对象和方法,是AudioRecord而不是MediaRecorder。
10 |
11 | 熟悉文件的流操作,以及AudioRecord的工作过程。
12 |
13 | Xposed的使用,注意是在before里面还是after中操作,另外这两者之间的操作也得掌握透彻。
14 |
15 |
16 | ### 【2018.1.18】 修改代码,实现进程间通信
17 |
18 | 1、通过控制一个在进程间通信的变量,把需要分开编译的代码全部整合在一起
19 |
20 | 2、模拟器上的hook需要给微信一个AudioRecord变量,并在微信语音输入的开始和结束时调用相应的操作
21 |
22 | ### 【2018.1.20】实现在模拟器中替换语音
23 |
24 | 由于模拟器点击录制语音时弹不出话筒,前两天的例子是自己实现一个AudioRecord,所以这次直接hook微信的AudioRecord的startRecording方法,并设置返回值,让微信的录制流程不能正常执行。然后hookgetRecordState方法,自己维护AudioRecord的开始和停止。在startRecording中设为starting状态,在stop时设置为stopped状态,这样微信就可以正常执行read函数了,我们在read中再hook改变发送的语音,即可实现在模拟器中替换发送的语音了。
25 |
26 |
27 |
28 | ### 【2018.1.22】 Xposed模块中封装两个Api接口
29 |
30 | 1、int pushAudio(String pcmfile);//使得每次微信发语音 都发出指定的pcm语音
31 |
32 | 2、int recordAudio(String pcmfile);//将微信录音 录到指定目录下
33 |
34 | ### 【2018.1.22】 自定义类继承XC_MethodHook,提高代码简洁性
35 |
36 | 1、将文件父目录的权限修改的从read中移动到stop操作
37 |
38 | 2、去掉多余的代码, 在stop删除临时文件
39 |
40 | 3、修改调用Api的逻辑,实现相应的功能调用
41 |
42 | ### 【2018.1.24】 实现在进程间调用封装好的Api接口
43 |
44 | 1、修改主界面布局。目前有五个语音可以录制,发送
45 |
46 | 2、利用ContentProvider来实现进程间通信
47 |
48 | ### 【2018.2.27】 目前的最终版
49 |
50 | 删除没有必要的类,简化代码
51 |
52 | ### 【2018.6.25】 更新兼容新版模拟器的语音发送
53 |
54 | 今天发现在模拟器上发送指定语音失败,经过测试后发现需要hook AudioRecord的构造函数,该构造函数有5个参数,其第五个参数bufferSizeInBytes的值在模拟器和
55 | 真机上有区别,所以将模拟器的值hook后修改为真机上的值即可。代码的修改即在Module里面增加对构造函数的hook
56 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
31 |
32 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
20 |
21 |
29 |
30 |
36 |
37 |
43 |
44 |
50 |
51 |
57 |
58 |
64 |
65 |
66 |
75 |
76 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/admin/hookwxyydemo/MyProvider.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.content.SharedPreferences;
7 | import android.database.Cursor;
8 | import android.net.Uri;
9 | import android.os.Bundle;
10 | import android.support.annotation.NonNull;
11 | import android.support.annotation.Nullable;
12 | import android.util.Log;
13 |
14 |
15 |
16 |
17 |
18 | /**
19 | * Created by admin on 2018/1/18.
20 | */
21 |
22 | public class MyProvider extends ContentProvider {
23 |
24 |
25 | private static final String TAG = "MyProvider";
26 |
27 | private int mMode = -1;
28 |
29 | private String mPath = "";
30 |
31 |
32 | @Override
33 | public boolean onCreate() {
34 | return false;
35 | }
36 |
37 | @Nullable
38 | @Override
39 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
40 | // SharedPreferences sp = getContext().getSharedPreferences("tmp", Context.MODE_WORLD_READABLE);
41 | // int mode = sp.getInt("mode",-1);
42 | // String path = sp.getString("path","");
43 |
44 | Bundle bundle = new Bundle();
45 | bundle.putInt("mode",mMode);
46 | bundle.putString("path",mPath);
47 | Log.i(TAG, "查询query: mMode -- "+mMode +", mPath -- "+mPath);
48 |
49 |
50 | MyCursor cursor = new MyCursor();
51 | cursor.setExtras(bundle);
52 | return cursor;
53 | // return null;
54 | }
55 |
56 | @Nullable
57 | @Override
58 | public String getType(@NonNull Uri uri) {
59 | return null;
60 | }
61 |
62 | @Nullable
63 | @Override
64 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
65 | int mode = values.getAsInteger("mode");
66 | String path = values.getAsString("path");
67 | // SharedPreferences sp = getContext().getSharedPreferences("tmp",Context.MODE_WORLD_READABLE);
68 | // SharedPreferences.Editor editor = sp.edit();
69 | // editor.putInt("mode",mode);
70 | // editor.putString("path",path);
71 | // editor.commit();
72 | Log.i(TAG, "insert: 插入数据 -- mode = "+ mode);
73 | Log.i(TAG, "insert: 插入数据 -- path = "+ path);
74 |
75 |
76 | mMode = mode;
77 | mPath = path;
78 | Log.i(TAG, "插入insert: mMode -- "+mMode +", mPath -- "+mPath);
79 | return null;
80 | }
81 |
82 | @Override
83 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
84 | return 0;
85 | }
86 |
87 | @Override
88 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
89 | int mode = values.getAsInteger("mode");
90 | String path = values.getAsString("path");
91 | // SharedPreferences sharedPreferences = getContext().getSharedPreferences("tmp", Context.MODE_WORLD_READABLE);
92 | // SharedPreferences.Editor editor = sharedPreferences.edit();
93 | // editor.putInt("mode",mode);
94 | // editor.putString("path",path);
95 | // editor.commit();
96 | // Log.i(TAG,"contentprovider更新数据 mode = "+mode + ", path = "+path);
97 |
98 | mMode = mode;
99 | mPath = path;
100 | Log.i(TAG, "更新update: mMode -- "+mMode +", mPath -- "+mPath);
101 | return 0;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/微信语音/MyProvider.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.content.SharedPreferences;
7 | import android.database.Cursor;
8 | import android.net.Uri;
9 | import android.os.Bundle;
10 | import android.support.annotation.NonNull;
11 | import android.support.annotation.Nullable;
12 | import android.util.Log;
13 |
14 | import java.io.File;
15 | import java.io.IOException;
16 |
17 | import static com.example.admin.hookwxyydemo.CommandUtils.execCommand;
18 |
19 | /**
20 | * Created by admin on 2018/1/18.
21 | */
22 |
23 | public class MyProvider extends ContentProvider {
24 |
25 |
26 | private static final String TAG = "MyProvider";
27 |
28 | private int mMode = -1;
29 |
30 | private String mPath = "";
31 |
32 |
33 | @Override
34 | public boolean onCreate() {
35 | return false;
36 | }
37 |
38 | @Nullable
39 | @Override
40 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
41 | // SharedPreferences sp = getContext().getSharedPreferences("tmp", Context.MODE_WORLD_READABLE);
42 | // int mode = sp.getInt("mode",-1);
43 | // String path = sp.getString("path","");
44 |
45 | Bundle bundle = new Bundle();
46 | bundle.putInt("mode",mMode);
47 | bundle.putString("path",mPath);
48 | Log.i(TAG, "查询query: mMode -- "+mMode +", mPath -- "+mPath);
49 |
50 |
51 | MyCursor cursor = new MyCursor();
52 | cursor.setExtras(bundle);
53 | return cursor;
54 | // return null;
55 | }
56 |
57 | @Nullable
58 | @Override
59 | public String getType(@NonNull Uri uri) {
60 | return null;
61 | }
62 |
63 | @Nullable
64 | @Override
65 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
66 | int mode = values.getAsInteger("mode");
67 | String path = values.getAsString("path");
68 | // SharedPreferences sp = getContext().getSharedPreferences("tmp",Context.MODE_WORLD_READABLE);
69 | // SharedPreferences.Editor editor = sp.edit();
70 | // editor.putInt("mode",mode);
71 | // editor.putString("path",path);
72 | // editor.commit();
73 | Log.i(TAG, "insert: 插入数据 -- mode = "+ mode);
74 | Log.i(TAG, "insert: 插入数据 -- path = "+ path);
75 |
76 |
77 | mMode = mode;
78 | mPath = path;
79 | Log.i(TAG, "插入insert: mMode -- "+mMode +", mPath -- "+mPath);
80 | return null;
81 | }
82 |
83 | @Override
84 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
85 | return 0;
86 | }
87 |
88 | @Override
89 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
90 | int mode = values.getAsInteger("mode");
91 | String path = values.getAsString("path");
92 | // SharedPreferences sharedPreferences = getContext().getSharedPreferences("tmp", Context.MODE_WORLD_READABLE);
93 | // SharedPreferences.Editor editor = sharedPreferences.edit();
94 | // editor.putInt("mode",mode);
95 | // editor.putString("path",path);
96 | // editor.commit();
97 | // Log.i(TAG,"contentprovider更新数据 mode = "+mode + ", path = "+path);
98 |
99 | mMode = mode;
100 | mPath = path;
101 | Log.i(TAG, "更新update: mMode -- "+mMode +", mPath -- "+mPath);
102 | return 0;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/微信语音/CommandUtils.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.DataOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 |
10 | /**
11 | * Created by admin on 2017/12/21.
12 | */
13 |
14 | public class CommandUtils {
15 | public static final String TAG = "CommandExecution";
16 |
17 | public final static String COMMAND_SU = "su";
18 | public final static String COMMAND_SH = "sh";
19 | public final static String COMMAND_EXIT = "exit\n";
20 | public final static String COMMAND_LINE_END = "\n";
21 |
22 | /**
23 | * Command执行结果
24 | * @author Mountain
25 | *
26 | */
27 | public static class CommandResult {
28 | public int result = -1;
29 | public String errorMsg;
30 | public String successMsg;
31 | }
32 |
33 | /**
34 | * 执行命令—单条
35 | * @param command
36 | * @param isRoot
37 | * @return
38 | */
39 | public static CommandResult execCommand(String command, boolean isRoot) {
40 | String[] commands = {command};
41 | return execCommand(commands, isRoot);
42 | }
43 |
44 | /**
45 | * 执行命令-多条
46 | * @param commands
47 | * @param isRoot
48 | * @return
49 | */
50 | public static CommandResult execCommand(String[] commands, boolean isRoot) {
51 | CommandResult commandResult = new CommandResult();
52 | if (commands == null || commands.length == 0) return commandResult;
53 | Process process = null;
54 | DataOutputStream os = null;
55 | BufferedReader successResult = null;
56 | BufferedReader errorResult = null;
57 | StringBuilder successMsg = null;
58 | StringBuilder errorMsg = null;
59 | try {
60 | process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
61 | os = new DataOutputStream(process.getOutputStream());
62 | for (String command : commands) {
63 | if (command != null) {
64 | os.write(command.getBytes());
65 | os.writeBytes(COMMAND_LINE_END);
66 | os.flush();
67 | }
68 | }
69 | os.writeBytes(COMMAND_EXIT);
70 | os.flush();
71 | commandResult.result = process.waitFor();
72 | //获取错误信息
73 | successMsg = new StringBuilder();
74 | errorMsg = new StringBuilder();
75 | successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
76 | errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
77 | String s;
78 | while ((s = successResult.readLine()) != null) successMsg.append(s);
79 | while ((s = errorResult.readLine()) != null) errorMsg.append(s);
80 | commandResult.successMsg = successMsg.toString();
81 | commandResult.errorMsg = errorMsg.toString();
82 | Log.i(TAG, commandResult.result + " | " + commandResult.successMsg
83 | + " | " + commandResult.errorMsg);
84 | } catch (IOException e) {
85 | String errmsg = e.getMessage();
86 | if (errmsg != null) {
87 | Log.e(TAG, errmsg);
88 | } else {
89 | e.printStackTrace();
90 | }
91 | } catch (Exception e) {
92 | String errmsg = e.getMessage();
93 | if (errmsg != null) {
94 | Log.e(TAG, errmsg);
95 | } else {
96 | e.printStackTrace();
97 | }
98 | } finally {
99 | try {
100 | if (os != null) os.close();
101 | if (successResult != null) successResult.close();
102 | if (errorResult != null) errorResult.close();
103 | } catch (IOException e) {
104 | String errmsg = e.getMessage();
105 | if (errmsg != null) {
106 | Log.e(TAG, errmsg);
107 | } else {
108 | e.printStackTrace();
109 | }
110 | }
111 | if (process != null) process.destroy();
112 | }
113 | return commandResult;
114 | }
115 |
116 | }
117 |
118 |
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/admin/hookwxyydemo/CommandUtils.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.DataOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 |
10 | import de.robv.android.xposed.XC_MethodHook;
11 |
12 | /**
13 | * Created by admin on 2017/12/21.
14 | */
15 |
16 | public class CommandUtils{
17 | public static final String TAG = "CommandExecution";
18 |
19 | public final static String COMMAND_SU = "su";
20 | public final static String COMMAND_SH = "sh";
21 | public final static String COMMAND_EXIT = "exit\n";
22 | public final static String COMMAND_LINE_END = "\n";
23 |
24 | /**
25 | * Command执行结果
26 | * @author Mountain
27 | *
28 | */
29 | public static class CommandResult {
30 | public int result = -1;
31 | public String errorMsg;
32 | public String successMsg;
33 | }
34 |
35 | /**
36 | * 执行命令—单条
37 | * @param command
38 | * @param isRoot
39 | * @return
40 | */
41 | public static CommandResult execCommand(String command, boolean isRoot) {
42 | String[] commands = {command};
43 | return execCommand(commands, isRoot);
44 | }
45 |
46 | /**
47 | * 执行命令-多条
48 | * @param commands
49 | * @param isRoot
50 | * @return
51 | */
52 | public static CommandResult execCommand(String[] commands, boolean isRoot) {
53 | CommandResult commandResult = new CommandResult();
54 | if (commands == null || commands.length == 0) return commandResult;
55 | Process process = null;
56 | DataOutputStream os = null;
57 | BufferedReader successResult = null;
58 | BufferedReader errorResult = null;
59 | StringBuilder successMsg = null;
60 | StringBuilder errorMsg = null;
61 | try {
62 | process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
63 | os = new DataOutputStream(process.getOutputStream());
64 | for (String command : commands) {
65 | if (command != null) {
66 | os.write(command.getBytes());
67 | os.writeBytes(COMMAND_LINE_END);
68 | os.flush();
69 | }
70 | }
71 | os.writeBytes(COMMAND_EXIT);
72 | os.flush();
73 | commandResult.result = process.waitFor();
74 | //获取错误信息
75 | successMsg = new StringBuilder();
76 | errorMsg = new StringBuilder();
77 | successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
78 | errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
79 | String s;
80 | while ((s = successResult.readLine()) != null) successMsg.append(s);
81 | while ((s = errorResult.readLine()) != null) errorMsg.append(s);
82 | commandResult.successMsg = successMsg.toString();
83 | commandResult.errorMsg = errorMsg.toString();
84 | Log.i(TAG, commandResult.result + " | " + commandResult.successMsg
85 | + " | " + commandResult.errorMsg);
86 | } catch (IOException e) {
87 | String errmsg = e.getMessage();
88 | if (errmsg != null) {
89 | Log.e(TAG, errmsg);
90 | } else {
91 | e.printStackTrace();
92 | }
93 | } catch (Exception e) {
94 | String errmsg = e.getMessage();
95 | if (errmsg != null) {
96 | Log.e(TAG, errmsg);
97 | } else {
98 | e.printStackTrace();
99 | }
100 | } finally {
101 | try {
102 | if (os != null) os.close();
103 | if (successResult != null) successResult.close();
104 | if (errorResult != null) errorResult.close();
105 | } catch (IOException e) {
106 | String errmsg = e.getMessage();
107 | if (errmsg != null) {
108 | Log.e(TAG, errmsg);
109 | } else {
110 | e.printStackTrace();
111 | }
112 | }
113 | if (process != null) process.destroy();
114 | }
115 | return commandResult;
116 | }
117 |
118 | }
119 |
120 |
121 |
--------------------------------------------------------------------------------
/微信语音/MyCursor.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.content.ContentResolver;
4 | import android.database.CharArrayBuffer;
5 | import android.database.ContentObserver;
6 | import android.database.Cursor;
7 | import android.database.DataSetObserver;
8 | import android.net.Uri;
9 | import android.os.Bundle;
10 |
11 | /**
12 | * Created by admin on 2018/1/18.
13 | */
14 |
15 | public class MyCursor implements Cursor {
16 |
17 | Bundle mBundle;
18 | @Override
19 | public int getCount() {
20 | return 0;
21 | }
22 |
23 | @Override
24 | public int getPosition() {
25 | return 0;
26 | }
27 |
28 | @Override
29 | public boolean move(int offset) {
30 | return false;
31 | }
32 |
33 | @Override
34 | public boolean moveToPosition(int position) {
35 | return false;
36 | }
37 |
38 | @Override
39 | public boolean moveToFirst() {
40 | return false;
41 | }
42 |
43 | @Override
44 | public boolean moveToLast() {
45 | return false;
46 | }
47 |
48 | @Override
49 | public boolean moveToNext() {
50 | return false;
51 | }
52 |
53 | @Override
54 | public boolean moveToPrevious() {
55 | return false;
56 | }
57 |
58 | @Override
59 | public boolean isFirst() {
60 | return false;
61 | }
62 |
63 | @Override
64 | public boolean isLast() {
65 | return false;
66 | }
67 |
68 | @Override
69 | public boolean isBeforeFirst() {
70 | return false;
71 | }
72 |
73 | @Override
74 | public boolean isAfterLast() {
75 | return false;
76 | }
77 |
78 | @Override
79 | public int getColumnIndex(String columnName) {
80 | return 0;
81 | }
82 |
83 | @Override
84 | public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
85 | return 0;
86 | }
87 |
88 | @Override
89 | public String getColumnName(int columnIndex) {
90 | return null;
91 | }
92 |
93 | @Override
94 | public String[] getColumnNames() {
95 | return new String[0];
96 | }
97 |
98 | @Override
99 | public int getColumnCount() {
100 | return 0;
101 | }
102 |
103 | @Override
104 | public byte[] getBlob(int columnIndex) {
105 | return new byte[0];
106 | }
107 |
108 | @Override
109 | public String getString(int columnIndex) {
110 | return null;
111 | }
112 |
113 | @Override
114 | public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
115 |
116 | }
117 |
118 | @Override
119 | public short getShort(int columnIndex) {
120 | return 0;
121 | }
122 |
123 | @Override
124 | public int getInt(int columnIndex) {
125 | return 0;
126 | }
127 |
128 | @Override
129 | public long getLong(int columnIndex) {
130 | return 0;
131 | }
132 |
133 | @Override
134 | public float getFloat(int columnIndex) {
135 | return 0;
136 | }
137 |
138 | @Override
139 | public double getDouble(int columnIndex) {
140 | return 0;
141 | }
142 |
143 | @Override
144 | public int getType(int columnIndex) {
145 | return 0;
146 | }
147 |
148 | @Override
149 | public boolean isNull(int columnIndex) {
150 | return false;
151 | }
152 |
153 | @Override
154 | public void deactivate() {
155 |
156 | }
157 |
158 | @Override
159 | public boolean requery() {
160 | return false;
161 | }
162 |
163 | @Override
164 | public void close() {
165 |
166 | }
167 |
168 | @Override
169 | public boolean isClosed() {
170 | return false;
171 | }
172 |
173 | @Override
174 | public void registerContentObserver(ContentObserver observer) {
175 |
176 | }
177 |
178 | @Override
179 | public void unregisterContentObserver(ContentObserver observer) {
180 |
181 | }
182 |
183 | @Override
184 | public void registerDataSetObserver(DataSetObserver observer) {
185 |
186 | }
187 |
188 | @Override
189 | public void unregisterDataSetObserver(DataSetObserver observer) {
190 |
191 | }
192 |
193 | @Override
194 | public void setNotificationUri(ContentResolver cr, Uri uri) {
195 |
196 | }
197 |
198 | @Override
199 | public Uri getNotificationUri() {
200 | return null;
201 | }
202 |
203 | @Override
204 | public boolean getWantsAllOnMoveCalls() {
205 | return false;
206 | }
207 |
208 | @Override
209 | public void setExtras(Bundle extras) {
210 | mBundle = extras;
211 | }
212 |
213 | @Override
214 | public Bundle getExtras() {
215 | return mBundle;
216 | }
217 |
218 | @Override
219 | public Bundle respond(Bundle extras) {
220 | return null;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/admin/hookwxyydemo/MyCursor.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.content.ContentResolver;
4 | import android.database.CharArrayBuffer;
5 | import android.database.ContentObserver;
6 | import android.database.Cursor;
7 | import android.database.DataSetObserver;
8 | import android.net.Uri;
9 | import android.os.Bundle;
10 |
11 | /**
12 | * Created by admin on 2018/1/18.
13 | */
14 |
15 | public class MyCursor implements Cursor {
16 |
17 | Bundle mBundle;
18 | @Override
19 | public int getCount() {
20 | return 0;
21 | }
22 |
23 | @Override
24 | public int getPosition() {
25 | return 0;
26 | }
27 |
28 | @Override
29 | public boolean move(int offset) {
30 | return false;
31 | }
32 |
33 | @Override
34 | public boolean moveToPosition(int position) {
35 | return false;
36 | }
37 |
38 | @Override
39 | public boolean moveToFirst() {
40 | return false;
41 | }
42 |
43 | @Override
44 | public boolean moveToLast() {
45 | return false;
46 | }
47 |
48 | @Override
49 | public boolean moveToNext() {
50 | return false;
51 | }
52 |
53 | @Override
54 | public boolean moveToPrevious() {
55 | return false;
56 | }
57 |
58 | @Override
59 | public boolean isFirst() {
60 | return false;
61 | }
62 |
63 | @Override
64 | public boolean isLast() {
65 | return false;
66 | }
67 |
68 | @Override
69 | public boolean isBeforeFirst() {
70 | return false;
71 | }
72 |
73 | @Override
74 | public boolean isAfterLast() {
75 | return false;
76 | }
77 |
78 | @Override
79 | public int getColumnIndex(String columnName) {
80 | return 0;
81 | }
82 |
83 | @Override
84 | public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
85 | return 0;
86 | }
87 |
88 | @Override
89 | public String getColumnName(int columnIndex) {
90 | return null;
91 | }
92 |
93 | @Override
94 | public String[] getColumnNames() {
95 | return new String[0];
96 | }
97 |
98 | @Override
99 | public int getColumnCount() {
100 | return 0;
101 | }
102 |
103 | @Override
104 | public byte[] getBlob(int columnIndex) {
105 | return new byte[0];
106 | }
107 |
108 | @Override
109 | public String getString(int columnIndex) {
110 | return null;
111 | }
112 |
113 | @Override
114 | public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
115 |
116 | }
117 |
118 | @Override
119 | public short getShort(int columnIndex) {
120 | return 0;
121 | }
122 |
123 | @Override
124 | public int getInt(int columnIndex) {
125 | return 0;
126 | }
127 |
128 | @Override
129 | public long getLong(int columnIndex) {
130 | return 0;
131 | }
132 |
133 | @Override
134 | public float getFloat(int columnIndex) {
135 | return 0;
136 | }
137 |
138 | @Override
139 | public double getDouble(int columnIndex) {
140 | return 0;
141 | }
142 |
143 | @Override
144 | public int getType(int columnIndex) {
145 | return 0;
146 | }
147 |
148 | @Override
149 | public boolean isNull(int columnIndex) {
150 | return false;
151 | }
152 |
153 | @Override
154 | public void deactivate() {
155 |
156 | }
157 |
158 | @Override
159 | public boolean requery() {
160 | return false;
161 | }
162 |
163 | @Override
164 | public void close() {
165 |
166 | }
167 |
168 | @Override
169 | public boolean isClosed() {
170 | return false;
171 | }
172 |
173 | @Override
174 | public void registerContentObserver(ContentObserver observer) {
175 |
176 | }
177 |
178 | @Override
179 | public void unregisterContentObserver(ContentObserver observer) {
180 |
181 | }
182 |
183 | @Override
184 | public void registerDataSetObserver(DataSetObserver observer) {
185 |
186 | }
187 |
188 | @Override
189 | public void unregisterDataSetObserver(DataSetObserver observer) {
190 |
191 | }
192 |
193 | @Override
194 | public void setNotificationUri(ContentResolver cr, Uri uri) {
195 |
196 | }
197 |
198 | @Override
199 | public Uri getNotificationUri() {
200 | return null;
201 | }
202 |
203 | @Override
204 | public boolean getWantsAllOnMoveCalls() {
205 | return false;
206 | }
207 |
208 | @Override
209 | public void setExtras(Bundle extras) {
210 | mBundle = extras;
211 | }
212 |
213 | @Override
214 | public Bundle getExtras() {
215 | return mBundle;
216 | }
217 |
218 | @Override
219 | public Bundle respond(Bundle extras) {
220 | return null;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/admin/hookwxyydemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.ContentResolver;
5 | import android.content.ContentUris;
6 | import android.content.ContentValues;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.database.Cursor;
10 | import android.net.Uri;
11 | import android.os.Build;
12 | import android.os.Environment;
13 | import android.provider.DocumentsContract;
14 | import android.provider.MediaStore;
15 | import android.support.v7.app.AppCompatActivity;
16 | import android.os.Bundle;
17 | import android.text.TextUtils;
18 | import android.util.Log;
19 | import android.view.View;
20 | import android.widget.Button;
21 | import android.widget.RadioButton;
22 | import android.widget.RadioGroup;
23 | import android.widget.TextView;
24 | import android.widget.Toast;
25 |
26 | import java.io.File;
27 | import java.io.IOException;
28 |
29 | import static android.widget.Toast.LENGTH_SHORT;
30 |
31 |
32 | public class MainActivity extends AppCompatActivity {
33 |
34 | private static final String TAG = "MainActivity";
35 | ContentResolver mResolver;
36 | Uri uri = Uri.parse("content://com.example.admin.hookwxyydemo.provider");
37 |
38 | String mFilePath = null;
39 |
40 | RadioGroup mGroup;
41 |
42 | Button mRecordBtn;
43 |
44 | Button mSendBtn;
45 |
46 |
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | setContentView(R.layout.activity_main);
52 |
53 |
54 |
55 | mGroup = findViewById(R.id.radioGroup);
56 | mGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
57 | @Override
58 | public void onCheckedChanged(RadioGroup group, int checkedId) {
59 | if (checkedId == R.id.radioButton1){
60 | mFilePath = "/data/local/tmp/1.pcm";
61 | }
62 | if (checkedId == R.id.radioButton2){
63 | mFilePath = "/data/local/tmp/2.pcm";
64 | }
65 | if (checkedId == R.id.radioButton3){
66 | mFilePath = "/data/local/tmp/3.pcm";
67 | }
68 | if (checkedId == R.id.radioButton4){
69 | mFilePath = "/data/local/tmp/4.pcm";
70 | }
71 | if (checkedId == R.id.radioButton5){
72 | mFilePath = "/data/local/tmp/5.pcm";
73 | }
74 | }
75 | });
76 |
77 |
78 | mRecordBtn = findViewById(R.id.record);
79 | mRecordBtn.setOnClickListener(new View.OnClickListener() {
80 | @Override
81 | public void onClick(View v) {
82 | if (mFilePath==null || mFilePath.equals("")){
83 | Toast.makeText(MainActivity.this,"请选择要录制的语音",Toast.LENGTH_SHORT).show();
84 | }else {
85 | Log.i(TAG, "mRecordBtn onClick: mFilePath "+mFilePath);
86 | recordAudio(mFilePath);
87 | Toast.makeText(MainActivity.this,"请前往微信录制",Toast.LENGTH_SHORT).show();
88 | }
89 | }
90 | });
91 |
92 | mSendBtn = findViewById(R.id.send);
93 | mSendBtn.setOnClickListener(new View.OnClickListener() {
94 | @Override
95 | public void onClick(View v) {
96 | if (mFilePath==null || mFilePath.equals("")){
97 | Toast.makeText(MainActivity.this,"请选择要发送的语音",Toast.LENGTH_SHORT).show();
98 | }else {
99 | Log.i(TAG, "mSendBtn onClick: mFilePath "+mFilePath);
100 | pushAudio(mFilePath);
101 | Toast.makeText(MainActivity.this,"请前往微信发送",Toast.LENGTH_SHORT).show();
102 | }
103 | }
104 | });
105 |
106 |
107 |
108 | // findViewById(R.id.id_record).setOnClickListener(new View.OnClickListener() {
109 | // @Override
110 | // public void onClick(View v) {
111 | // recordAudio("/data/local/tmp/zzz.pcm");
112 | // }
113 | // });
114 | //
115 | //
116 | // findViewById(R.id.id_replace).setOnClickListener(new View.OnClickListener() {
117 | // @Override
118 | // public void onClick(View v) {
119 | // pushAudio("/data/local/tmp/1.pcm");
120 | // }
121 | // });
122 |
123 | }
124 |
125 |
126 |
127 |
128 | /**
129 | * 将微信语音录到指定目录下
130 | * @param pcmFile 输出到指定的文件目录 例如:/data/local/tmp/zzz.pcm
131 | * @return
132 | */
133 | private void recordAudio(String pcmFile){
134 | mResolver = getApplicationContext().getContentResolver();
135 | ContentValues values = new ContentValues();
136 | values.put("mode",1);
137 | values.put("path",pcmFile);
138 | // 调用ContentProvider更新数据
139 | mResolver.update(uri,values,null,null);
140 | }
141 |
142 |
143 | /**
144 | * 发送指定语音
145 | * @param pcmFile 指定语音的文件名 例如: /data/local/tmp/1.pcm
146 | */
147 | private void pushAudio(String pcmFile){
148 | mResolver = getApplicationContext().getContentResolver();
149 | ContentValues values = new ContentValues();
150 | values.put("mode",0);
151 | values.put("path",pcmFile);
152 | // 调用ContentProvider更新数据
153 | mResolver.update(uri,values,null,null);
154 | }
155 |
156 |
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/微信语音/Module.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.Manifest;
4 | import android.annotation.SuppressLint;
5 | import android.app.Activity;
6 | import android.content.ContentResolver;
7 | import android.content.Context;
8 | import android.content.pm.PackageManager;
9 | import android.database.Cursor;
10 | import android.media.AudioFormat;
11 | import android.media.AudioManager;
12 | import android.media.AudioRecord;
13 | import android.media.MediaRecorder;
14 | import android.media.MediaSyncEvent;
15 | import android.net.Uri;
16 | import android.os.Bundle;
17 | import android.os.Handler;
18 | import android.support.v4.app.ActivityCompat;
19 | import android.util.Log;
20 |
21 | import java.io.File;
22 | import java.io.FileDescriptor;
23 | import java.io.FileInputStream;
24 | import java.io.FileOutputStream;
25 | import java.io.OutputStream;
26 | import java.nio.ByteBuffer;
27 | import java.nio.file.Files;
28 | import java.util.Arrays;
29 | import java.util.HashMap;
30 |
31 | import de.robv.android.xposed.IXposedHookLoadPackage;
32 | import de.robv.android.xposed.XC_MethodHook;
33 | import de.robv.android.xposed.XposedBridge;
34 | import de.robv.android.xposed.XposedHelpers;
35 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
36 |
37 | import static com.example.admin.hookwxyydemo.CommandUtils.execCommand;
38 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
39 | import static de.robv.android.xposed.XposedHelpers.findClass;
40 | import static de.robv.android.xposed.XposedHelpers.newInstance;
41 |
42 | /**
43 | * Created by admin on 2018/1/11.
44 | */
45 |
46 | public class Module implements IXposedHookLoadPackage {
47 |
48 | private static final String TAG = "Module";
49 |
50 | // 每个AudioRecord对应的文件输出流(FileOutputStream 指向自己录制语音的临时文件)
51 | private HashMap mFosMap = new HashMap<>();
52 |
53 |
54 | // 当前临时文件名的num值,例如 myPcmFile1 对应就是语音输入(自己)的第一个pcm文件
55 | int mNum = 0;
56 |
57 | // 每个AudioRecord对应的临时文件名
58 | private HashMap mPcmFileMap = new HashMap<>();
59 |
60 |
61 | // 指定模式 1为录音 0为发送指定语音
62 | private int mMode = -1;
63 |
64 |
65 | // 每个AudioRecord对应的state状态,用于维护当前录音状态
66 | private HashMap mRecordingFlagMap = new HashMap<>();
67 |
68 |
69 | // 将语音录到指定的pcm文件
70 | private String mRecordPcmFileName;
71 |
72 | // 发送指定的pcm文件
73 | private String mSendPcmFileName;
74 |
75 |
76 | // 每个AudioRecord对应的文件输入流(FileInputStream指向要替换的pcm文件)
77 | private HashMap mFisMap = new HashMap<>();
78 |
79 |
80 |
81 | Uri uri = Uri.parse("content://com.example.admin.hookwxyydemo.provider");
82 |
83 | ContentResolver mResolver;
84 |
85 | Context applicationContext;
86 |
87 |
88 |
89 | @Override
90 | public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
91 | try {
92 |
93 | if (loadPackageParam.packageName.equals("com.tencent.mm") ) {
94 | Log.i(TAG, "handleLoadPackage: 进来了com.tencent.mm方法");
95 |
96 | Runtime.getRuntime().exec("su");
97 |
98 | // 获取到当前进程的上下文
99 | try{
100 | Class> ContextClass = findClass("android.content.ContextWrapper",loadPackageParam.classLoader);
101 | findAndHookMethod(ContextClass, "getApplicationContext", new XC_MethodHook() {
102 | @Override
103 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
104 |
105 | super.afterHookedMethod(param);
106 | applicationContext = (Context) param.getResult();
107 | XposedBridge.log("得到上下文");
108 |
109 | if (applicationContext!=null){
110 | Log.i(TAG, "applicationContext 不为null ");
111 | mResolver = applicationContext.getContentResolver();
112 | }
113 |
114 |
115 |
116 | }
117 | });
118 | }catch (Throwable throwable){
119 | XposedBridge.log("获取上下文失败 "+throwable);
120 | }
121 |
122 |
123 | // // TODO
124 | // recordAudio("/data/local/tmp/zzz.pcm");
125 | // pushAudio("/data/local/tmp/1.pcm");
126 |
127 | //
128 | // if (mMode == 1){
129 | // recordAudio(mRecordPcmFileName);
130 | // }else if (mMode==0){
131 | // pushAudio(mSendPcmFileName);
132 | // }
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | // hook startRecording 方法,当发送指定语音时需要hook这个函数,将微信的流程打断,自己维护整个发送过程
143 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
144 | loadPackageParam.classLoader, "startRecording",new StartRecordingMethodHook());
145 |
146 |
147 | // hook getRecordingState 方法,当发送指定语音时需要hook这个函数
148 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
149 | loadPackageParam.classLoader, "getRecordingState",new GetRecordingStateMethodHook());
150 |
151 |
152 | // hook read 方法,当发送指定语音时需要hook这个函数需要在before操作,当录入自己的语音文件时需要在after操作
153 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
154 | loadPackageParam.classLoader, "read", byte[].class, int.class,
155 | int.class, new ReadMethodHook());
156 |
157 |
158 | // hook stop 方法,当发送指定语音时需要hook这个函数需要在before操作,当录入自己的语音文件时需要在after操作
159 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
160 | loadPackageParam.classLoader, "stop",new StopMethodHook() );
161 |
162 |
163 | // hook release 方法,当发送指定语音时需要hook这个函数,打断微信
164 | XposedHelpers.findAndHookMethod("android.media.AudioRecord", loadPackageParam.classLoader,
165 | "release", new ReleaseMethodHook());
166 |
167 |
168 |
169 | }
170 |
171 | } catch (Exception e) {
172 | Log.i(TAG, "handleLoadPackage Exception: " + e.getMessage());
173 | e.printStackTrace();
174 | }
175 | }
176 |
177 | // 获取当前的模式以及指定文件目录
178 | private void getCurrentModeAndPath() {
179 | int mode = -1;
180 | String path = "";
181 | if (mResolver!=null){
182 | Log.i(TAG, "mResolver 不为null ");
183 | Cursor cursor = mResolver.query(uri,null,null,null,null);
184 | Bundle bundle = cursor.getExtras();
185 | mode = bundle.getInt("mode");
186 | path =bundle.getString("path");
187 | }
188 | Log.i(TAG, "query 得到的: mode = "+mode);
189 | Log.i(TAG, "query 得到的: path = "+path);
190 | if (mode ==1){
191 | Log.i(TAG, "mode ==1 ");
192 | mRecordPcmFileName = path;
193 | mMode = 1;
194 | Log.i(TAG, "mMode = : "+mMode);
195 | }
196 | if (mode ==0){
197 | Log.i(TAG, "mode ==0 ");
198 | mSendPcmFileName = path;
199 | mMode = 0;
200 | Log.i(TAG, "mMode = : "+mMode);
201 | }
202 | Log.i(TAG, "mMode ---- " + mMode);
203 |
204 | }
205 |
206 |
207 | // 当发送指定语音时需要hook这个函数需要在before操作,当录入自己的语音文件时需要在after操作
208 | private class ReadMethodHook extends XC_MethodHook{
209 | // 发送指定语音
210 | @Override
211 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
212 | try {
213 | if (mMode == 0) {
214 | AudioRecord record = (AudioRecord) param.thisObject;
215 | byte[] buffer = (byte[]) param.args[0];
216 | int off = (int) param.args[1];
217 | int size = (int) param.args[2];
218 |
219 | FileInputStream fis;
220 |
221 | // 指定发送的语音文件
222 | if (mFisMap.get(record)==null) {
223 | fis = new FileInputStream(mSendPcmFileName);
224 | mFisMap.put(record,fis);
225 | }else {
226 | fis = mFisMap.get(record);
227 | }
228 |
229 | // 创建byte[]数据,用来替换微信的buffer
230 | int min = Math.min(buffer.length - off, size);
231 | byte[] bytes = new byte[min];
232 | // 将指定的语音文件读取到微信的语音文件,实现替换发送指定语音
233 | int res = fis.read(bytes);
234 |
235 | if (res == -1) {
236 | param.setResult(0);
237 | } else {
238 | for (int i = 0; i < bytes.length; i++) {
239 | buffer[off + i] = bytes[i];
240 | }
241 | param.setResult(res);
242 | }
243 | }
244 | } catch (Exception e) {
245 | e.printStackTrace();
246 | }
247 | }
248 |
249 | // 录入自己的语音文件时
250 | @Override
251 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
252 |
253 |
254 | try {
255 |
256 | getCurrentModeAndPath();
257 | if (mMode == 1) {
258 |
259 | // File ff = new File(mRecordPcmFileName);
260 | // if (ff.exists()){
261 | // ff.delete();
262 | // }
263 | // // 问题就出在这,在这里修改父目录的权限就会报错,将这个操作放在stop方法也就是copy到指定的目录前再操作
264 | // String fp = ff.getParent();
265 | // Log.i(TAG, "父目录 : "+fp);
266 | // execCommand("chmod 777 "+fp,true);
267 |
268 |
269 | FileOutputStream fileOutputStream ;
270 | // 拿到当前的AudioRecord对象
271 | AudioRecord record = (AudioRecord) param.thisObject;
272 |
273 | byte[] buffer = (byte[]) param.args[0];
274 | Integer integer = (Integer) param.args[1];
275 | int offset = integer.intValue();
276 | Integer integer2 = (Integer) param.args[2];
277 | int size = integer2.intValue();
278 |
279 | // 创建输出的临时文件流
280 | if (mFosMap.get(record) == null) {
281 | execCommand("chmod 777 /data/local/tmp",true);
282 | String pcmFileName = "myPcmFile";
283 | mNum++;
284 | pcmFileName = pcmFileName + mNum;
285 | File file = new File("/data/local/tmp/" + pcmFileName + ".pcm");
286 | File fileParent = file.getParentFile();
287 | if (!fileParent.exists()) {
288 | fileParent.mkdirs();
289 | }
290 | file.createNewFile();
291 | Log.i(TAG, "pcmFileName: " + pcmFileName);
292 | fileOutputStream = new FileOutputStream("/data/local/tmp/" + pcmFileName + ".pcm");
293 | mFosMap.put(record, fileOutputStream);
294 | mPcmFileMap.put(record, pcmFileName);
295 | } else {
296 | fileOutputStream = mFosMap.get(record);
297 | }
298 |
299 | // 获取当前AudioRecord的read方法的返回值
300 | int read = (int) param.getResult();
301 |
302 | // read方法还在不断的执行中
303 | if (AudioRecord.ERROR_INVALID_OPERATION != read) {
304 | // 新建一个byte[] ,用于拿到微信的buffer数据
305 | byte[] bytes = new byte[read];
306 | // 将微信的buffer数据赋予到自己的byte[]中
307 | for (int i = 0; i < bytes.length; i++) {
308 | bytes[i] = buffer[i + offset];
309 | }
310 |
311 | // 将byte[] 写到临时的语音文件中,这样既可拿到当前语音输入的内容
312 | fileOutputStream.write(bytes);
313 | }
314 |
315 |
316 | }
317 | } catch (Exception e) {
318 | Log.i(TAG, "afterHookedMethod Exception: " + e.getMessage());
319 | e.printStackTrace();
320 | }
321 | }
322 | }
323 |
324 | private class StopMethodHook extends XC_MethodHook{
325 |
326 | // 发送指定语音
327 | @Override
328 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
329 |
330 | try {
331 | getCurrentModeAndPath();
332 | if (mMode == 0) {
333 | AudioRecord record = (AudioRecord) param.thisObject;
334 | // 关闭自己的文件输入,清理map
335 | if (mFisMap.get(record) != null) {
336 | FileInputStream fis = mFisMap.get(record);
337 | fis.close();
338 | mFisMap.remove(record);
339 | }
340 |
341 | // 将录音状态设置为stopped
342 | int flag = -1;
343 | if (mRecordingFlagMap.get(record) == null || mRecordingFlagMap.get(record) != AudioRecord.RECORDSTATE_STOPPED) {
344 | flag = AudioRecord.RECORDSTATE_STOPPED;
345 | mRecordingFlagMap.put(record, flag);
346 | }
347 | // 打断微信,完成发送指定的语音文件
348 | Object o = new Object();
349 | param.setResult(o);
350 | }
351 | } catch (Exception e) {
352 | Log.i(TAG, "AudioRecord # stop 里的 beforeHookedMethod出错 : " + e.getMessage());
353 | }
354 | }
355 |
356 | // 录入自己的语音文件
357 | @Override
358 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
359 | try {
360 | getCurrentModeAndPath();
361 | if (mMode == 1) {
362 | AudioRecord record = (AudioRecord) param.thisObject;
363 | if (mFosMap.get(record) != null) {
364 | // 关闭文件输出流,清理map
365 | FileOutputStream fos = mFosMap.get(record);
366 | fos.close();
367 | mFosMap.remove(record);
368 |
369 | // 修改指定输出文件的父目录权限。
370 | File file = new File(mRecordPcmFileName);
371 | String parentPath = file.getParent();
372 | execCommand("chmod 777 "+parentPath,true);
373 |
374 | // 覆盖拷贝到指定的文件目录
375 | String pcmFileName = mPcmFileMap.get(record);
376 | execCommand("chmod 777 /data/local/tmp/" + pcmFileName + ".pcm", true);
377 | execCommand("\\cp /data/local/tmp/" + pcmFileName + ".pcm " + mRecordPcmFileName, true);
378 | Log.i(TAG, "命令 : \\cp /data/local/tmp/" + pcmFileName + ".pcm " + mRecordPcmFileName);
379 | execCommand("chmod 777 " + mRecordPcmFileName, true);
380 | // 删除临时文件
381 | execCommand("rm /data/local/tmp/" + pcmFileName + ".pcm", true);
382 |
383 | // 清理map
384 | mPcmFileMap.remove(record);
385 | }
386 | }
387 | } catch (Exception e) {
388 | Log.i(TAG, "AudioRecord # stop 里的 afterHookedMethod出错 : " + e.getMessage());
389 | }
390 | }
391 | }
392 |
393 |
394 | // StartRecordingMethodHook类,当发送指定语音时需要hook这个函数
395 | private class StartRecordingMethodHook extends XC_MethodHook{
396 |
397 | // 将recordingState置为RECORDSTATE_RECORDING,打断微信的发送过程,为了发送自己指定文件。
398 | @Override
399 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
400 |
401 | getCurrentModeAndPath();
402 |
403 | if (mMode == 0) {
404 | try {
405 | // 修改要发送的语音文件的权限
406 | File file = new File(mSendPcmFileName);
407 | String parentName = file.getParent();
408 | Log.i(TAG, "parentName: " + parentName);
409 |
410 | // 创建并修改要发送的语音文件的父目录
411 | File fileParent = file.getParentFile();
412 | if (!fileParent.exists()) {
413 | fileParent.mkdirs();
414 | }
415 | execCommand("chmod 777 " + parentName, true);
416 | file.createNewFile();
417 |
418 | execCommand("chmod 777 " + mSendPcmFileName, true);
419 |
420 | AudioRecord record = (AudioRecord) param.thisObject;
421 | int flag = -1;
422 |
423 | // 将录音状态置为RECORDSTATE_RECORDING状态
424 | if (mRecordingFlagMap.get(record) == null || mRecordingFlagMap.get(record) != AudioRecord.RECORDSTATE_RECORDING) {
425 | flag = AudioRecord.RECORDSTATE_RECORDING;
426 | mRecordingFlagMap.put(record, flag);
427 | }
428 |
429 | // 打断微信的录音过程
430 | Object o = new Object();
431 | param.setResult(o);
432 |
433 |
434 | } catch (Exception e) {
435 | Log.i(TAG, "AudioRecord # startRecording beforeHookedMethod 出错");
436 | Log.i(TAG, "出错原因 —— " + e.getMessage());
437 | }
438 | }
439 | }
440 | }
441 |
442 |
443 | // getRecordingState,获取我们在startRecording和Stop中维护的state的值
444 | private class GetRecordingStateMethodHook extends XC_MethodHook{
445 | @Override
446 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
447 | getCurrentModeAndPath();
448 | if (mMode == 0) {
449 | try {
450 | // 获取我们在startRecording和Stop中维护的state的值
451 | AudioRecord record = (AudioRecord) param.thisObject;
452 | int res = mRecordingFlagMap.get(record) == null ? AudioRecord.RECORDSTATE_STOPPED : mRecordingFlagMap.get(record);
453 | // 将返回值给微信
454 | param.setResult(res);
455 | // 清理mRecordFlagMap
456 | mRecordingFlagMap.remove(record);
457 | } catch (Exception e) {
458 | Log.i(TAG, "AudioRecord # getRecordingState beforeHookedMethod 出错: " + e.getMessage());
459 | }
460 | }
461 | }
462 | }
463 |
464 | // release方法,打断微信的
465 | private class ReleaseMethodHook extends XC_MethodHook{
466 | @Override
467 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
468 | getCurrentModeAndPath();
469 | if (mMode == 0) {
470 | Log.i(TAG, "AudioRecord # release beforeHookedMethod ");
471 | Object o = new Object();
472 | param.setResult(o);
473 | }
474 | }
475 | }
476 | }
477 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/admin/hookwxyydemo/Module.java:
--------------------------------------------------------------------------------
1 | package com.example.admin.hookwxyydemo;
2 |
3 | import android.Manifest;
4 | import android.annotation.SuppressLint;
5 | import android.app.Activity;
6 | import android.app.AndroidAppHelper;
7 | import android.content.ContentResolver;
8 | import android.content.Context;
9 | import android.content.pm.ApplicationInfo;
10 | import android.content.pm.PackageManager;
11 | import android.database.Cursor;
12 | import android.media.AudioFormat;
13 | import android.media.AudioManager;
14 | import android.media.AudioRecord;
15 | import android.media.MediaRecorder;
16 | import android.media.MediaSyncEvent;
17 | import android.net.Uri;
18 | import android.os.Bundle;
19 | import android.os.Handler;
20 | import android.support.v4.app.ActivityCompat;
21 | import android.util.Log;
22 |
23 | import java.io.File;
24 | import java.io.FileDescriptor;
25 | import java.io.FileInputStream;
26 | import java.io.FileOutputStream;
27 | import java.io.OutputStream;
28 | import java.nio.ByteBuffer;
29 | import java.nio.file.Files;
30 | import java.util.Arrays;
31 | import java.util.HashMap;
32 |
33 | import de.robv.android.xposed.IXposedHookLoadPackage;
34 | import de.robv.android.xposed.XC_MethodHook;
35 | import de.robv.android.xposed.XposedBridge;
36 | import de.robv.android.xposed.XposedHelpers;
37 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
38 |
39 | import static com.example.admin.hookwxyydemo.CommandUtils.execCommand;
40 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
41 | import static de.robv.android.xposed.XposedHelpers.findClass;
42 | import static de.robv.android.xposed.XposedHelpers.newInstance;
43 |
44 | /**
45 | * Created by admin on 2018/1/11.
46 | */
47 |
48 | public class Module implements IXposedHookLoadPackage {
49 |
50 | private static final String TAG = "Module";
51 |
52 | // 每个AudioRecord对应的文件输出流(FileOutputStream 指向自己录制语音的临时文件)
53 | private HashMap mFosMap = new HashMap<>();
54 |
55 |
56 | // 当前临时文件名的num值,例如 myPcmFile1 对应就是语音输入(自己)的第一个pcm文件
57 | int mNum = 0;
58 |
59 | // 每个AudioRecord对应的临时文件名
60 | private HashMap mPcmFileMap = new HashMap<>();
61 |
62 |
63 | // 指定模式 1为录音 0为发送指定语音
64 | private int mMode = -1;
65 |
66 |
67 | // 每个AudioRecord对应的state状态,用于维护当前录音状态
68 | private HashMap mRecordingFlagMap = new HashMap<>();
69 |
70 |
71 | // 将语音录到指定的pcm文件
72 | private String mRecordPcmFileName;
73 |
74 | // 发送指定的pcm文件
75 | private String mSendPcmFileName;
76 |
77 |
78 | // 每个AudioRecord对应的文件输入流(FileInputStream指向要替换的pcm文件)
79 | private HashMap mFisMap = new HashMap<>();
80 |
81 |
82 |
83 | Uri uri = Uri.parse("content://com.example.admin.hookwxyydemo.provider");
84 |
85 | ContentResolver mResolver;
86 |
87 | Context applicationContext;
88 |
89 |
90 |
91 | @Override
92 | public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
93 | try {
94 |
95 | if ( loadPackageParam.packageName.equals("com.tencent.mm") ) {
96 | Log.i(TAG, "handleLoadPackage: 进来了com.tencent.mm方法");
97 |
98 | AndroidAppHelper.currentApplication();
99 |
100 | AndroidAppHelper.currentApplicationInfo();
101 | Runtime.getRuntime().exec("su");
102 |
103 | // 获取到当前进程的上下文
104 | try{
105 | Class> ContextClass = findClass("android.content.ContextWrapper",loadPackageParam.classLoader);
106 | findAndHookMethod(ContextClass, "getApplicationContext", new XC_MethodHook() {
107 | @Override
108 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
109 | applicationContext = (Context) param.getResult();
110 | XposedBridge.log("得到上下文");
111 |
112 |
113 |
114 | if (applicationContext!=null){
115 | Log.i(TAG, "applicationContext 不为null ");
116 | mResolver = applicationContext.getContentResolver();
117 | }
118 | }
119 | });
120 | }catch (Throwable throwable){
121 | XposedBridge.log("获取上下文失败 "+throwable);
122 | }
123 |
124 |
125 |
126 |
127 | // hook startRecording 方法,当发送指定语音时需要hook这个函数,将微信的流程打断,自己维护整个发送过程
128 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
129 | loadPackageParam.classLoader, "startRecording",new StartRecordingMethodHook());
130 |
131 |
132 | // hook getRecordingState 方法,当发送指定语音时需要hook这个函数
133 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
134 | loadPackageParam.classLoader, "getRecordingState",new GetRecordingStateMethodHook());
135 |
136 |
137 | // hook read 方法,当发送指定语音时需要hook这个函数需要在before操作,当录入自己的语音文件时需要在after操作
138 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
139 | loadPackageParam.classLoader, "read", byte[].class, int.class,
140 | int.class, new ReadMethodHook());
141 |
142 |
143 | // hook stop 方法,当发送指定语音时需要hook这个函数需要在before操作,当录入自己的语音文件时需要在after操作
144 | XposedHelpers.findAndHookMethod("android.media.AudioRecord",
145 | loadPackageParam.classLoader, "stop",new StopMethodHook() );
146 |
147 |
148 | // hook release 方法,当发送指定语音时需要hook这个函数,打断微信
149 | XposedHelpers.findAndHookMethod("android.media.AudioRecord", loadPackageParam.classLoader,
150 | "release", new ReleaseMethodHook());
151 |
152 | // --- 构造方法int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
153 | // int bufferSizeInBytes
154 | XposedHelpers.findAndHookConstructor("android.media.AudioRecord", loadPackageParam.classLoader,
155 | int.class, int.class, int.class, int.class, int.class, new XC_MethodHook() {
156 | @Override
157 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
158 | Log.i(TAG, "AudioRecord # 构造方法beforeHookedMethod: ");
159 | // 修改
160 | param.args[0] = 1;
161 | param.args[1] = 16000;
162 | param.args[2] = 2;
163 | param.args[3] = 2;
164 | param.args[4] = 12800;
165 |
166 | }
167 |
168 | });
169 |
170 | }
171 |
172 | } catch (Exception e) {
173 | Log.i(TAG, "handleLoadPackage Exception: " + e.getMessage());
174 | e.printStackTrace();
175 | }
176 | }
177 |
178 | // 获取当前的模式以及指定文件目录
179 | private void getCurrentModeAndPath() {
180 | int mode = -1;
181 | String path = "";
182 | if (mResolver!=null){
183 | Log.i(TAG, "mResolver 不为null ");
184 | Cursor cursor = mResolver.query(uri,null,null,null,null);
185 | Bundle bundle = cursor.getExtras();
186 | mode = bundle.getInt("mode");
187 | path =bundle.getString("path");
188 | }
189 | Log.i(TAG, "query 得到的: mode = "+mode);
190 | Log.i(TAG, "query 得到的: path = "+path);
191 | if (mode ==1){
192 | Log.i(TAG, "mode ==1 ");
193 | mRecordPcmFileName = path;
194 | mMode = 1;
195 | Log.i(TAG, "mMode = : "+mMode);
196 | }
197 | if (mode ==0){
198 | Log.i(TAG, "mode ==0 ");
199 | mSendPcmFileName = path;
200 | mMode = 0;
201 | Log.i(TAG, "mMode = : "+mMode);
202 | }
203 | Log.i(TAG, "mMode ---- " + mMode);
204 |
205 | }
206 |
207 |
208 | // 当发送指定语音时需要hook这个函数需要在before操作,当录入自己的语音文件时需要在after操作
209 | private class ReadMethodHook extends XC_MethodHook{
210 | // 发送指定语音
211 | @Override
212 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
213 | try {
214 | getCurrentModeAndPath();
215 |
216 | if (mMode == 0) {
217 | AudioRecord record = (AudioRecord) param.thisObject;
218 | byte[] buffer = (byte[]) param.args[0];
219 | int off = (int) param.args[1];
220 | int size = (int) param.args[2];
221 |
222 | FileInputStream fis;
223 |
224 | // 指定发送的语音文件
225 | if (mFisMap.get(record)==null) {
226 | fis = new FileInputStream(mSendPcmFileName);
227 | mFisMap.put(record,fis);
228 | }else {
229 | fis = mFisMap.get(record);
230 | }
231 |
232 | // 创建byte[]数据,用来替换微信的buffer
233 | int min = Math.min(buffer.length - off, size);
234 | byte[] bytes = new byte[min];
235 | // 将指定的语音文件读取到微信的语音文件,实现替换发送指定语音
236 | int res = fis.read(bytes);
237 |
238 | if (res == -1) {
239 | param.setResult(0);
240 | } else {
241 | for (int i = 0; i < bytes.length; i++) {
242 | buffer[off + i] = bytes[i];
243 | }
244 | param.setResult(res);
245 | }
246 | }
247 | } catch (Exception e) {
248 | e.printStackTrace();
249 | }
250 | }
251 |
252 | // 录入自己的语音文件时
253 | @Override
254 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
255 | try {
256 | getCurrentModeAndPath();
257 | if (mMode == 1) {
258 |
259 | // File ff = new File(mRecordPcmFileName);
260 | // if (ff.exists()){
261 | // ff.delete();
262 | // }
263 | // // 问题就出在这,在这里修改父目录的权限就会报错,将这个操作放在stop方法也就是copy到指定的目录前再操作
264 | // String fp = ff.getParent();
265 | // Log.i(TAG, "父目录 : "+fp);
266 | // execCommand("chmod 777 "+fp,true);
267 |
268 |
269 | FileOutputStream fileOutputStream ;
270 | // 拿到当前的AudioRecord对象
271 | AudioRecord record = (AudioRecord) param.thisObject;
272 |
273 | byte[] buffer = (byte[]) param.args[0];
274 | Integer integer = (Integer) param.args[1];
275 | int offset = integer.intValue();
276 | Integer integer2 = (Integer) param.args[2];
277 | int size = integer2.intValue();
278 |
279 | // 创建输出的临时文件流
280 | if (mFosMap.get(record) == null) {
281 | execCommand("chmod 777 /data/local/tmp",true);
282 | String pcmFileName = "myPcmFile";
283 | mNum++;
284 | pcmFileName = pcmFileName + mNum;
285 | File file = new File("/data/local/tmp/" + pcmFileName + ".pcm");
286 | File fileParent = file.getParentFile();
287 | if (!fileParent.exists()) {
288 | fileParent.mkdirs();
289 | }
290 | file.createNewFile();
291 | Log.i(TAG, "pcmFileName: " + pcmFileName);
292 | fileOutputStream = new FileOutputStream("/data/local/tmp/" + pcmFileName + ".pcm");
293 | mFosMap.put(record, fileOutputStream);
294 | mPcmFileMap.put(record, pcmFileName);
295 | } else {
296 | fileOutputStream = mFosMap.get(record);
297 | }
298 |
299 | // 获取当前AudioRecord的read方法的返回值
300 | int read = (int) param.getResult();
301 |
302 | // read方法还在不断的执行中
303 | if (AudioRecord.ERROR_INVALID_OPERATION != read) {
304 | // 新建一个byte[] ,用于拿到微信的buffer数据
305 | byte[] bytes = new byte[read];
306 | // 将微信的buffer数据赋予到自己的byte[]中
307 | for (int i = 0; i < bytes.length; i++) {
308 | bytes[i] = buffer[i + offset];
309 | }
310 |
311 | // 将byte[] 写到临时的语音文件中,这样既可拿到当前语音输入的内容
312 | fileOutputStream.write(bytes);
313 | }
314 |
315 |
316 | }
317 | } catch (Exception e) {
318 | Log.i(TAG, "afterHookedMethod Exception: " + e.getMessage());
319 | e.printStackTrace();
320 | }
321 | }
322 | }
323 |
324 | private class StopMethodHook extends XC_MethodHook{
325 |
326 | // 发送指定语音
327 | @Override
328 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
329 | try {
330 | getCurrentModeAndPath();
331 | if (mMode == 0) {
332 | AudioRecord record = (AudioRecord) param.thisObject;
333 | // 关闭自己的文件输入,清理map
334 | if (mFisMap.get(record) != null) {
335 | FileInputStream fis = mFisMap.get(record);
336 | fis.close();
337 | mFisMap.remove(record);
338 | }
339 |
340 | // 将录音状态设置为stopped
341 | int flag = -1;
342 | if (mRecordingFlagMap.get(record) == null || mRecordingFlagMap.get(record) != AudioRecord.RECORDSTATE_STOPPED) {
343 | flag = AudioRecord.RECORDSTATE_STOPPED;
344 | mRecordingFlagMap.put(record, flag);
345 | }
346 | // 打断微信,完成发送指定的语音文件
347 | Object o = new Object();
348 | param.setResult(o);
349 | }
350 | } catch (Exception e) {
351 | Log.i(TAG, "AudioRecord # stop 里的 beforeHookedMethod出错 : " + e.getMessage());
352 | }
353 | }
354 |
355 | // 录入自己的语音文件
356 | @Override
357 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
358 | try {
359 | getCurrentModeAndPath();
360 | if (mMode == 1) {
361 | AudioRecord record = (AudioRecord) param.thisObject;
362 | if (mFosMap.get(record) != null) {
363 | // 关闭文件输出流,清理map
364 | FileOutputStream fos = mFosMap.get(record);
365 | fos.close();
366 | mFosMap.remove(record);
367 |
368 | // 修改指定输出文件的父目录权限。
369 | File file = new File(mRecordPcmFileName);
370 | String parentPath = file.getParent();
371 | execCommand("chmod 777 "+parentPath,true);
372 |
373 | // 覆盖拷贝到指定的文件目录
374 | String pcmFileName = mPcmFileMap.get(record);
375 | execCommand("chmod 777 /data/local/tmp/" + pcmFileName + ".pcm", true);
376 | execCommand("\\cp /data/local/tmp/" + pcmFileName + ".pcm " + mRecordPcmFileName, true);
377 | Log.i(TAG, "命令 : \\cp /data/local/tmp/" + pcmFileName + ".pcm " + mRecordPcmFileName);
378 | execCommand("chmod 777 " + mRecordPcmFileName, true);
379 | // 删除临时文件
380 | execCommand("rm /data/local/tmp/" + pcmFileName + ".pcm", true);
381 |
382 | // 清理map
383 | mPcmFileMap.remove(record);
384 | }
385 | }
386 | } catch (Exception e) {
387 | Log.i(TAG, "AudioRecord # stop 里的 afterHookedMethod出错 : " + e.getMessage());
388 | }
389 | }
390 | }
391 |
392 |
393 | // StartRecordingMethodHook类,当发送指定语音时需要hook这个函数
394 | private class StartRecordingMethodHook extends XC_MethodHook{
395 |
396 | // 将recordingState置为RECORDSTATE_RECORDING,打断微信的发送过程,为了发送自己指定文件。
397 | @Override
398 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
399 | try {
400 |
401 | getCurrentModeAndPath();
402 | if (mMode == 0) {
403 | // 修改要发送的语音文件的权限
404 | File file = new File(mSendPcmFileName);
405 | String parentName = file.getParent();
406 | Log.i(TAG, "parentName: " + parentName);
407 |
408 | // 创建并修改要发送的语音文件的父目录
409 | File fileParent = file.getParentFile();
410 | if (!fileParent.exists()) {
411 | fileParent.mkdirs();
412 | }
413 | execCommand("chmod 777 " + parentName, true);
414 | file.createNewFile();
415 |
416 | execCommand("chmod 777 " + mSendPcmFileName, true);
417 |
418 | AudioRecord record = (AudioRecord) param.thisObject;
419 | int flag = -1;
420 |
421 | // 将录音状态置为RECORDSTATE_RECORDING状态
422 | if (mRecordingFlagMap.get(record) == null || mRecordingFlagMap.get(record) != AudioRecord.RECORDSTATE_RECORDING) {
423 | flag = AudioRecord.RECORDSTATE_RECORDING;
424 | mRecordingFlagMap.put(record, flag);
425 | }
426 |
427 | // 打断微信的录音过程
428 | Object o = new Object();
429 | param.setResult(o);
430 |
431 | }
432 | } catch (Exception e) {
433 | Log.i(TAG, "AudioRecord # startRecording beforeHookedMethod 出错");
434 | Log.i(TAG, "出错原因 —— " + e.getMessage());
435 | }
436 | }
437 | }
438 |
439 |
440 | // getRecordingState,获取我们在startRecording和Stop中维护的state的值
441 | private class GetRecordingStateMethodHook extends XC_MethodHook{
442 | @Override
443 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
444 | try {
445 | getCurrentModeAndPath();
446 | if (mMode == 0) {
447 |
448 | // 获取我们在startRecording和Stop中维护的state的值
449 | AudioRecord record = (AudioRecord) param.thisObject;
450 | int res = mRecordingFlagMap.get(record) == null ? AudioRecord.RECORDSTATE_STOPPED : mRecordingFlagMap.get(record);
451 | // 将返回值给微信
452 | param.setResult(res);
453 | // 清理mRecordFlagMap
454 | mRecordingFlagMap.remove(record);
455 |
456 | }
457 | } catch (Exception e) {
458 | Log.i(TAG, "AudioRecord # getRecordingState beforeHookedMethod 出错: " + e.getMessage());
459 | }
460 | }
461 | }
462 |
463 | // release方法,打断微信的
464 | private class ReleaseMethodHook extends XC_MethodHook{
465 | @Override
466 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
467 | try {
468 | getCurrentModeAndPath();
469 | if (mMode == 0) {
470 | Log.i(TAG, "AudioRecord # release beforeHookedMethod ");
471 | Object o = new Object();
472 | param.setResult(o);
473 | }
474 | }catch (Exception e){
475 | Log.i(TAG, "AudioRecord # release beforeHookedMethod 出错: " + e.getMessage());
476 | }
477 |
478 | }
479 | }
480 | }
481 |
--------------------------------------------------------------------------------