From f1bc7926062d7e81d731907d09bda90197df1b01 Mon Sep 17 00:00:00 2001 From: Atomos Date: Wed, 15 Apr 2026 11:53:58 +0800 Subject: [PATCH 1/7] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d284528 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**OS version And package name** + - Android OS version(ColorOS 16.0 etc.) + - hooked app package name + +**adb logcat** +``` +put log here. +``` From 039db7daa4f285dbdd30e86d3931236dc17d3d4e Mon Sep 17 00:00:00 2001 From: Atomos Date: Wed, 15 Apr 2026 11:57:56 +0800 Subject: [PATCH 2/7] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index d284528..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**OS version And package name** - - Android OS version(ColorOS 16.0 etc.) - - hooked app package name - -**adb logcat** -``` -put log here. -``` From 42135fbedd273fef25021d390a78d08b21e3f1d0 Mon Sep 17 00:00:00 2001 From: rifuki Date: Fri, 22 May 2026 19:33:23 +0700 Subject: [PATCH 3/7] Allow builds with prebuilt native library --- app/build.gradle | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ad514af..824396c 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,6 +32,8 @@ def gitCommitCount = getGitCommitCount() def startTasks = gradle.startParameter.taskNames def isExplicitNativeBuild = startTasks.any { it.contains("buildNative") } +def prebuiltNativeLibs = fileTree(dir: "src/main/jniLibs", include: "**/libcamera3.so") +def usePrebuiltNativeLibs = !isExplicitNativeBuild && !prebuiltNativeLibs.isEmpty() android { flavorDimensions.add("api") @@ -55,10 +57,12 @@ android { versionCode versionCodeOffset + gitCommitCount versionName "${majorVersion}.${minorVersion}.${patchVersion}" - externalNativeBuild { - cmake { - cppFlags += "" - arguments "-DANDROID_PLATFORM=android-29" + if (!usePrebuiltNativeLibs) { + externalNativeBuild { + cmake { + cppFlags += "" + arguments "-DANDROID_PLATFORM=android-29" + } } } @@ -95,10 +99,12 @@ android { jvmTarget = '11' } - externalNativeBuild { - cmake { - path file("src/main/cpp/CMakeLists.txt") - version = "3.22.1" + if (!usePrebuiltNativeLibs) { + externalNativeBuild { + cmake { + path file("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } } } @@ -121,8 +127,7 @@ android { afterEvaluate { if (!isExplicitNativeBuild) { - def jniLibTree = fileTree(dir: "src/main/jniLibs", include: "**/libcamera3.so") - if (jniLibTree.isEmpty()) { + if (!usePrebuiltNativeLibs) { println ">>> [警告] jniLibs 文件不存在, 无法快速编译 <<<" return } From 72981625eeac508fb4cc7dd6143677fc18117594 Mon Sep 17 00:00:00 2001 From: rifuki Date: Fri, 22 May 2026 19:33:50 +0700 Subject: [PATCH 4/7] Resolve media IDs from picker URIs --- .../viewmodel/SpotlightViewModel.kt | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nothing/camera2magic/viewmodel/SpotlightViewModel.kt b/app/src/main/java/com/nothing/camera2magic/viewmodel/SpotlightViewModel.kt index 2ca6d78..1809abc 100755 --- a/app/src/main/java/com/nothing/camera2magic/viewmodel/SpotlightViewModel.kt +++ b/app/src/main/java/com/nothing/camera2magic/viewmodel/SpotlightViewModel.kt @@ -2,6 +2,7 @@ package com.nothing.camera2magic.viewmodel import android.app.Application import android.content.ContentUris +import android.provider.DocumentsContract import android.graphics.Bitmap import android.net.Uri import android.provider.MediaStore @@ -61,9 +62,7 @@ class SpotlightViewModel( fun onMediaSelected(type: MediaType, uri: Uri?) { if (uri == null) return - val mediaId = try { - uri.lastPathSegment?.toLongOrNull() - } catch (_: kotlin.Exception) { null } + val mediaId = resolveMediaId(type, uri) if (mediaId != null) { saveMediaId(type, mediaId) loadAndVerifyMedia(type, mediaId) @@ -103,6 +102,31 @@ class SpotlightViewModel( MediaType.IMAGE -> repository.imageId } } + + private fun resolveMediaId(type: MediaType, uri: Uri): Long? { + uri.lastPathSegment?.toLongOrNull()?.let { return it } + + val documentId = runCatching { + if (DocumentsContract.isDocumentUri(app, uri)) { + DocumentsContract.getDocumentId(uri) + } else { + null + } + }.getOrNull() + documentId?.substringAfter(':')?.toLongOrNull()?.let { return it } + documentId?.toLongOrNull()?.let { return it } + + val projection = when (type) { + MediaType.VIDEO -> arrayOf(MediaStore.Video.Media._ID) + MediaType.IMAGE -> arrayOf(MediaStore.Images.Media._ID) + } + return runCatching { + app.contentResolver.query(uri, projection, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) cursor.getLong(0) else null + } + }.getOrNull() + } + private fun loadAndVerifyMedia(type: MediaType, mediaIdOverride: Long? = null) { viewModelScope.launch(Dispatchers.IO) { val mediaId = mediaIdOverride ?: getMediaId(type) @@ -143,4 +167,4 @@ class SpotlightViewModel( currentMap + (type to thumbnail) } } -} \ No newline at end of file +} From 0285e44305987beb594d1e3d3443ea7d7e279529 Mon Sep 17 00:00:00 2001 From: rifuki Date: Fri, 22 May 2026 19:34:17 +0700 Subject: [PATCH 5/7] Apply EXIF orientation to image sources --- .../camera2magic/hook/SourceManager.kt | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nothing/camera2magic/hook/SourceManager.kt b/app/src/main/java/com/nothing/camera2magic/hook/SourceManager.kt index 4a5130d..3a307ec 100644 --- a/app/src/main/java/com/nothing/camera2magic/hook/SourceManager.kt +++ b/app/src/main/java/com/nothing/camera2magic/hook/SourceManager.kt @@ -4,6 +4,9 @@ import android.content.ContentUris import android.content.SharedPreferences import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.media.ExifInterface +import android.net.Uri import android.provider.MediaStore import com.nothing.camera2magic.GlobalState import java.io.FileNotFoundException @@ -155,6 +158,7 @@ object SourceManager { val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageId) val contentResolver = GlobalState.appContext.contentResolver val result = runCatching { + val exifOrientation = readExifOrientation(uri) val options = BitmapFactory.Options().apply { inJustDecodeBounds = true contentResolver.openInputStream(uri)?.use { @@ -169,10 +173,14 @@ object SourceManager { val bitmap = contentResolver.openInputStream(uri)?.use { stream -> BitmapFactory.decodeStream(stream, null, options) } ?: throw IllegalStateException("无法解码图片") + val orientedBitmap = applyExifOrientation(bitmap, exifOrientation) try { - NativeBridge.processBitmap(bitmap) + NativeBridge.processBitmap(orientedBitmap) } finally { + if (orientedBitmap !== bitmap) { + orientedBitmap.recycle() + } bitmap.recycle() } } @@ -189,6 +197,39 @@ object SourceManager { } } + private fun readExifOrientation(uri: Uri): Int { + val contentResolver = GlobalState.appContext.contentResolver + return runCatching { + contentResolver.openInputStream(uri)?.use { stream -> + ExifInterface(stream).getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL + ) + } ?: ExifInterface.ORIENTATION_NORMAL + }.getOrDefault(ExifInterface.ORIENTATION_NORMAL) + } + + private fun applyExifOrientation(bitmap: Bitmap, orientation: Int): Bitmap { + val matrix = Matrix() + when (orientation) { + ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f) + ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f) + ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f) + ExifInterface.ORIENTATION_TRANSPOSE -> { + matrix.postRotate(90f) + matrix.postScale(-1f, 1f) + } + ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f) + ExifInterface.ORIENTATION_TRANSVERSE -> { + matrix.postRotate(-90f) + matrix.postScale(-1f, 1f) + } + ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f) + else -> return bitmap + } + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + } + fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { val (height: Int, width: Int) = options.outHeight to options.outWidth var inSampleSize = 1 @@ -206,4 +247,4 @@ object SourceManager { mediaIsReady = ready toastMessage = message } -} \ No newline at end of file +} From 2cdf7e8f088be31bc0802cee770bfa43b272c0d6 Mon Sep 17 00:00:00 2001 From: rifuki Date: Fri, 22 May 2026 19:36:04 +0700 Subject: [PATCH 6/7] Stabilize Camera2 preview surface replacement --- .../camera2magic/hook/BlackHoleMapper.kt | 14 ++- .../nothing/camera2magic/hook/NativeBridge.kt | 6 +- .../camera2magic/hook/Camera2Hooker.kt | 107 ++++++++++++++---- 3 files changed, 97 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/nothing/camera2magic/hook/BlackHoleMapper.kt b/app/src/main/java/com/nothing/camera2magic/hook/BlackHoleMapper.kt index 0cf26ec..639fb96 100644 --- a/app/src/main/java/com/nothing/camera2magic/hook/BlackHoleMapper.kt +++ b/app/src/main/java/com/nothing/camera2magic/hook/BlackHoleMapper.kt @@ -1,7 +1,6 @@ package com.nothing.camera2magic.hook import android.graphics.ImageFormat -import android.graphics.SurfaceTexture import android.media.ImageReader import android.os.Handler import android.os.HandlerThread @@ -11,6 +10,8 @@ import java.util.WeakHashMap data class BlackHole( val identityId: Int, + val width: Int, + val height: Int, val surface: Surface, val reader: ImageReader ) @@ -19,11 +20,11 @@ object BlackHoleMapper { private val oabMap = WeakHashMap() private val camera3Thread = HandlerThread("camera3Thread").apply { start() } private val camera3Handler = Handler(camera3Thread.looper) - fun createBlackHole(origin: Surface): Surface { + fun createBlackHole(origin: Surface, width: Int, height: Int): Surface { return oabMap.getOrPut(origin) { val id = 20 + oabMap.size - val reader = ImageReader.newInstance(1280, 720, - ImageFormat.YUV_420_888, 2) + val reader = ImageReader.newInstance(width, height, + ImageFormat.PRIVATE, 4) reader.setOnImageAvailableListener({ r -> runCatching { val image = r.acquireLatestImage() @@ -32,7 +33,8 @@ object BlackHoleMapper { Dog.e(TAG, "acquireLatestImage Failed: ${exception.message}", exception, true) } }, camera3Handler) - BlackHole(id, reader.surface, reader) + Dog.i(TAG, "blackhole[$id] ${width}x${height}, format=PRIVATE", SourceManager.enableLog) + BlackHole(id, width, height, reader.surface, reader) }.surface } @@ -46,4 +48,4 @@ object BlackHoleMapper { } oabMap.clear() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/nothing/camera2magic/hook/NativeBridge.kt b/app/src/main/java/com/nothing/camera2magic/hook/NativeBridge.kt index f42e396..2d9c361 100644 --- a/app/src/main/java/com/nothing/camera2magic/hook/NativeBridge.kt +++ b/app/src/main/java/com/nothing/camera2magic/hook/NativeBridge.kt @@ -71,6 +71,10 @@ object NativeBridge { synchronized(surfaceLock) { val lastSurface = lastRegisteredSurface?.get() state.surface?.let { surface -> + if (!surface.isValid) { + Dog.e(TAG, "Skip invalid surface registration.", null, SourceManager.enableLog) + return + } if (forceRefresh || surface != lastSurface) { registerSurface(cameraState = state) lastRegisteredSurface = WeakReference(surface) @@ -84,4 +88,4 @@ object NativeBridge { lastRegisteredSurface = null } } -} \ No newline at end of file +} diff --git a/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt b/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt index 526597b..c62df1b 100644 --- a/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt +++ b/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt @@ -9,7 +9,6 @@ import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.params.OutputConfiguration import android.hardware.camera2.params.SessionConfiguration import android.os.Handler -import android.os.Looper import android.view.Surface import android.view.WindowManager import com.nothing.camera2magic.GlobalState @@ -20,6 +19,7 @@ import com.nothing.camera2magic.hook.NativeBridge.registerSurfaceIfNew import com.nothing.camera2magic.hook.NativeBridge.releaseLastRegisteredSurface import com.nothing.camera2magic.utils.Dog +import com.nothing.camera2magic.utils.shortId import io.github.libxposed.api.XposedModuleInterface.PackageReadyParam import java.lang.ref.WeakReference @@ -28,6 +28,7 @@ import java.util.WeakHashMap object Camera2Hooker { private const val TAG = "[CAM2]" + private const val MAX_PREVIEW_AREA = 3840 * 2160 private val CameraDevice?.shortId : String get() = if (this == null) "null" else "@0x${Integer.toHexString(System.identityHashCode(this))}" @@ -36,11 +37,45 @@ object Camera2Hooker { Collections.newSetFromMap(WeakHashMap, Boolean>())) private var activeCameraRef: WeakReference? = null private var cameraState = WeakHashMap() + private data class SurfaceInfo( + val surface: Surface, + val width: Int, + val height: Int, + val format: Int + ) + private fun getCameraState(camera: CameraDevice): CameraState { return synchronized(cameraState) { cameraState.getOrPut(camera) { CameraState() } } } + + private fun readSurfaceInfo(surface: Surface): SurfaceInfo? { + return runCatching { + val (width, height, format) = NativeBridge.getSurfaceInfo(surface) + SurfaceInfo(surface, width, height, format) + }.getOrNull() + } + + private fun SurfaceInfo.isPreviewCandidate(): Boolean { + val area = width.toLong() * height.toLong() + return format == 1 && area in 1..MAX_PREVIEW_AREA + } + + private fun selectPreviewSurface(surfaces: List): SurfaceInfo? { + val infos = surfaces.mapNotNull(::readSurfaceInfo) + infos.forEach { info -> + Dog.i( + TAG, + "surface[${info.surface.shortId}] ${info.width}x${info.height}, format=${info.format}", + SourceManager.enableLog + ) + } + return infos + .filter { it.isPreviewCandidate() } + .minByOrNull { it.width.toLong() * it.height.toLong() } + } + private fun CameraState.saveCameraInfo(camera: CameraDevice) { val cameraIdStr = camera.id val context = GlobalState.appContext @@ -57,8 +92,11 @@ object Camera2Hooker { this.displayOrientation = rotation * 90 this.packageName = GlobalState.packageName } - private fun CameraState.bindSurface(surface: Surface) { - val (width, height, _) = NativeBridge.getSurfaceInfo(surface) + private fun CameraState.bindSurface(info: SurfaceInfo) { + val surface = info.surface + val width = info.width + val height = info.height + Dog.i(TAG, "bind preview surface[${surface.shortId}] ${width}x${height}", SourceManager.enableLog) this.pictureWidth = width this.pictureHeight = height this.previewWidth = width @@ -74,10 +112,18 @@ object Camera2Hooker { magic.hook(onConfigured).intercept { chain -> val session = chain.args[0] as CameraCaptureSession val camera = session.device - val state = getCameraState(camera) - registerSurfaceIfNew(state, true) - Handler(Looper.getMainLooper()).post { needStartRenderer() } - chain.proceed() + val result = chain.proceed() + val activeCamera = activeCameraRef?.get() + if (activeCamera === camera && SourceManager.isReadyForHook()) { + val state = getCameraState(camera) + if (state.surface?.isValid == true) { + registerSurfaceIfNew(state, true) + needStartRenderer() + } else { + Dog.e(TAG, "Skip renderer start; preview surface is invalid.", null, SourceManager.enableLog) + } + } + result } val onConfigureFailed = clazz.getDeclaredMethod("onConfigureFailed", @@ -119,9 +165,11 @@ object Camera2Hooker { val state = getCameraState(camera) state.saveCameraInfo(camera) - BlackHoleMapper.clearAll() val sessionConfiguration = chain.args[0] as SessionConfiguration + val previewSurface = selectPreviewSurface( + sessionConfiguration.outputConfigurations.flatMap { it.surfaces } + ) @SuppressLint("SoonBlockedPrivateApi") val field = OutputConfiguration::class.java.getDeclaredField("mSurfaces") @@ -130,11 +178,14 @@ object Camera2Hooker { var modified = false val surfaces = outputConfiguration.surfaces val modifiedSurfaces = surfaces.mapTo(ArrayList()) { origin -> - val (_, _, format) = NativeBridge.getSurfaceInfo(origin) - if (format == 1) { + if (origin === previewSurface?.surface) { modified = true - state.bindSurface(origin) - return@mapTo BlackHoleMapper.createBlackHole(origin) + state.bindSurface(previewSurface) + return@mapTo BlackHoleMapper.createBlackHole( + origin, + previewSurface.width, + previewSurface.height + ) } origin } @@ -154,15 +205,20 @@ object Camera2Hooker { magic.hook(createCaptureSession).intercept { chain -> if (!SourceManager.isReadyForHook()) return@intercept chain.proceed() val camera = chain.thisObject as CameraDevice + activeCameraRef = WeakReference(camera) val state = getCameraState(camera) - BlackHoleMapper.clearAll() + state.saveCameraInfo(camera) @Suppress("UNCHECKED_CAST") val surfaces = chain.args[0] as List + val previewSurface = selectPreviewSurface(surfaces) val newList = surfaces.mapTo(ArrayList()) { origin -> - val (width, height, format) = NativeBridge.getSurfaceInfo(origin) - if (format == 1) { - state.bindSurface(origin) - return@mapTo BlackHoleMapper.createBlackHole(origin) + if (origin === previewSurface?.surface) { + state.bindSurface(previewSurface) + return@mapTo BlackHoleMapper.createBlackHole( + origin, + previewSurface.width, + previewSurface.height + ) } origin } @@ -184,7 +240,7 @@ object Camera2Hooker { if (activeCamera != null && closingCamera === activeCamera) { Dog.i(TAG, "camera[${closingCamera.shortId}] close.", true) - Handler(Looper.getMainLooper()).post { needStopRenderer() } + needStopRenderer() releaseLastRegisteredSurface() BlackHoleMapper.clearAll() activeCameraRef = null @@ -197,10 +253,18 @@ object Camera2Hooker { val addTarget = getDeclaredMethod("addTarget", Surface::class.java) magic.hook(addTarget).intercept { chain -> val origin = chain.args[0] as Surface - val blackHole = BlackHoleMapper.getBlackHole(origin) - if (!SourceManager.isReadyForHook() || blackHole == null) { + if (!SourceManager.isReadyForHook()) { return@intercept chain.proceed() } + val blackHole = BlackHoleMapper.getBlackHole(origin) ?: run { + val info = readSurfaceInfo(origin) + if (info?.isPreviewCandidate() == true) { + Dog.i(TAG, "early map request target[${origin.shortId}] ${info.width}x${info.height}", SourceManager.enableLog) + BlackHoleMapper.createBlackHole(origin, info.width, info.height) + } else { + null + } + } ?: return@intercept chain.proceed() chain.proceed(arrayOf(blackHole)) } } @@ -217,6 +281,3 @@ object Camera2Hooker { } } } - - - From a4704013ae2a165e7fd2e6cde553d5381f025006 Mon Sep 17 00:00:00 2001 From: rifuki Date: Fri, 22 May 2026 19:36:58 +0700 Subject: [PATCH 7/7] Compute Camera2 display orientation from sensor --- .../camera2magic/hook/Camera2Hooker.kt | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt b/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt index c62df1b..cec3f75 100644 --- a/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt +++ b/app/src/modern/java/com/nothing/camera2magic/hook/Camera2Hooker.kt @@ -9,6 +9,7 @@ import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.params.OutputConfiguration import android.hardware.camera2.params.SessionConfiguration import android.os.Handler +import android.view.Surface as AndroidSurface import android.view.Surface import android.view.WindowManager import com.nothing.camera2magic.GlobalState @@ -85,12 +86,30 @@ object Camera2Hooker { val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager @Suppress("DEPRECATION") val rotation = wm.defaultDisplay.rotation + val deviceDegrees = when (rotation) { + AndroidSurface.ROTATION_90 -> 90 + AndroidSurface.ROTATION_180 -> 180 + AndroidSurface.ROTATION_270 -> 270 + else -> 0 + } + val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 90 + val facingFront = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT + val displayOrientation = if (facingFront) { + (360 - ((sensorOrientation + deviceDegrees) % 360)) % 360 + } else { + (sensorOrientation - deviceDegrees + 360) % 360 + } this.apiLevel = 2 - this.sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 90 - this.facingFront = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT - this.displayOrientation = rotation * 90 + this.sensorOrientation = sensorOrientation + this.facingFront = facingFront + this.displayOrientation = displayOrientation this.packageName = GlobalState.packageName + Dog.i( + TAG, + "camera[$cameraIdStr] sensor=$sensorOrientation device=$deviceDegrees display=$displayOrientation front=$facingFront", + SourceManager.enableLog + ) } private fun CameraState.bindSurface(info: SurfaceInfo) { val surface = info.surface