• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hardware.camera2;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.annotation.SystemService;
25 import android.annotation.TestApi;
26 import android.content.Context;
27 import android.hardware.CameraStatus;
28 import android.hardware.ICameraService;
29 import android.hardware.ICameraServiceListener;
30 import android.hardware.camera2.impl.CameraDeviceImpl;
31 import android.hardware.camera2.impl.CameraInjectionSessionImpl;
32 import android.hardware.camera2.impl.CameraMetadataNative;
33 import android.hardware.camera2.params.ExtensionSessionConfiguration;
34 import android.hardware.camera2.params.SessionConfiguration;
35 import android.hardware.camera2.params.StreamConfiguration;
36 import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
37 import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
38 import android.hardware.display.DisplayManager;
39 import android.os.Binder;
40 import android.os.DeadObjectException;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.ServiceSpecificException;
46 import android.os.SystemProperties;
47 import android.util.ArrayMap;
48 import android.util.ArraySet;
49 import android.util.Log;
50 import android.util.Size;
51 import android.view.Display;
52 
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Comparator;
56 import java.util.HashMap;
57 import java.util.Map;
58 import java.util.Set;
59 import java.util.concurrent.Executor;
60 import java.util.concurrent.Executors;
61 import java.util.concurrent.RejectedExecutionException;
62 import java.util.concurrent.ScheduledExecutorService;
63 import java.util.concurrent.TimeUnit;
64 
65 /**
66  * <p>A system service manager for detecting, characterizing, and connecting to
67  * {@link CameraDevice CameraDevices}.</p>
68  *
69  * <p>For more details about communicating with camera devices, read the Camera
70  * developer guide or the {@link android.hardware.camera2 camera2}
71  * package documentation.</p>
72  */
73 @SystemService(Context.CAMERA_SERVICE)
74 public final class CameraManager {
75 
76     private static final String TAG = "CameraManager";
77     private final boolean DEBUG = false;
78 
79     private static final int USE_CALLING_UID = -1;
80 
81     @SuppressWarnings("unused")
82     private static final int API_VERSION_1 = 1;
83     private static final int API_VERSION_2 = 2;
84 
85     private static final int CAMERA_TYPE_BACKWARD_COMPATIBLE = 0;
86     private static final int CAMERA_TYPE_ALL = 1;
87 
88     private ArrayList<String> mDeviceIdList;
89 
90     private final Context mContext;
91     private final Object mLock = new Object();
92 
93     /**
94      * @hide
95      */
CameraManager(Context context)96     public CameraManager(Context context) {
97         synchronized(mLock) {
98             mContext = context;
99         }
100     }
101 
102     /**
103      * Return the list of currently connected camera devices by identifier, including
104      * cameras that may be in use by other camera API clients.
105      *
106      * <p>Non-removable cameras use integers starting at 0 for their
107      * identifiers, while removable cameras have a unique identifier for each
108      * individual device, even if they are the same model.</p>
109      *
110      * <p>This list doesn't contain physical cameras that can only be used as part of a logical
111      * multi-camera device.</p>
112      *
113      * @return The list of currently connected camera devices.
114      */
115     @NonNull
getCameraIdList()116     public String[] getCameraIdList() throws CameraAccessException {
117         return CameraManagerGlobal.get().getCameraIdList();
118     }
119 
120     /**
121      * Similar to getCameraIdList(). However, getCamerIdListNoLazy() necessarily communicates with
122      * cameraserver in order to get the list of camera ids. This is to faciliate testing since some
123      * camera ids may go 'offline' without callbacks from cameraserver because of changes in
124      * SYSTEM_CAMERA permissions (though this is not a changeable permission, tests may call
125      * adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call
126      * affects the camera ids returned by getCameraIdList() as well. Tests which do adopt shell
127      * permission identity should not mix getCameraIdList() and getCameraListNoLazyCalls().
128      */
129     /** @hide */
130     @TestApi
getCameraIdListNoLazy()131     public String[] getCameraIdListNoLazy() throws CameraAccessException {
132         return CameraManagerGlobal.get().getCameraIdListNoLazy();
133     }
134 
135     /**
136      * Return the set of combinations of currently connected camera device identifiers, which
137      * support configuring camera device sessions concurrently.
138      *
139      * <p>The devices in these combinations can be concurrently configured by the same
140      * client camera application. Using these camera devices concurrently by two different
141      * applications is not guaranteed to be supported, however.</p>
142      *
143      * <p>For concurrent operation, in chronological order :
144      * - Applications must first close any open cameras that have sessions configured, using
145      *   {@link CameraDevice#close}.
146      * - All camera devices intended to be operated concurrently, must be opened using
147      *   {@link #openCamera}, before configuring sessions on any of the camera devices.</p>
148      *
149      * <p>Each device in a combination, is guaranteed to support stream combinations which may be
150      * obtained by querying {@link #getCameraCharacteristics} for the key
151      * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}.</p>
152      *
153      * <p>For concurrent operation, if a camera device has a non null zoom ratio range as specified
154      * by
155      * {@link android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE},
156      * its complete zoom ratio range may not apply. Applications can use
157      * {@link android.hardware.camera2.CaptureRequest#CONTROL_ZOOM_RATIO} >=1 and  <=
158      * {@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM}
159      * during concurrent operation.
160      * <p>
161      *
162      * <p>The set of combinations may include camera devices that may be in use by other camera API
163      * clients.</p>
164      *
165      * <p>Concurrent camera extension sessions {@link CameraExtensionSession} are not currently
166      * supported.</p>
167      *
168      * <p>The set of combinations doesn't contain physical cameras that can only be used as
169      * part of a logical multi-camera device.</p>
170      *
171      * <p> If a new camera id becomes available through
172      * {@link AvailabilityCallback#onCameraUnavailable(String)}, clients can call
173      * this method to check if new combinations of camera ids which can stream concurrently are
174      * available.
175      *
176      * @return The set of combinations of currently connected camera devices, that may have
177      *         sessions configured concurrently. The set of combinations will be empty if no such
178      *         combinations are supported by the camera subsystem.
179      *
180      * @throws CameraAccessException if the camera device has been disconnected.
181      */
182     @NonNull
getConcurrentCameraIds()183     public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException {
184         return CameraManagerGlobal.get().getConcurrentCameraIds();
185     }
186 
187     /**
188      * Checks whether the provided set of camera devices and their corresponding
189      * {@link SessionConfiguration} can be configured concurrently.
190      *
191      * <p>This method performs a runtime check of the given {@link SessionConfiguration} and camera
192      * id combinations. The result confirms whether or not the passed session configurations can be
193      * successfully used to create camera capture sessions concurrently, on the given camera
194      * devices using {@link CameraDevice#createCaptureSession(SessionConfiguration)}.
195      * </p>
196      *
197      * <p>The method can be called at any point before, during and after active capture sessions.
198      * It will not impact normal camera behavior in any way and must complete significantly
199      * faster than creating a regular or constrained capture session.</p>
200      *
201      * <p>Although this method is faster than creating a new capture session, it is not intended
202      * to be used for exploring the entire space of supported concurrent stream combinations. The
203      * available mandatory concurrent stream combinations may be obtained by querying
204      * {@link #getCameraCharacteristics} for the key
205      * {@link android.hardware.camera2.CameraCharacteristics#SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS}. </p>
206      *
207      * <p>Note that session parameters will be ignored and calls to
208      * {@link SessionConfiguration#setSessionParameters} are not required.</p>
209      *
210      * @return {@code true} if the given combination of session configurations and corresponding
211      *                      camera ids are concurrently supported by the camera sub-system,
212      *         {@code false} otherwise OR if the set of camera devices provided is not a subset of
213      *                       those returned by {@link #getConcurrentCameraIds}.
214      *
215      * @throws CameraAccessException if one of the camera devices queried is no longer connected.
216      *
217      */
218     @RequiresPermission(android.Manifest.permission.CAMERA)
isConcurrentSessionConfigurationSupported( @onNull Map<String, SessionConfiguration> cameraIdAndSessionConfig)219     public boolean isConcurrentSessionConfigurationSupported(
220             @NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig)
221             throws CameraAccessException {
222         return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported(
223                 cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion);
224     }
225 
226     /**
227      * Register a callback to be notified about camera device availability.
228      *
229      * <p>Registering the same callback again will replace the handler with the
230      * new one provided.</p>
231      *
232      * <p>The first time a callback is registered, it is immediately called
233      * with the availability status of all currently known camera devices.</p>
234      *
235      * <p>{@link AvailabilityCallback#onCameraUnavailable(String)} will be called whenever a camera
236      * device is opened by any camera API client. As of API level 23, other camera API clients may
237      * still be able to open such a camera device, evicting the existing client if they have higher
238      * priority than the existing client of a camera device. See open() for more details.</p>
239      *
240      * <p>Since this callback will be registered with the camera service, remember to unregister it
241      * once it is no longer needed; otherwise the callback will continue to receive events
242      * indefinitely and it may prevent other resources from being released. Specifically, the
243      * callbacks will be invoked independently of the general activity lifecycle and independently
244      * of the state of individual CameraManager instances.</p>
245      *
246      * @param callback the new callback to send camera availability notices to
247      * @param handler The handler on which the callback should be invoked, or {@code null} to use
248      *             the current thread's {@link android.os.Looper looper}.
249      *
250      * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
251      *             no looper.
252      */
registerAvailabilityCallback(@onNull AvailabilityCallback callback, @Nullable Handler handler)253     public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback,
254             @Nullable Handler handler) {
255         CameraManagerGlobal.get().registerAvailabilityCallback(callback,
256                 CameraDeviceImpl.checkAndWrapHandler(handler));
257     }
258 
259     /**
260      * Register a callback to be notified about camera device availability.
261      *
262      * <p>The behavior of this method matches that of
263      * {@link #registerAvailabilityCallback(AvailabilityCallback, Handler)},
264      * except that it uses {@link java.util.concurrent.Executor} as an argument
265      * instead of {@link android.os.Handler}.</p>
266      *
267      * @param executor The executor which will be used to invoke the callback.
268      * @param callback the new callback to send camera availability notices to
269      *
270      * @throws IllegalArgumentException if the executor is {@code null}.
271      */
registerAvailabilityCallback(@onNull @allbackExecutor Executor executor, @NonNull AvailabilityCallback callback)272     public void registerAvailabilityCallback(@NonNull @CallbackExecutor Executor executor,
273             @NonNull AvailabilityCallback callback) {
274         if (executor == null) {
275             throw new IllegalArgumentException("executor was null");
276         }
277         CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor);
278     }
279 
280     /**
281      * Remove a previously-added callback; the callback will no longer receive connection and
282      * disconnection callbacks.
283      *
284      * <p>Removing a callback that isn't registered has no effect.</p>
285      *
286      * @param callback The callback to remove from the notification list
287      */
unregisterAvailabilityCallback(@onNull AvailabilityCallback callback)288     public void unregisterAvailabilityCallback(@NonNull AvailabilityCallback callback) {
289         CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
290     }
291 
292     /**
293      * Register a callback to be notified about torch mode status.
294      *
295      * <p>Registering the same callback again will replace the handler with the
296      * new one provided.</p>
297      *
298      * <p>The first time a callback is registered, it is immediately called
299      * with the torch mode status of all currently known camera devices with a flash unit.</p>
300      *
301      * <p>Since this callback will be registered with the camera service, remember to unregister it
302      * once it is no longer needed; otherwise the callback will continue to receive events
303      * indefinitely and it may prevent other resources from being released. Specifically, the
304      * callbacks will be invoked independently of the general activity lifecycle and independently
305      * of the state of individual CameraManager instances.</p>
306      *
307      * @param callback The new callback to send torch mode status to
308      * @param handler The handler on which the callback should be invoked, or {@code null} to use
309      *             the current thread's {@link android.os.Looper looper}.
310      *
311      * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
312      *             no looper.
313      */
registerTorchCallback(@onNull TorchCallback callback, @Nullable Handler handler)314     public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) {
315         CameraManagerGlobal.get().registerTorchCallback(callback,
316                 CameraDeviceImpl.checkAndWrapHandler(handler));
317     }
318 
319     /**
320      * Register a callback to be notified about torch mode status.
321      *
322      * <p>The behavior of this method matches that of
323      * {@link #registerTorchCallback(TorchCallback, Handler)},
324      * except that it uses {@link java.util.concurrent.Executor} as an argument
325      * instead of {@link android.os.Handler}.</p>
326      *
327      * @param executor The executor which will be used to invoke the callback
328      * @param callback The new callback to send torch mode status to
329      *
330      * @throws IllegalArgumentException if the executor is {@code null}.
331      */
registerTorchCallback(@onNull @allbackExecutor Executor executor, @NonNull TorchCallback callback)332     public void registerTorchCallback(@NonNull @CallbackExecutor Executor executor,
333             @NonNull TorchCallback callback) {
334         if (executor == null) {
335             throw new IllegalArgumentException("executor was null");
336         }
337         CameraManagerGlobal.get().registerTorchCallback(callback, executor);
338     }
339 
340     /**
341      * Remove a previously-added callback; the callback will no longer receive torch mode status
342      * callbacks.
343      *
344      * <p>Removing a callback that isn't registered has no effect.</p>
345      *
346      * @param callback The callback to remove from the notification list
347      */
unregisterTorchCallback(@onNull TorchCallback callback)348     public void unregisterTorchCallback(@NonNull TorchCallback callback) {
349         CameraManagerGlobal.get().unregisterTorchCallback(callback);
350     }
351 
352     // TODO(b/147726300): Investigate how to support foldables/multi-display devices.
getDisplaySize()353     private Size getDisplaySize() {
354         Size ret = new Size(0, 0);
355 
356         try {
357             DisplayManager displayManager =
358                     (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
359             Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
360             if (display != null) {
361                 int width = display.getWidth();
362                 int height = display.getHeight();
363 
364                 if (height > width) {
365                     height = width;
366                     width = display.getHeight();
367                 }
368 
369                 ret = new Size(width, height);
370             } else {
371                 Log.e(TAG, "Invalid default display!");
372             }
373         } catch (Exception e) {
374             Log.e(TAG, "getDisplaySize Failed. " + e.toString());
375         }
376 
377         return ret;
378     }
379 
380     /**
381      * Get all physical cameras' multi-resolution stream configuration map
382      *
383      * <p>For a logical multi-camera, query the map between physical camera id and
384      * the physical camera's multi-resolution stream configuration. This map is in turn
385      * combined to form the logical camera's multi-resolution stream configuration map.</p>
386      *
387      * <p>For an ultra high resolution camera, directly use
388      * android.scaler.physicalCameraMultiResolutionStreamConfigurations as the camera device's
389      * multi-resolution stream configuration map.</p>
390      */
getPhysicalCameraMultiResolutionConfigs( String cameraId, CameraMetadataNative info, ICameraService cameraService)391     private Map<String, StreamConfiguration[]> getPhysicalCameraMultiResolutionConfigs(
392             String cameraId, CameraMetadataNative info, ICameraService cameraService)
393             throws CameraAccessException {
394         HashMap<String, StreamConfiguration[]> multiResolutionStreamConfigurations =
395                 new HashMap<String, StreamConfiguration[]>();
396 
397         Boolean multiResolutionStreamSupported = info.get(
398                 CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_SUPPORTED);
399         if (multiResolutionStreamSupported == null || !multiResolutionStreamSupported) {
400             return multiResolutionStreamConfigurations;
401         }
402 
403         // Query the characteristics of all physical sub-cameras, and combine the multi-resolution
404         // stream configurations. Alternatively, for ultra-high resolution camera, direclty use
405         // its multi-resolution stream configurations. Note that framework derived formats such as
406         // HEIC and DEPTH_JPEG aren't supported as multi-resolution input or output formats.
407         Set<String> physicalCameraIds = info.getPhysicalCameraIds();
408         if (physicalCameraIds.size() == 0 && info.isUltraHighResolutionSensor()) {
409             StreamConfiguration[] configs = info.get(CameraCharacteristics.
410                     SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS);
411             if (configs != null) {
412                 multiResolutionStreamConfigurations.put(cameraId, configs);
413             }
414             return multiResolutionStreamConfigurations;
415         }
416         try {
417             for (String physicalCameraId : physicalCameraIds) {
418                 CameraMetadataNative physicalCameraInfo =
419                         cameraService.getCameraCharacteristics(physicalCameraId,
420                                 mContext.getApplicationInfo().targetSdkVersion);
421                 StreamConfiguration[] configs = physicalCameraInfo.get(
422                         CameraCharacteristics.
423                                 SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS);
424                 if (configs != null) {
425                     multiResolutionStreamConfigurations.put(physicalCameraId, configs);
426                 }
427             }
428         } catch (RemoteException e) {
429             ServiceSpecificException sse = new ServiceSpecificException(
430                     ICameraService.ERROR_DISCONNECTED,
431                     "Camera service is currently unavailable");
432             throwAsPublicException(sse);
433         }
434 
435         return multiResolutionStreamConfigurations;
436     }
437 
438     /**
439      * <p>Query the capabilities of a camera device. These capabilities are
440      * immutable for a given camera.</p>
441      *
442      * <p>From API level 29, this function can also be used to query the capabilities of physical
443      * cameras that can only be used as part of logical multi-camera. These cameras cannot be
444      * opened directly via {@link #openCamera}</p>
445      *
446      * <p>Also starting with API level 29, while most basic camera information is still available
447      * even without the CAMERA permission, some values are not available to apps that do not hold
448      * that permission. The keys not available are listed by
449      * {@link CameraCharacteristics#getKeysNeedingPermission}.</p>
450      *
451      * @param cameraId The id of the camera device to query. This could be either a standalone
452      * camera ID which can be directly opened by {@link #openCamera}, or a physical camera ID that
453      * can only used as part of a logical multi-camera.
454      * @return The properties of the given camera
455      *
456      * @throws IllegalArgumentException if the cameraId does not match any
457      *         known camera device.
458      * @throws CameraAccessException if the camera device has been disconnected.
459      *
460      * @see #getCameraIdList
461      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
462      */
463     @NonNull
getCameraCharacteristics(@onNull String cameraId)464     public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
465             throws CameraAccessException {
466         CameraCharacteristics characteristics = null;
467         if (CameraManagerGlobal.sCameraServiceDisabled) {
468             throw new IllegalArgumentException("No cameras available on device");
469         }
470         synchronized (mLock) {
471             ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
472             if (cameraService == null) {
473                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
474                         "Camera service is currently unavailable");
475             }
476             try {
477                 Size displaySize = getDisplaySize();
478 
479                 CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
480                         mContext.getApplicationInfo().targetSdkVersion);
481                 try {
482                     info.setCameraId(Integer.parseInt(cameraId));
483                 } catch (NumberFormatException e) {
484                     Log.v(TAG, "Failed to parse camera Id " + cameraId + " to integer");
485                 }
486 
487                 boolean hasConcurrentStreams =
488                         CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId);
489                 info.setHasMandatoryConcurrentStreams(hasConcurrentStreams);
490                 info.setDisplaySize(displaySize);
491 
492                 Map<String, StreamConfiguration[]> multiResolutionSizeMap =
493                         getPhysicalCameraMultiResolutionConfigs(cameraId, info, cameraService);
494                 if (multiResolutionSizeMap.size() > 0) {
495                     info.setMultiResolutionStreamConfigurationMap(multiResolutionSizeMap);
496                 }
497 
498                 characteristics = new CameraCharacteristics(info);
499             } catch (ServiceSpecificException e) {
500                 throwAsPublicException(e);
501             } catch (RemoteException e) {
502                 // Camera service died - act as if the camera was disconnected
503                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
504                         "Camera service is currently unavailable", e);
505             }
506         }
507         return characteristics;
508     }
509 
510     /**
511      * <p>Query the camera extension capabilities of a camera device.</p>
512      *
513      * @param cameraId The id of the camera device to query. This must be a standalone
514      * camera ID which can be directly opened by {@link #openCamera}.
515      * @return The properties of the given camera
516      *
517      * @throws IllegalArgumentException if the cameraId does not match any
518      *         known camera device.
519      * @throws CameraAccessException if the camera device has been disconnected.
520      *
521      * @see CameraExtensionCharacteristics
522      * @see CameraDevice#createExtensionSession(ExtensionSessionConfiguration)
523      * @see CameraExtensionSession
524      */
525     @NonNull
getCameraExtensionCharacteristics( @onNull String cameraId)526     public CameraExtensionCharacteristics getCameraExtensionCharacteristics(
527             @NonNull String cameraId) throws CameraAccessException {
528         CameraCharacteristics chars = getCameraCharacteristics(cameraId);
529         return new CameraExtensionCharacteristics(mContext, cameraId, chars);
530     }
531 
getPhysicalIdToCharsMap( CameraCharacteristics chars)532     private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap(
533             CameraCharacteristics chars) throws CameraAccessException {
534         HashMap<String, CameraCharacteristics> physicalIdsToChars =
535                 new HashMap<String, CameraCharacteristics>();
536         Set<String> physicalCameraIds = chars.getPhysicalCameraIds();
537         for (String physicalCameraId : physicalCameraIds) {
538             CameraCharacteristics physicalChars = getCameraCharacteristics(physicalCameraId);
539             physicalIdsToChars.put(physicalCameraId, physicalChars);
540         }
541         return physicalIdsToChars;
542     }
543 
544     /**
545      * Helper for opening a connection to a camera with the given ID.
546      *
547      * @param cameraId The unique identifier of the camera device to open
548      * @param callback The callback for the camera. Must not be null.
549      * @param executor The executor to invoke the callback with. Must not be null.
550      * @param uid      The UID of the application actually opening the camera.
551      *                 Must be USE_CALLING_UID unless the caller is a service
552      *                 that is trusted to open the device on behalf of an
553      *                 application and to forward the real UID.
554      *
555      * @throws CameraAccessException if the camera is disabled by device policy,
556      * too many camera devices are already open, or the cameraId does not match
557      * any currently available camera device.
558      *
559      * @throws SecurityException if the application does not have permission to
560      * access the camera
561      * @throws IllegalArgumentException if callback or handler is null.
562      * @return A handle to the newly-created camera device.
563      *
564      * @see #getCameraIdList
565      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
566      */
openCameraDeviceUserAsync(String cameraId, CameraDevice.StateCallback callback, Executor executor, final int uid, final int oomScoreOffset)567     private CameraDevice openCameraDeviceUserAsync(String cameraId,
568             CameraDevice.StateCallback callback, Executor executor, final int uid,
569             final int oomScoreOffset) throws CameraAccessException {
570         CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
571         CameraDevice device = null;
572         Map<String, CameraCharacteristics> physicalIdsToChars =
573                 getPhysicalIdToCharsMap(characteristics);
574         synchronized (mLock) {
575 
576             ICameraDeviceUser cameraUser = null;
577             android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
578                     new android.hardware.camera2.impl.CameraDeviceImpl(
579                         cameraId,
580                         callback,
581                         executor,
582                         characteristics,
583                         physicalIdsToChars,
584                         mContext.getApplicationInfo().targetSdkVersion,
585                         mContext);
586 
587             ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
588 
589             try {
590                 ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
591                 if (cameraService == null) {
592                     throw new ServiceSpecificException(
593                         ICameraService.ERROR_DISCONNECTED,
594                         "Camera service is currently unavailable");
595                 }
596                 cameraUser = cameraService.connectDevice(callbacks, cameraId,
597                     mContext.getOpPackageName(),  mContext.getAttributionTag(), uid,
598                     oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion);
599             } catch (ServiceSpecificException e) {
600                 if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
601                     throw new AssertionError("Should've gone down the shim path");
602                 } else if (e.errorCode == ICameraService.ERROR_CAMERA_IN_USE ||
603                         e.errorCode == ICameraService.ERROR_MAX_CAMERAS_IN_USE ||
604                         e.errorCode == ICameraService.ERROR_DISABLED ||
605                         e.errorCode == ICameraService.ERROR_DISCONNECTED ||
606                         e.errorCode == ICameraService.ERROR_INVALID_OPERATION) {
607                     // Received one of the known connection errors
608                     // The remote camera device cannot be connected to, so
609                     // set the local camera to the startup error state
610                     deviceImpl.setRemoteFailure(e);
611 
612                     if (e.errorCode == ICameraService.ERROR_DISABLED ||
613                             e.errorCode == ICameraService.ERROR_DISCONNECTED ||
614                             e.errorCode == ICameraService.ERROR_CAMERA_IN_USE) {
615                         // Per API docs, these failures call onError and throw
616                         throwAsPublicException(e);
617                     }
618                 } else {
619                     // Unexpected failure - rethrow
620                     throwAsPublicException(e);
621                 }
622             } catch (RemoteException e) {
623                 // Camera service died - act as if it's a CAMERA_DISCONNECTED case
624                 ServiceSpecificException sse = new ServiceSpecificException(
625                     ICameraService.ERROR_DISCONNECTED,
626                     "Camera service is currently unavailable");
627                 deviceImpl.setRemoteFailure(sse);
628                 throwAsPublicException(sse);
629             }
630 
631             // TODO: factor out callback to be non-nested, then move setter to constructor
632             // For now, calling setRemoteDevice will fire initial
633             // onOpened/onUnconfigured callbacks.
634             // This function call may post onDisconnected and throw CAMERA_DISCONNECTED if
635             // cameraUser dies during setup.
636             deviceImpl.setRemoteDevice(cameraUser);
637             device = deviceImpl;
638         }
639 
640         return device;
641     }
642 
643     /**
644      * Open a connection to a camera with the given ID.
645      *
646      * <p>Use {@link #getCameraIdList} to get the list of available camera
647      * devices. Note that even if an id is listed, open may fail if the device
648      * is disconnected between the calls to {@link #getCameraIdList} and
649      * {@link #openCamera}, or if a higher-priority camera API client begins using the
650      * camera device.</p>
651      *
652      * <p>As of API level 23, devices for which the
653      * {@link AvailabilityCallback#onCameraUnavailable(String)} callback has been called due to the
654      * device being in use by a lower-priority, background camera API client can still potentially
655      * be opened by calling this method when the calling camera API client has a higher priority
656      * than the current camera API client using this device.  In general, if the top, foreground
657      * activity is running within your application process, your process will be given the highest
658      * priority when accessing the camera, and this method will succeed even if the camera device is
659      * in use by another camera API client. Any lower-priority application that loses control of the
660      * camera in this way will receive an
661      * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.
662      * Opening the same camera ID twice in the same application will similarly cause the
663      * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback
664      * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks
665      * being droppped.</p>
666      *
667      * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will
668      * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
669      * for operation by calling {@link CameraDevice#createCaptureSession} and
670      * {@link CameraDevice#createCaptureRequest}</p>
671      *
672      * <p>Before API level 30, when the application tries to open multiple {@link CameraDevice} of
673      * different IDs and the device does not support opening such combination, either the
674      * {@link #openCamera} will fail and throw a {@link CameraAccessException} or one or more of
675      * already opened {@link CameraDevice} will be disconnected and receive
676      * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. Which
677      * behavior will happen depends on the device implementation and can vary on different devices.
678      * Starting in API level 30, if the device does not support the combination of cameras being
679      * opened, it is guaranteed the {@link #openCamera} call will fail and none of existing
680      * {@link CameraDevice} will be disconnected.</p>
681      *
682      * <!--
683      * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
684      * on the returned CameraDevice instance will be queued up until the device startup has
685      * completed and the callback's {@link CameraDevice.StateCallback#onOpened onOpened} method is
686      * called. The pending operations are then processed in order.</p>
687      * -->
688      * <p>If the camera becomes disconnected during initialization
689      * after this function call returns,
690      * {@link CameraDevice.StateCallback#onDisconnected} with a
691      * {@link CameraDevice} in the disconnected state (and
692      * {@link CameraDevice.StateCallback#onOpened} will be skipped).</p>
693      *
694      * <p>If opening the camera device fails, then the device callback's
695      * {@link CameraDevice.StateCallback#onError onError} method will be called, and subsequent
696      * calls on the camera device will throw a {@link CameraAccessException}.</p>
697      *
698      * @param cameraId
699      *             The unique identifier of the camera device to open
700      * @param callback
701      *             The callback which is invoked once the camera is opened
702      * @param handler
703      *             The handler on which the callback should be invoked, or
704      *             {@code null} to use the current thread's {@link android.os.Looper looper}.
705      *
706      * @throws CameraAccessException if the camera is disabled by device policy,
707      * has been disconnected, is being used by a higher-priority camera API client, or the device
708      * has reached its maximal resource and cannot open this camera device.
709      *
710      * @throws IllegalArgumentException if cameraId or the callback was null,
711      * or the cameraId does not match any currently or previously available
712      * camera device returned by {@link #getCameraIdList}.
713      *
714      * @throws SecurityException if the application does not have permission to
715      * access the camera
716      *
717      * @see #getCameraIdList
718      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
719      */
720     @RequiresPermission(android.Manifest.permission.CAMERA)
openCamera(@onNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)721     public void openCamera(@NonNull String cameraId,
722             @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
723             throws CameraAccessException {
724 
725         openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
726                 USE_CALLING_UID);
727     }
728 
729     /**
730      * Open a connection to a camera with the given ID.
731      *
732      * <p>The behavior of this method matches that of
733      * {@link #openCamera(String, StateCallback, Handler)}, except that it uses
734      * {@link java.util.concurrent.Executor} as an argument instead of
735      * {@link android.os.Handler}.</p>
736      *
737      * @param cameraId
738      *             The unique identifier of the camera device to open
739      * @param executor
740      *             The executor which will be used when invoking the callback.
741      * @param callback
742      *             The callback which is invoked once the camera is opened
743      *
744      * @throws CameraAccessException if the camera is disabled by device policy,
745      * has been disconnected, or is being used by a higher-priority camera API client.
746      *
747      * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
748      * or the cameraId does not match any currently or previously available
749      * camera device.
750      *
751      * @throws SecurityException if the application does not have permission to
752      * access the camera
753      *
754      * @see #getCameraIdList
755      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
756      */
757     @RequiresPermission(android.Manifest.permission.CAMERA)
openCamera(@onNull String cameraId, @NonNull @CallbackExecutor Executor executor, @NonNull final CameraDevice.StateCallback callback)758     public void openCamera(@NonNull String cameraId,
759             @NonNull @CallbackExecutor Executor executor,
760             @NonNull final CameraDevice.StateCallback callback)
761             throws CameraAccessException {
762         if (executor == null) {
763             throw new IllegalArgumentException("executor was null");
764         }
765         openCameraForUid(cameraId, callback, executor, USE_CALLING_UID);
766     }
767 
768     /**
769      * Open a connection to a camera with the given ID. Also specify what oom score must be offset
770      * by cameraserver for this client. This api can be useful for system
771      * components which want to assume a lower priority (for camera arbitration) than other clients
772      * which it might contend for camera devices with. Increasing the oom score of a client reduces
773      * its priority when the camera framework manages camera arbitration.
774      * Considering typical use cases:
775      *
776      * 1) oom score(apps hosting activities visible to the user) - oom score(of a foreground app)
777      *    is approximately 100.
778      *
779      * 2) The oom score (process which hosts components which that are perceptible to the user /
780      *    native vendor camera clients) - oom (foreground app) is approximately 200.
781      *
782      * 3) The oom score (process which is cached hosting activities not visible) - oom (foreground
783      *    app) is approximately 999.
784      *
785      * <p>The behavior of this method matches that of
786      * {@link #openCamera(String, StateCallback, Handler)}, except that it uses
787      * {@link java.util.concurrent.Executor} as an argument instead of
788      * {@link android.os.Handler}.</p>
789      *
790      * @param cameraId
791      *             The unique identifier of the camera device to open
792      * @param executor
793      *             The executor which will be used when invoking the callback.
794      * @param callback
795      *             The callback which is invoked once the camera is opened
796      * @param oomScoreOffset
797      *             The value by which the oom score of this client must be offset by the camera
798      *             framework in order to assist it with camera arbitration. This value must be > 0.
799      *             A positive value lowers the priority of this camera client compared to what the
800      *             camera framework would have originally seen.
801      *
802      * @throws CameraAccessException if the camera is disabled by device policy,
803      * has been disconnected, or is being used by a higher-priority camera API client.
804      *
805      * @throws IllegalArgumentException if cameraId, the callback or the executor was null,
806      * or the cameraId does not match any currently or previously available
807      * camera device.
808      *
809      * @throws SecurityException if the application does not have permission to
810      * access the camera
811      *
812      * @see #getCameraIdList
813      * @see android.app.admin.DevicePolicyManager#setCameraDisabled
814      *
815      * @hide
816      */
817     @SystemApi
818     @TestApi
819     @RequiresPermission(allOf = {
820             android.Manifest.permission.SYSTEM_CAMERA,
821             android.Manifest.permission.CAMERA,
822     })
openCamera(@onNull String cameraId, int oomScoreOffset, @NonNull @CallbackExecutor Executor executor, @NonNull final CameraDevice.StateCallback callback)823     public void openCamera(@NonNull String cameraId, int oomScoreOffset,
824             @NonNull @CallbackExecutor Executor executor,
825             @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException {
826         if (executor == null) {
827             throw new IllegalArgumentException("executor was null");
828         }
829         if (oomScoreOffset < 0) {
830             throw new IllegalArgumentException(
831                     "oomScoreOffset < 0, cannot increase priority of camera client");
832         }
833         openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset);
834     }
835 
836     /**
837      * Open a connection to a camera with the given ID, on behalf of another application
838      * specified by clientUid. Also specify the minimum oom score and process state the application
839      * should have, as seen by the cameraserver.
840      *
841      * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
842      * the caller to specify the UID to use for permission/etc verification. This can only be
843      * done by services trusted by the camera subsystem to act on behalf of applications and
844      * to forward the real UID.</p>
845      *
846      * @param clientUid
847      *             The UID of the application on whose behalf the camera is being opened.
848      *             Must be USE_CALLING_UID unless the caller is a trusted service.
849      * @param oomScoreOffset
850      *             The minimum oom score that cameraservice must see for this client.
851      * @hide
852      */
openCameraForUid(@onNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, int clientUid, int oomScoreOffset)853     public void openCameraForUid(@NonNull String cameraId,
854             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
855             int clientUid, int oomScoreOffset) throws CameraAccessException {
856 
857         if (cameraId == null) {
858             throw new IllegalArgumentException("cameraId was null");
859         } else if (callback == null) {
860             throw new IllegalArgumentException("callback was null");
861         }
862         if (CameraManagerGlobal.sCameraServiceDisabled) {
863             throw new IllegalArgumentException("No cameras available on device");
864         }
865 
866         openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
867     }
868 
869     /**
870      * Open a connection to a camera with the given ID, on behalf of another application
871      * specified by clientUid.
872      *
873      * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
874      * the caller to specify the UID to use for permission/etc verification. This can only be
875      * done by services trusted by the camera subsystem to act on behalf of applications and
876      * to forward the real UID.</p>
877      *
878      * @param clientUid
879      *             The UID of the application on whose behalf the camera is being opened.
880      *             Must be USE_CALLING_UID unless the caller is a trusted service.
881      *
882      * @hide
883      */
openCameraForUid(@onNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, int clientUid)884     public void openCameraForUid(@NonNull String cameraId,
885             @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
886             int clientUid) throws CameraAccessException {
887             openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0);
888     }
889 
890     /**
891      * Set the flash unit's torch mode of the camera of the given ID without opening the camera
892      * device.
893      *
894      * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use
895      * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit.
896      * Note that even if a camera device has a flash unit, turning on the torch mode may fail
897      * if the camera device or other camera resources needed to turn on the torch mode are in use.
898      * </p>
899      *
900      * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully,
901      * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked.
902      * However, even if turning on the torch mode is successful, the application does not have the
903      * exclusive ownership of the flash unit or the camera device. The torch mode will be turned
904      * off and becomes unavailable when the camera device that the flash unit belongs to becomes
905      * unavailable or when other camera resources to keep the torch on become unavailable (
906      * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also,
907      * other applications are free to call {@link #setTorchMode} to turn off the torch mode (
908      * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked). If the latest
909      * application that turned on the torch mode exits, the torch mode will be turned off.
910      *
911      * @param cameraId
912      *             The unique identifier of the camera device that the flash unit belongs to.
913      * @param enabled
914      *             The desired state of the torch mode for the target camera device. Set to
915      *             {@code true} to turn on the torch mode. Set to {@code false} to turn off the
916      *             torch mode.
917      *
918      * @throws CameraAccessException if it failed to access the flash unit.
919      *             {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
920      *             is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
921      *             other camera resources needed to turn on the torch mode are in use.
922      *             {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera
923      *             service is not available.
924      *
925      * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
926      *             or previously available camera device, or the camera device doesn't have a
927      *             flash unit.
928      */
setTorchMode(@onNull String cameraId, boolean enabled)929     public void setTorchMode(@NonNull String cameraId, boolean enabled)
930             throws CameraAccessException {
931         if (CameraManagerGlobal.sCameraServiceDisabled) {
932             throw new IllegalArgumentException("No cameras available on device");
933         }
934         CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
935     }
936 
937     /**
938      * A callback for camera devices becoming available or unavailable to open.
939      *
940      * <p>Cameras become available when they are no longer in use, or when a new
941      * removable camera is connected. They become unavailable when some
942      * application or service starts using a camera, or when a removable camera
943      * is disconnected.</p>
944      *
945      * <p>Extend this callback and pass an instance of the subclass to
946      * {@link CameraManager#registerAvailabilityCallback} to be notified of such availability
947      * changes.</p>
948      *
949      * @see #registerAvailabilityCallback
950      */
951     public static abstract class AvailabilityCallback {
952 
953         /**
954          * A new camera has become available to use.
955          *
956          * <p>The default implementation of this method does nothing.</p>
957          *
958          * @param cameraId The unique identifier of the new camera.
959          */
onCameraAvailable(@onNull String cameraId)960         public void onCameraAvailable(@NonNull String cameraId) {
961             // default empty implementation
962         }
963 
964         /**
965          * A previously-available camera has become unavailable for use.
966          *
967          * <p>If an application had an active CameraDevice instance for the
968          * now-disconnected camera, that application will receive a
969          * {@link CameraDevice.StateCallback#onDisconnected disconnection error}.</p>
970          *
971          * <p>The default implementation of this method does nothing.</p>
972          *
973          * @param cameraId The unique identifier of the disconnected camera.
974          */
onCameraUnavailable(@onNull String cameraId)975         public void onCameraUnavailable(@NonNull String cameraId) {
976             // default empty implementation
977         }
978 
979         /**
980          * Called whenever camera access priorities change.
981          *
982          * <p>Notification that camera access priorities have changed and the camera may
983          * now be openable. An application that was previously denied camera access due to
984          * a higher-priority user already using the camera, or that was disconnected from an
985          * active camera session due to a higher-priority user trying to open the camera,
986          * should try to open the camera again if it still wants to use it.  Note that
987          * multiple applications may receive this callback at the same time, and only one of
988          * them will succeed in opening the camera in practice, depending on exact access
989          * priority levels and timing. This method is useful in cases where multiple
990          * applications may be in the resumed state at the same time, and the user switches
991          * focus between them, or if the current camera-using application moves between
992          * full-screen and Picture-in-Picture (PiP) states. In such cases, the camera
993          * available/unavailable callbacks will not be invoked, but another application may
994          * now have higher priority for camera access than the current camera-using
995          * application.</p>
996          *
997          * <p>The default implementation of this method does nothing.</p>
998          *
999          */
onCameraAccessPrioritiesChanged()1000         public void onCameraAccessPrioritiesChanged() {
1001             // default empty implementation
1002         }
1003 
1004         /**
1005          * A physical camera has become available for use again.
1006          *
1007          * <p>By default, all of the physical cameras of a logical multi-camera are
1008          * available, so {@link #onPhysicalCameraAvailable} is not called for any of the physical
1009          * cameras of a logical multi-camera, when {@link #onCameraAvailable} for the logical
1010          * multi-camera is invoked. However, if some specific physical cameras are unavailable
1011          * to begin with, {@link #onPhysicalCameraUnavailable} may be invoked after
1012          * {@link #onCameraAvailable}.</p>
1013          *
1014          * <p>The default implementation of this method does nothing.</p>
1015          *
1016          * @param cameraId The unique identifier of the logical multi-camera.
1017          * @param physicalCameraId The unique identifier of the physical camera.
1018          *
1019          * @see #onCameraAvailable
1020          * @see #onPhysicalCameraUnavailable
1021          */
onPhysicalCameraAvailable(@onNull String cameraId, @NonNull String physicalCameraId)1022         public void onPhysicalCameraAvailable(@NonNull String cameraId,
1023                 @NonNull String physicalCameraId) {
1024             // default empty implementation
1025         }
1026 
1027         /**
1028          * A previously-available physical camera has become unavailable for use.
1029          *
1030          * <p>By default, all of the physical cameras of a logical multi-camera are
1031          * available, so {@link #onPhysicalCameraAvailable} is not called for any of the physical
1032          * cameras of a logical multi-camera, when {@link #onCameraAvailable} for the logical
1033          * multi-camera is invoked. If some specific physical cameras are unavailable
1034          * to begin with, {@link #onPhysicalCameraUnavailable} may be invoked after
1035          * {@link #onCameraAvailable}.</p>
1036          *
1037          * <p>The default implementation of this method does nothing.</p>
1038          *
1039          * @param cameraId The unique identifier of the logical multi-camera.
1040          * @param physicalCameraId The unique identifier of the physical camera.
1041          *
1042          * @see #onCameraAvailable
1043          * @see #onPhysicalCameraAvailable
1044          */
onPhysicalCameraUnavailable(@onNull String cameraId, @NonNull String physicalCameraId)1045         public void onPhysicalCameraUnavailable(@NonNull String cameraId,
1046                 @NonNull String physicalCameraId) {
1047             // default empty implementation
1048         }
1049 
1050         /**
1051          * A camera device has been opened by an application.
1052          *
1053          * <p>The default implementation of this method does nothing.</p>
1054          *    android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER is required to receive this
1055          *    callback
1056          * @param cameraId The unique identifier of the camera opened.
1057          * @param packageId The package Id of the application opening the camera.
1058          *
1059          * @see #onCameraClosed
1060          * @hide
1061          */
1062         @SystemApi
1063         @TestApi
1064         @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER)
onCameraOpened(@onNull String cameraId, @NonNull String packageId)1065         public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
1066             // default empty implementation
1067         }
1068 
1069         /**
1070          * A previously-opened camera has been closed.
1071          *
1072          * <p>The default implementation of this method does nothing.</p>
1073          *    android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER is required to receive this
1074          *    callback.
1075          * @param cameraId The unique identifier of the closed camera.
1076          * @hide
1077          */
1078         @SystemApi
1079         @TestApi
1080         @RequiresPermission(android.Manifest.permission.CAMERA_OPEN_CLOSE_LISTENER)
onCameraClosed(@onNull String cameraId)1081         public void onCameraClosed(@NonNull String cameraId) {
1082             // default empty implementation
1083         }
1084     }
1085 
1086     /**
1087      * A callback for camera flash torch modes becoming unavailable, disabled, or enabled.
1088      *
1089      * <p>The torch mode becomes unavailable when the camera device it belongs to becomes
1090      * unavailable or other camera resources it needs become busy due to other higher priority
1091      * camera activities. The torch mode becomes disabled when it was turned off or when the camera
1092      * device it belongs to is no longer in use and other camera resources it needs are no longer
1093      * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to
1094      * turn off the camera's torch mode, or when an application turns on another camera's torch mode
1095      * if keeping multiple torch modes on simultaneously is not supported. The torch mode becomes
1096      * enabled when it is turned on via {@link #setTorchMode}.</p>
1097      *
1098      * <p>The torch mode is available to set via {@link #setTorchMode} only when it's in a disabled
1099      * or enabled state.</p>
1100      *
1101      * <p>Extend this callback and pass an instance of the subclass to
1102      * {@link CameraManager#registerTorchCallback} to be notified of such status changes.
1103      * </p>
1104      *
1105      * @see #registerTorchCallback
1106      */
1107     public static abstract class TorchCallback {
1108         /**
1109          * A camera's torch mode has become unavailable to set via {@link #setTorchMode}.
1110          *
1111          * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be
1112          * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is
1113          * invoked. {@link #setTorchMode} will fail until the torch mode has entered a disabled or
1114          * enabled state again.</p>
1115          *
1116          * <p>The default implementation of this method does nothing.</p>
1117          *
1118          * @param cameraId The unique identifier of the camera whose torch mode has become
1119          *                 unavailable.
1120          */
onTorchModeUnavailable(@onNull String cameraId)1121         public void onTorchModeUnavailable(@NonNull String cameraId) {
1122             // default empty implementation
1123         }
1124 
1125         /**
1126          * A camera's torch mode has become enabled or disabled and can be changed via
1127          * {@link #setTorchMode}.
1128          *
1129          * <p>The default implementation of this method does nothing.</p>
1130          *
1131          * @param cameraId The unique identifier of the camera whose torch mode has been changed.
1132          *
1133          * @param enabled The state that the torch mode of the camera has been changed to.
1134          *                {@code true} when the torch mode has become on and available to be turned
1135          *                off. {@code false} when the torch mode has becomes off and available to
1136          *                be turned on.
1137          */
onTorchModeChanged(@onNull String cameraId, boolean enabled)1138         public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
1139             // default empty implementation
1140         }
1141     }
1142 
1143     /**
1144      * Convert ServiceSpecificExceptions and Binder RemoteExceptions from camera binder interfaces
1145      * into the correct public exceptions.
1146      *
1147      * @hide
1148      */
throwAsPublicException(Throwable t)1149     public static void throwAsPublicException(Throwable t) throws CameraAccessException {
1150         if (t instanceof ServiceSpecificException) {
1151             ServiceSpecificException e = (ServiceSpecificException) t;
1152             int reason = CameraAccessException.CAMERA_ERROR;
1153             switch(e.errorCode) {
1154                 case ICameraService.ERROR_DISCONNECTED:
1155                     reason = CameraAccessException.CAMERA_DISCONNECTED;
1156                     break;
1157                 case ICameraService.ERROR_DISABLED:
1158                     reason = CameraAccessException.CAMERA_DISABLED;
1159                     break;
1160                 case ICameraService.ERROR_CAMERA_IN_USE:
1161                     reason = CameraAccessException.CAMERA_IN_USE;
1162                     break;
1163                 case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
1164                     reason = CameraAccessException.MAX_CAMERAS_IN_USE;
1165                     break;
1166                 case ICameraService.ERROR_DEPRECATED_HAL:
1167                     reason = CameraAccessException.CAMERA_DEPRECATED_HAL;
1168                     break;
1169                 case ICameraService.ERROR_ILLEGAL_ARGUMENT:
1170                 case ICameraService.ERROR_ALREADY_EXISTS:
1171                     throw new IllegalArgumentException(e.getMessage(), e);
1172                 case ICameraService.ERROR_PERMISSION_DENIED:
1173                     throw new SecurityException(e.getMessage(), e);
1174                 case ICameraService.ERROR_TIMED_OUT:
1175                 case ICameraService.ERROR_INVALID_OPERATION:
1176                 default:
1177                     reason = CameraAccessException.CAMERA_ERROR;
1178             }
1179             throw new CameraAccessException(reason, e.getMessage(), e);
1180         } else if (t instanceof DeadObjectException) {
1181             throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
1182                     "Camera service has died unexpectedly",
1183                     t);
1184         } else if (t instanceof RemoteException) {
1185             throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
1186                     " which should never happen.", t);
1187         } else if (t instanceof RuntimeException) {
1188             RuntimeException e = (RuntimeException) t;
1189             throw e;
1190         }
1191     }
1192 
1193     /**
1194      * Queries the camera service if a cameraId is a hidden physical camera that belongs to a
1195      * logical camera device.
1196      *
1197      * A hidden physical camera is a camera that cannot be opened by the application. But it
1198      * can be used as part of a logical camera.
1199      *
1200      * @param cameraId a non-{@code null} camera identifier
1201      * @return {@code true} if cameraId is a hidden physical camera device
1202      *
1203      * @hide
1204      */
isHiddenPhysicalCamera(String cameraId)1205     public static boolean isHiddenPhysicalCamera(String cameraId) {
1206         try {
1207             ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
1208             // If no camera service, no support
1209             if (cameraService == null) return false;
1210 
1211             return cameraService.isHiddenPhysicalCamera(cameraId);
1212         } catch (RemoteException e) {
1213             // Camera service is now down, no support for any API level
1214         }
1215         return false;
1216     }
1217 
1218     /**
1219      * Inject the external camera to replace the internal camera session.
1220      *
1221      * <p>If injecting the external camera device fails, then the injection callback's
1222      * {@link CameraInjectionSession.InjectionStatusCallback#onInjectionError
1223      * onInjectionError} method will be called.</p>
1224      *
1225      * @param packageName   It scopes the injection to a particular app.
1226      * @param internalCamId The id of one of the physical or logical cameras on the phone.
1227      * @param externalCamId The id of one of the remote cameras that are provided by the dynamic
1228      *                      camera HAL.
1229      * @param executor      The executor which will be used when invoking the callback.
1230      * @param callback      The callback which is invoked once the external camera is injected.
1231      *
1232      * @throws CameraAccessException    If the camera device has been disconnected.
1233      *                                  {@link CameraAccessException#CAMERA_DISCONNECTED} will be
1234      *                                  thrown if camera service is not available.
1235      * @throws SecurityException        If the specific application that can cast to external
1236      *                                  devices does not have permission to inject the external
1237      *                                  camera.
1238      * @throws IllegalArgumentException If cameraId doesn't match any currently or previously
1239      *                                  available camera device or some camera functions might not
1240      *                                  work properly or the injection camera runs into a fatal
1241      *                                  error.
1242      * @hide
1243      */
1244     @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
injectCamera(@onNull String packageName, @NonNull String internalCamId, @NonNull String externalCamId, @NonNull @CallbackExecutor Executor executor, @NonNull CameraInjectionSession.InjectionStatusCallback callback)1245     public void injectCamera(@NonNull String packageName, @NonNull String internalCamId,
1246             @NonNull String externalCamId, @NonNull @CallbackExecutor Executor executor,
1247             @NonNull CameraInjectionSession.InjectionStatusCallback callback)
1248             throws CameraAccessException, SecurityException,
1249             IllegalArgumentException {
1250         if (CameraManagerGlobal.sCameraServiceDisabled) {
1251             throw new IllegalArgumentException("No cameras available on device");
1252         }
1253         ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
1254         if (cameraService == null) {
1255             throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
1256                     "Camera service is currently unavailable");
1257         }
1258         synchronized (mLock) {
1259             try {
1260                 CameraInjectionSessionImpl injectionSessionImpl =
1261                         new CameraInjectionSessionImpl(callback, executor);
1262                 ICameraInjectionCallback cameraInjectionCallback =
1263                         injectionSessionImpl.getCallback();
1264                 ICameraInjectionSession injectionSession = cameraService.injectCamera(packageName,
1265                         internalCamId, externalCamId, cameraInjectionCallback);
1266                 injectionSessionImpl.setRemoteInjectionSession(injectionSession);
1267             } catch (ServiceSpecificException e) {
1268                 throwAsPublicException(e);
1269             } catch (RemoteException e) {
1270                 // Camera service died - act as if it's a CAMERA_DISCONNECTED case
1271                 ServiceSpecificException sse = new ServiceSpecificException(
1272                         ICameraService.ERROR_DISCONNECTED,
1273                         "Camera service is currently unavailable");
1274                 throwAsPublicException(sse);
1275             }
1276         }
1277     }
1278 
1279     /**
1280      * A per-process global camera manager instance, to retain a connection to the camera service,
1281      * and to distribute camera availability notices to API-registered callbacks
1282      */
1283     private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
1284             implements IBinder.DeathRecipient {
1285 
1286         private static final String TAG = "CameraManagerGlobal";
1287         private final boolean DEBUG = false;
1288 
1289         private final int CAMERA_SERVICE_RECONNECT_DELAY_MS = 1000;
1290 
1291         // Singleton instance
1292         private static final CameraManagerGlobal gCameraManager =
1293             new CameraManagerGlobal();
1294 
1295         /**
1296          * This must match the ICameraService definition
1297          */
1298         private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
1299 
1300         private final ScheduledExecutorService mScheduler = Executors.newScheduledThreadPool(1);
1301         // Camera ID -> Status map
1302         private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
1303         // Camera ID -> (physical camera ID -> Status map)
1304         private final ArrayMap<String, ArrayList<String>> mUnavailablePhysicalDevices =
1305                 new ArrayMap<String, ArrayList<String>>();
1306 
1307         private final Set<Set<String>> mConcurrentCameraIdCombinations =
1308                 new ArraySet<Set<String>>();
1309 
1310         // Registered availablility callbacks and their executors
1311         private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap =
1312             new ArrayMap<AvailabilityCallback, Executor>();
1313 
1314         // torch client binder to set the torch mode with.
1315         private Binder mTorchClientBinder = new Binder();
1316 
1317         // Camera ID -> Torch status map
1318         private final ArrayMap<String, Integer> mTorchStatus = new ArrayMap<String, Integer>();
1319 
1320         // Registered torch callbacks and their executors
1321         private final ArrayMap<TorchCallback, Executor> mTorchCallbackMap =
1322                 new ArrayMap<TorchCallback, Executor>();
1323 
1324         private final Object mLock = new Object();
1325 
1326         // Access only through getCameraService to deal with binder death
1327         private ICameraService mCameraService;
1328 
1329         // Singleton, don't allow construction
CameraManagerGlobal()1330         private CameraManagerGlobal() {
1331         }
1332 
1333         public static final boolean sCameraServiceDisabled =
1334                 SystemProperties.getBoolean("config.disable_cameraservice", false);
1335 
get()1336         public static CameraManagerGlobal get() {
1337             return gCameraManager;
1338         }
1339 
1340         @Override
asBinder()1341         public IBinder asBinder() {
1342             return this;
1343         }
1344 
1345         /**
1346          * Return a best-effort ICameraService.
1347          *
1348          * <p>This will be null if the camera service is not currently available. If the camera
1349          * service has died since the last use of the camera service, will try to reconnect to the
1350          * service.</p>
1351          */
getCameraService()1352         public ICameraService getCameraService() {
1353             synchronized(mLock) {
1354                 connectCameraServiceLocked();
1355                 if (mCameraService == null && !sCameraServiceDisabled) {
1356                     Log.e(TAG, "Camera service is unavailable");
1357                 }
1358                 return mCameraService;
1359             }
1360         }
1361 
1362         /**
1363          * Connect to the camera service if it's available, and set up listeners.
1364          * If the service is already connected, do nothing.
1365          *
1366          * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
1367          */
connectCameraServiceLocked()1368         private void connectCameraServiceLocked() {
1369             // Only reconnect if necessary
1370             if (mCameraService != null || sCameraServiceDisabled) return;
1371 
1372             Log.i(TAG, "Connecting to camera service");
1373 
1374             IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
1375             if (cameraServiceBinder == null) {
1376                 // Camera service is now down, leave mCameraService as null
1377                 return;
1378             }
1379             try {
1380                 cameraServiceBinder.linkToDeath(this, /*flags*/ 0);
1381             } catch (RemoteException e) {
1382                 // Camera service is now down, leave mCameraService as null
1383                 return;
1384             }
1385 
1386             ICameraService cameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
1387 
1388             try {
1389                 CameraMetadataNative.setupGlobalVendorTagDescriptor();
1390             } catch (ServiceSpecificException e) {
1391                 handleRecoverableSetupErrors(e);
1392             }
1393 
1394             try {
1395                 CameraStatus[] cameraStatuses = cameraService.addListener(this);
1396                 for (CameraStatus c : cameraStatuses) {
1397                     onStatusChangedLocked(c.status, c.cameraId);
1398 
1399                     if (c.unavailablePhysicalCameras != null) {
1400                         for (String unavailPhysicalCamera : c.unavailablePhysicalCameras) {
1401                             onPhysicalCameraStatusChangedLocked(
1402                                     ICameraServiceListener.STATUS_NOT_PRESENT,
1403                                     c.cameraId, unavailPhysicalCamera);
1404                         }
1405                     }
1406                 }
1407                 mCameraService = cameraService;
1408             } catch(ServiceSpecificException e) {
1409                 // Unexpected failure
1410                 throw new IllegalStateException("Failed to register a camera service listener", e);
1411             } catch (RemoteException e) {
1412                 // Camera service is now down, leave mCameraService as null
1413             }
1414 
1415             try {
1416                 ConcurrentCameraIdCombination[] cameraIdCombinations =
1417                         cameraService.getConcurrentCameraIds();
1418                 for (ConcurrentCameraIdCombination comb : cameraIdCombinations) {
1419                     mConcurrentCameraIdCombinations.add(comb.getConcurrentCameraIdCombination());
1420                 }
1421             } catch (ServiceSpecificException e) {
1422                 // Unexpected failure
1423                 throw new IllegalStateException("Failed to get concurrent camera id combinations",
1424                         e);
1425             } catch (RemoteException e) {
1426                 // Camera service died in all probability
1427             }
1428         }
1429 
extractCameraIdListLocked()1430         private String[] extractCameraIdListLocked() {
1431             String[] cameraIds = null;
1432             int idCount = 0;
1433             for (int i = 0; i < mDeviceStatus.size(); i++) {
1434                 int status = mDeviceStatus.valueAt(i);
1435                 if (status == ICameraServiceListener.STATUS_NOT_PRESENT
1436                         || status == ICameraServiceListener.STATUS_ENUMERATING) continue;
1437                 idCount++;
1438             }
1439             cameraIds = new String[idCount];
1440             idCount = 0;
1441             for (int i = 0; i < mDeviceStatus.size(); i++) {
1442                 int status = mDeviceStatus.valueAt(i);
1443                 if (status == ICameraServiceListener.STATUS_NOT_PRESENT
1444                         || status == ICameraServiceListener.STATUS_ENUMERATING) continue;
1445                 cameraIds[idCount] = mDeviceStatus.keyAt(i);
1446                 idCount++;
1447             }
1448             return cameraIds;
1449         }
1450 
extractConcurrentCameraIdListLocked()1451         private Set<Set<String>> extractConcurrentCameraIdListLocked() {
1452             Set<Set<String>> concurrentCameraIds = new ArraySet<Set<String>>();
1453             for (Set<String> cameraIds : mConcurrentCameraIdCombinations) {
1454                 Set<String> extractedCameraIds = new ArraySet<String>();
1455                 for (String cameraId : cameraIds) {
1456                     // if the camera id status is NOT_PRESENT or ENUMERATING; skip the device.
1457                     // TODO: Would a device status NOT_PRESENT ever be in the map ? it gets removed
1458                     // in the callback anyway.
1459                     Integer status = mDeviceStatus.get(cameraId);
1460                     if (status == null) {
1461                         // camera id not present
1462                         continue;
1463                     }
1464                     if (status == ICameraServiceListener.STATUS_ENUMERATING
1465                             || status == ICameraServiceListener.STATUS_NOT_PRESENT) {
1466                         continue;
1467                     }
1468                     extractedCameraIds.add(cameraId);
1469                 }
1470                 concurrentCameraIds.add(extractedCameraIds);
1471             }
1472             return concurrentCameraIds;
1473         }
1474 
sortCameraIds(String[] cameraIds)1475         private static void sortCameraIds(String[] cameraIds) {
1476             // The sort logic must match the logic in
1477             // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds
1478             Arrays.sort(cameraIds, new Comparator<String>() {
1479                     @Override
1480                     public int compare(String s1, String s2) {
1481                         int s1Int = 0, s2Int = 0;
1482                         try {
1483                             s1Int = Integer.parseInt(s1);
1484                         } catch (NumberFormatException e) {
1485                             s1Int = -1;
1486                         }
1487 
1488                         try {
1489                             s2Int = Integer.parseInt(s2);
1490                         } catch (NumberFormatException e) {
1491                             s2Int = -1;
1492                         }
1493 
1494                         // Uint device IDs first
1495                         if (s1Int >= 0 && s2Int >= 0) {
1496                             return s1Int - s2Int;
1497                         } else if (s1Int >= 0) {
1498                             return -1;
1499                         } else if (s2Int >= 0) {
1500                             return 1;
1501                         } else {
1502                             // Simple string compare if both id are not uint
1503                             return s1.compareTo(s2);
1504                         }
1505                     }});
1506 
1507         }
1508 
cameraStatusesContains(CameraStatus[] cameraStatuses, String id)1509         public static boolean cameraStatusesContains(CameraStatus[] cameraStatuses, String id) {
1510             for (CameraStatus c : cameraStatuses) {
1511                 if (c.cameraId.equals(id)) {
1512                     return true;
1513                 }
1514             }
1515             return false;
1516         }
1517 
getCameraIdListNoLazy()1518         public String[] getCameraIdListNoLazy() {
1519             if (sCameraServiceDisabled) {
1520                 return new String[] {};
1521             }
1522 
1523             CameraStatus[] cameraStatuses;
1524             ICameraServiceListener.Stub testListener = new ICameraServiceListener.Stub() {
1525                 @Override
1526                 public void onStatusChanged(int status, String id) throws RemoteException {
1527                 }
1528                 @Override
1529                 public void onPhysicalCameraStatusChanged(int status,
1530                         String id, String physicalId) throws RemoteException {
1531                 }
1532                 @Override
1533                 public void onTorchStatusChanged(int status, String id) throws RemoteException {
1534                 }
1535                 @Override
1536                 public void onCameraAccessPrioritiesChanged() {
1537                 }
1538                 @Override
1539                 public void onCameraOpened(String id, String clientPackageId) {
1540                 }
1541                 @Override
1542                 public void onCameraClosed(String id) {
1543                 }};
1544 
1545             String[] cameraIds = null;
1546             synchronized (mLock) {
1547                 connectCameraServiceLocked();
1548                 try {
1549                     // The purpose of the addListener, removeListener pair here is to get a fresh
1550                     // list of camera ids from cameraserver. We do this since for in test processes,
1551                     // changes can happen w.r.t non-changeable permissions (eg: SYSTEM_CAMERA
1552                     // permissions can be effectively changed by calling
1553                     // adopt(drop)ShellPermissionIdentity()).
1554                     // Camera devices, which have their discovery affected by these permission
1555                     // changes, will not have clients get callbacks informing them about these
1556                     // devices going offline (in real world scenarios, these permissions aren't
1557                     // changeable). Future calls to getCameraIdList() will reflect the changes in
1558                     // the camera id list after getCameraIdListNoLazy() is called.
1559                     // We need to remove the torch ids which may have been associated with the
1560                     // devices removed as well. This is the same situation.
1561                     cameraStatuses = mCameraService.addListener(testListener);
1562                     mCameraService.removeListener(testListener);
1563                     for (CameraStatus c : cameraStatuses) {
1564                         onStatusChangedLocked(c.status, c.cameraId);
1565                     }
1566                     Set<String> deviceCameraIds = mDeviceStatus.keySet();
1567                     ArrayList<String> deviceIdsToRemove = new ArrayList<String>();
1568                     for (String deviceCameraId : deviceCameraIds) {
1569                         // Its possible that a device id was removed without a callback notifying
1570                         // us. This may happen in case a process 'drops' system camera permissions
1571                         // (even though the permission isn't a changeable one, tests may call
1572                         // adoptShellPermissionIdentity() and then dropShellPermissionIdentity().
1573                         if (!cameraStatusesContains(cameraStatuses, deviceCameraId)) {
1574                             deviceIdsToRemove.add(deviceCameraId);
1575                         }
1576                     }
1577                     for (String id : deviceIdsToRemove) {
1578                         onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, id);
1579                         mTorchStatus.remove(id);
1580                     }
1581                 } catch (ServiceSpecificException e) {
1582                     // Unexpected failure
1583                     throw new IllegalStateException("Failed to register a camera service listener",
1584                             e);
1585                 } catch (RemoteException e) {
1586                     // Camera service is now down, leave mCameraService as null
1587                 }
1588                 cameraIds = extractCameraIdListLocked();
1589             }
1590             sortCameraIds(cameraIds);
1591             return cameraIds;
1592         }
1593 
1594         /**
1595          * Get a list of all camera IDs that are at least PRESENT; ignore devices that are
1596          * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone.
1597          */
getCameraIdList()1598         public String[] getCameraIdList() {
1599             String[] cameraIds = null;
1600             synchronized (mLock) {
1601                 // Try to make sure we have an up-to-date list of camera devices.
1602                 connectCameraServiceLocked();
1603                 cameraIds = extractCameraIdListLocked();
1604             }
1605             sortCameraIds(cameraIds);
1606             return cameraIds;
1607         }
1608 
getConcurrentCameraIds()1609         public @NonNull Set<Set<String>> getConcurrentCameraIds() {
1610             Set<Set<String>> concurrentStreamingCameraIds = null;
1611             synchronized (mLock) {
1612                 // Try to make sure we have an up-to-date list of concurrent camera devices.
1613                 connectCameraServiceLocked();
1614                 concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked();
1615             }
1616             // TODO: Some sort of sorting  ?
1617             return concurrentStreamingCameraIds;
1618         }
1619 
isConcurrentSessionConfigurationSupported( @onNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations, int targetSdkVersion)1620         public boolean isConcurrentSessionConfigurationSupported(
1621                 @NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations,
1622                 int targetSdkVersion) throws CameraAccessException {
1623 
1624             if (cameraIdsAndSessionConfigurations == null) {
1625                 throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null");
1626             }
1627 
1628             int size = cameraIdsAndSessionConfigurations.size();
1629             if (size == 0) {
1630                 throw new IllegalArgumentException("camera id and session combination is empty");
1631             }
1632 
1633             synchronized (mLock) {
1634                 // Go through all the elements and check if the camera ids are valid at least /
1635                 // belong to one of the combinations returned by getConcurrentCameraIds()
1636                 boolean subsetFound = false;
1637                 for (Set<String> combination : mConcurrentCameraIdCombinations) {
1638                     if (combination.containsAll(cameraIdsAndSessionConfigurations.keySet())) {
1639                         subsetFound = true;
1640                     }
1641                 }
1642                 if (!subsetFound) {
1643                     Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of"
1644                             + "camera ids not returned by getConcurrentCameraIds");
1645                     return false;
1646                 }
1647                 CameraIdAndSessionConfiguration [] cameraIdsAndConfigs =
1648                         new CameraIdAndSessionConfiguration[size];
1649                 int i = 0;
1650                 for (Map.Entry<String, SessionConfiguration> pair :
1651                         cameraIdsAndSessionConfigurations.entrySet()) {
1652                     cameraIdsAndConfigs[i] =
1653                             new CameraIdAndSessionConfiguration(pair.getKey(), pair.getValue());
1654                     i++;
1655                 }
1656                 try {
1657                     return mCameraService.isConcurrentSessionConfigurationSupported(
1658                             cameraIdsAndConfigs, targetSdkVersion);
1659                 } catch (ServiceSpecificException e) {
1660                    throwAsPublicException(e);
1661                 } catch (RemoteException e) {
1662                   // Camera service died - act as if the camera was disconnected
1663                   throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
1664                           "Camera service is currently unavailable", e);
1665                 }
1666             }
1667 
1668             return false;
1669         }
1670 
1671       /**
1672         * Helper function to find out if a camera id is in the set of combinations returned by
1673         * getConcurrentCameraIds()
1674         * @param cameraId the unique identifier of the camera device to query
1675         * @return Whether the camera device was found in the set of combinations returned by
1676         *         getConcurrentCameraIds
1677         */
cameraIdHasConcurrentStreamsLocked(String cameraId)1678         public boolean cameraIdHasConcurrentStreamsLocked(String cameraId) {
1679             if (!mDeviceStatus.containsKey(cameraId)) {
1680                 // physical camera ids aren't advertised in concurrent camera id combinations.
1681                 if (DEBUG) {
1682                     Log.v(TAG, " physical camera id " + cameraId + " is hidden." +
1683                             " Available logical camera ids : " + mDeviceStatus.toString());
1684                 }
1685                 return false;
1686             }
1687             for (Set<String> comb : mConcurrentCameraIdCombinations) {
1688                 if (comb.contains(cameraId)) {
1689                     return true;
1690                 }
1691             }
1692             return false;
1693         }
1694 
setTorchMode(String cameraId, boolean enabled)1695         public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
1696             synchronized(mLock) {
1697 
1698                 if (cameraId == null) {
1699                     throw new IllegalArgumentException("cameraId was null");
1700                 }
1701 
1702                 ICameraService cameraService = getCameraService();
1703                 if (cameraService == null) {
1704                     throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
1705                         "Camera service is currently unavailable");
1706                 }
1707 
1708                 try {
1709                     cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
1710                 } catch(ServiceSpecificException e) {
1711                     throwAsPublicException(e);
1712                 } catch (RemoteException e) {
1713                     throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
1714                             "Camera service is currently unavailable");
1715                 }
1716             }
1717         }
1718 
handleRecoverableSetupErrors(ServiceSpecificException e)1719         private void handleRecoverableSetupErrors(ServiceSpecificException e) {
1720             switch (e.errorCode) {
1721                 case ICameraService.ERROR_DISCONNECTED:
1722                     Log.w(TAG, e.getMessage());
1723                     break;
1724                 default:
1725                     throw new IllegalStateException(e);
1726             }
1727         }
1728 
isAvailable(int status)1729         private boolean isAvailable(int status) {
1730             switch (status) {
1731                 case ICameraServiceListener.STATUS_PRESENT:
1732                     return true;
1733                 default:
1734                     return false;
1735             }
1736         }
1737 
validStatus(int status)1738         private boolean validStatus(int status) {
1739             switch (status) {
1740                 case ICameraServiceListener.STATUS_NOT_PRESENT:
1741                 case ICameraServiceListener.STATUS_PRESENT:
1742                 case ICameraServiceListener.STATUS_ENUMERATING:
1743                 case ICameraServiceListener.STATUS_NOT_AVAILABLE:
1744                     return true;
1745                 default:
1746                     return false;
1747             }
1748         }
1749 
validTorchStatus(int status)1750         private boolean validTorchStatus(int status) {
1751             switch (status) {
1752                 case ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE:
1753                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
1754                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF:
1755                     return true;
1756                 default:
1757                     return false;
1758             }
1759         }
1760 
postSingleAccessPriorityChangeUpdate(final AvailabilityCallback callback, final Executor executor)1761         private void postSingleAccessPriorityChangeUpdate(final AvailabilityCallback callback,
1762                 final Executor executor) {
1763             final long ident = Binder.clearCallingIdentity();
1764             try {
1765                 executor.execute(
1766                     new Runnable() {
1767                         @Override
1768                         public void run() {
1769                             callback.onCameraAccessPrioritiesChanged();
1770                         }
1771                     });
1772             } finally {
1773                 Binder.restoreCallingIdentity(ident);
1774             }
1775         }
1776 
postSingleCameraOpenedUpdate(final AvailabilityCallback callback, final Executor executor, final String id, final String packageId)1777         private void postSingleCameraOpenedUpdate(final AvailabilityCallback callback,
1778                 final Executor executor, final String id, final String packageId) {
1779             final long ident = Binder.clearCallingIdentity();
1780             try {
1781                 executor.execute(
1782                     new Runnable() {
1783                         @Override
1784                         public void run() {
1785                             callback.onCameraOpened(id, packageId);
1786                         }
1787                     });
1788             } finally {
1789                 Binder.restoreCallingIdentity(ident);
1790             }
1791         }
1792 
postSingleCameraClosedUpdate(final AvailabilityCallback callback, final Executor executor, final String id)1793         private void postSingleCameraClosedUpdate(final AvailabilityCallback callback,
1794                 final Executor executor, final String id) {
1795             final long ident = Binder.clearCallingIdentity();
1796             try {
1797                 executor.execute(
1798                     new Runnable() {
1799                         @Override
1800                         public void run() {
1801                             callback.onCameraClosed(id);
1802                         }
1803                     });
1804             } finally {
1805                 Binder.restoreCallingIdentity(ident);
1806             }
1807         }
1808 
postSingleUpdate(final AvailabilityCallback callback, final Executor executor, final String id, final String physicalId, final int status)1809         private void postSingleUpdate(final AvailabilityCallback callback, final Executor executor,
1810                 final String id, final String physicalId, final int status) {
1811             if (isAvailable(status)) {
1812                 final long ident = Binder.clearCallingIdentity();
1813                 try {
1814                     executor.execute(
1815                         new Runnable() {
1816                             @Override
1817                             public void run() {
1818                                 if (physicalId == null) {
1819                                     callback.onCameraAvailable(id);
1820                                 } else {
1821                                     callback.onPhysicalCameraAvailable(id, physicalId);
1822                                 }
1823                             }
1824                         });
1825                 } finally {
1826                     Binder.restoreCallingIdentity(ident);
1827                 }
1828             } else {
1829                 final long ident = Binder.clearCallingIdentity();
1830                 try {
1831                     executor.execute(
1832                         new Runnable() {
1833                             @Override
1834                             public void run() {
1835                                 if (physicalId == null) {
1836                                     callback.onCameraUnavailable(id);
1837                                 } else {
1838                                     callback.onPhysicalCameraUnavailable(id, physicalId);
1839                                 }
1840                             }
1841                         });
1842                 } finally {
1843                     Binder.restoreCallingIdentity(ident);
1844                 }
1845             }
1846         }
1847 
postSingleTorchUpdate(final TorchCallback callback, final Executor executor, final String id, final int status)1848         private void postSingleTorchUpdate(final TorchCallback callback, final Executor executor,
1849                 final String id, final int status) {
1850             switch(status) {
1851                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
1852                 case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF: {
1853                         final long ident = Binder.clearCallingIdentity();
1854                         try {
1855                             executor.execute(() -> {
1856                                 callback.onTorchModeChanged(id, status ==
1857                                         ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON);
1858                             });
1859                         } finally {
1860                             Binder.restoreCallingIdentity(ident);
1861                         }
1862                     }
1863                     break;
1864                 default: {
1865                         final long ident = Binder.clearCallingIdentity();
1866                         try {
1867                             executor.execute(() -> {
1868                                 callback.onTorchModeUnavailable(id);
1869                             });
1870                         } finally {
1871                             Binder.restoreCallingIdentity(ident);
1872                         }
1873                     }
1874                     break;
1875             }
1876         }
1877 
1878         /**
1879          * Send the state of all known cameras to the provided listener, to initialize
1880          * the listener's knowledge of camera state.
1881          */
updateCallbackLocked(AvailabilityCallback callback, Executor executor)1882         private void updateCallbackLocked(AvailabilityCallback callback, Executor executor) {
1883             for (int i = 0; i < mDeviceStatus.size(); i++) {
1884                 String id = mDeviceStatus.keyAt(i);
1885                 Integer status = mDeviceStatus.valueAt(i);
1886                 postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
1887 
1888                 // Send the NOT_PRESENT state for unavailable physical cameras
1889                 if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
1890                     ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
1891                     for (String unavailableId : unavailableIds) {
1892                         postSingleUpdate(callback, executor, id, unavailableId,
1893                                 ICameraServiceListener.STATUS_NOT_PRESENT);
1894                     }
1895                 }
1896             }
1897         }
1898 
onStatusChangedLocked(int status, String id)1899         private void onStatusChangedLocked(int status, String id) {
1900             if (DEBUG) {
1901                 Log.v(TAG,
1902                         String.format("Camera id %s has status changed to 0x%x", id, status));
1903             }
1904 
1905             if (!validStatus(status)) {
1906                 Log.e(TAG, String.format("Ignoring invalid device %s status 0x%x", id,
1907                                 status));
1908                 return;
1909             }
1910 
1911             Integer oldStatus;
1912             if (status == ICameraServiceListener.STATUS_NOT_PRESENT) {
1913                 oldStatus = mDeviceStatus.remove(id);
1914                 mUnavailablePhysicalDevices.remove(id);
1915             } else {
1916                 oldStatus = mDeviceStatus.put(id, status);
1917                 if (oldStatus == null) {
1918                     mUnavailablePhysicalDevices.put(id, new ArrayList<String>());
1919                 }
1920             }
1921 
1922             if (oldStatus != null && oldStatus == status) {
1923                 if (DEBUG) {
1924                     Log.v(TAG, String.format(
1925                         "Device status changed to 0x%x, which is what it already was",
1926                         status));
1927                 }
1928                 return;
1929             }
1930 
1931             // TODO: consider abstracting out this state minimization + transition
1932             // into a separate
1933             // more easily testable class
1934             // i.e. (new State()).addState(STATE_AVAILABLE)
1935             //                   .addState(STATE_NOT_AVAILABLE)
1936             //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
1937             //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
1938             //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
1939             //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);
1940 
1941             // Translate all the statuses to either 'available' or 'not available'
1942             //  available -> available         => no new update
1943             //  not available -> not available => no new update
1944             if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {
1945                 if (DEBUG) {
1946                     Log.v(TAG,
1947                             String.format(
1948                                 "Device status was previously available (%b), " +
1949                                 " and is now again available (%b)" +
1950                                 "so no new client visible update will be sent",
1951                                 isAvailable(oldStatus), isAvailable(status)));
1952                 }
1953                 return;
1954             }
1955 
1956             final int callbackCount = mCallbackMap.size();
1957             for (int i = 0; i < callbackCount; i++) {
1958                 Executor executor = mCallbackMap.valueAt(i);
1959                 final AvailabilityCallback callback = mCallbackMap.keyAt(i);
1960 
1961                 postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
1962             }
1963         } // onStatusChangedLocked
1964 
onPhysicalCameraStatusChangedLocked(int status, String id, String physicalId)1965         private void onPhysicalCameraStatusChangedLocked(int status,
1966                 String id, String physicalId) {
1967             if (DEBUG) {
1968                 Log.v(TAG,
1969                         String.format("Camera id %s physical camera id %s has status "
1970                         + "changed to 0x%x", id, physicalId, status));
1971             }
1972 
1973             if (!validStatus(status)) {
1974                 Log.e(TAG, String.format(
1975                         "Ignoring invalid device %s physical device %s status 0x%x", id,
1976                         physicalId, status));
1977                 return;
1978             }
1979 
1980             //TODO: Do we need to treat this as error?
1981             if (!mDeviceStatus.containsKey(id) || !isAvailable(mDeviceStatus.get(id))
1982                     || !mUnavailablePhysicalDevices.containsKey(id)) {
1983                 Log.e(TAG, String.format("Camera %s is not available. Ignore physical camera "
1984                         + "status change", id));
1985                 return;
1986             }
1987 
1988             ArrayList<String> unavailablePhysicalDevices = mUnavailablePhysicalDevices.get(id);
1989             if (!isAvailable(status)
1990                     && !unavailablePhysicalDevices.contains(physicalId)) {
1991                 unavailablePhysicalDevices.add(physicalId);
1992             } else if (isAvailable(status)
1993                     && unavailablePhysicalDevices.contains(physicalId)) {
1994                 unavailablePhysicalDevices.remove(physicalId);
1995             } else {
1996                 if (DEBUG) {
1997                     Log.v(TAG,
1998                             String.format(
1999                                 "Physical camera device status was previously available (%b), "
2000                                 + " and is now again available (%b)"
2001                                 + "so no new client visible update will be sent",
2002                                 !unavailablePhysicalDevices.contains(physicalId),
2003                                 isAvailable(status)));
2004                 }
2005                 return;
2006             }
2007 
2008             final int callbackCount = mCallbackMap.size();
2009             for (int i = 0; i < callbackCount; i++) {
2010                 Executor executor = mCallbackMap.valueAt(i);
2011                 final AvailabilityCallback callback = mCallbackMap.keyAt(i);
2012 
2013                 postSingleUpdate(callback, executor, id, physicalId, status);
2014             }
2015         } // onPhysicalCameraStatusChangedLocked
2016 
updateTorchCallbackLocked(TorchCallback callback, Executor executor)2017         private void updateTorchCallbackLocked(TorchCallback callback, Executor executor) {
2018             for (int i = 0; i < mTorchStatus.size(); i++) {
2019                 String id = mTorchStatus.keyAt(i);
2020                 Integer status = mTorchStatus.valueAt(i);
2021                 postSingleTorchUpdate(callback, executor, id, status);
2022             }
2023         }
2024 
onTorchStatusChangedLocked(int status, String id)2025         private void onTorchStatusChangedLocked(int status, String id) {
2026             if (DEBUG) {
2027                 Log.v(TAG,
2028                         String.format("Camera id %s has torch status changed to 0x%x", id, status));
2029             }
2030 
2031             if (!validTorchStatus(status)) {
2032                 Log.e(TAG, String.format("Ignoring invalid device %s torch status 0x%x", id,
2033                                 status));
2034                 return;
2035             }
2036 
2037             Integer oldStatus = mTorchStatus.put(id, status);
2038             if (oldStatus != null && oldStatus == status) {
2039                 if (DEBUG) {
2040                     Log.v(TAG, String.format(
2041                         "Torch status changed to 0x%x, which is what it already was",
2042                         status));
2043                 }
2044                 return;
2045             }
2046 
2047             final int callbackCount = mTorchCallbackMap.size();
2048             for (int i = 0; i < callbackCount; i++) {
2049                 final Executor executor = mTorchCallbackMap.valueAt(i);
2050                 final TorchCallback callback = mTorchCallbackMap.keyAt(i);
2051                 postSingleTorchUpdate(callback, executor, id, status);
2052             }
2053         } // onTorchStatusChangedLocked
2054 
2055         /**
2056          * Register a callback to be notified about camera device availability with the
2057          * global listener singleton.
2058          *
2059          * @param callback the new callback to send camera availability notices to
2060          * @param executor The executor which should invoke the callback. May not be null.
2061          */
registerAvailabilityCallback(AvailabilityCallback callback, Executor executor)2062         public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor) {
2063             synchronized (mLock) {
2064                 connectCameraServiceLocked();
2065 
2066                 Executor oldExecutor = mCallbackMap.put(callback, executor);
2067                 // For new callbacks, provide initial availability information
2068                 if (oldExecutor == null) {
2069                     updateCallbackLocked(callback, executor);
2070                 }
2071 
2072                 // If not connected to camera service, schedule a reconnect to camera service.
2073                 if (mCameraService == null) {
2074                     scheduleCameraServiceReconnectionLocked();
2075                 }
2076             }
2077         }
2078 
2079         /**
2080          * Remove a previously-added callback; the callback will no longer receive connection and
2081          * disconnection callbacks, and is no longer referenced by the global listener singleton.
2082          *
2083          * @param callback The callback to remove from the notification list
2084          */
unregisterAvailabilityCallback(AvailabilityCallback callback)2085         public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
2086             synchronized (mLock) {
2087                 mCallbackMap.remove(callback);
2088             }
2089         }
2090 
registerTorchCallback(TorchCallback callback, Executor executor)2091         public void registerTorchCallback(TorchCallback callback, Executor executor) {
2092             synchronized(mLock) {
2093                 connectCameraServiceLocked();
2094 
2095                 Executor oldExecutor = mTorchCallbackMap.put(callback, executor);
2096                 // For new callbacks, provide initial torch information
2097                 if (oldExecutor == null) {
2098                     updateTorchCallbackLocked(callback, executor);
2099                 }
2100 
2101                 // If not connected to camera service, schedule a reconnect to camera service.
2102                 if (mCameraService == null) {
2103                     scheduleCameraServiceReconnectionLocked();
2104                 }
2105             }
2106         }
2107 
unregisterTorchCallback(TorchCallback callback)2108         public void unregisterTorchCallback(TorchCallback callback) {
2109             synchronized(mLock) {
2110                 mTorchCallbackMap.remove(callback);
2111             }
2112         }
2113 
2114         /**
2115          * Callback from camera service notifying the process about camera availability changes
2116          */
2117         @Override
onStatusChanged(int status, String cameraId)2118         public void onStatusChanged(int status, String cameraId) throws RemoteException {
2119             synchronized(mLock) {
2120                 onStatusChangedLocked(status, cameraId);
2121             }
2122         }
2123 
2124         @Override
onPhysicalCameraStatusChanged(int status, String cameraId, String physicalCameraId)2125         public void onPhysicalCameraStatusChanged(int status, String cameraId,
2126                 String physicalCameraId) throws RemoteException {
2127             synchronized (mLock) {
2128                 onPhysicalCameraStatusChangedLocked(status, cameraId, physicalCameraId);
2129             }
2130         }
2131 
2132         @Override
onTorchStatusChanged(int status, String cameraId)2133         public void onTorchStatusChanged(int status, String cameraId) throws RemoteException {
2134             synchronized (mLock) {
2135                 onTorchStatusChangedLocked(status, cameraId);
2136             }
2137         }
2138 
2139         @Override
onCameraAccessPrioritiesChanged()2140         public void onCameraAccessPrioritiesChanged() {
2141             synchronized (mLock) {
2142                 final int callbackCount = mCallbackMap.size();
2143                 for (int i = 0; i < callbackCount; i++) {
2144                     Executor executor = mCallbackMap.valueAt(i);
2145                     final AvailabilityCallback callback = mCallbackMap.keyAt(i);
2146 
2147                     postSingleAccessPriorityChangeUpdate(callback, executor);
2148                 }
2149             }
2150         }
2151 
2152         @Override
onCameraOpened(String cameraId, String clientPackageId)2153         public void onCameraOpened(String cameraId, String clientPackageId) {
2154             synchronized (mLock) {
2155                 final int callbackCount = mCallbackMap.size();
2156                 for (int i = 0; i < callbackCount; i++) {
2157                     Executor executor = mCallbackMap.valueAt(i);
2158                     final AvailabilityCallback callback = mCallbackMap.keyAt(i);
2159 
2160                     postSingleCameraOpenedUpdate(callback, executor, cameraId, clientPackageId);
2161                 }
2162             }
2163         }
2164 
2165         @Override
onCameraClosed(String cameraId)2166         public void onCameraClosed(String cameraId) {
2167             synchronized (mLock) {
2168                 final int callbackCount = mCallbackMap.size();
2169                 for (int i = 0; i < callbackCount; i++) {
2170                     Executor executor = mCallbackMap.valueAt(i);
2171                     final AvailabilityCallback callback = mCallbackMap.keyAt(i);
2172 
2173                     postSingleCameraClosedUpdate(callback, executor, cameraId);
2174                 }
2175             }
2176         }
2177 
2178         /**
2179          * Try to connect to camera service after some delay if any client registered camera
2180          * availability callback or torch status callback.
2181          */
scheduleCameraServiceReconnectionLocked()2182         private void scheduleCameraServiceReconnectionLocked() {
2183             if (mCallbackMap.isEmpty() && mTorchCallbackMap.isEmpty()) {
2184                 // Not necessary to reconnect camera service if no client registers a callback.
2185                 return;
2186             }
2187 
2188             if (DEBUG) {
2189                 Log.v(TAG, "Reconnecting Camera Service in " + CAMERA_SERVICE_RECONNECT_DELAY_MS +
2190                         " ms");
2191             }
2192 
2193             try {
2194                 mScheduler.schedule(() -> {
2195                     ICameraService cameraService = getCameraService();
2196                     if (cameraService == null) {
2197                         synchronized(mLock) {
2198                             if (DEBUG) {
2199                                 Log.v(TAG, "Reconnecting Camera Service failed.");
2200                             }
2201                             scheduleCameraServiceReconnectionLocked();
2202                         }
2203                     }
2204                 }, CAMERA_SERVICE_RECONNECT_DELAY_MS, TimeUnit.MILLISECONDS);
2205             } catch (RejectedExecutionException e) {
2206                 Log.e(TAG, "Failed to schedule camera service re-connect: " + e);
2207             }
2208         }
2209 
2210         /**
2211          * Listener for camera service death.
2212          *
2213          * <p>The camera service isn't supposed to die under any normal circumstances, but can be
2214          * turned off during debug, or crash due to bugs.  So detect that and null out the interface
2215          * object, so that the next calls to the manager can try to reconnect.</p>
2216          */
binderDied()2217         public void binderDied() {
2218             synchronized(mLock) {
2219                 // Only do this once per service death
2220                 if (mCameraService == null) return;
2221 
2222                 mCameraService = null;
2223 
2224                 // Tell listeners that the cameras and torch modes are unavailable and schedule a
2225                 // reconnection to camera service. When camera service is reconnected, the camera
2226                 // and torch statuses will be updated.
2227                 // Iterate from the end to the beginning befcause onStatusChangedLocked removes
2228                 // entries from the ArrayMap.
2229                 for (int i = mDeviceStatus.size() - 1; i >= 0; i--) {
2230                     String cameraId = mDeviceStatus.keyAt(i);
2231                     onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, cameraId);
2232                 }
2233                 for (int i = 0; i < mTorchStatus.size(); i++) {
2234                     String cameraId = mTorchStatus.keyAt(i);
2235                     onTorchStatusChangedLocked(ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE,
2236                             cameraId);
2237                 }
2238 
2239                 mConcurrentCameraIdCombinations.clear();
2240 
2241                 scheduleCameraServiceReconnectionLocked();
2242             }
2243         }
2244 
2245     } // CameraManagerGlobal
2246 
2247 } // CameraManager
2248