Home | History | Annotate | Download | only in device
      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 
     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
     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)
    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      */
    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)
    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      */
    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
    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      */
    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      */
    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>
    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 
    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 
    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 
    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 
    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