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