Home | History | Annotate | Download | only in camera2
      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      */
     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
     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      */
    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      */
    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      */
    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      */
    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
    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      */
    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)
    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      */
    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      */
    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          */
    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          */
    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          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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
    821         private CameraManagerGlobal() {
    822         }
    823 
    824         public static CameraManagerGlobal get() {
    825             return gCameraManager;
    826         }
    827 
    828         @Override
    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          */
    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          */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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          */
   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 
   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 
   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 
   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          */
   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          */
   1143         public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
   1144             synchronized (mLock) {
   1145                 mCallbackMap.remove(callback);
   1146             }
   1147         }
   1148 
   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 
   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
   1176         public void onStatusChanged(int status, int cameraId) throws RemoteException {
   1177             synchronized(mLock) {
   1178                 onStatusChangedLocked(status, String.valueOf(cameraId));
   1179             }
   1180         }
   1181 
   1182         @Override
   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          */
   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          */
   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