commit 2905fe6e2dd21fba1d494e695e0921a15660e50c
parent 1f4eb18aa832630ee88951cad1d50d259c4228f2
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sun, 14 Jan 2024 00:35:55 +0100

feat(camera): custom frame rate
- experimental disable cameras

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 20++++++++++++++------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Camera.kt | 10+++++-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/CameraTweaks.kt | 60+++++++++++++++++++++++++++++++++++++++++++++---------------
3 files changed, 64 insertions(+), 26 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -532,9 +532,9 @@ "name": "Camera", "description": "Adjust the right settings for the perfect snap", "properties": { - "disable_camera": { - "name": "Disable Camera", - "description": "Prevents Snapchat from using the cameras available on your device" + "disable_cameras": { + "name": "Disable Cameras", + "description": "Prevents Snapchat from using the selected cameras" }, "black_photos": { "name": "Black Photos", @@ -556,9 +556,13 @@ "name": "Custom Resolution", "description": "Sets a custom camera resolution, width x height (e.g. 1920x1080).\nThe custom resolution must be supported by your device" }, - "custom_frame_rate": { - "name": "Custom Frame Rate", - "description": "Overrides the camera frame rate" + "front_custom_frame_rate": { + "name": "Front Custom Frame Rate", + "description": "Overrides the front camera frame rate" + }, + "back_custom_frame_rate": { + "name": "Back Custom Frame Rate", + "description": "Overrides the back camera frame rate" }, "force_camera_source_encoding": { "name": "Force Camera Source Encoding", @@ -866,6 +870,10 @@ "friends": "Friends", "following": "Following", "discover": "Discover" + }, + "disable_cameras":{ + "front": "Front Camera", + "back": "Back Camera" } } }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Camera.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Camera.kt @@ -13,6 +13,7 @@ import me.rhunk.snapenhance.common.logger.AbstractLogger class Camera : ConfigContainer() { companion object { private val defaultResolutions = listOf("3264x2448", "3264x1840", "3264x1504", "2688x1512", "2560x1920", "2448x2448", "2340x1080", "2160x1080", "1920x1440", "1920x1080", "1600x1200", "1600x960", "1600x900", "1600x736", "1600x720", "1560x720", "1520x720", "1440x1080", "1440x720", "1280x720", "1080x1080", "1080x720", "960x720", "720x720", "720x480", "640x480", "352x288", "320x240", "176x144").toTypedArray() + private val customFrameRates = arrayOf("5", "10", "20", "25", "30", "48", "60", "90", "120") } private lateinit var _overrideFrontResolution: PropertyValue<String> @@ -49,13 +50,12 @@ class Camera : ConfigContainer() { { addFlags(ConfigFlag.NO_TRANSLATE) } } - val disable = boolean("disable_camera") + val disableCameras = multiple("disable_cameras", "front", "back") { addNotices(FeatureNotice.INTERNAL_BEHAVIOR); requireRestart() } val immersiveCameraPreview = boolean("immersive_camera_preview") { addNotices(FeatureNotice.UNSTABLE) } val blackPhotos = boolean("black_photos") - val customFrameRate = unique("custom_frame_rate", - "5", "10", "20", "25", "30", "48", "60", "90", "120" - ) { addNotices(FeatureNotice.UNSTABLE); addFlags(ConfigFlag.NO_TRANSLATE) } - val hevcRecording = boolean("hevc_recording") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) } + val frontCustomFrameRate = unique("front_custom_frame_rate", *customFrameRates) { requireRestart(); addFlags(ConfigFlag.NO_TRANSLATE) } + val backCustomFrameRate = unique("back_custom_frame_rate", *customFrameRates) { requireRestart(); addFlags(ConfigFlag.NO_TRANSLATE) } + val hevcRecording = boolean("hevc_recording") { requireRestart() } val forceCameraSourceEncoding = boolean("force_camera_source_encoding") val overrideFrontResolution get() = _overrideFrontResolution val overrideBackResolution get() = _overrideBackResolution diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/CameraTweaks.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/CameraTweaks.kt @@ -28,25 +28,41 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT @SuppressLint("MissingPermission", "DiscouragedApi") override fun onActivityCreate() { val config = context.config.camera - if (config.disable.get()) { + + val frontCameraId = runCatching { context.androidContext.getSystemService(CameraManager::class.java).run { + cameraIdList.firstOrNull { getCameraCharacteristics(it).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT } + } }.getOrNull() + + if (config.disableCameras.get().isNotEmpty() && frontCameraId != null) { ContextWrapper::class.java.hook("checkPermission", HookStage.BEFORE) { param -> val permission = param.arg<String>(0) if (permission == Manifest.permission.CAMERA) { param.setResult(PackageManager.PERMISSION_GRANTED) } } - } var isLastCameraFront = false CameraManager::class.java.hook("openCamera", HookStage.BEFORE) { param -> - if (config.disable.get()) { + val cameraManager = param.thisObject() as? CameraManager ?: return@hook + val cameraId = param.arg<String>(0) + val disabledCameras = config.disableCameras.get() + + if (disabledCameras.size >= 2) { param.setResult(null) return@hook } - val cameraManager = param.thisObject() as? CameraManager ?: return@hook - isLastCameraFront = cameraManager.getCameraCharacteristics(param.arg(0)).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT + + isLastCameraFront = cameraId == frontCameraId + + if (disabledCameras.size != 1) return@hook + + // trick to replace unwanted camera with another one + if ((disabledCameras.contains("front") && isLastCameraFront) || (disabledCameras.contains("back") && !isLastCameraFront)) { + param.setArg(0, cameraManager.cameraIdList.filterNot { it == cameraId }.firstOrNull() ?: return@hook) + isLastCameraFront = !isLastCameraFront + } } ImageReader::class.java.hook("newInstance", HookStage.BEFORE) { param -> @@ -56,19 +72,33 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT param.setArg(1, captureResolutionConfig[1]) } - config.customFrameRate.getNullable()?.also { value -> - val customFrameRate = value.toInt() - CameraCharacteristics::class.java.hook("get", HookStage.AFTER) { param -> - val key = param.arg<Key<*>>(0) - if (key == CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES) { - val fpsRanges = param.getResult() as? Array<*> ?: return@hook - fpsRanges.forEach { - val range = it as? Range<*> ?: return@forEach - range.setObjectField("mUpper", customFrameRate) - range.setObjectField("mLower", customFrameRate) + CameraCharacteristics::class.java.hook("get", HookStage.AFTER) { param -> + val key = param.argNullable<Key<*>>(0) ?: return@hook + + if (key == CameraCharacteristics.LENS_FACING) { + val disabledCameras = config.disableCameras.get() + //FIXME: unexpected behavior when app is resumed + if (disabledCameras.size == 1) { + val isFrontCamera = param.getResult() as? Int == CameraCharacteristics.LENS_FACING_FRONT + if ((disabledCameras.contains("front") && isFrontCamera) || (disabledCameras.contains("back") && !isFrontCamera)) { + param.setResult(if (isFrontCamera) CameraCharacteristics.LENS_FACING_BACK else CameraCharacteristics.LENS_FACING_FRONT) } } } + + if (key == CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES) { + val isFrontCamera = param.invokeOriginal( + arrayOf(CameraCharacteristics.LENS_FACING) + ) == CameraCharacteristics.LENS_FACING_FRONT + val customFrameRate = (if (isFrontCamera) config.frontCustomFrameRate.getNullable() else config.backCustomFrameRate.getNullable())?.toIntOrNull() ?: return@hook + val fpsRanges = param.getResult() as? Array<*> ?: return@hook + + fpsRanges.forEach { + val range = it as? Range<*> ?: return@forEach + range.setObjectField("mUpper", customFrameRate) + range.setObjectField("mLower", customFrameRate) + } + } } if (config.blackPhotos.get()) {