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