1 /*
<lambda>null2  * Copyright 2020 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 
17 package androidx.camera.integration.uiwidgets.viewpager
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.hardware.camera2.CameraCaptureSession
22 import android.hardware.camera2.CaptureRequest
23 import android.hardware.camera2.TotalCaptureResult
24 import android.os.Bundle
25 import android.text.TextUtils
26 import android.util.Log
27 import android.view.LayoutInflater
28 import android.view.View
29 import android.view.ViewGroup
30 import androidx.annotation.OptIn
31 import androidx.annotation.VisibleForTesting
32 import androidx.camera.camera2.Camera2Config
33 import androidx.camera.camera2.interop.Camera2Interop
34 import androidx.camera.camera2.interop.ExperimentalCamera2Interop
35 import androidx.camera.camera2.pipe.integration.CameraPipeConfig
36 import androidx.camera.core.CameraSelector
37 import androidx.camera.core.Preview
38 import androidx.camera.integration.uiwidgets.databinding.FragmentTextureviewBinding
39 import androidx.camera.integration.uiwidgets.viewpager.BaseActivity.Companion.COMPATIBLE_MODE
40 import androidx.camera.integration.uiwidgets.viewpager.BaseActivity.Companion.PERFORMANCE_MODE
41 import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration
42 import androidx.camera.lifecycle.ProcessCameraProvider
43 import androidx.camera.view.PreviewView.ImplementationMode
44 import androidx.core.content.ContextCompat
45 import androidx.fragment.app.Fragment
46 import androidx.lifecycle.Lifecycle
47 import com.google.common.util.concurrent.ListenableFuture
48 import java.util.concurrent.CountDownLatch
49 
50 /** A Fragment that displays a {@link PreviewView} with TextureView mode. */
51 class CameraFragment : Fragment() {
52 
53     companion object {
54         fun newInstance() = CameraFragment()
55 
56         private const val TAG = "CameraFragment"
57         const val KEY_CAMERA_IMPLEMENTATION = "camera_implementation"
58         const val KEY_CAMERA_IMPLEMENTATION_NO_HISTORY = "camera_implementation_no_history"
59         const val CAMERA2_IMPLEMENTATION_OPTION = "camera2"
60         const val CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe"
61         private var cameraImpl: String? = null
62     }
63 
64     private var _binding: FragmentTextureviewBinding? = null
65     private val binding
66         get() = _binding!!
67 
68     private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
69     private lateinit var cameraProvider: ProcessCameraProvider
70 
71     // for testing preview updates
72     private var previewUpdatingLatch: CountDownLatch? = null
73 
74     @OptIn(ExperimentalCameraProviderConfiguration::class)
75     override fun onAttach(context: Context) {
76         super.onAttach(context)
77         val newCameraImpl = activity?.intent?.getStringExtra(KEY_CAMERA_IMPLEMENTATION)
78         Log.d(TAG, "Set up cameraImpl: $newCameraImpl")
79         if (!TextUtils.isEmpty(newCameraImpl) && newCameraImpl != cameraImpl) {
80             try {
81                 Log.d(TAG, "ProcessCameraProvider initialize using $newCameraImpl")
82                 ProcessCameraProvider.configureInstance(
83                     when (newCameraImpl) {
84                         CAMERA2_IMPLEMENTATION_OPTION -> Camera2Config.defaultConfig()
85                         CAMERA_PIPE_IMPLEMENTATION_OPTION -> CameraPipeConfig.defaultConfig()
86                         else -> Camera2Config.defaultConfig()
87                     }
88                 )
89                 cameraImpl = newCameraImpl
90             } catch (e: IllegalStateException) {
91                 throw IllegalStateException(
92                     "WARNING: CameraX is currently configured to a different implementation " +
93                         "this would have resulted in unexpected behavior.",
94                     e
95                 )
96             }
97         }
98 
99         activity?.intent?.let { intent ->
100             if (intent.getBooleanExtra(KEY_CAMERA_IMPLEMENTATION_NO_HISTORY, false)) {
101                 activity?.intent =
102                     Intent(intent).apply {
103                         removeExtra(KEY_CAMERA_IMPLEMENTATION)
104                         removeExtra(KEY_CAMERA_IMPLEMENTATION_NO_HISTORY)
105                     }
106                 cameraImpl = null
107             }
108         }
109 
110         cameraProviderFuture = ProcessCameraProvider.getInstance(context)
111     }
112 
113     override fun onCreateView(
114         inflater: LayoutInflater,
115         container: ViewGroup?,
116         savedInstanceState: Bundle?
117     ): View {
118         _binding = FragmentTextureviewBinding.inflate(inflater, container, false)
119         return binding.root
120     }
121 
122     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
123         Log.d(TAG, "onViewCreated")
124         (requireActivity() as BaseActivity).previewView = binding.previewTextureview
125 
126         cameraProviderFuture.addListener(
127             Runnable {
128                 cameraProvider = cameraProviderFuture.get()
129                 val lifecycleOwner = viewLifecycleOwnerLiveData.value
130                 if (
131                     lifecycleOwner != null &&
132                         lifecycleOwner.lifecycle.currentState != Lifecycle.State.DESTROYED
133                 ) {
134                     bindPreview()
135                 } else {
136                     Log.d(TAG, "Skip camera setup since the lifecycle is closed")
137                 }
138             },
139             ContextCompat.getMainExecutor(requireContext())
140         )
141     }
142 
143     override fun onDestroyView() {
144         super.onDestroyView()
145         _binding = null
146     }
147 
148     private fun bindPreview() {
149         Log.d(TAG, "bindPreview")
150 
151         val previewBuilder = Preview.Builder()
152         previewBuilder.addCaptureCompletedCallback()
153         val preview = previewBuilder.setTargetName("Preview").build()
154 
155         cameraProvider.bindToLifecycle(this, getCameraSelector(), preview)
156 
157         binding.previewTextureview.implementationMode = getImplementationMode()
158         preview.setSurfaceProvider(binding.previewTextureview.surfaceProvider)
159     }
160 
161     private fun getCameraSelector(): CameraSelector {
162         val lensFacing =
163             (requireActivity() as BaseActivity)
164                 .intent
165                 .getIntExtra(BaseActivity.INTENT_LENS_FACING, CameraSelector.LENS_FACING_BACK)
166         return CameraSelector.Builder().requireLensFacing(lensFacing).build()
167     }
168 
169     /**
170      * Returns the implementation mode from the intent, or return the compatibility mode if not set.
171      */
172     private fun getImplementationMode(): ImplementationMode {
173         val mode =
174             (requireActivity() as BaseActivity)
175                 .intent
176                 .getIntExtra(BaseActivity.INTENT_IMPLEMENTATION_MODE, COMPATIBLE_MODE)
177 
178         return when (mode) {
179             PERFORMANCE_MODE -> ImplementationMode.PERFORMANCE
180             else -> ImplementationMode.COMPATIBLE
181         }
182     }
183 
184     /**
185      * Implements preview updating latch with interop to workaround the situation that SurfaceView's
186      * content can not be got.
187      */
188     @OptIn(ExperimentalCamera2Interop::class)
189     @kotlin.OptIn(
190         androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop::class
191     )
192     private fun Preview.Builder.addCaptureCompletedCallback() {
193         val captureCallback =
194             object : CameraCaptureSession.CaptureCallback() {
195                 override fun onCaptureCompleted(
196                     session: CameraCaptureSession,
197                     request: CaptureRequest,
198                     result: TotalCaptureResult
199                 ) {
200                     super.onCaptureCompleted(session, request, result)
201 
202                     if (previewUpdatingLatch != null) {
203                         previewUpdatingLatch!!.countDown()
204                     }
205                 }
206             }
207 
208         if (cameraImpl.equals(CAMERA_PIPE_IMPLEMENTATION_OPTION)) {
209             androidx.camera.camera2.pipe.integration.interop.Camera2Interop.Extender(this)
210                 .setSessionCaptureCallback(captureCallback)
211         } else {
212             Camera2Interop.Extender(this).setSessionCaptureCallback(captureCallback)
213         }
214         Camera2Interop.Extender(this).setSessionCaptureCallback(captureCallback)
215     }
216 
217     @VisibleForTesting
218     fun setPreviewUpdatingLatch(latch: CountDownLatch) {
219         previewUpdatingLatch = latch
220     }
221 }
222