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