1 /*
2  * Copyright (C) 2013 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 android.hardware.camera2;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemService;
24 import android.content.Context;
25 import android.hardware.CameraInfo;
26 import android.hardware.CameraStatus;
27 import android.hardware.ICameraService;
28 import android.hardware.ICameraServiceListener;
29 import android.hardware.camera2.impl.CameraDeviceImpl;
30 import android.hardware.camera2.impl.CameraMetadataNative;
31 import android.hardware.camera2.legacy.CameraDeviceUserShim;
32 import android.hardware.camera2.legacy.LegacyMetadataMapper;
33 import android.os.Binder;
34 import android.os.DeadObjectException;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Looper;
38 import android.os.RemoteException;
39 import android.os.ServiceManager;
40 import android.os.ServiceSpecificException;
41 import android.os.SystemProperties;
42 import android.util.ArrayMap;
43 import android.util.Log;
44 
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Comparator;
48 
49 import java.util.concurrent.Executor;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.RejectedExecutionException;
52 import java.util.concurrent.ScheduledExecutorService;
53 import java.util.concurrent.TimeUnit;
54 
55 /**
56  * <p>A system service manager for detecting, characterizing, and connecting to
57  * {@link CameraDevice CameraDevices}.</p>
58  *
59  * <p>For more details about communicating with camera devices, read the Camera
60  * developer guide or the {@link android.hardware.camera2 camera2}
61  * package documentation.</p>
62  */
63 @SystemService(Context.CAMERA_SERVICE)
64 public final class CameraManager {
65 
66     private static final String TAG = "CameraManager";
67     private final boolean DEBUG = false;
68 
69     private static final int USE_CALLING_UID = -1;
70 
71     @SuppressWarnings("unused")
72     private static final int API_VERSION_1 = 1;
73     private static final int API_VERSION_2 = 2;
74 
75     private static final int CAMERA_TYPE_BACKWARD_COMPATIBLE = 0;
76     private static final int CAMERA_TYPE_ALL = 1;
77 
78     private ArrayList<String> mDeviceIdList;
79 
80     private final Context mContext;
81     private final Object mLock = new Object();
82 
83     /**
84      * @hide
85      */
CameraManager(Context context)86     public CameraManager(Context context) {
87         synchronized(mLock) {
88             mContext = context;
89         }
90     }
91 
92     /**
93      * Return the list of currently connected camera devices by identifier, including
94      * cameras that may be in use by other camera API clients.
95      *
96      * <p>Non-removable cameras use integers starting at 0 for their
97      * identifiers, while removable cameras have a unique identifier for each
98      * individual device, even if they are the same model.</p>
99      *
100      * @return The list of currently connected camera devices.
101      */
102     @NonNull
getCameraIdList()103     public String[] getCameraIdList() throws CameraAccessException {
104         return CameraManagerGlobal.get().getCameraIdList();
105     }
106 
107     /**
108      * Register a callback to be notified about camera device availability.
109      *
110      * <p>Registering the same callback again will replace the handler with the
111      * new one provided.</p>
112      *
113      * <p>The first time a callback is registered, it is immediately called
114      * with the availability status of all currently known camera devices.</p>
115      *
116      * <p>{@link AvailabilityCallback#onCameraUnavailable(String)} will be called whenever a camera
117      * device is opened by any camera API client. As of API level 23, other camera API clients may
118      * still be able to open such a camera device, evicting the existing client if they have higher
119      * priority than the existing client of a camera device. See open() for more details.</p>
120      *
121      * <p>Since this callback will be registered with the camera service, remember to unregister it
122      * once it is no longer needed; otherwise the callback will continue to receive events
123      * indefinitely and it may prevent other resources from being released. Specifically, the
124      * callbacks will be invoked independently of the general activity lifecycle and independently
125      * of the state of individual CameraManager instances.</p>
126      *
127      * @param callback the new callback to send camera availability notices to
128      * @param handler The handler on which the callback should be invoked, or {@code null} to use
129      *             the current thread's {@link android.os.Looper looper}.
130      *
131      * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
132      *             no looper.
133      */
registerAvailabilityCallback(@onNull AvailabilityCallback callback, @Nullable Handler handler)134     public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback,
135             @Nullable Handler handler) {
136         CameraManagerGlobal.get().registerAvailabilityCallback(callback,
137                 CameraDeviceImpl.checkAndWrapHandler(handler));
138     }
139 
140     /**
141      * Register a callback to be notified about camera device availability.
142      *
143      * <p>The behavior of this method matches that of
144      * {@link #registerAvailabilityCallback(AvailabilityCallback, Handler)},
145      * except that it uses {@link java.util.concurrent.Executor} as an argument
146      * instead of {@link android.os.Handler}.</p>
147      *
148      * @param executor The executor which will be used to invoke the callback.
149      * @param callback the new callback to send camera availability notices to
150      *
151      * @throws IllegalArgumentException if the executor is {@code null}.
152      */
registerAvailabilityCallback(@onNull @allbackExecutor Executor executor, @NonNull AvailabilityCallback callback)153     public void registerAvailabilityCallback(@NonNull @CallbackExecutor Executor executor,
154             @NonNull AvailabilityCallback callback) {
155         if (executor == null) {
156             throw new IllegalArgumentException("executor was null");
157         }
158         CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor);
159     }
160 
161     /**
162      * Remove a previously-added callback; the callback will no longer receive connection and
163      * disconnection callbacks.
164      *
165      * <p>Removing a callback that isn't registered has no effect.</p>
166      *
167      * @param callback The callback to remove from the notification list
168      */
unregisterAvailabilityCallback(@onNull AvailabilityCallback callback)169     public void unregisterAvailabilityCallback(@NonNull AvailabilityCallback callback) {
170         CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
171     }
172 
173     /**
174      * Register a callback to be notified about torch mode status.
175      *
176      * <p>Registering the same callback again will replace the handler with the
177      * new one provided.</p>
178      *
179      * <p>The first time a callback is registered, it is immediately called
180      * with the torch mode status of all currently known camera devices with a flash unit.</p>
181      *
182      * <p>Since this callback will be registered with the camera service, remember to unregister it
183      * once it is no longer needed; otherwise the callback will continue to receive events
184      * indefinitely and it may prevent other resources from being released. Specifically, the
185      * callbacks will be invoked independently of the general activity lifecycle and independently
186      * of the state of individual CameraManager instances.</p>
187      *
188      * @param callback The new callback to send torch mode status to
189      * @param handler The handler on which the callback should be invoked, or {@code null} to use
190      *             the current thread's {@link android.os.Looper looper}.
191      *
192      * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
193      *             no looper.
194      */
registerTorchCallback(@onNull TorchCallback callback, @Nullable Handler handler)195     public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) {
196         CameraManagerGlobal.get().registerTorchCallback(callback,
197                 CameraDeviceImpl.checkAndWrapHandler(handler));
198     }
199 
200     /**
201      * Register a callback to be notified about torch mode status.
202      *
203      * <p>The behavior of this method matches that of
204      * {@link #registerTorchCallback(TorchCallback, Handler)},
205      * except that it uses {@link java.util.concurrent.Executor} as an argument
206      * instead of {@link android.os.Handler}.</p>
207      *
208      * @param executor The executor which will be used to invoke the callback
209      * @param callback The new callback to send torch mode status to
210      *
211      * @throws IllegalArgumentException if the executor is {@code null}.
212      */
registerTorchCallback(@onNull @allbackExecutor Executor executor, @NonNull TorchCallback callback)213     public void registerTorchCallback(@NonNull @CallbackExecutor Executor executor,
214             @NonNull TorchCallback callback) {
215         if (executor == null) {
216             throw new IllegalArgumentException("executor was null");
217         }
218         CameraManagerGlobal.get().registerTorchCallback(callback, executor);
219     }
220 
221     /**
222      * Remove a previously-added callback; the callback will no longer receive torch mode status
223      * callbacks.
224      *
225      * <p>Removing a callback that isn't registered has no effect.</p>
226      *
227      * @param callback The callback to remove from the notification list
228      */
unregisterTorchCallback(@onNull TorchCallback callback)229     public void unregisterTorchCallback(@NonNull TorchCallback callback) {
230         CameraManagerGlobal.get().unregisterTorchCallback(callback);
231     }
232 
233     /**
234      * <p>Query the capabilities of a camera device. These capabilities are
235      * immutable for a given camera.</p>
236      *
237      * @param cameraId The id of the camera device to query
238      * @return The properties of the given camera
239      *
240      * @throws IllegalArgumentException if the cameraId does not match any
241      *         known camera device.
242      * @throws CameraAccessException if the camera device has been disconnected.
243      *
244      * @see #getCameraIdList
245      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
246      */
247     @NonNull
getCameraCharacteristics(@onNull String cameraId)248     public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
249             throws CameraAccessException {
250         CameraCharacteristics characteristics = null;
251         if (CameraManagerGlobal.sCameraServiceDisabled) {
252             throw new IllegalArgumentException("No cameras available on device");
253         }
254         synchronized (mLock) {
255             /*
256              * Get the camera characteristics from the camera service directly if it supports it,
257              * otherwise get them from the legacy shim instead.
258              */
259             ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
260             if (cameraService == null) {
261                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
262                         "Camera service is currently unavailable");
263             }
264             try {
265                 if (!supportsCamera2ApiLocked(cameraId)) {
266                     // Legacy backwards compatibility path; build static info from the camera
267                     // parameters
268                     int id = Integer.parseInt(cameraId);
269 
270                     String parameters = cameraService.getLegacyParameters(id);
271 
272                     CameraInfo info = cameraService.getCameraInfo(id);
273 
274                     characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info);
275                 } else {
276                     // Normal path: Get the camera characteristics directly from the camera service
277                     CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId);
278 
279                     characteristics = new CameraCharacteristics(info);
280                 }
281             } catch (ServiceSpecificException e) {
282                 throwAsPublicException(e);
283             } catch (RemoteException e) {
284                 // Camera service died - act as if the camera was disconnected
285                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
286                         "Camera service is currently unavailable", e);
287             }
288         }
289         return characteristics;
290     }
291 
292     /**
293      * Helper for opening a connection to a camera with the given ID.
294      *
295      * @param cameraId The unique identifier of the camera device to open
296      * @param callback The callback for the camera. Must not be null.
297      * @param executor The executor to invoke the callback with. Must not be null.
298      * @param uid      The UID of the application actually opening the camera.
299      *                 Must be USE_CALLING_UID unless the caller is a service
300      *                 that is trusted to open the device on behalf of an
301      *                 application and to forward the real UID.
302      *
303      * @throws CameraAccessException if the camera is disabled by device policy,
304      * too many camera devices are already open, or the cameraId does not match
305      * any currently available camera device.
306      *
307      * @throws SecurityException if the application does not have permission to
308      * access the camera
309      * @throws IllegalArgumentException if callback or handler is null.
310      * @return A handle to the newly-created camera device.
311      *
312      * @see #getCameraIdList
313      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
314      */
openCameraDeviceUserAsync(String cameraId, CameraDevice.StateCallback callback, Executor executor, final int uid)315     private CameraDevice openCameraDeviceUserAsync(String cameraId,
316             CameraDevice.StateCallback callback, Executor executor, final int uid)
317             throws CameraAccessException {
318         CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
319         CameraDevice device = null;
320 
321         synchronized (mLock) {
322 
323             ICameraDeviceUser cameraUser = null;
324 
325             android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
326                     new android.hardware.camera2.impl.CameraDeviceImpl(
327                         cameraId,
328                         callback,
329                         executor,
330                         characteristics,
331                         mContext.getApplicationInfo().targetSdkVersion);
332 
333             ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
334 
335             try {
336                 if (supportsCamera2ApiLocked(cameraId)) {
337                     // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
338                     ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
339                     if (cameraService == null) {
340                         throw new ServiceSpecificException(
341                             ICameraService.ERROR_DISCONNECTED,
342                             "Camera service is currently unavailable");
343                     }
344                     cameraUser = cameraService.connectDevice(callbacks, cameraId,
345                             mContext.getOpPackageName(), uid);
346                 } else {
347                     // Use legacy camera implementation for HAL1 devices
348                     int id;
349                     try {
350                         id = Integer.parseInt(cameraId);
351                     } catch (NumberFormatException e) {
352                         throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
353                                 + cameraId);
354                     }
355 
356                     Log.i(TAG, "Using legacy camera HAL.");
357                     cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
358                 }
359             } catch (ServiceSpecificException e) {
360                 if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
361                     throw new AssertionError("Should've gone down the shim path");
362                 } else if (e.errorCode == ICameraService.ERROR_CAMERA_IN_USE ||
363                         e.errorCode == ICameraService.ERROR_MAX_CAMERAS_IN_USE ||
364                         e.errorCode == ICameraService.ERROR_DISABLED ||
365                         e.errorCode == ICameraService.ERROR_DISCONNECTED ||
366                         e.errorCode == ICameraService.ERROR_INVALID_OPERATION) {
367                     // Received one of the known connection errors
368                     // The remote camera device cannot be connected to, so
369                     // set the local camera to the startup error state
370                     deviceImpl.setRemoteFailure(e);
371 
372                     if (e.errorCode == ICameraService.ERROR_DISABLED ||
373                             e.errorCode == ICameraService.ERROR_DISCONNECTED ||
374                             e.errorCode == ICameraService.ERROR_CAMERA_IN_USE) {
375                         // Per API docs, these failures call onError and throw
376                         throwAsPublicException(e);
377                     }
378                 } else {
379                     // Unexpected failure - rethrow
380                     throwAsPublicException(e);
381                 }
382             } catch (RemoteException e) {
383                 // Camera service died - act as if it's a CAMERA_DISCONNECTED case
384                 ServiceSpecificException sse = new ServiceSpecificException(
385                     ICameraService.ERROR_DISCONNECTED,
386                     "Camera service is currently unavailable");
387                 deviceImpl.setRemoteFailure(sse);
388                 throwAsPublicException(sse);
389             }
390 
391             // TODO: factor out callback to be non-nested, then move setter to constructor
392             // For now, calling setRemoteDevice will fire initial
393             // onOpened/onUnconfigured callbacks.
394             // This function call may post onDisconnected and throw CAMERA_DISCONNECTED if
395             // cameraUser dies during setup.
396             deviceImpl.setRemoteDevice(cameraUser);
397             device = deviceImpl;
398         }
399 
400         return device;
401     }
402 
403     /**
404      * Open a connection to a camera with the given ID.
405      *
406      * <p>Use {@link #getCameraIdList} to get the list of available camera
407      * devices. Note that even if an id is listed, open may fail if the device
408      * is disconnected between the calls to {@link #getCameraIdList} and
409      * {@link #openCamera}, or if a higher-priority camera API client begins using the
410      * camera device.</p>
411      *
412      * <p>As of API level 23, devices for which the
413      * {@link AvailabilityCallback#onCameraUnavailable(String)} callback has been called due to the
414      * device being in use by a lower-priority, background camera API client can still potentially
415      * be opened by calling this method when the calling camera API client has a higher priority
416      * than the current camera API client using this device.  In general, if the top, foreground
417      * activity is running within your application process, your process will be given the highest
418      * priority when accessing the camera, and this method will succeed even if the camera device is
419      * in use by another camera API client. Any lower-priority application that loses control of the
420      * camera in this way will receive an
421      * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.</p>
422      *
423      * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will
424      * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
425      * for operation by calling {@link CameraDevice#createCaptureSession} and
426      * {@link CameraDevice#createCaptureRequest}</p>
427      *
428      * <!--
429      * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
430      * on the returned CameraDevice instance will be queued up until the device startup has
431      * completed and the callback's {@link CameraDevice.StateCallback#onOpened onOpened} method is
432      * called. The pending operations are then processed in order.</p>
433      * -->
434      * <p>If the camera becomes disconnected during initialization
435      * after this function call returns,
436      * {@link CameraDevice.StateCallback#onDisconnected} with a
437      * {@link CameraDevice} in the disconnected state (and
438      * {@link CameraDevice.StateCallback#onOpened} will be skipped).</p>
439      *
440      * <p>If opening the camera device fails, then the device callback's
441      * {@link CameraDevice.StateCallback#onError onError} method will be called, and subsequent
442      * calls on the camera device will throw a {@link CameraAccessException}.</p>
443      *
444      * @param cameraId
445      *             The unique identifier of the camera device to open
446      * @param callback
447      *             The callback which is invoked once the camera is opened
448      * @param handler
449      *             The handler on which the callback should be invoked, or
450      *             {@code null} to use the current thread's {@link android.os.Looper looper}.
451      *
452      * @throws CameraAccessException if the camera is disabled by device policy,
453      * has been disconnected, or is being used by a higher-priority camera API client.
454      *
455      * @throws IllegalArgumentException if cameraId or the callback was null,
456      * or the cameraId does not match any currently or previously available
457      * camera device.
458      *
459      * @throws SecurityException if the application does not have permission to
460      * access the camera
461      *
462      * @see #getCameraIdList
463      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
464      */
465     @RequiresPermission(android.Manifest.permission.CAMERA)
openCamera(@onNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)466     public void openCamera(@NonNull String cameraId,
467             @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
468             throws CameraAccessException {
469 
470         openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
471                 USE_CALLING_UID);
472     }
473 
474     /**
475      * Open a connection to a camera with the given ID.
476      *
477      * <p>The behavior of this method matches that of
478      * {@link #openCamera(String, StateCallback, Handler)}, except that it uses
479      * {@link java.util.concurrent.Executor} as an argument instead of
480      * {@link android.os.Handler}.</p>
481      *
482      * @param cameraId
483      *             The unique identifier of the camera device to open
484      * @param executor
485      *             The executor which will be used when invoking the callback.
486      * @param callback
487      *             The callback which is invoked once the camera is opened
488      *
489      * @throws CameraAccessException if the camera is disabled by device policy,
490      * has been disconnected, or is being used by a higher-priority camera API client.
491      *
492      * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
493      * or the cameraId does not match any currently or previously available
494      * camera device.
495      *
496      * @throws SecurityException if the application does not have permission to
497      * access the camera
498      *
499      * @see #getCameraIdList
500      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
501      */
502     @RequiresPermission(android.Manifest.permission.CAMERA)
openCamera(@onNull String cameraId, @NonNull @CallbackExecutor Executor executor, @NonNull final CameraDevice.StateCallback callback)503     public void openCamera(@NonNull String cameraId,
504             @NonNull @CallbackExecutor Executor executor,
505             @NonNull final CameraDevice.StateCallback callback)
506             throws CameraAccessException {
507         if (executor == null) {
508             throw new IllegalArgumentException("executor was null");
509         }
510         openCameraForUid(cameraId, callback, executor, USE_CALLING_UID);
511     }
512 
513     /**
514      * Open a connection to a camera with the given ID, on behalf of another application
515      * specified by clientUid.
516      *
517      * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
518      * the caller to specify the UID to use for permission/etc verification. This can only be
519      * done by services trusted by the camera subsystem to act on behalf of applications and
520      * to forward the real UID.</p>
521      *
522      * @param clientUid
523      *             The UID of the application on whose behalf the camera is being opened.
524      *             Must be USE_CALLING_UID unless the caller is a trusted service.
525      *
526      * @hide
527      */
openCameraForUid(@onNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, int clientUid)528     public void openCameraForUid(@NonNull String cameraId,
529             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
530             int clientUid)
531             throws CameraAccessException {
532 
533         if (cameraId == null) {
534             throw new IllegalArgumentException("cameraId was null");
535         } else if (callback == null) {
536             throw new IllegalArgumentException("callback was null");
537         }
538         if (CameraManagerGlobal.sCameraServiceDisabled) {
539             throw new IllegalArgumentException("No cameras available on device");
540         }
541 
542         openCameraDeviceUserAsync(cameraId, callback, executor, clientUid);
543     }
544 
545     /**
546      * Set the flash unit's torch mode of the camera of the given ID without opening the camera
547      * device.
548      *
549      * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use
550      * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit.
551      * Note that even if a camera device has a flash unit, turning on the torch mode may fail
552      * if the camera device or other camera resources needed to turn on the torch mode are in use.
553      * </p>
554      *
555      * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully,
556      * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked.
557      * However, even if turning on the torch mode is successful, the application does not have the
558      * exclusive ownership of the flash unit or the camera device. The torch mode will be turned
559      * off and becomes unavailable when the camera device that the flash unit belongs to becomes
560      * unavailable or when other camera resources to keep the torch on become unavailable (
561      * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also,
562      * other applications are free to call {@link #setTorchMode} to turn off the torch mode (
563      * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked). If the latest
564      * application that turned on the torch mode exits, the torch mode will be turned off.
565      *
566      * @param cameraId
567      *             The unique identifier of the camera device that the flash unit belongs to.
568      * @param enabled
569      *             The desired state of the torch mode for the target camera device. Set to
570      *             {@code true} to turn on the torch mode. Set to {@code false} to turn off the
571      *             torch mode.
572      *
573      * @throws CameraAccessException if it failed to access the flash unit.
574      *             {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
575      *             is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
576      *             other camera resources needed to turn on the torch mode are in use.
577      *             {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera
578      *             service is not available.
579      *
580      * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
581      *             or previously available camera device, or the camera device doesn't have a
582      *             flash unit.
583      */
setTorchMode(@onNull String cameraId, boolean enabled)584     public void setTorchMode(@NonNull String cameraId, boolean enabled)
585             throws CameraAccessException {
586         if (CameraManagerGlobal.sCameraServiceDisabled) {
587             throw new IllegalArgumentException("No cameras available on device");
588         }
589         CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
590     }
591 
592     /**
593      * A callback for camera devices becoming available or unavailable to open.
594      *
595      * <p>Cameras become available when they are no longer in use, or when a new
596      * removable camera is connected. They become unavailable when some
597      * application or service starts using a camera, or when a removable camera
598      * is disconnected.</p>
599      *
600      * <p>Extend this callback and pass an instance of the subclass to
601      * {@link CameraManager#registerAvailabilityCallback} to be notified of such availability
602      * changes.</p>
603      *
604      * @see #registerAvailabilityCallback
605      */
606     public static abstract class AvailabilityCallback {
607 
608         /**
609          * A new camera has become available to use.
610          *
611          * <p>The default implementation of this method does nothing.</p>
612          *
613          * @param cameraId The unique identifier of the new camera.
614          */
onCameraAvailable(@onNull String cameraId)615         public void onCameraAvailable(@NonNull String cameraId) {
616             // default empty implementation
617         }
618 
619         /**
620          * A previously-available camera has become unavailable for use.
621          *
622          * <p>If an application had an active CameraDevice instance for the
623          * now-disconnected camera, that application will receive a
624          * {@link CameraDevice.StateCallback#onDisconnected disconnection error}.</p>
625          *
626          * <p>The default implementation of this method does nothing.</p>
627          *
628          * @param cameraId The unique identifier of the disconnected camera.
629          */
onCameraUnavailable(@onNull String cameraId)630         public void onCameraUnavailable(@NonNull String cameraId) {
631             // default empty implementation
632         }
633     }
634 
635     /**
636      * A callback for camera flash torch modes becoming unavailable, disabled, or enabled.
637      *
638      * <p>The torch mode becomes unavailable when the camera device it belongs to becomes
639      * unavailable or other camera resources it needs become busy due to other higher priority
640      * camera activities. The torch mode becomes disabled when it was turned off or when the camera
641      * device it belongs to is no longer in use and other camera resources it needs are no longer
642      * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to
643      * turn off the camera's torch mode, or when an application turns on another camera's torch mode
644      * if keeping multiple torch modes on simultaneously is not supported. The torch mode becomes
645      * enabled when it is turned on via {@link #setTorchMode}.</p>
646      *
647      * <p>The torch mode is available to set via {@link #setTorchMode} only when it's in a disabled
648      * or enabled state.</p>
649      *
650      * <p>Extend this callback and pass an instance of the subclass to
651      * {@link CameraManager#registerTorchCallback} to be notified of such status changes.
652      * </p>
653      *
654      * @see #registerTorchCallback
655      */
656     public static abstract class TorchCallback {
657         /**
658          * A camera's torch mode has become unavailable to set via {@link #setTorchMode}.
659          *
660          * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be
661          * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is
662          * invoked. {@link #setTorchMode} will fail until the torch mode has entered a disabled or
663          * enabled state again.</p>
664          *
665          * <p>The default implementation of this method does nothing.</p>
666          *
667          * @param cameraId The unique identifier of the camera whose torch mode has become
668          *                 unavailable.
669          */
onTorchModeUnavailable(@onNull String cameraId)670         public void onTorchModeUnavailable(@NonNull String cameraId) {
671             // default empty implementation
672         }
673 
674         /**
675          * A camera's torch mode has become enabled or disabled and can be changed via
676          * {@link #setTorchMode}.
677          *
678          * <p>The default implementation of this method does nothing.</p>
679          *
680          * @param cameraId The unique identifier of the camera whose torch mode has been changed.
681          *
682          * @param enabled The state that the torch mode of the camera has been changed to.
683          *                {@code true} when the torch mode has become on and available to be turned
684          *                off. {@code false} when the torch mode has becomes off and available to
685          *                be turned on.
686          */
onTorchModeChanged(@onNull String cameraId, boolean enabled)687         public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
688             // default empty implementation
689         }
690     }
691 
692     /**
693      * Convert ServiceSpecificExceptions and Binder RemoteExceptions from camera binder interfaces
694      * into the correct public exceptions.
695      *
696      * @hide
697      */
throwAsPublicException(Throwable t)698     public static void throwAsPublicException(Throwable t) throws CameraAccessException {
699         if (t instanceof ServiceSpecificException) {
700             ServiceSpecificException e = (ServiceSpecificException) t;
701             int reason = CameraAccessException.CAMERA_ERROR;
702             switch(e.errorCode) {
703                 case ICameraService.ERROR_DISCONNECTED:
704                     reason = CameraAccessException.CAMERA_DISCONNECTED;
705                     break;
706                 case ICameraService.ERROR_DISABLED:
707                     reason = CameraAccessException.CAMERA_DISABLED;
708                     break;
709                 case ICameraService.ERROR_CAMERA_IN_USE:
710                     reason = CameraAccessException.CAMERA_IN_USE;
711                     break;
712                 case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
713                     reason = CameraAccessException.MAX_CAMERAS_IN_USE;
714                     break;
715                 case ICameraService.ERROR_DEPRECATED_HAL:
716                     reason = CameraAccessException.CAMERA_DEPRECATED_HAL;
717                     break;
718                 case ICameraService.ERROR_ILLEGAL_ARGUMENT:
719                 case ICameraService.ERROR_ALREADY_EXISTS:
720                     throw new IllegalArgumentException(e.getMessage(), e);
721                 case ICameraService.ERROR_PERMISSION_DENIED:
722                     throw new SecurityException(e.getMessage(), e);
723                 case ICameraService.ERROR_TIMED_OUT:
724                 case ICameraService.ERROR_INVALID_OPERATION:
725                 default:
726                     reason = CameraAccessException.CAMERA_ERROR;
727             }
728             throw new CameraAccessException(reason, e.getMessage(), e);
729         } else if (t instanceof DeadObjectException) {
730             throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
731                     "Camera service has died unexpectedly",
732                     t);
733         } else if (t instanceof RemoteException) {
734             throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
735                     " which should never happen.", t);
736         } else if (t instanceof RuntimeException) {
737             RuntimeException e = (RuntimeException) t;
738             throw e;
739         }
740     }
741 
742     /**
743      * Queries the camera service if it supports the camera2 api directly, or needs a shim.
744      *
745      * @param cameraId a non-{@code null} camera identifier
746      * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise.
747      */
supportsCamera2ApiLocked(String cameraId)748     private boolean supportsCamera2ApiLocked(String cameraId) {
749         return supportsCameraApiLocked(cameraId, API_VERSION_2);
750     }
751 
752     /**
753      * Queries the camera service if it supports a camera api directly, or needs a shim.
754      *
755      * @param cameraId a non-{@code null} camera identifier
756      * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2}
757      * @return {@code true} if connecting will work for that device version.
758      */
supportsCameraApiLocked(String cameraId, int apiVersion)759     private boolean supportsCameraApiLocked(String cameraId, int apiVersion) {
760         /*
761          * Possible return values:
762          * - NO_ERROR => CameraX API is supported
763          * - CAMERA_DEPRECATED_HAL => CameraX API is *not* supported (thrown as an exception)
764          * - Remote exception => If the camera service died
765          *
766          * Anything else is an unexpected error we don't want to recover from.
767          */
768         try {
769             ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
770             // If no camera service, no support
771             if (cameraService == null) return false;
772 
773             return cameraService.supportsCameraApi(cameraId, apiVersion);
774         } catch (RemoteException e) {
775             // Camera service is now down, no support for any API level
776         }
777         return false;
778     }
779 
780     /**
781      * A per-process global camera manager instance, to retain a connection to the camera service,
782      * and to distribute camera availability notices to API-registered callbacks
783      */
784     private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
785             implements IBinder.DeathRecipient {
786 
787         private static final String TAG = "CameraManagerGlobal";
788         private final boolean DEBUG = false;
789 
790         private final int CAMERA_SERVICE_RECONNECT_DELAY_MS = 1000;
791 
792         // Singleton instance
793         private static final CameraManagerGlobal gCameraManager =
794             new CameraManagerGlobal();
795 
796         /**
797          * This must match the ICameraService definition
798          */
799         private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
800 
801         private final ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1);
802         // Camera ID -> Status map
803         private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
804 
805         // Registered availablility callbacks and their executors
806         private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap =
807             new ArrayMap<AvailabilityCallback, Executor>();
808 
809         // torch client binder to set the torch mode with.
810         private Binder mTorchClientBinder = new Binder();
811 
812         // Camera ID -> Torch status map
813         private final ArrayMap<String, Integer> mTorchStatus = new ArrayMap<String, Integer>();
814 
815         // Registered torch callbacks and their executors
816         private final ArrayMap<TorchCallback, Executor> mTorchCallbackMap =
817                 new ArrayMap<TorchCallback, Executor>();
818 
819         private final Object mLock = new Object();
820 
821         // Access only through getCameraService to deal with binder death
822         private ICameraService mCameraService;
823 
824         // Singleton, don't allow construction
CameraManagerGlobal()825         private CameraManagerGlobal() {
826         }
827 
828         public static final boolean sCameraServiceDisabled =
829                 SystemProperties.getBoolean("config.disable_cameraservice", false);
830 
get()831         public static CameraManagerGlobal get() {
832             return gCameraManager;
833         }
834 
835         @Override
asBinder()836         public IBinder asBinder() {
837             return this;
838         }
839 
840         /**
841          * Return a best-effort ICameraService.
842          *
843          * <p>This will be null if the camera service is not currently available. If the camera
844          * service has died since the last use of the camera service, will try to reconnect to the
845          * service.</p>
846          */
getCameraService()847         public ICameraService getCameraService() {
848             synchronized(mLock) {
849                 connectCameraServiceLocked();
850                 if (mCameraService == null && !sCameraServiceDisabled) {
851                     Log.e(TAG, "Camera service is unavailable");
852                 }
853                 return mCameraService;
854             }
855         }
856 
857         /**
858          * Connect to the camera service if it's available, and set up listeners.
859          * If the service is already connected, do nothing.
860          *
861          * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
862          */
connectCameraServiceLocked()863         private void connectCameraServiceLocked() {
864             // Only reconnect if necessary
865             if (mCameraService != null || sCameraServiceDisabled) return;
866 
867             Log.i(TAG, "Connecting to camera service");
868 
869             IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
870             if (cameraServiceBinder == null) {
871                 // Camera service is now down, leave mCameraService as null
872                 return;
873             }
874             try {
875                 cameraServiceBinder.linkToDeath(this, /*flags*/ 0);
876             } catch (RemoteException e) {
877                 // Camera service is now down, leave mCameraService as null
878                 return;
879             }
880 
881             ICameraService cameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
882 
883             try {
884                 CameraMetadataNative.setupGlobalVendorTagDescriptor();
885             } catch (ServiceSpecificException e) {
886                 handleRecoverableSetupErrors(e);
887             }
888 
889             try {
890                 CameraStatus[] cameraStatuses = cameraService.addListener(this);
891                 for (CameraStatus c : cameraStatuses) {
892                     onStatusChangedLocked(c.status, c.cameraId);
893                 }
894                 mCameraService = cameraService;
895             } catch(ServiceSpecificException e) {
896                 // Unexpected failure
897                 throw new IllegalStateException("Failed to register a camera service listener", e);
898             } catch (RemoteException e) {
899                 // Camera service is now down, leave mCameraService as null
900             }
901         }
902 
903         /**
904          * Get a list of all camera IDs that are at least PRESENT; ignore devices that are
905          * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone.
906          */
getCameraIdList()907         public String[] getCameraIdList() {
908             String[] cameraIds = null;
909             synchronized(mLock) {
910                 // Try to make sure we have an up-to-date list of camera devices.
911                 connectCameraServiceLocked();
912 
913                 int idCount = 0;
914                 for (int i = 0; i < mDeviceStatus.size(); i++) {
915                     int status = mDeviceStatus.valueAt(i);
916                     if (status == ICameraServiceListener.STATUS_NOT_PRESENT ||
917                             status == ICameraServiceListener.STATUS_ENUMERATING) continue;
918                     idCount++;
919                 }
920                 cameraIds = new String[idCount];
921                 idCount = 0;
922                 for (int i = 0; i < mDeviceStatus.size(); i++) {
923                     int status = mDeviceStatus.valueAt(i);
924                     if (status == ICameraServiceListener.STATUS_NOT_PRESENT ||
925                             status == ICameraServiceListener.STATUS_ENUMERATING) continue;
926                     cameraIds[idCount] = mDeviceStatus.keyAt(i);
927                     idCount++;
928                 }
929             }
930 
931             // The sort logic must match the logic in
932             // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds
933             Arrays.sort(cameraIds, new Comparator<String>() {
934                     @Override
935                     public int compare(String s1, String s2) {
936                         int s1Int = 0, s2Int = 0;
937                         try {
938                             s1Int = Integer.parseInt(s1);
939                         } catch (NumberFormatException e) {
940                             s1Int = -1;
941                         }
942 
943                         try {
944                             s2Int = Integer.parseInt(s2);
945                         } catch (NumberFormatException e) {
946                             s2Int = -1;
947                         }
948 
949                         // Uint device IDs first
950                         if (s1Int >= 0 && s2Int >= 0) {
951                             return s1Int - s2Int;
952                         } else if (s1Int >= 0) {
953                             return -1;
954                         } else if (s2Int >= 0) {
955                             return 1;
956                         } else {
957                             // Simple string compare if both id are not uint
958                             return s1.compareTo(s2);
959                         }
960                     }});
961             return cameraIds;
962         }
963 
setTorchMode(String cameraId, boolean enabled)964         public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
965             synchronized(mLock) {
966 
967                 if (cameraId == null) {
968                     throw new IllegalArgumentException("cameraId was null");
969                 }
970 
971                 ICameraService cameraService = getCameraService();
972                 if (cameraService == null) {
973                     throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
974                         "Camera service is currently unavailable");
975                 }
976 
977                 try {
978                     cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
979                 } catch(ServiceSpecificException e) {
980                     throwAsPublicException(e);
981                 } catch (RemoteException e) {
982                     throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
983                             "Camera service is currently unavailable");
984                 }
985             }
986         }
987 
handleRecoverableSetupErrors(ServiceSpecificException e)988         private void handleRecoverableSetupErrors(ServiceSpecificException e) {
989             switch (e.errorCode) {
990                 case ICameraService.ERROR_DISCONNECTED:
991                     Log.w(TAG, e.getMessage());
992                     break;
993                 default:
994                     throw new IllegalStateException(e);
995             }
996         }
997 
isAvailable(int status)998         private boolean isAvailable(int status) {
999             switch (status) {
1000                 case ICameraServiceListener.STATUS_PRESENT:
1001                     return true;
1002                 default:
1003                     return false;
1004             }
1005         }
1006 
validStatus(int status)1007         private boolean validStatus(int status) {
1008             switch (status) {
1009                 case ICameraServiceListener.STATUS_NOT_PRESENT:
1010                 case ICameraServiceListener.STATUS_PRESENT:
1011                 case ICameraServiceListener.STATUS_ENUMERATING:
1012                 case ICameraServiceListener.STATUS_NOT_AVAILABLE:
1013                     return true;
1014                 default:
1015                     return false;
1016             }
1017         }
1018 
validTorchStatus(int status)1019         private boolean validTorchStatus(int status) {
1020             switch (status) {
1021                 case ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE:
1022                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
1023                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF:
1024                     return true;
1025                 default:
1026                     return false;
1027             }
1028         }
1029 
postSingleUpdate(final AvailabilityCallback callback, final Executor executor, final String id, final int status)1030         private void postSingleUpdate(final AvailabilityCallback callback, final Executor executor,
1031                 final String id, final int status) {
1032             if (isAvailable(status)) {
1033                 final long ident = Binder.clearCallingIdentity();
1034                 try {
1035                     executor.execute(
1036                         new Runnable() {
1037                             @Override
1038                             public void run() {
1039                                 callback.onCameraAvailable(id);
1040                             }
1041                         });
1042                 } finally {
1043                     Binder.restoreCallingIdentity(ident);
1044                 }
1045             } else {
1046                 final long ident = Binder.clearCallingIdentity();
1047                 try {
1048                     executor.execute(
1049                         new Runnable() {
1050                             @Override
1051                             public void run() {
1052                                 callback.onCameraUnavailable(id);
1053                             }
1054                         });
1055                 } finally {
1056                     Binder.restoreCallingIdentity(ident);
1057                 }
1058             }
1059         }
1060 
postSingleTorchUpdate(final TorchCallback callback, final Executor executor, final String id, final int status)1061         private void postSingleTorchUpdate(final TorchCallback callback, final Executor executor,
1062                 final String id, final int status) {
1063             switch(status) {
1064                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
1065                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: {
1066                         final long ident = Binder.clearCallingIdentity();
1067                         try {
1068                             executor.execute(() -> {
1069                                 callback.onTorchModeChanged(id, status ==
1070                                         ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON);
1071                             });
1072                         } finally {
1073                             Binder.restoreCallingIdentity(ident);
1074                         }
1075                     }
1076                     break;
1077                 default: {
1078                         final long ident = Binder.clearCallingIdentity();
1079                         try {
1080                             executor.execute(() -> {
1081                                 callback.onTorchModeUnavailable(id);
1082                             });
1083                         } finally {
1084                             Binder.restoreCallingIdentity(ident);
1085                         }
1086                     }
1087                     break;
1088             }
1089         }
1090 
1091         /**
1092          * Send the state of all known cameras to the provided listener, to initialize
1093          * the listener's knowledge of camera state.
1094          */
updateCallbackLocked(AvailabilityCallback callback, Executor executor)1095         private void updateCallbackLocked(AvailabilityCallback callback, Executor executor) {
1096             for (int i = 0; i < mDeviceStatus.size(); i++) {
1097                 String id = mDeviceStatus.keyAt(i);
1098                 Integer status = mDeviceStatus.valueAt(i);
1099                 postSingleUpdate(callback, executor, id, status);
1100             }
1101         }
1102 
onStatusChangedLocked(int status, String id)1103         private void onStatusChangedLocked(int status, String id) {
1104             if (DEBUG) {
1105                 Log.v(TAG,
1106                         String.format("Camera id %s has status changed to 0x%x", id, status));
1107             }
1108 
1109             if (!validStatus(status)) {
1110                 Log.e(TAG, String.format("Ignoring invalid device %s status 0x%x", id,
1111                                 status));
1112                 return;
1113             }
1114 
1115             Integer oldStatus;
1116             if (status == ICameraServiceListener.STATUS_NOT_PRESENT) {
1117                 oldStatus = mDeviceStatus.remove(id);
1118             } else {
1119                 oldStatus = mDeviceStatus.put(id, status);
1120             }
1121 
1122             if (oldStatus != null && oldStatus == status) {
1123                 if (DEBUG) {
1124                     Log.v(TAG, String.format(
1125                         "Device status changed to 0x%x, which is what it already was",
1126                         status));
1127                 }
1128                 return;
1129             }
1130 
1131             // TODO: consider abstracting out this state minimization + transition
1132             // into a separate
1133             // more easily testable class
1134             // i.e. (new State()).addState(STATE_AVAILABLE)
1135             //                   .addState(STATE_NOT_AVAILABLE)
1136             //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
1137             //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
1138             //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
1139             //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);
1140 
1141             // Translate all the statuses to either 'available' or 'not available'
1142             //  available -> available         => no new update
1143             //  not available -> not available => no new update
1144             if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {
1145                 if (DEBUG) {
1146                     Log.v(TAG,
1147                             String.format(
1148                                 "Device status was previously available (%b), " +
1149                                 " and is now again available (%b)" +
1150                                 "so no new client visible update will be sent",
1151                                 isAvailable(oldStatus), isAvailable(status)));
1152                 }
1153                 return;
1154             }
1155 
1156             final int callbackCount = mCallbackMap.size();
1157             for (int i = 0; i < callbackCount; i++) {
1158                 Executor executor = mCallbackMap.valueAt(i);
1159                 final AvailabilityCallback callback = mCallbackMap.keyAt(i);
1160 
1161                 postSingleUpdate(callback, executor, id, status);
1162             }
1163         } // onStatusChangedLocked
1164 
updateTorchCallbackLocked(TorchCallback callback, Executor executor)1165         private void updateTorchCallbackLocked(TorchCallback callback, Executor executor) {
1166             for (int i = 0; i < mTorchStatus.size(); i++) {
1167                 String id = mTorchStatus.keyAt(i);
1168                 Integer status = mTorchStatus.valueAt(i);
1169                 postSingleTorchUpdate(callback, executor, id, status);
1170             }
1171         }
1172 
onTorchStatusChangedLocked(int status, String id)1173         private void onTorchStatusChangedLocked(int status, String id) {
1174             if (DEBUG) {
1175                 Log.v(TAG,
1176                         String.format("Camera id %s has torch status changed to 0x%x", id, status));
1177             }
1178 
1179             if (!validTorchStatus(status)) {
1180                 Log.e(TAG, String.format("Ignoring invalid device %s torch status 0x%x", id,
1181                                 status));
1182                 return;
1183             }
1184 
1185             Integer oldStatus = mTorchStatus.put(id, status);
1186             if (oldStatus != null && oldStatus == status) {
1187                 if (DEBUG) {
1188                     Log.v(TAG, String.format(
1189                         "Torch status changed to 0x%x, which is what it already was",
1190                         status));
1191                 }
1192                 return;
1193             }
1194 
1195             final int callbackCount = mTorchCallbackMap.size();
1196             for (int i = 0; i < callbackCount; i++) {
1197                 final Executor executor = mTorchCallbackMap.valueAt(i);
1198                 final TorchCallback callback = mTorchCallbackMap.keyAt(i);
1199                 postSingleTorchUpdate(callback, executor, id, status);
1200             }
1201         } // onTorchStatusChangedLocked
1202 
1203         /**
1204          * Register a callback to be notified about camera device availability with the
1205          * global listener singleton.
1206          *
1207          * @param callback the new callback to send camera availability notices to
1208          * @param executor The executor which should invoke the callback. May not be null.
1209          */
registerAvailabilityCallback(AvailabilityCallback callback, Executor executor)1210         public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor) {
1211             synchronized (mLock) {
1212                 connectCameraServiceLocked();
1213 
1214                 Executor oldExecutor = mCallbackMap.put(callback, executor);
1215                 // For new callbacks, provide initial availability information
1216                 if (oldExecutor == null) {
1217                     updateCallbackLocked(callback, executor);
1218                 }
1219 
1220                 // If not connected to camera service, schedule a reconnect to camera service.
1221                 if (mCameraService == null) {
1222                     scheduleCameraServiceReconnectionLocked();
1223                 }
1224             }
1225         }
1226 
1227         /**
1228          * Remove a previously-added callback; the callback will no longer receive connection and
1229          * disconnection callbacks, and is no longer referenced by the global listener singleton.
1230          *
1231          * @param callback The callback to remove from the notification list
1232          */
unregisterAvailabilityCallback(AvailabilityCallback callback)1233         public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
1234             synchronized (mLock) {
1235                 mCallbackMap.remove(callback);
1236             }
1237         }
1238 
registerTorchCallback(TorchCallback callback, Executor executor)1239         public void registerTorchCallback(TorchCallback callback, Executor executor) {
1240             synchronized(mLock) {
1241                 connectCameraServiceLocked();
1242 
1243                 Executor oldExecutor = mTorchCallbackMap.put(callback, executor);
1244                 // For new callbacks, provide initial torch information
1245                 if (oldExecutor == null) {
1246                     updateTorchCallbackLocked(callback, executor);
1247                 }
1248 
1249                 // If not connected to camera service, schedule a reconnect to camera service.
1250                 if (mCameraService == null) {
1251                     scheduleCameraServiceReconnectionLocked();
1252                 }
1253             }
1254         }
1255 
unregisterTorchCallback(TorchCallback callback)1256         public void unregisterTorchCallback(TorchCallback callback) {
1257             synchronized(mLock) {
1258                 mTorchCallbackMap.remove(callback);
1259             }
1260         }
1261 
1262         /**
1263          * Callback from camera service notifying the process about camera availability changes
1264          */
1265         @Override
onStatusChanged(int status, String cameraId)1266         public void onStatusChanged(int status, String cameraId) throws RemoteException {
1267             synchronized(mLock) {
1268                 onStatusChangedLocked(status, cameraId);
1269             }
1270         }
1271 
1272         @Override
onTorchStatusChanged(int status, String cameraId)1273         public void onTorchStatusChanged(int status, String cameraId) throws RemoteException {
1274             synchronized (mLock) {
1275                 onTorchStatusChangedLocked(status, cameraId);
1276             }
1277         }
1278 
1279         /**
1280          * Try to connect to camera service after some delay if any client registered camera
1281          * availability callback or torch status callback.
1282          */
scheduleCameraServiceReconnectionLocked()1283         private void scheduleCameraServiceReconnectionLocked() {
1284             if (mCallbackMap.isEmpty() && mTorchCallbackMap.isEmpty()) {
1285                 // Not necessary to reconnect camera service if no client registers a callback.
1286                 return;
1287             }
1288 
1289             if (DEBUG) {
1290                 Log.v(TAG, "Reconnecting Camera Service in " + CAMERA_SERVICE_RECONNECT_DELAY_MS +
1291                         " ms");
1292             }
1293 
1294             try {
1295                 mScheduler.schedule(() -> {
1296                     ICameraService cameraService = getCameraService();
1297                     if (cameraService == null) {
1298                         synchronized(mLock) {
1299                             if (DEBUG) {
1300                                 Log.v(TAG, "Reconnecting Camera Service failed.");
1301                             }
1302                             scheduleCameraServiceReconnectionLocked();
1303                         }
1304                     }
1305                 }, CAMERA_SERVICE_RECONNECT_DELAY_MS, TimeUnit.MILLISECONDS);
1306             } catch (RejectedExecutionException e) {
1307                 Log.e(TAG, "Failed to schedule camera service re-connect: " + e);
1308             }
1309         }
1310 
1311         /**
1312          * Listener for camera service death.
1313          *
1314          * <p>The camera service isn't supposed to die under any normal circumstances, but can be
1315          * turned off during debug, or crash due to bugs.  So detect that and null out the interface
1316          * object, so that the next calls to the manager can try to reconnect.</p>
1317          */
binderDied()1318         public void binderDied() {
1319             synchronized(mLock) {
1320                 // Only do this once per service death
1321                 if (mCameraService == null) return;
1322 
1323                 mCameraService = null;
1324 
1325                 // Tell listeners that the cameras and torch modes are unavailable and schedule a
1326                 // reconnection to camera service. When camera service is reconnected, the camera
1327                 // and torch statuses will be updated.
1328                 for (int i = 0; i < mDeviceStatus.size(); i++) {
1329                     String cameraId = mDeviceStatus.keyAt(i);
1330                     onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, cameraId);
1331                 }
1332                 for (int i = 0; i < mTorchStatus.size(); i++) {
1333                     String cameraId = mTorchStatus.keyAt(i);
1334                     onTorchStatusChangedLocked(ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE,
1335                             cameraId);
1336                 }
1337 
1338                 scheduleCameraServiceReconnectionLocked();
1339             }
1340         }
1341 
1342     } // CameraManagerGlobal
1343 
1344 } // CameraManager
1345