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