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