[go: nahoru, domu]

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.parseInt(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            // This function call may post onDisconnected and throw CAMERA_DISCONNECTED if
362            // cameraUser dies during setup.
363            deviceImpl.setRemoteDevice(cameraUser);
364            device = deviceImpl;
365        }
366
367        return device;
368    }
369
370    /**
371     * Open a connection to a camera with the given ID.
372     *
373     * <p>Use {@link #getCameraIdList} to get the list of available camera
374     * devices. Note that even if an id is listed, open may fail if the device
375     * is disconnected between the calls to {@link #getCameraIdList} and
376     * {@link #openCamera}, or if a higher-priority camera API client begins using the
377     * camera device.</p>
378     *
379     * <p>As of API level 23, devices for which the
380     * {@link AvailabilityCallback#onCameraUnavailable(String)} callback has been called due to the
381     * device being in use by a lower-priority, background camera API client can still potentially
382     * be opened by calling this method when the calling camera API client has a higher priority
383     * than the current camera API client using this device.  In general, if the top, foreground
384     * activity is running within your application process, your process will be given the highest
385     * priority when accessing the camera, and this method will succeed even if the camera device is
386     * in use by another camera API client. Any lower-priority application that loses control of the
387     * camera in this way will receive an
388     * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.</p>
389     *
390     * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will
391     * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
392     * for operation by calling {@link CameraDevice#createCaptureSession} and
393     * {@link CameraDevice#createCaptureRequest}</p>
394     *
395     * <!--
396     * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
397     * on the returned CameraDevice instance will be queued up until the device startup has
398     * completed and the callback's {@link CameraDevice.StateCallback#onOpened onOpened} method is
399     * called. The pending operations are then processed in order.</p>
400     * -->
401     * <p>If the camera becomes disconnected during initialization
402     * after this function call returns,
403     * {@link CameraDevice.StateCallback#onDisconnected} with a
404     * {@link CameraDevice} in the disconnected state (and
405     * {@link CameraDevice.StateCallback#onOpened} will be skipped).</p>
406     *
407     * <p>If opening the camera device fails, then the device callback's
408     * {@link CameraDevice.StateCallback#onError onError} method will be called, and subsequent
409     * calls on the camera device will throw a {@link CameraAccessException}.</p>
410     *
411     * @param cameraId
412     *             The unique identifier of the camera device to open
413     * @param callback
414     *             The callback which is invoked once the camera is opened
415     * @param handler
416     *             The handler on which the callback should be invoked, or
417     *             {@code null} to use the current thread's {@link android.os.Looper looper}.
418     *
419     * @throws CameraAccessException if the camera is disabled by device policy,
420     * has been disconnected, or is being used by a higher-priority camera API client.
421     *
422     * @throws IllegalArgumentException if cameraId or the callback was null,
423     * or the cameraId does not match any currently or previously available
424     * camera device.
425     *
426     * @throws SecurityException if the application does not have permission to
427     * access the camera
428     *
429     * @see #getCameraIdList
430     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
431     */
432    @RequiresPermission(android.Manifest.permission.CAMERA)
433    public void openCamera(@NonNull String cameraId,
434            @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
435            throws CameraAccessException {
436
437        if (cameraId == null) {
438            throw new IllegalArgumentException("cameraId was null");
439        } else if (callback == null) {
440            throw new IllegalArgumentException("callback was null");
441        } else if (handler == null) {
442            if (Looper.myLooper() != null) {
443                handler = new Handler();
444            } else {
445                throw new IllegalArgumentException(
446                        "Handler argument is null, but no looper exists in the calling thread");
447            }
448        }
449
450        openCameraDeviceUserAsync(cameraId, callback, handler);
451    }
452
453    /**
454     * Set the flash unit's torch mode of the camera of the given ID without opening the camera
455     * device.
456     *
457     * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use
458     * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit.
459     * Note that even if a camera device has a flash unit, turning on the torch mode may fail
460     * if the camera device or other camera resources needed to turn on the torch mode are in use.
461     * </p>
462     *
463     * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully,
464     * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked.
465     * However, even if turning on the torch mode is successful, the application does not have the
466     * exclusive ownership of the flash unit or the camera device. The torch mode will be turned
467     * off and becomes unavailable when the camera device that the flash unit belongs to becomes
468     * unavailable or when other camera resources to keep the torch on become unavailable (
469     * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also,
470     * other applications are free to call {@link #setTorchMode} to turn off the torch mode (
471     * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked). If the latest
472     * application that turned on the torch mode exits, the torch mode will be turned off.
473     *
474     * @param cameraId
475     *             The unique identifier of the camera device that the flash unit belongs to.
476     * @param enabled
477     *             The desired state of the torch mode for the target camera device. Set to
478     *             {@code true} to turn on the torch mode. Set to {@code false} to turn off the
479     *             torch mode.
480     *
481     * @throws CameraAccessException if it failed to access the flash unit.
482     *             {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
483     *             is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
484     *             other camera resources needed to turn on the torch mode are in use.
485     *             {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera
486     *             service is not available.
487     *
488     * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
489     *             or previously available camera device, or the camera device doesn't have a
490     *             flash unit.
491     */
492    public void setTorchMode(@NonNull String cameraId, boolean enabled)
493            throws CameraAccessException {
494        CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
495    }
496
497    /**
498     * A callback for camera devices becoming available or unavailable to open.
499     *
500     * <p>Cameras become available when they are no longer in use, or when a new
501     * removable camera is connected. They become unavailable when some
502     * application or service starts using a camera, or when a removable camera
503     * is disconnected.</p>
504     *
505     * <p>Extend this callback and pass an instance of the subclass to
506     * {@link CameraManager#registerAvailabilityCallback} to be notified of such availability
507     * changes.</p>
508     *
509     * @see #registerAvailabilityCallback
510     */
511    public static abstract class AvailabilityCallback {
512
513        /**
514         * A new camera has become available to use.
515         *
516         * <p>The default implementation of this method does nothing.</p>
517         *
518         * @param cameraId The unique identifier of the new camera.
519         */
520        public void onCameraAvailable(@NonNull String cameraId) {
521            // default empty implementation
522        }
523
524        /**
525         * A previously-available camera has become unavailable for use.
526         *
527         * <p>If an application had an active CameraDevice instance for the
528         * now-disconnected camera, that application will receive a
529         * {@link CameraDevice.StateCallback#onDisconnected disconnection error}.</p>
530         *
531         * <p>The default implementation of this method does nothing.</p>
532         *
533         * @param cameraId The unique identifier of the disconnected camera.
534         */
535        public void onCameraUnavailable(@NonNull String cameraId) {
536            // default empty implementation
537        }
538    }
539
540    /**
541     * A callback for camera flash torch modes becoming unavailable, disabled, or enabled.
542     *
543     * <p>The torch mode becomes unavailable when the camera device it belongs to becomes
544     * unavailable or other camera resources it needs become busy due to other higher priority
545     * camera activities. The torch mode becomes disabled when it was turned off or when the camera
546     * device it belongs to is no longer in use and other camera resources it needs are no longer
547     * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to
548     * turn off the camera's torch mode, or when an application turns on another camera's torch mode
549     * if keeping multiple torch modes on simultaneously is not supported. The torch mode becomes
550     * enabled when it is turned on via {@link #setTorchMode}.</p>
551     *
552     * <p>The torch mode is available to set via {@link #setTorchMode} only when it's in a disabled
553     * or enabled state.</p>
554     *
555     * <p>Extend this callback and pass an instance of the subclass to
556     * {@link CameraManager#registerTorchCallback} to be notified of such status changes.
557     * </p>
558     *
559     * @see #registerTorchCallback
560     */
561    public static abstract class TorchCallback {
562        /**
563         * A camera's torch mode has become unavailable to set via {@link #setTorchMode}.
564         *
565         * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be
566         * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is
567         * invoked. {@link #setTorchMode} will fail until the torch mode has entered a disabled or
568         * enabled state again.</p>
569         *
570         * <p>The default implementation of this method does nothing.</p>
571         *
572         * @param cameraId The unique identifier of the camera whose torch mode has become
573         *                 unavailable.
574         */
575        public void onTorchModeUnavailable(@NonNull String cameraId) {
576            // default empty implementation
577        }
578
579        /**
580         * A camera's torch mode has become enabled or disabled and can be changed via
581         * {@link #setTorchMode}.
582         *
583         * <p>The default implementation of this method does nothing.</p>
584         *
585         * @param cameraId The unique identifier of the camera whose torch mode has been changed.
586         *
587         * @param enabled The state that the torch mode of the camera has been changed to.
588         *                {@code true} when the torch mode has become on and available to be turned
589         *                off. {@code false} when the torch mode has becomes off and available to
590         *                be turned on.
591         */
592        public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
593            // default empty implementation
594        }
595    }
596
597    /**
598     * Convert ServiceSpecificExceptions and Binder RemoteExceptions from camera binder interfaces
599     * into the correct public exceptions.
600     *
601     * @hide
602     */
603    public static void throwAsPublicException(Throwable t) throws CameraAccessException {
604        if (t instanceof ServiceSpecificException) {
605            ServiceSpecificException e = (ServiceSpecificException) t;
606            int reason = CameraAccessException.CAMERA_ERROR;
607            switch(e.errorCode) {
608                case ICameraService.ERROR_DISCONNECTED:
609                    reason = CameraAccessException.CAMERA_DISCONNECTED;
610                    break;
611                case ICameraService.ERROR_DISABLED:
612                    reason = CameraAccessException.CAMERA_DISABLED;
613                    break;
614                case ICameraService.ERROR_CAMERA_IN_USE:
615                    reason = CameraAccessException.CAMERA_IN_USE;
616                    break;
617                case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
618                    reason = CameraAccessException.MAX_CAMERAS_IN_USE;
619                    break;
620                case ICameraService.ERROR_DEPRECATED_HAL:
621                    reason = CameraAccessException.CAMERA_DEPRECATED_HAL;
622                    break;
623                case ICameraService.ERROR_ILLEGAL_ARGUMENT:
624                case ICameraService.ERROR_ALREADY_EXISTS:
625                    throw new IllegalArgumentException(e.getMessage(), e);
626                case ICameraService.ERROR_PERMISSION_DENIED:
627                    throw new SecurityException(e.getMessage(), e);
628                case ICameraService.ERROR_TIMED_OUT:
629                case ICameraService.ERROR_INVALID_OPERATION:
630                default:
631                    reason = CameraAccessException.CAMERA_ERROR;
632            }
633            throw new CameraAccessException(reason, e.getMessage(), e);
634        } else if (t instanceof DeadObjectException) {
635            throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
636                    "Camera service has died unexpectedly",
637                    t);
638        } else if (t instanceof RemoteException) {
639            throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
640                    " which should never happen.", t);
641        } else if (t instanceof RuntimeException) {
642            RuntimeException e = (RuntimeException) t;
643            throw e;
644        }
645    }
646
647    /**
648     * Return or create the list of currently connected camera devices.
649     *
650     * <p>In case of errors connecting to the camera service, will return an empty list.</p>
651     */
652    private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
653        if (mDeviceIdList == null) {
654            int numCameras = 0;
655            ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
656            ArrayList<String> deviceIdList = new ArrayList<>();
657
658            // If no camera service, then no devices
659            if (cameraService == null) {
660                return deviceIdList;
661            }
662
663            try {
664                numCameras = cameraService.getNumberOfCameras(CAMERA_TYPE_ALL);
665            } catch(ServiceSpecificException e) {
666                throwAsPublicException(e);
667            } catch (RemoteException e) {
668                // camera service just died - if no camera service, then no devices
669                return deviceIdList;
670            }
671
672            for (int i = 0; i < numCameras; ++i) {
673                // Non-removable cameras use integers starting at 0 for their
674                // identifiers
675                boolean isDeviceSupported = false;
676                try {
677                    CameraMetadataNative info = cameraService.getCameraCharacteristics(i);
678                    if (!info.isEmpty()) {
679                        isDeviceSupported = true;
680                    } else {
681                        throw new AssertionError("Expected to get non-empty characteristics");
682                    }
683                } catch(ServiceSpecificException e) {
684                    // DISCONNECTED means that the HAL reported an low-level error getting the
685                    // device info; ILLEGAL_ARGUMENT means that this devices is not supported.
686                    // Skip listing the device.  Other errors,
687                    // propagate exception onward
688                    if (e.errorCode != ICameraService.ERROR_DISCONNECTED ||
689                            e.errorCode != ICameraService.ERROR_ILLEGAL_ARGUMENT) {
690                        throwAsPublicException(e);
691                    }
692                } catch(RemoteException e) {
693                    // Camera service died - no devices to list
694                    deviceIdList.clear();
695                    return deviceIdList;
696                }
697
698                if (isDeviceSupported) {
699                    deviceIdList.add(String.valueOf(i));
700                } else {
701                    Log.w(TAG, "Error querying camera device " + i + " for listing.");
702                }
703
704            }
705            mDeviceIdList = deviceIdList;
706        }
707        return mDeviceIdList;
708    }
709
710    /**
711     * Queries the camera service if it supports the camera2 api directly, or needs a shim.
712     *
713     * @param cameraId a non-{@code null} camera identifier
714     * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise.
715     */
716    private boolean supportsCamera2ApiLocked(String cameraId) {
717        return supportsCameraApiLocked(cameraId, API_VERSION_2);
718    }
719
720    /**
721     * Queries the camera service if it supports a camera api directly, or needs a shim.
722     *
723     * @param cameraId a non-{@code null} camera identifier
724     * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2}
725     * @return {@code true} if connecting will work for that device version.
726     */
727    private boolean supportsCameraApiLocked(String cameraId, int apiVersion) {
728        int id = Integer.parseInt(cameraId);
729
730        /*
731         * Possible return values:
732         * - NO_ERROR => CameraX API is supported
733         * - CAMERA_DEPRECATED_HAL => CameraX API is *not* supported (thrown as an exception)
734         * - Remote exception => If the camera service died
735         *
736         * Anything else is an unexpected error we don't want to recover from.
737         */
738        try {
739            ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
740            // If no camera service, no support
741            if (cameraService == null) return false;
742
743            return cameraService.supportsCameraApi(id, apiVersion);
744        } catch (RemoteException e) {
745            // Camera service is now down, no support for any API level
746        }
747        return false;
748    }
749
750    /**
751     * A per-process global camera manager instance, to retain a connection to the camera service,
752     * and to distribute camera availability notices to API-registered callbacks
753     */
754    private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
755            implements IBinder.DeathRecipient {
756
757        private static final String TAG = "CameraManagerGlobal";
758        private final boolean DEBUG = false;
759
760        private final int CAMERA_SERVICE_RECONNECT_DELAY_MS = 1000;
761
762        // Singleton instance
763        private static final CameraManagerGlobal gCameraManager =
764            new CameraManagerGlobal();
765
766        /**
767         * This must match the ICameraService definition
768         */
769        private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
770
771        // Camera ID -> Status map
772        private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
773
774        // Registered availablility callbacks and their handlers
775        private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
776            new ArrayMap<AvailabilityCallback, Handler>();
777
778        // torch client binder to set the torch mode with.
779        private Binder mTorchClientBinder = new Binder();
780
781        // Camera ID -> Torch status map
782        private final ArrayMap<String, Integer> mTorchStatus = new ArrayMap<String, Integer>();
783
784        // Registered torch callbacks and their handlers
785        private final ArrayMap<TorchCallback, Handler> mTorchCallbackMap =
786                new ArrayMap<TorchCallback, Handler>();
787
788        private final Object mLock = new Object();
789
790        // Access only through getCameraService to deal with binder death
791        private ICameraService mCameraService;
792
793        // Singleton, don't allow construction
794        private CameraManagerGlobal() {
795        }
796
797        public static CameraManagerGlobal get() {
798            return gCameraManager;
799        }
800
801        @Override
802        public IBinder asBinder() {
803            return this;
804        }
805
806        /**
807         * Return a best-effort ICameraService.
808         *
809         * <p>This will be null if the camera service is not currently available. If the camera
810         * service has died since the last use of the camera service, will try to reconnect to the
811         * service.</p>
812         */
813        public ICameraService getCameraService() {
814            synchronized(mLock) {
815                connectCameraServiceLocked();
816                if (mCameraService == null) {
817                    Log.e(TAG, "Camera service is unavailable");
818                }
819                return mCameraService;
820            }
821        }
822
823        /**
824         * Connect to the camera service if it's available, and set up listeners.
825         * If the service is already connected, do nothing.
826         *
827         * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
828         */
829        private void connectCameraServiceLocked() {
830            // Only reconnect if necessary
831            if (mCameraService != null) return;
832
833            Log.i(TAG, "Connecting to camera service");
834
835            IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
836            if (cameraServiceBinder == null) {
837                // Camera service is now down, leave mCameraService as null
838                return;
839            }
840            try {
841                cameraServiceBinder.linkToDeath(this, /*flags*/ 0);
842            } catch (RemoteException e) {
843                // Camera service is now down, leave mCameraService as null
844                return;
845            }
846
847            ICameraService cameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
848
849            try {
850                CameraMetadataNative.setupGlobalVendorTagDescriptor();
851            } catch (ServiceSpecificException e) {
852                handleRecoverableSetupErrors(e);
853            }
854
855            try {
856                cameraService.addListener(this);
857                mCameraService = cameraService;
858            } catch(ServiceSpecificException e) {
859                // Unexpected failure
860                throw new IllegalStateException("Failed to register a camera service listener", e);
861            } catch (RemoteException e) {
862                // Camera service is now down, leave mCameraService as null
863            }
864        }
865
866        public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
867            synchronized(mLock) {
868
869                if (cameraId == null) {
870                    throw new IllegalArgumentException("cameraId was null");
871                }
872
873                ICameraService cameraService = getCameraService();
874                if (cameraService == null) {
875                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
876                        "Camera service is currently unavailable");
877                }
878
879                try {
880                    cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
881                } catch(ServiceSpecificException e) {
882                    throwAsPublicException(e);
883                } catch (RemoteException e) {
884                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
885                            "Camera service is currently unavailable");
886                }
887            }
888        }
889
890        private void handleRecoverableSetupErrors(ServiceSpecificException e) {
891            switch (e.errorCode) {
892                case ICameraService.ERROR_DISCONNECTED:
893                    Log.w(TAG, e.getMessage());
894                    break;
895                default:
896                    throw new IllegalStateException(e);
897            }
898        }
899
900        private boolean isAvailable(int status) {
901            switch (status) {
902                case ICameraServiceListener.STATUS_PRESENT:
903                    return true;
904                default:
905                    return false;
906            }
907        }
908
909        private boolean validStatus(int status) {
910            switch (status) {
911                case ICameraServiceListener.STATUS_NOT_PRESENT:
912                case ICameraServiceListener.STATUS_PRESENT:
913                case ICameraServiceListener.STATUS_ENUMERATING:
914                case ICameraServiceListener.STATUS_NOT_AVAILABLE:
915                    return true;
916                default:
917                    return false;
918            }
919        }
920
921        private boolean validTorchStatus(int status) {
922            switch (status) {
923                case ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE:
924                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
925                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF:
926                    return true;
927                default:
928                    return false;
929            }
930        }
931
932        private void postSingleUpdate(final AvailabilityCallback callback, final Handler handler,
933                final String id, final int status) {
934            if (isAvailable(status)) {
935                handler.post(
936                    new Runnable() {
937                        @Override
938                        public void run() {
939                            callback.onCameraAvailable(id);
940                        }
941                    });
942            } else {
943                handler.post(
944                    new Runnable() {
945                        @Override
946                        public void run() {
947                            callback.onCameraUnavailable(id);
948                        }
949                    });
950            }
951        }
952
953        private void postSingleTorchUpdate(final TorchCallback callback, final Handler handler,
954                final String id, final int status) {
955            switch(status) {
956                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
957                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF:
958                    handler.post(
959                            new Runnable() {
960                                @Override
961                                public void run() {
962                                    callback.onTorchModeChanged(id, status ==
963                                            ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON);
964                                }
965                            });
966                    break;
967                default:
968                    handler.post(
969                            new Runnable() {
970                                @Override
971                                public void run() {
972                                    callback.onTorchModeUnavailable(id);
973                                }
974                            });
975                    break;
976            }
977        }
978
979        /**
980         * Send the state of all known cameras to the provided listener, to initialize
981         * the listener's knowledge of camera state.
982         */
983        private void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
984            for (int i = 0; i < mDeviceStatus.size(); i++) {
985                String id = mDeviceStatus.keyAt(i);
986                Integer status = mDeviceStatus.valueAt(i);
987                postSingleUpdate(callback, handler, id, status);
988            }
989        }
990
991        private void onStatusChangedLocked(int status, String id) {
992            if (DEBUG) {
993                Log.v(TAG,
994                        String.format("Camera id %s has status changed to 0x%x", id, status));
995            }
996
997            if (!validStatus(status)) {
998                Log.e(TAG, String.format("Ignoring invalid device %s status 0x%x", id,
999                                status));
1000                return;
1001            }
1002
1003            Integer oldStatus = mDeviceStatus.put(id, status);
1004
1005            if (oldStatus != null && oldStatus == status) {
1006                if (DEBUG) {
1007                    Log.v(TAG, String.format(
1008                        "Device status changed to 0x%x, which is what it already was",
1009                        status));
1010                }
1011                return;
1012            }
1013
1014            // TODO: consider abstracting out this state minimization + transition
1015            // into a separate
1016            // more easily testable class
1017            // i.e. (new State()).addState(STATE_AVAILABLE)
1018            //                   .addState(STATE_NOT_AVAILABLE)
1019            //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
1020            //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
1021            //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
1022            //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);
1023
1024            // Translate all the statuses to either 'available' or 'not available'
1025            //  available -> available         => no new update
1026            //  not available -> not available => no new update
1027            if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {
1028                if (DEBUG) {
1029                    Log.v(TAG,
1030                            String.format(
1031                                "Device status was previously available (%b), " +
1032                                " and is now again available (%b)" +
1033                                "so no new client visible update will be sent",
1034                                isAvailable(oldStatus), isAvailable(status)));
1035                }
1036                return;
1037            }
1038
1039            final int callbackCount = mCallbackMap.size();
1040            for (int i = 0; i < callbackCount; i++) {
1041                Handler handler = mCallbackMap.valueAt(i);
1042                final AvailabilityCallback callback = mCallbackMap.keyAt(i);
1043
1044                postSingleUpdate(callback, handler, id, status);
1045            }
1046        } // onStatusChangedLocked
1047
1048        private void updateTorchCallbackLocked(TorchCallback callback, Handler handler) {
1049            for (int i = 0; i < mTorchStatus.size(); i++) {
1050                String id = mTorchStatus.keyAt(i);
1051                Integer status = mTorchStatus.valueAt(i);
1052                postSingleTorchUpdate(callback, handler, id, status);
1053            }
1054        }
1055
1056        private void onTorchStatusChangedLocked(int status, String id) {
1057            if (DEBUG) {
1058                Log.v(TAG,
1059                        String.format("Camera id %s has torch status changed to 0x%x", id, status));
1060            }
1061
1062            if (!validTorchStatus(status)) {
1063                Log.e(TAG, String.format("Ignoring invalid device %s torch status 0x%x", id,
1064                                status));
1065                return;
1066            }
1067
1068            Integer oldStatus = mTorchStatus.put(id, status);
1069            if (oldStatus != null && oldStatus == status) {
1070                if (DEBUG) {
1071                    Log.v(TAG, String.format(
1072                        "Torch status changed to 0x%x, which is what it already was",
1073                        status));
1074                }
1075                return;
1076            }
1077
1078            final int callbackCount = mTorchCallbackMap.size();
1079            for (int i = 0; i < callbackCount; i++) {
1080                final Handler handler = mTorchCallbackMap.valueAt(i);
1081                final TorchCallback callback = mTorchCallbackMap.keyAt(i);
1082                postSingleTorchUpdate(callback, handler, id, status);
1083            }
1084        } // onTorchStatusChangedLocked
1085
1086        /**
1087         * Register a callback to be notified about camera device availability with the
1088         * global listener singleton.
1089         *
1090         * @param callback the new callback to send camera availability notices to
1091         * @param handler The handler on which the callback should be invoked. May not be null.
1092         */
1093        public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
1094            synchronized (mLock) {
1095                connectCameraServiceLocked();
1096
1097                Handler oldHandler = mCallbackMap.put(callback, handler);
1098                // For new callbacks, provide initial availability information
1099                if (oldHandler == null) {
1100                    updateCallbackLocked(callback, handler);
1101                }
1102
1103                // If not connected to camera service, schedule a reconnect to camera service.
1104                if (mCameraService == null) {
1105                    scheduleCameraServiceReconnectionLocked();
1106                }
1107            }
1108        }
1109
1110        /**
1111         * Remove a previously-added callback; the callback will no longer receive connection and
1112         * disconnection callbacks, and is no longer referenced by the global listener singleton.
1113         *
1114         * @param callback The callback to remove from the notification list
1115         */
1116        public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
1117            synchronized (mLock) {
1118                mCallbackMap.remove(callback);
1119            }
1120        }
1121
1122        public void registerTorchCallback(TorchCallback callback, Handler handler) {
1123            synchronized(mLock) {
1124                connectCameraServiceLocked();
1125
1126                Handler oldHandler = mTorchCallbackMap.put(callback, handler);
1127                // For new callbacks, provide initial torch information
1128                if (oldHandler == null) {
1129                    updateTorchCallbackLocked(callback, handler);
1130                }
1131
1132                // If not connected to camera service, schedule a reconnect to camera service.
1133                if (mCameraService == null) {
1134                    scheduleCameraServiceReconnectionLocked();
1135                }
1136            }
1137        }
1138
1139        public void unregisterTorchCallback(TorchCallback callback) {
1140            synchronized(mLock) {
1141                mTorchCallbackMap.remove(callback);
1142            }
1143        }
1144
1145        /**
1146         * Callback from camera service notifying the process about camera availability changes
1147         */
1148        @Override
1149        public void onStatusChanged(int status, int cameraId) throws RemoteException {
1150            synchronized(mLock) {
1151                onStatusChangedLocked(status, String.valueOf(cameraId));
1152            }
1153        }
1154
1155        @Override
1156        public void onTorchStatusChanged(int status, String cameraId) throws RemoteException {
1157            synchronized (mLock) {
1158                onTorchStatusChangedLocked(status, cameraId);
1159            }
1160        }
1161
1162        /**
1163         * Try to connect to camera service after some delay if any client registered camera
1164         * availability callback or torch status callback.
1165         */
1166        private void scheduleCameraServiceReconnectionLocked() {
1167            final Handler handler;
1168
1169            if (mCallbackMap.size() > 0) {
1170                handler = mCallbackMap.valueAt(0);
1171            } else if (mTorchCallbackMap.size() > 0) {
1172                handler = mTorchCallbackMap.valueAt(0);
1173            } else {
1174                // Not necessary to reconnect camera service if no client registers a callback.
1175                return;
1176            }
1177
1178            if (DEBUG) {
1179                Log.v(TAG, "Reconnecting Camera Service in " + CAMERA_SERVICE_RECONNECT_DELAY_MS +
1180                        " ms");
1181            }
1182
1183            handler.postDelayed(
1184                    new Runnable() {
1185                        @Override
1186                        public void run() {
1187                            ICameraService cameraService = getCameraService();
1188                            if (cameraService == null) {
1189                                synchronized(mLock) {
1190                                    if (DEBUG) {
1191                                        Log.v(TAG, "Reconnecting Camera Service failed.");
1192                                    }
1193                                    scheduleCameraServiceReconnectionLocked();
1194                                }
1195                            }
1196                        }
1197                    },
1198                    CAMERA_SERVICE_RECONNECT_DELAY_MS);
1199        }
1200
1201        /**
1202         * Listener for camera service death.
1203         *
1204         * <p>The camera service isn't supposed to die under any normal circumstances, but can be
1205         * turned off during debug, or crash due to bugs.  So detect that and null out the interface
1206         * object, so that the next calls to the manager can try to reconnect.</p>
1207         */
1208        public void binderDied() {
1209            synchronized(mLock) {
1210                // Only do this once per service death
1211                if (mCameraService == null) return;
1212
1213                mCameraService = null;
1214
1215                // Tell listeners that the cameras and torch modes are unavailable and schedule a
1216                // reconnection to camera service. When camera service is reconnected, the camera
1217                // and torch statuses will be updated.
1218                for (int i = 0; i < mDeviceStatus.size(); i++) {
1219                    String cameraId = mDeviceStatus.keyAt(i);
1220                    onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, cameraId);
1221                }
1222                for (int i = 0; i < mTorchStatus.size(); i++) {
1223                    String cameraId = mTorchStatus.keyAt(i);
1224                    onTorchStatusChangedLocked(ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE,
1225                            cameraId);
1226                }
1227
1228                scheduleCameraServiceReconnectionLocked();
1229            }
1230        }
1231
1232    } // CameraManagerGlobal
1233
1234} // CameraManager
1235