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
17package com.android.camera.device;
18
19import android.annotation.TargetApi;
20import android.hardware.Camera;
21import android.os.Build.VERSION_CODES;
22
23import com.android.camera.async.Lifetime;
24import com.android.camera.debug.Log.Tag;
25import com.android.camera.debug.Logger;
26import com.android.camera.debug.Loggers;
27import com.android.camera.device.CameraDeviceKey.ApiType;
28import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
29import com.google.common.annotations.VisibleForTesting;
30import com.google.common.util.concurrent.Futures;
31import com.google.common.util.concurrent.ListenableFuture;
32import com.google.common.util.concurrent.SettableFuture;
33
34import java.util.concurrent.Future;
35
36import javax.annotation.Nullable;
37import javax.annotation.ParametersAreNonnullByDefault;
38import 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
61public 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