diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..2bf2342
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 25a69b3..86abbe5 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,9 @@
9. 目录重定向消息默认只显示一次,如果错过了目录重定向的Toast消息,可以在`/[内部存储]/DCIM/Camera1/`目录下创建`force_show.jpg`文件来覆盖默认设定。(全局实时生效)
+10. 如果需要为每一个应用程序分配视频,可以在`/[内部存储]/DCIM/Camera1/`目录下创建`private_dir.jpg`强制使用应用程序私有目录。(全局实时生效)
+
+> 注意:6~10的配置开关均在应用程序中,您可以快捷地在应用程序中配置,也可以手动创建文件。
## 常见问题
diff --git a/README_en.md b/README_en.md
index 932be15..6bbcb1b 100644
--- a/README_en.md
+++ b/README_en.md
@@ -32,6 +32,9 @@ A virtual camera based on Xposed
9. The directory redirection message is displayed only once by default. If you miss the toast message of directory redirection, you can create a `force_show.jpg` file in the `/[INTERNEL_STORAGE]/DCIM/Camera1/` directory to override the default setting. (Global real-time effective)
+10. If you need to allocate videos for each application, you can create `private_dir.jpg` in the `/[INTERNEL_STORAGE]/DCIM/Camera1/` directory to enforce apps use private directory. (Global real-time effective)
+
+> Note: the configuration switches of 6 ~ 10 are in the application. You can quickly configure them in the application or create files manually.
## FAQ
diff --git a/app/build.gradle b/app/build.gradle
index 6d3d89c..0d669f4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,9 +8,9 @@ android {
defaultConfig {
applicationId "com.example.vcam"
minSdk 21
- targetSdk 27
- versionCode 26
- versionName "4.2"
+ targetSdk 26
+ versionCode 27
+ versionName "4.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
diff --git a/app/release/app-release.apk b/app/release/app-release.apk
index 1574c8f..e44237a 100644
Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
index 7fd8a82..5aef3bb 100644
--- a/app/release/output-metadata.json
+++ b/app/release/output-metadata.json
@@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
- "versionCode": 26,
- "versionName": "4.2",
+ "versionCode": 27,
+ "versionName": "4.3",
"outputFile": "app-release.apk"
}
],
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c2a045b..b6677c4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,4 +34,7 @@
android:value="51" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/vcam/HookMain.java b/app/src/main/java/com/example/vcam/HookMain.java
index 5094610..e2d13d8 100644
--- a/app/src/main/java/com/example/vcam/HookMain.java
+++ b/app/src/main/java/com/example/vcam/HookMain.java
@@ -4,6 +4,9 @@
import android.Manifest;
import android.app.Application;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.SurfaceTexture;
@@ -33,7 +36,6 @@
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
-import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
@@ -63,7 +65,6 @@ public class HookMain implements IXposedHookLoadPackage {
public static Camera mcamera1;
public int imageReaderFormat = 0;
public static boolean is_first_hook_build = true;
- public static XSharedPreferences xSharedPreferences ;
public static int onemhight;
public static int onemwidth;
@@ -135,11 +136,10 @@ protected void beforeHookedMethod(MethodHookParam param) {
need_to_show_toast = !toast_control.exists();
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
- XposedBridge.log("【VCAM】[toast]" + ee);
+ XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
-
}
}
}
@@ -166,12 +166,12 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
XposedBridge.log("【VCAM】1位参数初始化相机,类:" + c2_state_callback.toString());
is_first_hook_build = true;
@@ -201,12 +201,12 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
c2_state_callback = param.args[2].getClass();
XposedBridge.log("【VCAM】2位参数初始化相机,类:" + c2_state_callback.toString());
@@ -294,7 +294,8 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
} catch (Exception ee) {
XposedBridge.log("【VCAM】" + ee.toString());
}
- if (toast_content != null) {
+ File force_private = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/private_dir.jpg");
+ if (toast_content != null) {//后半段用于强制私有目录
int auth_statue = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
@@ -309,14 +310,13 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
} catch (Exception ee) {
XposedBridge.log("【VCAM】[permission-check]" + ee.toString());
}
+ }else {
+ if (toast_content.checkCallingPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ){
+ auth_statue = 2;
+ }
}
-
- File DCIM_dic = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/");
- if ((!DCIM_dic.canRead()) && auth_statue < 1) {
- auth_statue = -1;
-
- }
- if (auth_statue < 1) {
+ //权限判断完毕
+ if (auth_statue < 1 || force_private.exists()) {
File shown_file = new File(toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/");
if ((!shown_file.isDirectory()) && shown_file.exists()) {
shown_file.delete();
@@ -328,7 +328,7 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
File toast_force_file = new File(Environment.getExternalStorageDirectory().getPath()+ "/DCIM/Camera1/force_show.jpg");
if ((!lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) && ((!shown_file.exists()) || toast_force_file.exists())) {
try {
- Toast.makeText(toast_content, "未授予读取本地目录权限,请检查权限\nCamera1目前重定向为 " + toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/", Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, lpparam.packageName+"未授予读取本地目录权限,请检查权限\nCamera1目前重定向为 " + toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/", Toast.LENGTH_SHORT).show();
FileOutputStream fos = new FileOutputStream(toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/" + "has_shown");
String info = "shown";
fos.write(info.getBytes());
@@ -339,8 +339,11 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
}
}
video_path = toast_content.getExternalFilesDir(null).getAbsolutePath() + "/Camera1/";
+ }else {
+ video_path = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/";
}
} else {
+ video_path = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/";
File uni_DCIM_path = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/");
if (uni_DCIM_path.canWrite()) {
File uni_Camera1_path = new File(video_path);
@@ -362,12 +365,12 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg");
if (control_file.exists()) {
@@ -467,12 +470,12 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg");
if (control_file.exists()) {
@@ -517,12 +520,12 @@ protected void beforeHookedMethod(MethodHookParam param) {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
if (param.args[0].equals(c2_virtual_surface)) {
return;
@@ -571,12 +574,12 @@ protected void beforeHookedMethod(MethodHookParam param) {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg");
if (control_file.exists()) {
@@ -616,12 +619,12 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (!file.exists() && need_to_show_toast) {
if (toast_content != null) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + lpparam.packageName + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee.toString());
}
- return;
}
+ return;
}
File control_file = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "disable.jpg");
@@ -781,12 +784,11 @@ public void onPrepared(MediaPlayer mp) {
XposedBridge.log("【VCAM】[c2player1]" + "[ " + c2_preview_Surfcae_1.toString() + "]" + e);
}
}
- XposedBridge.log("【VCAM】处理过程完全执行");
+ XposedBridge.log("【VCAM】Camera2处理过程完全执行");
}
private Surface create_virtual_surface() {
if (need_recreate) {
- XposedBridge.log("【VCAM】重建垃圾场");
if (c2_virtual_surfaceTexture != null) {
c2_virtual_surfaceTexture.release();
c2_virtual_surfaceTexture = null;
@@ -804,7 +806,7 @@ private Surface create_virtual_surface() {
c2_virtual_surface = create_virtual_surface();
}
}
- XposedBridge.log("【重建垃圾场】" + c2_virtual_surface.toString());
+ XposedBridge.log("【VCAM】【重建垃圾场】" + c2_virtual_surface.toString());
return c2_virtual_surface;
}
@@ -841,36 +843,30 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
c2_preview_Surfcae = null;
is_first_hook_build = true;
XposedBridge.log("【VCAM】打开相机C2");
+
+ File file = new File(video_path + "virtual.mp4");
+ File toast_control = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera1/" + "no_toast.jpg");
+ need_to_show_toast = !toast_control.exists();
+ if (!file.exists()) {
+ if (toast_content != null && need_to_show_toast) {
+ try {
+ Toast.makeText(toast_content, "不存在替换视频\n" + toast_content.getPackageName() + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ } catch (Exception ee) {
+ XposedBridge.log("【VCAM】[toast]" + ee.toString());
+ }
+ }
+ return;
+ }
XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSession", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable {
- XposedBridge.log("【VCAM】创捷捕获,原始:" + paramd.args[0].toString() + "虚拟:" + c2_virtual_surface.toString());
- paramd.args[0] = Arrays.asList(c2_virtual_surface);
- XposedHelpers.findAndHookMethod(paramd.args[1].getClass(), "onConfigureFailed", CameraCaptureSession.class, new XC_MethodHook() {
- @Override
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- XposedBridge.log("【VCAM】onConfigureFailed :" + param.args[0].toString());
- }
-
- });
-
- XposedHelpers.findAndHookMethod(paramd.args[1].getClass(), "onConfigured", CameraCaptureSession.class, new XC_MethodHook() {
- @Override
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- XposedBridge.log("【VCAM】onConfigured :" + param.args[0].toString());
- }
-
- });
-
- /*XposedHelpers.findAndHookMethod( paramd.args[1].getClass(), "onClosed", CameraCaptureSession.class, new XC_MethodHook() {
- @Override
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- XposedBridge.log("onClosed :"+ param.args[0].toString());
+ if (paramd.args[0] != null) {
+ XposedBridge.log("【VCAM】createCaptureSession创捷捕获,原始:" + paramd.args[0].toString() + "虚拟:" + c2_virtual_surface.toString());
+ paramd.args[0] = Arrays.asList(c2_virtual_surface);
+ if (paramd.args[1] != null) {
+ process_camera2Session_callback((CameraCaptureSession.StateCallback) paramd.args[1]);
}
-
- });*/
-
-
+ }
}
});
@@ -903,25 +899,38 @@ protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable {
}
});*/
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSessionByOutputConfigurations", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ super.beforeHookedMethod(param);
+ if (param.args[0] != null) {
+ outputConfiguration = new OutputConfiguration(c2_virtual_surface);
+ param.args[0] = Arrays.asList(outputConfiguration);
- XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createCaptureSessionByOutputConfigurations", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() {
- @Override
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- super.beforeHookedMethod(param);
-
- XposedBridge.log("【VCAM】执行了createCaptureSessionByOutputConfigurations-144777");
- }
- });
-
+ XposedBridge.log("【VCAM】执行了createCaptureSessionByOutputConfigurations-144777");
+ if (param.args[1] != null) {
+ process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[1]);
+ }
+ }
+ }
+ });
+ }
- XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createConstrainedHighSpeedCaptureSession", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() {
- @Override
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- super.beforeHookedMethod(param);
- XposedBridge.log("【VCAM】执行了 createConstrainedHighSpeedCaptureSession -5484987");
- }
- });
+ XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createConstrainedHighSpeedCaptureSession", List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ super.beforeHookedMethod(param);
+ if (param.args[0] != null) {
+ param.args[0] = Arrays.asList(c2_virtual_surface);
+ XposedBridge.log("【VCAM】执行了 createConstrainedHighSpeedCaptureSession -5484987");
+ if (param.args[1] != null) {
+ process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[1]);
+ }
+ }
+ }
+ });
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -929,20 +938,31 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
-
- XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSession -5484987");
+ if (param.args[1] != null) {
+ param.args[1] = Arrays.asList(c2_virtual_surface);
+ XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSession ");
+ if (param.args[2] != null) {
+ process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[2]);
+ }
+ }
}
});
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
XposedHelpers.findAndHookMethod(param.args[0].getClass(), "createReprocessableCaptureSessionByConfigurations", InputConfiguration.class, List.class, CameraCaptureSession.StateCallback.class, Handler.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
-
- XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSessionByConfigurations -5484987");
+ if (param.args[1] != null) {
+ outputConfiguration = new OutputConfiguration(c2_virtual_surface);
+ param.args[0] = Arrays.asList(outputConfiguration);
+ XposedBridge.log("【VCAM】执行了 createReprocessableCaptureSessionByConfigurations");
+ if (param.args[2] != null) {
+ process_camera2Session_callback((CameraCaptureSession.StateCallback) param.args[2]);
+ }
+ }
}
});
}
@@ -955,15 +975,13 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.args[0] != null) {
XposedBridge.log("【VCAM】执行了 createCaptureSession -5484987");
sessionConfiguration = (SessionConfiguration) param.args[0];
-
outputConfiguration = new OutputConfiguration(c2_virtual_surface);
-
fake_sessionConfiguration = new SessionConfiguration(sessionConfiguration.getSessionType(),
Arrays.asList(outputConfiguration),
sessionConfiguration.getExecutor(),
sessionConfiguration.getStateCallback());
-
param.args[0] = fake_sessionConfiguration;
+ process_camera2Session_callback(sessionConfiguration.getStateCallback());
}
}
});
@@ -1085,12 +1103,12 @@ private void process_callback(XC_MethodHook.MethodHookParam param) {
if (!file.exists()) {
if (toast_content != null && need_to_show_toast) {
try {
- Toast.makeText(toast_content, "不存在替换视频\n当前路径:" + video_path, Toast.LENGTH_SHORT).show();
+ Toast.makeText(toast_content, "不存在替换视频\n" + toast_content.getPackageName() + "当前路径:" + video_path, Toast.LENGTH_SHORT).show();
} catch (Exception ee) {
XposedBridge.log("【VCAM】[toast]" + ee);
}
- need_stop = 1;
}
+ need_stop = 1;
}
int finalNeed_stop = need_stop;
XposedHelpers.findAndHookMethod(preview_cb_class, "onPreviewFrame", byte[].class, android.hardware.Camera.class, new XC_MethodHook() {
@@ -1136,6 +1154,35 @@ protected void beforeHookedMethod(MethodHookParam paramd) throws Throwable {
}
+ private void process_camera2Session_callback(CameraCaptureSession.StateCallback callback_calss){
+ if (callback_calss == null){
+ return;
+ }
+ XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onConfigureFailed", CameraCaptureSession.class, new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ XposedBridge.log("【VCAM】onConfigureFailed :" + param.args[0].toString());
+ }
+
+ });
+
+ XposedHelpers.findAndHookMethod(callback_calss.getClass(), "onConfigured", CameraCaptureSession.class, new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ XposedBridge.log("【VCAM】onConfigured :" + param.args[0].toString());
+ }
+ });
+
+ XposedHelpers.findAndHookMethod( callback_calss.getClass(), "onClosed", CameraCaptureSession.class, new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ XposedBridge.log("【VCAM】onClosed :"+ param.args[0].toString());
+ }
+ });
+ }
+
+
+
//以下代码来源:https://blog.csdn.net/jacke121/article/details/73888732
private Bitmap getBMP(String file) throws Throwable {
return BitmapFactory.decodeFile(file);
diff --git a/app/src/main/java/com/example/vcam/MainActivity.java b/app/src/main/java/com/example/vcam/MainActivity.java
index ec52f92..e3e8bb9 100644
--- a/app/src/main/java/com/example/vcam/MainActivity.java
+++ b/app/src/main/java/com/example/vcam/MainActivity.java
@@ -1,101 +1,257 @@
package com.example.vcam;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
+import android.Manifest;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Application;
+import android.content.DialogInterface;
import android.content.Intent;
-import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.view.View;
+import android.os.Environment;
+import android.util.Log;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Switch;
+import android.widget.Toast;
import java.io.File;
-
-import de.robv.android.xposed.XposedBridge;
+import java.io.IOException;
public class MainActivity extends Activity {
- @SuppressLint("CommitPrefEdits")
- public void onCreate(Bundle savedInstanceState) {
- makeWorldReadable();
- SharedPreferences preference;
- preference = this.getSharedPreferences("module_set", MODE_PRIVATE);
+ private Switch force_show_switch;
+ private Switch disable_switch;
+ private Switch play_sound_switch;
+ private Switch force_private_dir;
+ private Switch disable_toast_switch;
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults.length > 0) {
+ if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ Toast.makeText(MainActivity.this, R.string.permission_lack_warn, Toast.LENGTH_SHORT).show();
+ }else {
+ File camera_dir = new File (Environment.getExternalStorageDirectory().getAbsolutePath()+"/DCIM/Camera1/");
+ if (!camera_dir.exists()){
+ camera_dir.mkdir();
+ }
+ }
+ }
+ }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ sync_statue_with_files();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button repo_button = findViewById(R.id.button);
- Switch force_display = findViewById(R.id.switch2);
- Switch disable_module_switch = findViewById(R.id.switch1);
+ force_show_switch = findViewById(R.id.switch1);
+ disable_switch = findViewById(R.id.switch2);
+ play_sound_switch = findViewById(R.id.switch3);
+ force_private_dir = findViewById(R.id.switch4);
+ disable_toast_switch = findViewById(R.id.switch5);
- disable_module_switch.setChecked(preference.getBoolean("disable", false));
- force_display.setChecked(preference.getBoolean("force_display",false));
- repo_button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
+ sync_statue_with_files();
- Uri uri = Uri.parse("https://github.com/w2016561536/android_virtual_cam");
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivity(intent);
- }
+ repo_button.setOnClickListener(v -> {
+
+ Uri uri = Uri.parse("https://github.com/w2016561536/android_virtual_cam");
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
});
Button repo_button_chinamainland = findViewById(R.id.button2);
- repo_button_chinamainland.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Uri uri = Uri.parse("https://gitee.com/w2016561536/android_virtual_cam");
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivity(intent);
+ repo_button_chinamainland.setOnClickListener(view -> {
+ Uri uri = Uri.parse("https://gitee.com/w2016561536/android_virtual_cam");
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ });
+
+ disable_switch.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (compoundButton.isPressed()) {
+ if (!has_permission()) {
+ request_permission();
+ } else {
+ File disable_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/disable.jpg");
+ if (disable_file.exists() != b){
+ if (b){
+ try {
+ disable_file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }else {
+ disable_file.delete();
+ }
+ }
+ }
+ sync_statue_with_files();
}
});
+ force_show_switch.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (compoundButton.isPressed()) {
+ if (!has_permission()) {
+ request_permission();
+ } else {
+ File force_show_switch = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/force_show.jpg");
+ if (force_show_switch.exists() != b){
+ if (b){
+ try {
+ force_show_switch.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }else {
+ force_show_switch.delete();
+ }
+ }
+ }
+ sync_statue_with_files();
+ }
+ });
- SharedPreferences finalPreference = preference;
- disable_module_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ play_sound_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- SharedPreferences.Editor editor = finalPreference.edit();
- editor.putBoolean("disable",b);
- editor.apply();
- editor.commit();
- makeWorldReadable();
+ if (compoundButton.isPressed()) {
+ if (!has_permission()) {
+ request_permission();
+ } else {
+ File play_sound_switch = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/no-silent.jpg");
+ if (play_sound_switch.exists() != b){
+ if (b){
+ try {
+ play_sound_switch.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }else {
+ play_sound_switch.delete();
+ }
+ }
+ }
+ sync_statue_with_files();
+ }
}
});
- force_display.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- SharedPreferences.Editor editor = finalPreference.edit();
- editor.putBoolean("force_display",b);
- editor.apply();
- editor.commit();
- makeWorldReadable();
+ force_private_dir.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (compoundButton.isPressed()) {
+ if (!has_permission()) {
+ request_permission();
+ } else {
+ File force_private_dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/private_dir.jpg");
+ if (force_private_dir.exists() != b){
+ if (b){
+ try {
+ force_private_dir.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }else {
+ force_private_dir.delete();
+ }
+ }
+ }
+ sync_statue_with_files();
}
});
+ disable_toast_switch.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (compoundButton.isPressed()) {
+ if (!has_permission()) {
+ request_permission();
+ } else {
+ File disable_toast_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/no_toast.jpg");
+ if (disable_toast_file.exists() != b){
+ if (b){
+ try {
+ disable_toast_file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }else {
+ disable_toast_file.delete();
+ }
+ }
+ }
+ sync_statue_with_files();
+ }
+ });
+
}
- @TargetApi(Build.VERSION_CODES.N)
- @SuppressLint({"SetWorldReadable", "SetWorldWritable"})
- private void makeWorldReadable() {
- try {
- File f = new File(this.getDataDir().getAbsolutePath()+"/shared_prefs/module_set.xml");
- f.setReadable(true, false);
- f.setExecutable(true, false);
- f.setWritable(true, false);
- } catch (Exception e) {
- XposedBridge.log("【VCAM】权限设置失败"+ e.toString());
+ private void request_permission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED
+ || this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+ builder.setTitle(R.string.permission_lack_warn);
+ builder.setMessage(R.string.permission_description);
+
+ builder.setNegativeButton(R.string.negative, (dialogInterface, i) -> Toast.makeText(MainActivity.this, R.string.permission_lack_warn, Toast.LENGTH_SHORT).show());
+
+ builder.setPositiveButton(R.string.positive, (dialogInterface, i) -> requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1));
+ builder.show();
+ }
+ }
+ }
+
+ private boolean has_permission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED
+ && this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
}
+ return true;
}
+
+
+ private void sync_statue_with_files() {
+ Log.d(this.getApplication().getPackageName(), "【VCAM】[sync]同步开关状态");
+
+ if (!has_permission()){
+ request_permission();
+ }else {
+ File camera_dir = new File (Environment.getExternalStorageDirectory().getAbsolutePath()+"/DCIM/Camera1");
+ if (!camera_dir.exists()){
+ camera_dir.mkdir();
+ }
+ }
+
+ File disable_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/disable.jpg");
+ disable_switch.setChecked(disable_file.exists());
+
+ File force_show_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/force_show.jpg");
+ force_show_switch.setChecked(force_show_file.exists());
+
+ File play_sound_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/no-silent.jpg");
+ play_sound_switch.setChecked(play_sound_file.exists());
+
+ File force_private_dir_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/private_dir.jpg");
+ force_private_dir.setChecked(force_private_dir_file.exists());
+
+ File disable_toast_file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera1/no_toast.jpg");
+ disable_toast_switch.setChecked(disable_toast_file.exists());
+
+ }
+
+
}
+
diff --git a/app/src/main/java/com/example/vcam/VideoToFrames.java b/app/src/main/java/com/example/vcam/VideoToFrames.java
index 66ec20e..6d6f34b 100644
--- a/app/src/main/java/com/example/vcam/VideoToFrames.java
+++ b/app/src/main/java/com/example/vcam/VideoToFrames.java
@@ -125,7 +125,7 @@ public void videoDecode(String videoFilePath) throws IOException {
decoder.stop();
}
}catch (Exception e){
- XposedBridge.log("【VCAM】[videofile]"+ e.getMessage());
+ XposedBridge.log("【VCAM】[videofile]"+ e.toString());
} finally {
if (decoder != null) {
decoder.stop();
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 85b537b..d285526 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,8 +1,8 @@
-
+
+ android:text="@string/gitee" />
+ android:visibility="visible">
@@ -61,10 +61,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index 881e492..0d06391 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -6,4 +6,11 @@
DO NOT USE FOR ANY ILLEGAL PURPOSE !!YOU NEED TO TAKE ALL RESPONSIBILITY AND CONSEQUENCE!!
Force to show permission lack warning
Disable the module temporarily (On is disable)
+ Play sounds of videos
+ Force every app use private Force every app use private directory (No matter the permission)
+ Disable toast message
+ Missing permissions
+ The permission to access local storage is used to set up configuration files. If refused you need to set up file manually.
+ Sure
+ Refuse
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 75ad4ea..2f59fac 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -5,4 +5,11 @@
请勿用于非法用途,所有后果自负!
强制显示权限缺失提示
临时关闭模块
+ 播放视频声音
+ 强制每个应用程序使用私有目录(无论权限如何)
+ 关闭提示消息
+ 无权限
+ 访问本地目录的权限用于设定配置文件,如果拒绝,您需要手动设定配置文件。
+ 确定
+ 拒绝
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index d45a4d3..b22902e 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -5,4 +5,11 @@
請勿用於非法用途,所有後果自負!
臨時關閉模組
強制顯示許可權缺失提示
+ 播放影片聲音
+ 強制每個應用程序使用私有目錄(無論許可權如何)
+ 關閉提示消息
+ 無許可權
+ 訪問本地目錄的許可權用於設定設定檔,如果拒絕,您需要手動設定設定檔。
+ 確定
+ 拒絕
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml
index 66b795c..561a93b 100644
--- a/app/src/main/res/values-zh-rMO/strings.xml
+++ b/app/src/main/res/values-zh-rMO/strings.xml
@@ -5,4 +5,11 @@
請勿用於非法用途,所有後果自負!
強制顯示許可權缺失提示
臨時關閉模組
+ 播放影片聲音
+ 強制每個應用程序使用私有目錄(無論許可權如何)
+ 關閉提示消息
+ 無許可權
+ 訪問本地目錄的許可權用於設定設定檔,如果拒絕,您需要手動設定設定檔。
+ 確定
+ 拒絕
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml
index 66b795c..561a93b 100644
--- a/app/src/main/res/values-zh-rSG/strings.xml
+++ b/app/src/main/res/values-zh-rSG/strings.xml
@@ -5,4 +5,11 @@
請勿用於非法用途,所有後果自負!
強制顯示許可權缺失提示
臨時關閉模組
+ 播放影片聲音
+ 強制每個應用程序使用私有目錄(無論許可權如何)
+ 關閉提示消息
+ 無許可權
+ 訪問本地目錄的許可權用於設定設定檔,如果拒絕,您需要手動設定設定檔。
+ 確定
+ 拒絕
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index ea1e12b..f483f10 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -5,4 +5,11 @@
請勿用於非法用途,所有後果自負!
強制顯示許可權缺失提示
臨時關閉模組
+ 播放影片聲音
+ 強制每個應用程序使用私有目錄(無論許可權如何)
+ 關閉提示消息
+ 無許可權
+ 訪問本地目錄的許可權用於設定設定檔,如果拒絕,您需要手動設定設定檔。
+ 確定
+ 拒絕
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 75ad4ea..0ecb4d7 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -5,4 +5,11 @@
请勿用于非法用途,所有后果自负!
强制显示权限缺失提示
临时关闭模块
+ 播放视频声音
+ 强制每个应用程序使用私有目录(无论权限)
+ 关闭提示消息
+ 无权限
+ 访问本地目录的权限用于设定配置文件,如果拒绝,您需要手动设定配置文件。
+ 确定
+ 拒绝
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 881e492..8151e43 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,4 +6,12 @@
DO NOT USE FOR ANY ILLEGAL PURPOSE !!YOU NEED TO TAKE ALL RESPONSIBILITY AND CONSEQUENCE!!
Force to show permission lack warning
Disable the module temporarily (On is disable)
+ Play sounds of videos
+ Force every app use private directory
+ Gitee平台(中国大陆更快)
+ Disable toast message
+ Missing permissions
+ The permission to access local storage is used to set up configuration files. If refused you need to set up file manually.
+ Sure
+ Refuse
\ No newline at end of file