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