CameraManager.java revision bd9b106806f9792be210cc2d9848d8b1f4b9664d
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 17package android.hardware.camera2; 18 19import android.content.Context; 20import android.hardware.ICameraService; 21import android.hardware.ICameraServiceListener; 22import android.hardware.CameraInfo; 23import android.hardware.camera2.impl.CameraMetadataNative; 24import android.hardware.camera2.legacy.CameraDeviceUserShim; 25import android.hardware.camera2.legacy.LegacyMetadataMapper; 26import android.hardware.camera2.utils.CameraServiceBinderDecorator; 27import android.hardware.camera2.utils.CameraRuntimeException; 28import android.hardware.camera2.utils.BinderHolder; 29import android.os.IBinder; 30import android.os.Handler; 31import android.os.Looper; 32import android.os.RemoteException; 33import android.os.ServiceManager; 34import android.util.Log; 35import android.util.ArrayMap; 36 37import java.util.ArrayList; 38 39/** 40 * <p>A system service manager for detecting, characterizing, and connecting to 41 * {@link CameraDevice CameraDevices}.</p> 42 * 43 * <p>You can get an instance of this class by calling 44 * {@link android.content.Context#getSystemService(String) Context.getSystemService()}.</p> 45 * 46 * <pre>CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);</pre> 47 * 48 * <p>For more details about communicating with camera devices, read the Camera 49 * developer guide or the {@link android.hardware.camera2 camera2} 50 * package documentation.</p> 51 */ 52public final class CameraManager { 53 54 private static final String TAG = "CameraManager"; 55 private final boolean DEBUG; 56 57 /** 58 * This should match the ICameraService definition 59 */ 60 private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; 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 // Access only through getCameraServiceLocked to deal with binder death 68 private ICameraService mCameraService; 69 70 private ArrayList<String> mDeviceIdList; 71 72 private final ArrayMap<AvailabilityListener, Handler> mListenerMap = 73 new ArrayMap<AvailabilityListener, Handler>(); 74 75 private final Context mContext; 76 private final Object mLock = new Object(); 77 78 private final CameraServiceListener mServiceListener = new CameraServiceListener(); 79 80 /** 81 * @hide 82 */ 83 public CameraManager(Context context) { 84 DEBUG = Log.isLoggable(TAG, Log.DEBUG); 85 synchronized(mLock) { 86 mContext = context; 87 88 connectCameraServiceLocked(); 89 } 90 } 91 92 /** 93 * Return the list of currently connected camera devices by 94 * identifier. 95 * 96 * <p>Non-removable cameras use integers starting at 0 for their 97 * identifiers, while removable cameras have a unique identifier for each 98 * individual device, even if they are the same model.</p> 99 * 100 * @return The list of currently connected camera devices. 101 */ 102 public String[] getCameraIdList() throws CameraAccessException { 103 synchronized (mLock) { 104 // ID list creation handles various known failures in device enumeration, so only 105 // exceptions it'll throw are unexpected, and should be propagated upward. 106 return getOrCreateDeviceIdListLocked().toArray(new String[0]); 107 } 108 } 109 110 /** 111 * Register a listener to be notified about camera device availability. 112 * 113 * <p>Registering the same listener again will replace the handler with the 114 * new one provided.</p> 115 * 116 * <p>The first time a listener is registered, it is immediately called 117 * with the availability status of all currently known camera devices.</p> 118 * 119 * @param listener The new listener to send camera availability notices to 120 * @param handler The handler on which the listener should be invoked, or 121 * {@code null} to use the current thread's {@link android.os.Looper looper}. 122 */ 123 public void addAvailabilityListener(AvailabilityListener listener, Handler handler) { 124 if (handler == null) { 125 Looper looper = Looper.myLooper(); 126 if (looper == null) { 127 throw new IllegalArgumentException( 128 "No handler given, and current thread has no looper!"); 129 } 130 handler = new Handler(looper); 131 } 132 133 synchronized (mLock) { 134 Handler oldHandler = mListenerMap.put(listener, handler); 135 // For new listeners, provide initial availability information 136 if (oldHandler == null) { 137 mServiceListener.updateListenerLocked(listener, handler); 138 } 139 } 140 } 141 142 /** 143 * Remove a previously-added listener; the listener will no longer receive 144 * connection and disconnection callbacks. 145 * 146 * <p>Removing a listener that isn't registered has no effect.</p> 147 * 148 * @param listener The listener to remove from the notification list 149 */ 150 public void removeAvailabilityListener(AvailabilityListener listener) { 151 synchronized (mLock) { 152 mListenerMap.remove(listener); 153 } 154 } 155 156 /** 157 * <p>Query the capabilities of a camera device. These capabilities are 158 * immutable for a given camera.</p> 159 * 160 * @param cameraId The id of the camera device to query 161 * @return The properties of the given camera 162 * 163 * @throws IllegalArgumentException if the cameraId does not match any 164 * known camera device. 165 * @throws CameraAccessException if the camera is disabled by device policy, or 166 * the camera device has been disconnected. 167 * @throws SecurityException if the application does not have permission to 168 * access the camera 169 * 170 * @see #getCameraIdList 171 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 172 */ 173 public CameraCharacteristics getCameraCharacteristics(String cameraId) 174 throws CameraAccessException { 175 CameraCharacteristics characteristics = null; 176 177 synchronized (mLock) { 178 if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { 179 throw new IllegalArgumentException(String.format("Camera id %s does not match any" + 180 " currently connected camera device", cameraId)); 181 } 182 183 int id = Integer.valueOf(cameraId); 184 185 /* 186 * Get the camera characteristics from the camera service directly if it supports it, 187 * otherwise get them from the legacy shim instead. 188 */ 189 190 ICameraService cameraService = getCameraServiceLocked(); 191 if (cameraService == null) { 192 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, 193 "Camera service is currently unavailable"); 194 } 195 try { 196 if (!supportsCamera2ApiLocked(cameraId)) { 197 // Legacy backwards compatibility path; build static info from the camera 198 // parameters 199 String[] outParameters = new String[1]; 200 201 cameraService.getLegacyParameters(id, /*out*/outParameters); 202 String parameters = outParameters[0]; 203 204 CameraInfo info = new CameraInfo(); 205 cameraService.getCameraInfo(id, /*out*/info); 206 207 characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info); 208 } else { 209 // Normal path: Get the camera characteristics directly from the camera service 210 CameraMetadataNative info = new CameraMetadataNative(); 211 212 cameraService.getCameraCharacteristics(id, info); 213 214 characteristics = new CameraCharacteristics(info); 215 } 216 } catch (CameraRuntimeException e) { 217 throw e.asChecked(); 218 } catch (RemoteException e) { 219 // Camera service died - act as if the camera was disconnected 220 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, 221 "Camera service is currently unavailable", e); 222 } 223 } 224 return characteristics; 225 } 226 227 /** 228 * Helper for openning a connection to a camera with the given ID. 229 * 230 * @param cameraId The unique identifier of the camera device to open 231 * @param listener The listener for the camera. Must not be null. 232 * @param handler The handler to call the listener on. Must not be null. 233 * 234 * @throws CameraAccessException if the camera is disabled by device policy, 235 * or too many camera devices are already open, or the cameraId does not match 236 * any currently available camera device. 237 * 238 * @throws SecurityException if the application does not have permission to 239 * access the camera 240 * @throws IllegalArgumentException if listener or handler is null. 241 * @return A handle to the newly-created camera device. 242 * 243 * @see #getCameraIdList 244 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 245 */ 246 private CameraDevice openCameraDeviceUserAsync(String cameraId, 247 CameraDevice.StateListener listener, Handler handler) 248 throws CameraAccessException { 249 CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); 250 CameraDevice device = null; 251 try { 252 253 synchronized (mLock) { 254 255 ICameraDeviceUser cameraUser = null; 256 257 android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = 258 new android.hardware.camera2.impl.CameraDeviceImpl( 259 cameraId, 260 listener, 261 handler, 262 characteristics); 263 264 BinderHolder holder = new BinderHolder(); 265 266 ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); 267 int id = Integer.parseInt(cameraId); 268 try { 269 if (supportsCamera2ApiLocked(cameraId)) { 270 // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices 271 ICameraService cameraService = getCameraServiceLocked(); 272 if (cameraService == null) { 273 throw new CameraRuntimeException( 274 CameraAccessException.CAMERA_DISCONNECTED, 275 "Camera service is currently unavailable"); 276 } 277 cameraService.connectDevice(callbacks, id, 278 mContext.getPackageName(), USE_CALLING_UID, holder); 279 cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); 280 } else { 281 // Use legacy camera implementation for HAL1 devices 282 Log.i(TAG, "Using legacy camera HAL."); 283 cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); 284 } 285 } catch (CameraRuntimeException e) { 286 if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) { 287 throw new AssertionError("Should've gone down the shim path"); 288 } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE || 289 e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE || 290 e.getReason() == CameraAccessException.CAMERA_DISABLED || 291 e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || 292 e.getReason() == CameraAccessException.CAMERA_ERROR) { 293 // Received one of the known connection errors 294 // The remote camera device cannot be connected to, so 295 // set the local camera to the startup error state 296 deviceImpl.setRemoteFailure(e); 297 298 if (e.getReason() == CameraAccessException.CAMERA_DISABLED || 299 e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { 300 // Per API docs, these failures call onError and throw 301 throw e.asChecked(); 302 } 303 } else { 304 // Unexpected failure - rethrow 305 throw e; 306 } 307 } catch (RemoteException e) { 308 // Camera service died - act as if it's a CAMERA_DISCONNECTED case 309 CameraRuntimeException ce = new CameraRuntimeException( 310 CameraAccessException.CAMERA_DISCONNECTED, 311 "Camera service is currently unavailable", e); 312 deviceImpl.setRemoteFailure(ce); 313 throw ce.asChecked(); 314 } 315 316 // TODO: factor out listener to be non-nested, then move setter to constructor 317 // For now, calling setRemoteDevice will fire initial 318 // onOpened/onUnconfigured callbacks. 319 deviceImpl.setRemoteDevice(cameraUser); 320 device = deviceImpl; 321 } 322 323 } catch (NumberFormatException e) { 324 throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " 325 + cameraId); 326 } catch (CameraRuntimeException e) { 327 throw e.asChecked(); 328 } 329 return device; 330 } 331 332 /** 333 * Open a connection to a camera with the given ID. 334 * 335 * <p>Use {@link #getCameraIdList} to get the list of available camera 336 * devices. Note that even if an id is listed, open may fail if the device 337 * is disconnected between the calls to {@link #getCameraIdList} and 338 * {@link #openCamera}.</p> 339 * 340 * <p>Once the camera is successfully opened, {@link CameraDevice.StateListener#onOpened} will 341 * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up 342 * for operation by calling {@link CameraDevice#createCaptureSession} and 343 * {@link CameraDevice#createCaptureRequest}</p> 344 * 345 * <!-- 346 * <p>Since the camera device will be opened asynchronously, any asynchronous operations done 347 * on the returned CameraDevice instance will be queued up until the device startup has 348 * completed and the listener's {@link CameraDevice.StateListener#onOpened onOpened} method is 349 * called. The pending operations are then processed in order.</p> 350 * --> 351 * <p>If the camera becomes disconnected during initialization 352 * after this function call returns, 353 * {@link CameraDevice.StateListener#onDisconnected} with a 354 * {@link CameraDevice} in the disconnected state (and 355 * {@link CameraDevice.StateListener#onOpened} will be skipped).</p> 356 * 357 * <p>If opening the camera device fails, then the device listener's 358 * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent 359 * calls on the camera device will throw a {@link CameraAccessException}.</p> 360 * 361 * @param cameraId 362 * The unique identifier of the camera device to open 363 * @param listener 364 * The listener which is invoked once the camera is opened 365 * @param handler 366 * The handler on which the listener should be invoked, or 367 * {@code null} to use the current thread's {@link android.os.Looper looper}. 368 * 369 * @throws CameraAccessException if the camera is disabled by device policy, 370 * or the camera has become or was disconnected. 371 * 372 * @throws IllegalArgumentException if cameraId or the listener was null, 373 * or the cameraId does not match any currently or previously available 374 * camera device. 375 * 376 * @throws SecurityException if the application does not have permission to 377 * access the camera 378 * 379 * @see #getCameraIdList 380 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 381 */ 382 public void openCamera(String cameraId, final CameraDevice.StateListener listener, 383 Handler handler) 384 throws CameraAccessException { 385 386 if (cameraId == null) { 387 throw new IllegalArgumentException("cameraId was null"); 388 } else if (listener == null) { 389 throw new IllegalArgumentException("listener was null"); 390 } else if (handler == null) { 391 if (Looper.myLooper() != null) { 392 handler = new Handler(); 393 } else { 394 throw new IllegalArgumentException( 395 "Looper doesn't exist in the calling thread"); 396 } 397 } 398 399 openCameraDeviceUserAsync(cameraId, listener, handler); 400 } 401 402 /** 403 * A listener for camera devices becoming available or 404 * unavailable to open. 405 * 406 * <p>Cameras become available when they are no longer in use, or when a new 407 * removable camera is connected. They become unavailable when some 408 * application or service starts using a camera, or when a removable camera 409 * is disconnected.</p> 410 * 411 * <p>Extend this listener and pass an instance of the subclass to 412 * {@link CameraManager#addAvailabilityListener} to be notified of such availability 413 * changes.</p> 414 * 415 * @see addAvailabilityListener 416 */ 417 public static abstract class AvailabilityListener { 418 419 /** 420 * A new camera has become available to use. 421 * 422 * <p>The default implementation of this method does nothing.</p> 423 * 424 * @param cameraId The unique identifier of the new camera. 425 */ 426 public void onCameraAvailable(String cameraId) { 427 // default empty implementation 428 } 429 430 /** 431 * A previously-available camera has become unavailable for use. 432 * 433 * <p>If an application had an active CameraDevice instance for the 434 * now-disconnected camera, that application will receive a 435 * {@link CameraDevice.StateListener#onDisconnected disconnection error}.</p> 436 * 437 * <p>The default implementation of this method does nothing.</p> 438 * 439 * @param cameraId The unique identifier of the disconnected camera. 440 */ 441 public void onCameraUnavailable(String cameraId) { 442 // default empty implementation 443 } 444 } 445 446 /** 447 * Return or create the list of currently connected camera devices. 448 * 449 * <p>In case of errors connecting to the camera service, will return an empty list.</p> 450 */ 451 private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { 452 if (mDeviceIdList == null) { 453 int numCameras = 0; 454 ICameraService cameraService = getCameraServiceLocked(); 455 ArrayList<String> deviceIdList = new ArrayList<>(); 456 457 // If no camera service, then no devices 458 if (cameraService == null) { 459 return deviceIdList; 460 } 461 462 try { 463 numCameras = cameraService.getNumberOfCameras(); 464 } catch(CameraRuntimeException e) { 465 throw e.asChecked(); 466 } catch (RemoteException e) { 467 // camera service just died - if no camera service, then no devices 468 return deviceIdList; 469 } 470 471 CameraMetadataNative info = new CameraMetadataNative(); 472 for (int i = 0; i < numCameras; ++i) { 473 // Non-removable cameras use integers starting at 0 for their 474 // identifiers 475 boolean isDeviceSupported = false; 476 try { 477 cameraService.getCameraCharacteristics(i, info); 478 if (!info.isEmpty()) { 479 isDeviceSupported = true; 480 } else { 481 throw new AssertionError("Expected to get non-empty characteristics"); 482 } 483 } catch(IllegalArgumentException e) { 484 // Got a BAD_VALUE from service, meaning that this 485 // device is not supported. 486 } catch(CameraRuntimeException e) { 487 // DISCONNECTED means that the HAL reported an low-level error getting the 488 // device info; skip listing the device. Other errors, 489 // propagate exception onward 490 if (e.getReason() != CameraAccessException.CAMERA_DISCONNECTED) { 491 throw e.asChecked(); 492 } 493 } catch(RemoteException e) { 494 // Camera service died - no devices to list 495 deviceIdList.clear(); 496 return deviceIdList; 497 } 498 499 if (isDeviceSupported) { 500 deviceIdList.add(String.valueOf(i)); 501 } else { 502 Log.w(TAG, "Error querying camera device " + i + " for listing."); 503 } 504 505 } 506 mDeviceIdList = deviceIdList; 507 } 508 return mDeviceIdList; 509 } 510 511 private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) { 512 int problem = e.getReason(); 513 switch (problem) { 514 case CameraAccessException.CAMERA_DISCONNECTED: 515 String errorMsg = CameraAccessException.getDefaultMessage(problem); 516 Log.w(TAG, msg + ": " + errorMsg); 517 break; 518 default: 519 throw new IllegalStateException(msg, e.asChecked()); 520 } 521 } 522 523 /** 524 * Queries the camera service if it supports the camera2 api directly, or needs a shim. 525 * 526 * @param cameraId a non-{@code null} camera identifier 527 * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise. 528 */ 529 private boolean supportsCamera2ApiLocked(String cameraId) { 530 return supportsCameraApiLocked(cameraId, API_VERSION_2); 531 } 532 533 /** 534 * Queries the camera service if it supports a camera api directly, or needs a shim. 535 * 536 * @param cameraId a non-{@code null} camera identifier 537 * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2} 538 * @return {@code true} if connecting will work for that device version. 539 */ 540 private boolean supportsCameraApiLocked(String cameraId, int apiVersion) { 541 int id = Integer.parseInt(cameraId); 542 543 /* 544 * Possible return values: 545 * - NO_ERROR => CameraX API is supported 546 * - CAMERA_DEPRECATED_HAL => CameraX API is *not* supported (thrown as an exception) 547 * - Remote exception => If the camera service died 548 * 549 * Anything else is an unexpected error we don't want to recover from. 550 */ 551 try { 552 ICameraService cameraService = getCameraServiceLocked(); 553 // If no camera service, no support 554 if (cameraService == null) return false; 555 556 int res = cameraService.supportsCameraApi(id, apiVersion); 557 558 if (res != CameraServiceBinderDecorator.NO_ERROR) { 559 throw new AssertionError("Unexpected value " + res); 560 } 561 return true; 562 } catch (CameraRuntimeException e) { 563 if (e.getReason() != CameraAccessException.CAMERA_DEPRECATED_HAL) { 564 throw e; 565 } 566 // API level is not supported 567 } catch (RemoteException e) { 568 // Camera service is now down, no support for any API level 569 } 570 return false; 571 } 572 573 /** 574 * Connect to the camera service if it's available, and set up listeners. 575 * 576 * <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p> 577 */ 578 private void connectCameraServiceLocked() { 579 mCameraService = null; 580 IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); 581 if (cameraServiceBinder == null) { 582 // Camera service is now down, leave mCameraService as null 583 return; 584 } 585 try { 586 cameraServiceBinder.linkToDeath(new CameraServiceDeathListener(), /*flags*/ 0); 587 } catch (RemoteException e) { 588 // Camera service is now down, leave mCameraService as null 589 return; 590 } 591 592 ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); 593 594 /** 595 * Wrap the camera service in a decorator which automatically translates return codes 596 * into exceptions. 597 */ 598 ICameraService cameraService = CameraServiceBinderDecorator.newInstance(cameraServiceRaw); 599 600 try { 601 CameraServiceBinderDecorator.throwOnError( 602 CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); 603 } catch (CameraRuntimeException e) { 604 handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); 605 } 606 607 try { 608 cameraService.addListener(mServiceListener); 609 mCameraService = cameraService; 610 } catch(CameraRuntimeException e) { 611 // Unexpected failure 612 throw new IllegalStateException("Failed to register a camera service listener", 613 e.asChecked()); 614 } catch (RemoteException e) { 615 // Camera service is now down, leave mCameraService as null 616 } 617 } 618 619 /** 620 * Return a best-effort ICameraService. 621 * 622 * <p>This will be null if the camera service 623 * is not currently available. If the camera service has died since the last 624 * use of the camera service, will try to reconnect to the service.</p> 625 */ 626 private ICameraService getCameraServiceLocked() { 627 if (mCameraService == null) { 628 Log.i(TAG, "getCameraServiceLocked: Reconnecting to camera service"); 629 connectCameraServiceLocked(); 630 if (mCameraService == null) { 631 Log.e(TAG, "Camera service is unavailable"); 632 } 633 } 634 return mCameraService; 635 } 636 637 /** 638 * Listener for camera service death. 639 * 640 * <p>The camera service isn't supposed to die under any normal circumstances, but can be turned 641 * off during debug, or crash due to bugs. So detect that and null out the interface object, so 642 * that the next calls to the manager can try to reconnect.</p> 643 */ 644 private class CameraServiceDeathListener implements IBinder.DeathRecipient { 645 public void binderDied() { 646 synchronized(mLock) { 647 mCameraService = null; 648 // Tell listeners that the cameras are _available_, because any existing clients 649 // will have gotten disconnected. This is optimistic under the assumption that the 650 // service will be back shortly. 651 // 652 // Without this, a camera service crash while a camera is open will never signal to 653 // listeners that previously in-use cameras are now available. 654 for (String cameraId : mDeviceIdList) { 655 mServiceListener.onStatusChangedLocked(CameraServiceListener.STATUS_PRESENT, 656 cameraId); 657 } 658 } 659 } 660 } 661 662 // TODO: this class needs unit tests 663 // TODO: extract class into top level 664 private class CameraServiceListener extends ICameraServiceListener.Stub { 665 666 // Keep up-to-date with ICameraServiceListener.h 667 668 // Device physically unplugged 669 public static final int STATUS_NOT_PRESENT = 0; 670 // Device physically has been plugged in 671 // and the camera can be used exclusively 672 public static final int STATUS_PRESENT = 1; 673 // Device physically has been plugged in 674 // but it will not be connect-able until enumeration is complete 675 public static final int STATUS_ENUMERATING = 2; 676 // Camera is in use by another app and cannot be used exclusively 677 public static final int STATUS_NOT_AVAILABLE = 0x80000000; 678 679 // Camera ID -> Status map 680 private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>(); 681 682 private static final String TAG = "CameraServiceListener"; 683 684 @Override 685 public IBinder asBinder() { 686 return this; 687 } 688 689 private boolean isAvailable(int status) { 690 switch (status) { 691 case STATUS_PRESENT: 692 return true; 693 default: 694 return false; 695 } 696 } 697 698 private boolean validStatus(int status) { 699 switch (status) { 700 case STATUS_NOT_PRESENT: 701 case STATUS_PRESENT: 702 case STATUS_ENUMERATING: 703 case STATUS_NOT_AVAILABLE: 704 return true; 705 default: 706 return false; 707 } 708 } 709 710 private void postSingleUpdate(final AvailabilityListener listener, final Handler handler, 711 final String id, final int status) { 712 if (isAvailable(status)) { 713 handler.post( 714 new Runnable() { 715 @Override 716 public void run() { 717 listener.onCameraAvailable(id); 718 } 719 }); 720 } else { 721 handler.post( 722 new Runnable() { 723 @Override 724 public void run() { 725 listener.onCameraUnavailable(id); 726 } 727 }); 728 } 729 } 730 731 /** 732 * Send the state of all known cameras to the provided listener, to initialize 733 * the listener's knowledge of camera state. 734 */ 735 public void updateListenerLocked(AvailabilityListener listener, Handler handler) { 736 for (int i = 0; i < mDeviceStatus.size(); i++) { 737 String id = mDeviceStatus.keyAt(i); 738 Integer status = mDeviceStatus.valueAt(i); 739 postSingleUpdate(listener, handler, id, status); 740 } 741 } 742 743 @Override 744 public void onStatusChanged(int status, int cameraId) throws RemoteException { 745 synchronized(CameraManager.this.mLock) { 746 onStatusChangedLocked(status, String.valueOf(cameraId)); 747 } 748 } 749 750 public void onStatusChangedLocked(int status, String id) { 751 if (DEBUG) { 752 Log.v(TAG, 753 String.format("Camera id %s has status changed to 0x%x", id, status)); 754 } 755 756 if (!validStatus(status)) { 757 Log.e(TAG, String.format("Ignoring invalid device %s status 0x%x", id, 758 status)); 759 return; 760 } 761 762 Integer oldStatus = mDeviceStatus.put(id, status); 763 764 if (oldStatus != null && oldStatus == status) { 765 if (DEBUG) { 766 Log.v(TAG, String.format( 767 "Device status changed to 0x%x, which is what it already was", 768 status)); 769 } 770 return; 771 } 772 773 // TODO: consider abstracting out this state minimization + transition 774 // into a separate 775 // more easily testable class 776 // i.e. (new State()).addState(STATE_AVAILABLE) 777 // .addState(STATE_NOT_AVAILABLE) 778 // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), 779 // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) 780 // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); 781 // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); 782 783 // Translate all the statuses to either 'available' or 'not available' 784 // available -> available => no new update 785 // not available -> not available => no new update 786 if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { 787 if (DEBUG) { 788 Log.v(TAG, 789 String.format( 790 "Device status was previously available (%d), " + 791 " and is now again available (%d)" + 792 "so no new client visible update will be sent", 793 isAvailable(status), isAvailable(status))); 794 } 795 return; 796 } 797 798 final int listenerCount = mListenerMap.size(); 799 for (int i = 0; i < listenerCount; i++) { 800 Handler handler = mListenerMap.valueAt(i); 801 final AvailabilityListener listener = mListenerMap.keyAt(i); 802 803 postSingleUpdate(listener, handler, id, status); 804 } 805 } // onStatusChangedLocked 806 807 } // CameraServiceListener 808} // CameraManager 809