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