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