CameraManager.java revision 85e5f85f2e963f5093023050084ef6fbbe20a7ce
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.camera2.impl.CameraMetadataNative; 23import android.hardware.camera2.legacy.CameraDeviceUserShim; 24import android.hardware.camera2.utils.CameraBinderDecorator; 25import android.hardware.camera2.utils.CameraRuntimeException; 26import android.hardware.camera2.utils.BinderHolder; 27import android.os.IBinder; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.util.Log; 33import android.util.ArrayMap; 34 35import java.util.ArrayList; 36 37/** 38 * <p>An interface for iterating, listing, and connecting to 39 * {@link CameraDevice CameraDevices}.</p> 40 * 41 * <p>You can get an instance of this class by calling 42 * {@link android.content.Context#getSystemService(String) Context.getSystemService()}.</p> 43 * 44 * <pre>CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);</pre> 45 * 46 * <p>For more details about communicating with camera devices, read the Camera 47 * developer guide or the {@link android.hardware.camera2 camera2} 48 * package documentation.</p> 49 */ 50public final class CameraManager { 51 52 private static final String TAG = "CameraManager"; 53 54 /** 55 * This should match the ICameraService definition 56 */ 57 private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; 58 private static final int USE_CALLING_UID = -1; 59 60 private final ICameraService mCameraService; 61 private ArrayList<String> mDeviceIdList; 62 63 private final ArrayMap<AvailabilityListener, Handler> mListenerMap = 64 new ArrayMap<AvailabilityListener, Handler>(); 65 66 private final Context mContext; 67 private final Object mLock = new Object(); 68 69 /** 70 * @hide 71 */ 72 public CameraManager(Context context) { 73 mContext = context; 74 75 IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); 76 ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); 77 78 /** 79 * Wrap the camera service in a decorator which automatically translates return codes 80 * into exceptions, and RemoteExceptions into other exceptions. 81 */ 82 mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw); 83 84 try { 85 CameraBinderDecorator.throwOnError( 86 CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); 87 } catch (CameraRuntimeException e) { 88 handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); 89 } 90 91 try { 92 mCameraService.addListener(new CameraServiceListener()); 93 } catch(CameraRuntimeException e) { 94 throw new IllegalStateException("Failed to register a camera service listener", 95 e.asChecked()); 96 } catch (RemoteException e) { 97 // impossible 98 } 99 } 100 101 /** 102 * Return the list of currently connected camera devices by 103 * identifier. 104 * 105 * <p>Non-removable cameras use integers starting at 0 for their 106 * identifiers, while removable cameras have a unique identifier for each 107 * individual device, even if they are the same model.</p> 108 * 109 * @return The list of currently connected camera devices. 110 */ 111 public String[] getCameraIdList() throws CameraAccessException { 112 synchronized (mLock) { 113 try { 114 return getOrCreateDeviceIdListLocked().toArray(new String[0]); 115 } catch(CameraAccessException e) { 116 // this should almost never happen, except if mediaserver crashes 117 throw new IllegalStateException( 118 "Failed to query camera service for device ID list", e); 119 } 120 } 121 } 122 123 /** 124 * Register a listener to be notified about camera device availability. 125 * 126 * <p>Registering the same listener again will replace the handler with the 127 * new one provided.</p> 128 * 129 * @param listener The new listener to send camera availability notices to 130 * @param handler The handler on which the listener should be invoked, or 131 * {@code null} to use the current thread's {@link android.os.Looper looper}. 132 */ 133 public void addAvailabilityListener(AvailabilityListener listener, Handler handler) { 134 if (handler == null) { 135 Looper looper = Looper.myLooper(); 136 if (looper == null) { 137 throw new IllegalArgumentException( 138 "No handler given, and current thread has no looper!"); 139 } 140 handler = new Handler(looper); 141 } 142 143 synchronized (mLock) { 144 mListenerMap.put(listener, handler); 145 } 146 } 147 148 /** 149 * Remove a previously-added listener; the listener will no longer receive 150 * connection and disconnection callbacks. 151 * 152 * <p>Removing a listener that isn't registered has no effect.</p> 153 * 154 * @param listener The listener to remove from the notification list 155 */ 156 public void removeAvailabilityListener(AvailabilityListener listener) { 157 synchronized (mLock) { 158 mListenerMap.remove(listener); 159 } 160 } 161 162 /** 163 * <p>Query the capabilities of a camera device. These capabilities are 164 * immutable for a given camera.</p> 165 * 166 * @param cameraId The id of the camera device to query 167 * @return The properties of the given camera 168 * 169 * @throws IllegalArgumentException if the cameraId does not match any 170 * currently connected camera device. 171 * @throws CameraAccessException if the camera is disabled by device policy. 172 * @throws SecurityException if the application does not have permission to 173 * access the camera 174 * 175 * @see #getCameraIdList 176 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 177 */ 178 public CameraCharacteristics getCameraCharacteristics(String cameraId) 179 throws CameraAccessException { 180 181 synchronized (mLock) { 182 if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { 183 throw new IllegalArgumentException(String.format("Camera id %s does not match any" + 184 " currently connected camera device", cameraId)); 185 } 186 } 187 188 CameraMetadataNative info = new CameraMetadataNative(); 189 try { 190 mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info); 191 } catch(CameraRuntimeException e) { 192 throw e.asChecked(); 193 } catch(RemoteException e) { 194 // impossible 195 return null; 196 } 197 return new CameraCharacteristics(info); 198 } 199 200 /** 201 * Helper for openning a connection to a camera with the given ID. 202 * 203 * @param cameraId The unique identifier of the camera device to open 204 * @param listener The listener for the camera. Must not be null. 205 * @param handler The handler to call the listener on. Must not be null. 206 * 207 * @throws CameraAccessException if the camera is disabled by device policy, 208 * or too many camera devices are already open, or the cameraId does not match 209 * any currently available camera device. 210 * 211 * @throws SecurityException if the application does not have permission to 212 * access the camera 213 * @throws IllegalArgumentException if listener or handler is null. 214 * @return A handle to the newly-created camera device. 215 * 216 * @see #getCameraIdList 217 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 218 */ 219 private CameraDevice openCameraDeviceUserAsync(String cameraId, 220 CameraDevice.StateListener listener, Handler handler) 221 throws CameraAccessException { 222 CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); 223 CameraDevice device = null; 224 try { 225 226 synchronized (mLock) { 227 228 ICameraDeviceUser cameraUser = null; 229 230 android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = 231 new android.hardware.camera2.impl.CameraDeviceImpl( 232 cameraId, 233 listener, 234 handler, 235 characteristics); 236 237 BinderHolder holder = new BinderHolder(); 238 239 ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); 240 int id = Integer.parseInt(cameraId); 241 try { 242 mCameraService.connectDevice(callbacks, id, mContext.getPackageName(), 243 USE_CALLING_UID, holder); 244 cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); 245 } catch (CameraRuntimeException e) { 246 if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) { 247 // Use legacy camera implementation for HAL1 devices 248 Log.i(TAG, "Using legacy camera HAL."); 249 cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); 250 } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE || 251 e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE || 252 e.getReason() == CameraAccessException.CAMERA_DISABLED || 253 e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || 254 e.getReason() == CameraAccessException.CAMERA_ERROR) { 255 // Received one of the known connection errors 256 // The remote camera device cannot be connected to, so 257 // set the local camera to the startup error state 258 deviceImpl.setRemoteFailure(e); 259 260 if (e.getReason() == CameraAccessException.CAMERA_DISABLED || 261 e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { 262 // Per API docs, these failures call onError and throw 263 throw e; 264 } 265 } else { 266 // Unexpected failure - rethrow 267 throw e; 268 } 269 } 270 271 // TODO: factor out listener to be non-nested, then move setter to constructor 272 // For now, calling setRemoteDevice will fire initial 273 // onOpened/onUnconfigured callbacks. 274 deviceImpl.setRemoteDevice(cameraUser); 275 device = deviceImpl; 276 } 277 278 } catch (NumberFormatException e) { 279 throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " 280 + cameraId); 281 } catch (CameraRuntimeException e) { 282 throw e.asChecked(); 283 } catch (RemoteException e) { 284 // impossible 285 } 286 return device; 287 } 288 289 /** 290 * Open a connection to a camera with the given ID. 291 * 292 * <p>Use {@link #getCameraIdList} to get the list of available camera 293 * devices. Note that even if an id is listed, open may fail if the device 294 * is disconnected between the calls to {@link #getCameraIdList} and 295 * {@link #openCamera}.</p> 296 * 297 * <p>Once the camera is successfully opened, {@link CameraDevice.StateListener#onOpened} will 298 * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up 299 * for operation by calling {@link CameraDevice#createCaptureSession} and 300 * {@link CameraDevice#createCaptureRequest}</p> 301 * 302 * <!-- 303 * <p>Since the camera device will be opened asynchronously, any asynchronous operations done 304 * on the returned CameraDevice instance will be queued up until the device startup has 305 * completed and the listener's {@link CameraDevice.StateListener#onOpened onOpened} method is 306 * called. The pending operations are then processed in order.</p> 307 * --> 308 * <p>If the camera becomes disconnected during initialization 309 * after this function call returns, 310 * {@link CameraDevice.StateListener#onDisconnected} with a 311 * {@link CameraDevice} in the disconnected state (and 312 * {@link CameraDevice.StateListener#onOpened} will be skipped).</p> 313 * 314 * <p>If opening the camera device fails, then the device listener's 315 * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent 316 * calls on the camera device will throw a {@link CameraAccessException}.</p> 317 * 318 * @param cameraId 319 * The unique identifier of the camera device to open 320 * @param listener 321 * The listener which is invoked once the camera is opened 322 * @param handler 323 * The handler on which the listener should be invoked, or 324 * {@code null} to use the current thread's {@link android.os.Looper looper}. 325 * 326 * @throws CameraAccessException if the camera is disabled by device policy, 327 * or the camera has become or was disconnected. 328 * 329 * @throws IllegalArgumentException if cameraId or the listener was null, 330 * or the cameraId does not match any currently or previously available 331 * camera device. 332 * 333 * @throws SecurityException if the application does not have permission to 334 * access the camera 335 * 336 * @see #getCameraIdList 337 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 338 */ 339 public void openCamera(String cameraId, final CameraDevice.StateListener listener, 340 Handler handler) 341 throws CameraAccessException { 342 343 if (cameraId == null) { 344 throw new IllegalArgumentException("cameraId was null"); 345 } else if (listener == null) { 346 throw new IllegalArgumentException("listener was null"); 347 } else if (handler == null) { 348 if (Looper.myLooper() != null) { 349 handler = new Handler(); 350 } else { 351 throw new IllegalArgumentException( 352 "Looper doesn't exist in the calling thread"); 353 } 354 } 355 356 openCameraDeviceUserAsync(cameraId, listener, handler); 357 } 358 359 /** 360 * Interface for listening to camera devices becoming available or 361 * unavailable. 362 * 363 * <p>Cameras become available when they are no longer in use, or when a new 364 * removable camera is connected. They become unavailable when some 365 * application or service starts using a camera, or when a removable camera 366 * is disconnected.</p> 367 * 368 * @see addAvailabilityListener 369 */ 370 public static abstract class AvailabilityListener { 371 372 /** 373 * A new camera has become available to use. 374 * 375 * <p>The default implementation of this method does nothing.</p> 376 * 377 * @param cameraId The unique identifier of the new camera. 378 */ 379 public void onCameraAvailable(String cameraId) { 380 // default empty implementation 381 } 382 383 /** 384 * A previously-available camera has become unavailable for use. 385 * 386 * <p>If an application had an active CameraDevice instance for the 387 * now-disconnected camera, that application will receive a 388 * {@link CameraDevice.StateListener#onDisconnected disconnection error}.</p> 389 * 390 * <p>The default implementation of this method does nothing.</p> 391 * 392 * @param cameraId The unique identifier of the disconnected camera. 393 */ 394 public void onCameraUnavailable(String cameraId) { 395 // default empty implementation 396 } 397 } 398 399 private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { 400 if (mDeviceIdList == null) { 401 int numCameras = 0; 402 403 try { 404 numCameras = mCameraService.getNumberOfCameras(); 405 } catch(CameraRuntimeException e) { 406 throw e.asChecked(); 407 } catch (RemoteException e) { 408 // impossible 409 return null; 410 } 411 412 mDeviceIdList = new ArrayList<String>(); 413 CameraMetadataNative info = new CameraMetadataNative(); 414 for (int i = 0; i < numCameras; ++i) { 415 // Non-removable cameras use integers starting at 0 for their 416 // identifiers 417 boolean isDeviceSupported = false; 418 try { 419 mCameraService.getCameraCharacteristics(i, info); 420 if (!info.isEmpty()) { 421 isDeviceSupported = true; 422 } else { 423 throw new AssertionError("Expected to get non-empty characteristics"); 424 } 425 } catch(IllegalArgumentException e) { 426 // Got a BAD_VALUE from service, meaning that this 427 // device is not supported. 428 } catch(CameraRuntimeException e) { 429 throw e.asChecked(); 430 } catch(RemoteException e) { 431 // impossible 432 } 433 434 if (isDeviceSupported) { 435 mDeviceIdList.add(String.valueOf(i)); 436 } 437 } 438 439 } 440 return mDeviceIdList; 441 } 442 443 private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) { 444 int problem = e.getReason(); 445 switch (problem) { 446 case CameraAccessException.CAMERA_DISCONNECTED: 447 String errorMsg = CameraAccessException.getDefaultMessage(problem); 448 Log.w(TAG, msg + ": " + errorMsg); 449 break; 450 default: 451 throw new IllegalStateException(msg, e.asChecked()); 452 } 453 } 454 455 // TODO: this class needs unit tests 456 // TODO: extract class into top level 457 private class CameraServiceListener extends ICameraServiceListener.Stub { 458 459 // Keep up-to-date with ICameraServiceListener.h 460 461 // Device physically unplugged 462 public static final int STATUS_NOT_PRESENT = 0; 463 // Device physically has been plugged in 464 // and the camera can be used exclusively 465 public static final int STATUS_PRESENT = 1; 466 // Device physically has been plugged in 467 // but it will not be connect-able until enumeration is complete 468 public static final int STATUS_ENUMERATING = 2; 469 // Camera is in use by another app and cannot be used exclusively 470 public static final int STATUS_NOT_AVAILABLE = 0x80000000; 471 472 // Camera ID -> Status map 473 private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>(); 474 475 private static final String TAG = "CameraServiceListener"; 476 477 @Override 478 public IBinder asBinder() { 479 return this; 480 } 481 482 private boolean isAvailable(int status) { 483 switch (status) { 484 case STATUS_PRESENT: 485 return true; 486 default: 487 return false; 488 } 489 } 490 491 private boolean validStatus(int status) { 492 switch (status) { 493 case STATUS_NOT_PRESENT: 494 case STATUS_PRESENT: 495 case STATUS_ENUMERATING: 496 case STATUS_NOT_AVAILABLE: 497 return true; 498 default: 499 return false; 500 } 501 } 502 503 @Override 504 public void onStatusChanged(int status, int cameraId) throws RemoteException { 505 synchronized(CameraManager.this.mLock) { 506 507 Log.v(TAG, 508 String.format("Camera id %d has status changed to 0x%x", cameraId, status)); 509 510 final String id = String.valueOf(cameraId); 511 512 if (!validStatus(status)) { 513 Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId, 514 status)); 515 return; 516 } 517 518 Integer oldStatus = mDeviceStatus.put(id, status); 519 520 if (oldStatus != null && oldStatus == status) { 521 Log.v(TAG, String.format( 522 "Device status changed to 0x%x, which is what it already was", 523 status)); 524 return; 525 } 526 527 // TODO: consider abstracting out this state minimization + transition 528 // into a separate 529 // more easily testable class 530 // i.e. (new State()).addState(STATE_AVAILABLE) 531 // .addState(STATE_NOT_AVAILABLE) 532 // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), 533 // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) 534 // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); 535 // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); 536 537 // Translate all the statuses to either 'available' or 'not available' 538 // available -> available => no new update 539 // not available -> not available => no new update 540 if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { 541 542 Log.v(TAG, 543 String.format( 544 "Device status was previously available (%d), " + 545 " and is now again available (%d)" + 546 "so no new client visible update will be sent", 547 isAvailable(status), isAvailable(status))); 548 return; 549 } 550 551 final int listenerCount = mListenerMap.size(); 552 for (int i = 0; i < listenerCount; i++) { 553 Handler handler = mListenerMap.valueAt(i); 554 final AvailabilityListener listener = mListenerMap.keyAt(i); 555 if (isAvailable(status)) { 556 handler.post( 557 new Runnable() { 558 @Override 559 public void run() { 560 listener.onCameraAvailable(id); 561 } 562 }); 563 } else { 564 handler.post( 565 new Runnable() { 566 @Override 567 public void run() { 568 listener.onCameraUnavailable(id); 569 } 570 }); 571 } 572 } // for 573 } // synchronized 574 } // onStatusChanged 575 } // CameraServiceListener 576} // CameraManager 577