1 /* 2 * Copyright (C) 2015 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 com.android.camera.device; 18 19 import android.annotation.TargetApi; 20 import android.hardware.Camera; 21 import android.os.Build.VERSION_CODES; 22 23 import com.android.camera.async.Lifetime; 24 import com.android.camera.debug.Log.Tag; 25 import com.android.camera.debug.Logger; 26 import com.android.camera.debug.Loggers; 27 import com.android.camera.device.CameraDeviceKey.ApiType; 28 import com.android.ex.camera2.portability.CameraAgent.CameraProxy; 29 import com.google.common.annotations.VisibleForTesting; 30 import com.google.common.util.concurrent.Futures; 31 import com.google.common.util.concurrent.ListenableFuture; 32 import com.google.common.util.concurrent.SettableFuture; 33 34 import java.util.concurrent.Future; 35 36 import javax.annotation.Nullable; 37 import javax.annotation.ParametersAreNonnullByDefault; 38 import javax.annotation.concurrent.GuardedBy; 39 40 /** 41 * This class's only job is to open and close camera devices safely, such that 42 * only one device of any type is open at any point in time. This provider 43 * operates on the principle that the most recent request wins. 44 * 45 * The logic for opening a camera device proceeds as follows: 46 * 47 * 1. If there is no open camera, create a new camera request, and open 48 * the device. 49 * 2. If there is an existing request, and the device id's match, 50 * then reuse the old request and cancel any outstanding "please 51 * open this device next" requests. However, if the previous future 52 * for that current device was not yet completed, cancel it, and 53 * create a new future that will then get returned. 54 * 3. If there is an existing request, but the device ids don't match, 55 * cancel any outstanding "please open this device next" requests. 56 * Then create a new request and return the future and begin the shutdown 57 * process on the current device holder. However, do NOT begin opening 58 * this device until the current device is closed. 59 */ 60 @ParametersAreNonnullByDefault 61 public class MultiCameraDeviceLifecycle { 62 private static final Tag TAG = new Tag("MltiDeviceLife"); 63 64 private static class Singleton { 65 private static final MultiCameraDeviceLifecycle INSTANCE = new MultiCameraDeviceLifecycle( 66 CameraModuleHelper.provideLegacyCameraActionProvider(), 67 CameraModuleHelper.providePortabilityActionProvider(), 68 CameraModuleHelper.provideCamera2ActionProvider(), 69 ActiveCameraDeviceTracker.instance(), 70 Loggers.tagFactory()); 71 } 72 instance()73 public static MultiCameraDeviceLifecycle instance() { 74 return Singleton.INSTANCE; 75 } 76 77 private final LegacyCameraActionProvider mLegacyCameraActionProvider; 78 private final PortabilityCameraActionProvider mPortabilityCameraActionProvider; 79 private final Camera2ActionProvider mCamera2ActionProvider; 80 private final ActiveCameraDeviceTracker mActiveCameraDeviceTracker; 81 82 private final Object mDeviceLock = new Object(); 83 private final Logger.Factory mLogFactory; 84 private final Logger mLogger; 85 86 @Nullable 87 @GuardedBy("mDeviceLock") 88 private SingleDeviceLifecycle mCurrentDevice; 89 90 @Nullable 91 @GuardedBy("mDeviceLock") 92 private SingleDeviceLifecycle mTargetDevice; 93 94 @Nullable 95 @GuardedBy("mDeviceLock") 96 private SettableFuture<Void> mShutdownFuture; 97 98 @VisibleForTesting MultiCameraDeviceLifecycle( LegacyCameraActionProvider legacyCameraActionProvider, PortabilityCameraActionProvider portabilityCameraActionProvider, Camera2ActionProvider camera2ActionProvider, ActiveCameraDeviceTracker activeCameraDeviceTracker, Logger.Factory logFactory)99 MultiCameraDeviceLifecycle( 100 LegacyCameraActionProvider legacyCameraActionProvider, 101 PortabilityCameraActionProvider portabilityCameraActionProvider, 102 Camera2ActionProvider camera2ActionProvider, 103 ActiveCameraDeviceTracker activeCameraDeviceTracker, 104 Logger.Factory logFactory) { 105 mLegacyCameraActionProvider = legacyCameraActionProvider; 106 mPortabilityCameraActionProvider = portabilityCameraActionProvider; 107 mCamera2ActionProvider = camera2ActionProvider; 108 mActiveCameraDeviceTracker = activeCameraDeviceTracker; 109 mLogFactory = logFactory; 110 mLogger = logFactory.create(TAG); 111 112 mLogger.d("Creating the CameraDeviceProvider."); 113 } 114 115 /** 116 * !!! Warning !!! 117 * Code using this class should close the camera device by closing the 118 * provided lifetime instead of calling close directly on the camera 119 * object. Failing to do so may leave the multi camera lifecycle in an 120 * inconsistent state. 121 * 122 * Returns a future to an API2 Camera device. The future will only 123 * complete if the camera is fully opened successfully. If the device cannot 124 * be opened, the future will be canceled or provided with an exception 125 * depending on the nature of the internal failure. This call will not block 126 * the calling thread. 127 * 128 * @param requestLifetime the lifetime for the duration of the request. 129 * Closing the lifetime will cancel any outstanding request and will 130 * cause the camera to close. Closing the lifetime instead of the device 131 * will ensure everything is shut down properly. 132 * @param cameraId the specific camera device to open. 133 */ 134 @TargetApi(VERSION_CODES.LOLLIPOP) openCamera2Device( Lifetime requestLifetime, CameraId cameraId)135 public ListenableFuture<android.hardware.camera2.CameraDevice> openCamera2Device( 136 Lifetime requestLifetime, CameraId cameraId) { 137 CameraDeviceKey key = new CameraDeviceKey(ApiType.CAMERA_API2, cameraId); 138 return openDevice(requestLifetime, key, mCamera2ActionProvider); 139 } 140 141 /** 142 * !!! Warning !!! 143 * Code using this class should close the camera device by closing the 144 * provided lifetime instead of calling close directly on the camera 145 * object. Failing to do so may leave the multi camera lifecycle in an 146 * inconsistent state. 147 * 148 * This returns a future to a CameraProxy device in auto mode and does not 149 * make any guarantees about the backing API version. The future will only 150 * return if the device is fully opened successfully. If the device cannot 151 * be opened, the future will be canceled or provided with an exception 152 * depending on the nature of the internal failure. This call will not block 153 * the calling thread. 154 * 155 * @param requestLifetime the lifetime for the duration of the request. 156 * Closing the lifetime will cancel any outstanding request and will 157 * cause the camera to close. Closing the lifetime instead of the device 158 * will ensure everything is shut down properly. 159 * @param cameraId the specific camera device to open. 160 */ openPortabilityDevice( Lifetime requestLifetime, CameraId cameraId)161 public ListenableFuture<CameraProxy> openPortabilityDevice( 162 Lifetime requestLifetime, CameraId cameraId) { 163 CameraDeviceKey key = new CameraDeviceKey(ApiType.CAMERA_API_PORTABILITY_AUTO, 164 cameraId); 165 return openDevice(requestLifetime, key, mPortabilityCameraActionProvider); 166 } 167 168 /** 169 * !!! Warning !!! 170 * Code using this class should close the camera device by closing the 171 * provided lifetime instead of calling close directly on the camera 172 * object. Failing to do so may leave the multi camera lifecycle in an 173 * inconsistent state. 174 * 175 * This returns a future to a CameraProxy device opened explicitly with an 176 * API2 backing device. The future will only return if the device is fully 177 * opened successfully. If the device cannot be opened, the future will be 178 * canceled or provided with an exception depending on the nature of the 179 * internal failure. This call will not block the calling thread. 180 * 181 * @param requestLifetime the lifetime for the duration of the request. 182 * Closing the lifetime will cancel any outstanding request and will 183 * cause the camera to close. Closing the lifetime instead of the device 184 * will ensure everything is shut down properly. 185 * @param cameraId the specific camera device to open. 186 */ 187 @TargetApi(VERSION_CODES.LOLLIPOP) openCamera2PortabilityDevice( Lifetime requestLifetime, CameraId cameraId)188 public ListenableFuture<CameraProxy> openCamera2PortabilityDevice( 189 Lifetime requestLifetime, CameraId cameraId) { 190 CameraDeviceKey key = new CameraDeviceKey(ApiType.CAMERA_API_PORTABILITY_API2, 191 cameraId); 192 return openDevice(requestLifetime, key, mPortabilityCameraActionProvider); 193 } 194 195 /** 196 * !!! Warning !!! 197 * Code using this class should close the camera device by closing the 198 * provided lifetime instead of calling close directly on the camera 199 * object. Failing to do so may leave the multi camera lifecycle in an 200 * inconsistent state. 201 * 202 * This returns a future to a CameraProxy device opened explicitly with an 203 * legacy backing API. The future will only return if the device is fully 204 * opened successfully. If the device cannot be opened, the future will be 205 * canceled or provided with an exception depending on the nature of the 206 * internal failure. This call will not block the calling thread. 207 * 208 * @param requestLifetime the lifetime for the duration of the request. 209 * Closing the lifetime will cancel any outstanding request and will 210 * cause the camera to close. Closing the lifetime instead of the device 211 * will ensure everything is shut down properly. 212 * @param cameraId the specific camera device to open. 213 */ openLegacyPortabilityDevice( Lifetime requestLifetime, CameraId cameraId)214 public ListenableFuture<CameraProxy> openLegacyPortabilityDevice( 215 Lifetime requestLifetime, CameraId cameraId) { 216 CameraDeviceKey key = new CameraDeviceKey(ApiType.CAMERA_API_PORTABILITY_API1, cameraId); 217 return openDevice(requestLifetime, key, mPortabilityCameraActionProvider); 218 } 219 /** 220 * !!! Warning !!! 221 * Code using this class should close the camera device by closing the 222 * provided lifetime instead of calling close directly on the camera 223 * object. Failing to do so may leave the multi camera lifecycle in an 224 * inconsistent state. 225 * 226 * This returns a future to a legacy Camera device The future will only return 227 * if the device is fully opened successfully. If the device cannot be opened, 228 * the future will be canceled or provided with an exception depending on the 229 * nature of the internal failure. This call will not block the calling thread. 230 * 231 * @param requestLifetime the lifetime for the duration of the request. 232 * Closing the lifetime will cancel any outstanding request and will 233 * cause the camera to close. Closing the lifetime instead of the device 234 * will ensure everything is shut down properly. 235 * @param cameraId the specific camera device to open. 236 */ 237 @Deprecated openLegacyCameraDevice(Lifetime requestLifetime, CameraId cameraId)238 public ListenableFuture<Camera> openLegacyCameraDevice(Lifetime requestLifetime, 239 CameraId cameraId) { 240 CameraDeviceKey key = new CameraDeviceKey(ApiType.CAMERA_API1, cameraId); 241 return openDevice(requestLifetime, key, mLegacyCameraActionProvider); 242 } 243 244 /** 245 * This will close any open or pending requests and will execute the future 246 * when all requests have been cleared out. This method executes immediately. 247 */ shutdown()248 public ListenableFuture<Void> shutdown() { 249 synchronized (mDeviceLock) { 250 mLogger.d("shutdownAsync()"); 251 if (mCurrentDevice != null) { 252 // Ensure there are no queued requests. Cancel any existing requests. 253 clearTargetDevice(); 254 255 // Create a future that we can complete when the device completes 256 // its shutdown cycle. 257 mShutdownFuture = SettableFuture.create(); 258 259 // Execute close on the current device. 260 mCurrentDevice.close(); 261 return mShutdownFuture; 262 } else if (mShutdownFuture != null) { 263 // This could occur if a previous shutdown call occurred, and 264 // the receiver called cancel on the future before it completed. 265 if (mShutdownFuture.isDone()) { 266 mShutdownFuture = null; 267 } else { 268 return mShutdownFuture; 269 } 270 } 271 // If there is no currently open device, this instance is already in a 272 // clean shutdown state. 273 return Futures.immediateFuture(null); 274 } 275 } 276 277 /** 278 * Given a request lifetime, a key and a provider, open a new device. 279 */ openDevice(Lifetime requestLifetime, CameraDeviceKey key, CameraDeviceActionProvider<TDevice> provider)280 private <TDevice> ListenableFuture<TDevice> openDevice(Lifetime requestLifetime, 281 CameraDeviceKey key, CameraDeviceActionProvider<TDevice> provider) { 282 283 final SingleDeviceLifecycle<TDevice, CameraDeviceKey> deviceLifecycle; 284 final ListenableFuture<TDevice> result; 285 286 synchronized (mDeviceLock) { 287 mLogger.d("[openDevice()] open(cameraId: '" + key + "')"); 288 cancelShutdown(); 289 290 if (mCurrentDevice == null) { 291 mLogger.d("[openDevice()] No existing request. Creating a new device."); 292 deviceLifecycle = createLifecycle(key, provider); 293 mCurrentDevice = deviceLifecycle; 294 result = deviceLifecycle.createRequest(requestLifetime); 295 deviceLifecycle.open(); 296 mActiveCameraDeviceTracker.onCameraOpening(key.getCameraId()); 297 } else if (mCurrentDevice.getId().equals(key)) { 298 mLogger.d("[openDevice()] Existing request with the same id."); 299 deviceLifecycle = 300 (SingleDeviceLifecycle<TDevice, CameraDeviceKey>) mCurrentDevice; 301 clearTargetDevice(); 302 result = deviceLifecycle.createRequest(requestLifetime); 303 deviceLifecycle.open(); 304 mActiveCameraDeviceTracker.onCameraOpening(key.getCameraId()); 305 } else { 306 mLogger.d("[openDevice()] Existing request with a different id."); 307 mCurrentDevice.close(); 308 deviceLifecycle = createLifecycle(key, provider); 309 clearTargetDevice(); 310 mTargetDevice = deviceLifecycle; 311 result = deviceLifecycle.createRequest(requestLifetime); 312 } 313 314 mLogger.d("[openDevice()] Returning future."); 315 return result; 316 } 317 } 318 319 private <TDevice> SingleDeviceLifecycle<TDevice, CameraDeviceKey> createLifecycle(CameraDeviceKey key, CameraDeviceActionProvider<TDevice> provider)320 createLifecycle(CameraDeviceKey key, 321 CameraDeviceActionProvider<TDevice> provider) { 322 SingleDeviceShutdownListener<CameraDeviceKey> listener = 323 new SingleDeviceShutdownListener<CameraDeviceKey>() { 324 @Override 325 public void onShutdown(CameraDeviceKey key) { 326 onCameraDeviceShutdown(key); 327 } 328 }; 329 330 SingleDeviceStateMachine<TDevice, CameraDeviceKey> deviceState = 331 new SingleDeviceStateMachine<>(provider.get(key), key, listener, mLogFactory); 332 333 return new CameraDeviceLifecycle<>(key, deviceState); 334 } 335 clearTargetDevice()336 private void clearTargetDevice() { 337 if (mTargetDevice != null) { 338 mLogger.d("Target request exists. cancel() and clear."); 339 mTargetDevice.close(); 340 mTargetDevice = null; 341 } 342 } 343 cancelShutdown()344 private void cancelShutdown() { 345 if (mShutdownFuture != null) { 346 mLogger.i("Canceling shutdown."); 347 Future<Void> shutdownFuture = mShutdownFuture; 348 mShutdownFuture = null; 349 shutdownFuture.cancel(true /* mayInterruptIfRunning */); 350 } 351 } 352 completeShutdown()353 private void completeShutdown() { 354 if (mShutdownFuture != null) { 355 mLogger.i("Completing shutdown."); 356 SettableFuture<Void> shutdownFuture = mShutdownFuture; 357 mShutdownFuture = null; 358 shutdownFuture.set(null); 359 } 360 } 361 onCameraDeviceShutdown(CameraDeviceKey key)362 private void onCameraDeviceShutdown(CameraDeviceKey key) { 363 synchronized (mDeviceLock) { 364 mLogger.d("onCameraClosed(id: " + key + ")."); 365 if (mShutdownFuture != null && 366 (mCurrentDevice == null || (mCurrentDevice.getId().equals(key)))) { 367 // if the shutdown future is set but there is no current device, 368 // we should call shutdown, just in case so that it clears out the 369 // shutdown state. If 370 mCurrentDevice = null; 371 completeShutdown(); 372 } if (mCurrentDevice != null && mCurrentDevice.getId().equals(key)) { 373 374 mLogger.d("Current device was closed."); 375 376 if (mTargetDevice != null) { 377 mLogger.d("Target request exists, calling open()."); 378 mCurrentDevice = mTargetDevice; 379 mTargetDevice = null; 380 mCurrentDevice.open(); 381 mActiveCameraDeviceTracker.onCameraOpening(((CameraDeviceKey) 382 mCurrentDevice.getId()).getCameraId()); 383 } else { 384 mLogger.d("No target request exists. Clearing current device."); 385 mCurrentDevice = null; 386 mActiveCameraDeviceTracker.onCameraClosed(key.getCameraId()); 387 } 388 } 389 } 390 } 391 } 392