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