1 /*
2  * 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.core;
18 
19 import android.app.Application;
20 import android.util.Log;
21 
22 import androidx.annotation.MainThread;
23 import androidx.annotation.OptIn;
24 import androidx.camera.camera2.Camera2Config;
25 import androidx.camera.camera2.pipe.integration.CameraPipeConfig;
26 import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration;
27 import androidx.camera.lifecycle.ProcessCameraProvider;
28 import androidx.core.content.ContextCompat;
29 import androidx.lifecycle.AndroidViewModel;
30 import androidx.lifecycle.LiveData;
31 import androidx.lifecycle.MutableLiveData;
32 
33 import com.google.common.util.concurrent.ListenableFuture;
34 
35 import org.jspecify.annotations.NonNull;
36 import org.jspecify.annotations.Nullable;
37 
38 import java.util.Objects;
39 import java.util.concurrent.CancellationException;
40 import java.util.concurrent.ExecutionException;
41 
42 /** View model providing access to the camera */
43 public class CameraXViewModel extends AndroidViewModel {
44     private static final String TAG = "CameraXViewModel";
45 
46     private static @Nullable String sConfiguredCameraXCameraImplementation = null;
47     // Does not explicitly configure with an implementation and relies on default config provider
48     // or previously configured implementation.
49     public static final String IMPLICIT_IMPLEMENTATION_OPTION = "implicit";
50     // Camera2 implementation.
51     public static final String CAMERA2_IMPLEMENTATION_OPTION = "camera2";
52     // Camera-pipe implementation.
53     public static final String CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe";
54     private static final String DEFAULT_CAMERA_IMPLEMENTATION = IMPLICIT_IMPLEMENTATION_OPTION;
55 
56 
57     private MutableLiveData<CameraProviderResult> mProcessCameraProviderLiveData;
58 
CameraXViewModel(@onNull Application application)59     public CameraXViewModel(@NonNull Application application) {
60         super(application);
61     }
62 
63     /**
64      * Returns a {@link LiveData} containing CameraX's {@link ProcessCameraProvider} once it has
65      * been initialized.
66      */
67     @MainThread
getCameraProvider()68     LiveData<CameraProviderResult> getCameraProvider() {
69         if (mProcessCameraProviderLiveData == null) {
70             mProcessCameraProviderLiveData = new MutableLiveData<>();
71             tryConfigureCameraProvider();
72             try {
73                 ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
74                         ProcessCameraProvider.getInstance(getApplication());
75 
76                 cameraProviderFuture.addListener(() -> {
77                     try {
78                         ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
79                         mProcessCameraProviderLiveData.setValue(
80                                 CameraProviderResult.fromProvider(cameraProvider));
81                     } catch (ExecutionException e) {
82                         if (!(e.getCause() instanceof CancellationException)) {
83                             mProcessCameraProviderLiveData.setValue(
84                                     CameraProviderResult.fromError(
85                                             Objects.requireNonNull(e.getCause())));
86                         }
87                     } catch (InterruptedException e) {
88                         throw new AssertionError("Unexpected thread interrupt.", e);
89                     }
90                 }, ContextCompat.getMainExecutor(getApplication()));
91             } catch (IllegalStateException e) {
92                 // Failure during ProcessCameraProvider.getInstance()
93                 mProcessCameraProviderLiveData.setValue(CameraProviderResult.fromError(e));
94             }
95         }
96         return mProcessCameraProviderLiveData;
97     }
98 
99     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
100     @MainThread
tryConfigureCameraProvider()101     private static void tryConfigureCameraProvider() {
102         if (sConfiguredCameraXCameraImplementation == null) {
103             configureCameraProvider(DEFAULT_CAMERA_IMPLEMENTATION);
104         }
105     }
106 
107     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
108     @MainThread
isCameraProviderUnInitializedOrSameAsParameter( @ullable String cameraImplementation)109     public static boolean isCameraProviderUnInitializedOrSameAsParameter(
110             @Nullable String cameraImplementation) {
111 
112         if (sConfiguredCameraXCameraImplementation == null) {
113             return true;
114         }
115         String currentCameraProvider = getCameraProviderName(
116                 sConfiguredCameraXCameraImplementation);
117         cameraImplementation = getCameraProviderName(cameraImplementation);
118 
119         return currentCameraProvider.equals(cameraImplementation);
120     }
121 
122     /**
123      * convert null and IMPLICIT_IMPLEMENTATION_OPTION Camera Provider name to
124      * CAMERA2_IMPLEMENTATION_OPTION
125      */
126     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
127     @MainThread
getCameraProviderName(@ullable String mCameraProvider)128     private static String getCameraProviderName(@Nullable String mCameraProvider) {
129         if (mCameraProvider == null) {
130             mCameraProvider = CAMERA2_IMPLEMENTATION_OPTION;
131         }
132         if (mCameraProvider.equals(IMPLICIT_IMPLEMENTATION_OPTION)) {
133             mCameraProvider = CAMERA2_IMPLEMENTATION_OPTION;
134         }
135         return mCameraProvider;
136     }
137 
138     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
139     @MainThread
configureCameraProvider(@onNull String cameraImplementation)140     static void configureCameraProvider(@NonNull String cameraImplementation) {
141         configureCameraProvider(cameraImplementation, false);
142     }
143 
144     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
145     @MainThread
configureCameraProvider(@onNull String cameraImplementation, boolean noHistory)146     static void configureCameraProvider(@NonNull String cameraImplementation, boolean noHistory) {
147         if (!cameraImplementation.equals(sConfiguredCameraXCameraImplementation)) {
148             // Attempt to configure. This will throw an ISE if singleton is already configured.
149             try {
150                 // If IMPLICIT_IMPLEMENTATION_OPTION is specified, we won't use explicit
151                 // configuration, but will depend on the default config provider or the
152                 // previously configured implementation.
153                 if (!cameraImplementation.equals(IMPLICIT_IMPLEMENTATION_OPTION)) {
154                     if (cameraImplementation.equals(CAMERA2_IMPLEMENTATION_OPTION)) {
155                         ProcessCameraProvider.configureInstance(Camera2Config.defaultConfig());
156                     } else if (cameraImplementation.equals(CAMERA_PIPE_IMPLEMENTATION_OPTION)) {
157                         ProcessCameraProvider.configureInstance(
158                                 CameraPipeConfig.defaultConfig());
159                     } else {
160                         throw new IllegalArgumentException("Failed to configure the CameraProvider "
161                                 + "using unknown " + cameraImplementation
162                                 + " implementation option.");
163                     }
164                 }
165 
166                 Log.d(TAG, "ProcessCameraProvider initialized using " + cameraImplementation);
167                 if (!noHistory) {
168                     sConfiguredCameraXCameraImplementation = cameraImplementation;
169                 }
170             } catch (IllegalStateException e) {
171                 throw new IllegalStateException("WARNING: CameraX is currently configured to use "
172                         + sConfiguredCameraXCameraImplementation + " which is different "
173                         + "from the desired implementation: " + cameraImplementation + " this "
174                         + "would have resulted in unexpected behavior.", e);
175             }
176         }
177     }
178 
getConfiguredCameraXCameraImplementation()179     public static @Nullable String getConfiguredCameraXCameraImplementation() {
180         return sConfiguredCameraXCameraImplementation;
181     }
182 
183     /**
184      * Class for wrapping success/error of initializing the {@link ProcessCameraProvider}.
185      */
186     public static final class CameraProviderResult {
187 
188         private final ProcessCameraProvider mProvider;
189         private final Throwable mError;
190 
fromProvider(@onNull ProcessCameraProvider provider)191         static CameraProviderResult fromProvider(@NonNull ProcessCameraProvider provider) {
192             return new CameraProviderResult(provider, /*error=*/null);
193         }
194 
fromError(@onNull Throwable error)195         static CameraProviderResult fromError(@NonNull Throwable error) {
196             return new CameraProviderResult(/*provider=*/null, error);
197         }
198 
CameraProviderResult(@ullable ProcessCameraProvider provider, @Nullable Throwable error)199         private CameraProviderResult(@Nullable ProcessCameraProvider provider,
200                 @Nullable Throwable error) {
201             mProvider = provider;
202             mError = error;
203         }
204 
205         /**
206          * Returns {@code true} if this result contains a {@link ProcessCameraProvider}. Returns
207          * {@code false} if it contains an error.
208          */
hasProvider()209         public boolean hasProvider() {
210             return mProvider != null;
211         }
212 
213         /**
214          * Returns a {@link ProcessCameraProvider} if the result does not contain an error,
215          * otherwise returns {@code null}.
216          *
217          * <p>Use {@link #hasProvider()} to check if this result contains a provider.
218          */
getProvider()219         public @Nullable ProcessCameraProvider getProvider() {
220             return mProvider;
221         }
222 
223         /**
224          * Returns a {@link Throwable} containing the error that prevented the
225          * {@link ProcessCameraProvider} from being available. Returns {@code null} if no error
226          * occurred.
227          *
228          * <p>Use {@link #hasProvider()} to check if this result contains a provider.
229          */
getError()230         public @Nullable Throwable getError() {
231             return mError;
232         }
233     }
234 }
235