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