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