• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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