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