1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.jetpackcamera.feature.preview.ui 17 18 import android.content.pm.ActivityInfo 19 import android.os.Build 20 import androidx.camera.core.DynamicRange 21 import androidx.camera.core.Preview 22 import androidx.camera.core.SurfaceRequest 23 import androidx.camera.core.SurfaceRequest.TransformationInfo as CXTransformationInfo 24 import androidx.camera.viewfinder.compose.Viewfinder 25 import androidx.camera.viewfinder.surface.ImplementationMode 26 import androidx.camera.viewfinder.surface.TransformationInfo 27 import androidx.camera.viewfinder.surface.ViewfinderSurfaceRequest 28 import androidx.compose.foundation.layout.fillMaxSize 29 import androidx.compose.runtime.Composable 30 import androidx.compose.runtime.LaunchedEffect 31 import androidx.compose.runtime.getValue 32 import androidx.compose.runtime.produceState 33 import androidx.compose.runtime.rememberUpdatedState 34 import androidx.compose.runtime.snapshotFlow 35 import androidx.compose.ui.Modifier 36 import kotlinx.coroutines.CoroutineStart 37 import kotlinx.coroutines.Runnable 38 import kotlinx.coroutines.flow.MutableStateFlow 39 import kotlinx.coroutines.flow.collect 40 import kotlinx.coroutines.flow.collectLatest 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.distinctUntilChanged 43 import kotlinx.coroutines.flow.filterNotNull 44 import kotlinx.coroutines.flow.map 45 import kotlinx.coroutines.flow.onCompletion 46 import kotlinx.coroutines.flow.onEach 47 import kotlinx.coroutines.flow.takeWhile 48 import kotlinx.coroutines.launch 49 50 /** 51 * A composable viewfinder that adapts CameraX's [Preview.SurfaceProvider] to [Viewfinder] 52 * 53 * This adapter code will eventually be upstreamed to CameraX, but for now can be copied 54 * in its entirety to connect CameraX to [Viewfinder]. 55 * 56 * @param[modifier] the modifier to be applied to the layout 57 * @param[surfaceRequest] a [SurfaceRequest] from [Preview.SurfaceProvider]. 58 * @param[implementationMode] the implementation mode, either [ImplementationMode.EXTERNAL] or 59 * [ImplementationMode.EMBEDDED]. 60 */ 61 @Composable 62 fun CameraXViewfinder( 63 surfaceRequest: SurfaceRequest, 64 modifier: Modifier = Modifier, 65 implementationMode: ImplementationMode = ImplementationMode.EXTERNAL, 66 onRequestWindowColorMode: (Int) -> Unit = {} 67 ) { 68 val currentImplementationMode by rememberUpdatedState(implementationMode) 69 val currentOnRequestWindowColorMode by rememberUpdatedState(onRequestWindowColorMode) 70 <lambda>null71 val viewfinderArgs by produceState<ViewfinderArgs?>(initialValue = null, surfaceRequest) { 72 val viewfinderSurfaceRequest = ViewfinderSurfaceRequest.Builder(surfaceRequest.resolution) 73 .build() 74 75 surfaceRequest.addRequestCancellationListener(Runnable::run) { 76 viewfinderSurfaceRequest.markSurfaceSafeToRelease() 77 } 78 79 // Launch undispatched so we always reach the try/finally in this coroutine 80 launch(start = CoroutineStart.UNDISPATCHED) { 81 try { 82 val surface = viewfinderSurfaceRequest.getSurface() 83 surfaceRequest.provideSurface(surface, Runnable::run) { 84 viewfinderSurfaceRequest.markSurfaceSafeToRelease() 85 } 86 } finally { 87 // If we haven't provided the surface, such as if we're cancelled 88 // while suspending on getSurface(), this call will succeed. Otherwise 89 // it will be a no-op. 90 surfaceRequest.willNotProvideSurface() 91 } 92 } 93 94 val transformationInfos = MutableStateFlow<CXTransformationInfo?>(null) 95 surfaceRequest.setTransformationInfoListener(Runnable::run) { 96 transformationInfos.value = it 97 } 98 99 // The ImplementationMode that will be used for all TransformationInfo updates. 100 // This is locked in once we have updated ViewfinderArgs and won't change until 101 // this produceState block is cancelled and restarted 102 var snapshotImplementationMode: ImplementationMode? = null 103 104 snapshotFlow { currentImplementationMode } 105 .combine(transformationInfos.filterNotNull()) { implMode, transformInfo -> 106 Pair(implMode, transformInfo) 107 }.takeWhile { (implMode, _) -> 108 val shouldAbort = 109 snapshotImplementationMode != null && implMode != snapshotImplementationMode 110 if (shouldAbort) { 111 // Abort flow and invalidate SurfaceRequest so a new one will be sent 112 surfaceRequest.invalidate() 113 } 114 !shouldAbort 115 }.collectLatest { (implMode, transformInfo) -> 116 // We'll only ever get here with a single non-null implMode, 117 // so setting it every time is ok 118 snapshotImplementationMode = implMode 119 value = ViewfinderArgs( 120 viewfinderSurfaceRequest, 121 isSourceHdr = surfaceRequest.dynamicRange.encoding != DynamicRange.ENCODING_SDR, 122 implMode, 123 TransformationInfo( 124 sourceRotation = transformInfo.rotationDegrees, 125 cropRectLeft = transformInfo.cropRect.left, 126 cropRectTop = transformInfo.cropRect.top, 127 cropRectRight = transformInfo.cropRect.right, 128 cropRectBottom = transformInfo.cropRect.bottom, 129 shouldMirror = transformInfo.isMirroring 130 ) 131 ) 132 } 133 } 134 135 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { <lambda>null136 LaunchedEffect(Unit) { 137 snapshotFlow { viewfinderArgs } 138 .filterNotNull() 139 .map { args -> 140 if (args.isSourceHdr && 141 args.implementationMode == ImplementationMode.EXTERNAL 142 ) { 143 ActivityInfo.COLOR_MODE_HDR 144 } else { 145 ActivityInfo.COLOR_MODE_DEFAULT 146 } 147 }.distinctUntilChanged() 148 .onEach { currentOnRequestWindowColorMode(it) } 149 .onCompletion { currentOnRequestWindowColorMode(ActivityInfo.COLOR_MODE_DEFAULT) } 150 .collect() 151 } 152 } 153 argsnull154 viewfinderArgs?.let { args -> 155 Viewfinder( 156 surfaceRequest = args.viewfinderSurfaceRequest, 157 implementationMode = args.implementationMode, 158 transformationInfo = args.transformationInfo, 159 modifier = modifier.fillMaxSize() 160 ) 161 } 162 } 163 164 private data class ViewfinderArgs( 165 val viewfinderSurfaceRequest: ViewfinderSurfaceRequest, 166 val isSourceHdr: Boolean, 167 val implementationMode: ImplementationMode, 168 val transformationInfo: TransformationInfo 169 ) 170