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