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