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