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