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