• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.hardware.camera2;
18 
19 import android.content.Context;
20 import android.hardware.ICameraService;
21 import android.hardware.ICameraServiceListener;
22 import android.hardware.CameraInfo;
23 import android.hardware.camera2.impl.CameraMetadataNative;
24 import android.hardware.camera2.legacy.CameraDeviceUserShim;
25 import android.hardware.camera2.legacy.LegacyMetadataMapper;
26 import android.hardware.camera2.utils.CameraServiceBinderDecorator;
27 import android.hardware.camera2.utils.CameraRuntimeException;
28 import android.hardware.camera2.utils.BinderHolder;
29 import android.os.IBinder;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.util.Log;
35 import android.util.ArrayMap;
36 
37 import 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  */
52 public 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      */
CameraManager(Context context)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      */
getCameraIdList()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      */
registerAvailabilityCallback(AvailabilityCallback callback, Handler handler)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      */
unregisterAvailabilityCallback(AvailabilityCallback callback)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      */
getCameraCharacteristics(String cameraId)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      */
openCameraDeviceUserAsync(String cameraId, CameraDevice.StateCallback callback, Handler handler)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      */
openCamera(String cameraId, final CameraDevice.StateCallback callback, Handler handler)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          */
onCameraAvailable(String cameraId)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          */
onCameraUnavailable(String cameraId)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      */
getOrCreateDeviceIdListLocked()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      */
supportsCamera2ApiLocked(String cameraId)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      */
supportsCameraApiLocked(String cameraId, int apiVersion)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
CameraManagerGlobal()592         private CameraManagerGlobal() {
593         }
594 
get()595         public static CameraManagerGlobal get() {
596             return gCameraManager;
597         }
598 
599         @Override
asBinder()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          */
getCameraService()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          */
connectCameraServiceLocked()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 
handleRecoverableSetupErrors(CameraRuntimeException e, String msg)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 
isAvailable(int status)683         private boolean isAvailable(int status) {
684             switch (status) {
685                 case STATUS_PRESENT:
686                     return true;
687                 default:
688                     return false;
689             }
690         }
691 
validStatus(int status)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 
postSingleUpdate(final AvailabilityCallback callback, final Handler handler, final String id, final int status)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          */
updateCallbackLocked(AvailabilityCallback callback, Handler handler)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 
onStatusChangedLocked(int status, String id)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          */
registerAvailabilityCallback(AvailabilityCallback callback, Handler handler)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          */
unregisterAvailabilityCallback(AvailabilityCallback callback)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
onStatusChanged(int status, int cameraId)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          */
binderDied()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