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