CameraManager.java revision 2f1a2e423e0fbb64467d6fcfa4e82c6384f31210
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.utils.CameraBinderDecorator; 24import android.hardware.camera2.utils.CameraRuntimeException; 25import android.os.Binder; 26import android.os.IBinder; 27import android.os.RemoteException; 28import android.os.ServiceManager; 29import android.util.Log; 30 31import java.util.ArrayList; 32import java.util.HashMap; 33import java.util.HashSet; 34 35/** 36 * <p>An interface for iterating, listing, and connecting to 37 * {@link CameraDevice CameraDevices}.</p> 38 * 39 * <p>You can get an instance of this class by calling 40 * {@link android.content.Context#getSystemService(String) Context.getSystemService()}.</p> 41 * 42 * <pre>CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);</pre> 43 * 44 * <p>For more details about communicating with camera devices, read the Camera 45 * developer guide or the {@link android.hardware.camera2 camera2} 46 * package documentation.</p> 47 */ 48public final class CameraManager { 49 50 /** 51 * This should match the ICameraService definition 52 */ 53 private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; 54 private static final int USE_CALLING_UID = -1; 55 56 private final ICameraService mCameraService; 57 private ArrayList<String> mDeviceIdList; 58 private HashSet<CameraListener> mListenerSet; 59 private final Context mContext; 60 private final Object mLock = new Object(); 61 62 /** 63 * @hide 64 */ 65 public CameraManager(Context context) { 66 mContext = context; 67 68 IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); 69 ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); 70 71 /** 72 * Wrap the camera service in a decorator which automatically translates return codes 73 * into exceptions, and RemoteExceptions into other exceptions. 74 */ 75 mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw); 76 77 try { 78 mCameraService.addListener(new CameraServiceListener()); 79 } catch(CameraRuntimeException e) { 80 throw new IllegalStateException("Failed to register a camera service listener", 81 e.asChecked()); 82 } catch (RemoteException e) { 83 // impossible 84 } 85 } 86 87 /** 88 * <p>Return the list of currently connected camera devices by 89 * identifier. Non-removable cameras use integers starting at 0 for their 90 * identifiers, while removable cameras have a unique identifier for each 91 * individual device, even if they are the same model.</p> 92 * 93 * @return The list of currently connected camera devices. 94 */ 95 public String[] getDeviceIdList() throws CameraAccessException { 96 synchronized (mLock) { 97 try { 98 return getOrCreateDeviceIdListLocked().toArray(new String[0]); 99 } catch(CameraAccessException e) { 100 // this should almost never happen, except if mediaserver crashes 101 throw new IllegalStateException( 102 "Failed to query camera service for device ID list", e); 103 } 104 } 105 } 106 107 /** 108 * Register a listener to be notified about camera device availability. 109 * 110 * Registering a listener more than once has no effect. 111 * 112 * @param listener the new listener to send camera availability notices to. 113 */ 114 public void registerCameraListener(CameraListener listener) { 115 synchronized (mLock) { 116 mListenerSet.add(listener); 117 } 118 } 119 120 /** 121 * Remove a previously-added listener; the listener will no longer receive 122 * connection and disconnection callbacks. 123 * 124 * Removing a listener that isn't registered has no effect. 125 * 126 * @param listener the listener to remove from the notification list 127 */ 128 public void unregisterCameraListener(CameraListener listener) { 129 synchronized (mLock) { 130 mListenerSet.remove(listener); 131 } 132 } 133 134 /** 135 * <p>Query the capabilities of a camera device. These capabilities are 136 * immutable for a given camera.</p> 137 * 138 * @param cameraId The id of the camera device to query 139 * @return The properties of the given camera 140 * 141 * @throws IllegalArgumentException if the cameraId does not match any 142 * currently connected camera device. 143 * @throws CameraAccessException if the camera is disabled by device policy. 144 * @throws SecurityException if the application does not have permission to 145 * access the camera 146 * 147 * @see #getDeviceIdList 148 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 149 */ 150 public CameraProperties getCameraProperties(String cameraId) 151 throws CameraAccessException { 152 153 synchronized (mLock) { 154 if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { 155 throw new IllegalArgumentException(String.format("Camera id %s does not match any" + 156 " currently connected camera device", cameraId)); 157 } 158 } 159 160 // TODO: implement and call a service function to get the capabilities on C++ side 161 162 // TODO: get properties from service 163 return new CameraProperties(); 164 } 165 166 /** 167 * Open a connection to a camera with the given ID. Use 168 * {@link #getDeviceIdList} to get the list of available camera 169 * devices. Note that even if an id is listed, open may fail if the device 170 * is disconnected between the calls to {@link #getDeviceIdList} and 171 * {@link #openCamera}. 172 * 173 * @param cameraId The unique identifier of the camera device to open 174 * 175 * @throws IllegalArgumentException if the cameraId does not match any 176 * currently connected camera device. 177 * @throws CameraAccessException if the camera is disabled by device policy, 178 * or too many camera devices are already open. 179 * @throws SecurityException if the application does not have permission to 180 * access the camera 181 * 182 * @see #getDeviceIdList 183 * @see android.app.admin.DevicePolicyManager#setCameraDisabled 184 */ 185 public CameraDevice openCamera(String cameraId) throws CameraAccessException { 186 187 try { 188 189 synchronized (mLock) { 190 191 ICameraDeviceUser cameraUser; 192 193 android.hardware.camera2.impl.CameraDevice device = 194 new android.hardware.camera2.impl.CameraDevice(cameraId); 195 196 cameraUser = mCameraService.connectDevice(device.getCallbacks(), 197 Integer.parseInt(cameraId), 198 mContext.getPackageName(), USE_CALLING_UID); 199 200 // TODO: change ICameraService#connectDevice to return status_t 201 if (cameraUser == null) { 202 // TEMPORARY CODE. 203 // catch-all exception since we aren't yet getting the actual error code 204 throw new IllegalStateException("Failed to open camera device"); 205 } 206 207 // TODO: factor out listener to be non-nested, then move setter to constructor 208 device.setRemoteDevice(cameraUser); 209 210 return device; 211 212 } 213 214 } catch (NumberFormatException e) { 215 throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " 216 + cameraId); 217 } catch (CameraRuntimeException e) { 218 if (e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { 219 throw new IllegalArgumentException("Invalid camera ID specified -- " + 220 "perhaps the camera was physically disconnected", e); 221 } else { 222 throw e.asChecked(); 223 } 224 } catch (RemoteException e) { 225 // impossible 226 return null; 227 } 228 } 229 230 /** 231 * Interface for listening to cameras becoming available or unavailable. 232 * Cameras become available when they are no longer in use, or when a new 233 * removable camera is connected. They become unavailable when some 234 * application or service starts using a camera, or when a removable camera 235 * is disconnected. 236 */ 237 public interface CameraListener { 238 /** 239 * A new camera has become available to use. 240 * 241 * @param cameraId The unique identifier of the new camera. 242 */ 243 public void onCameraAvailable(String cameraId); 244 245 /** 246 * A previously-available camera has become unavailable for use. If an 247 * application had an active CameraDevice instance for the 248 * now-disconnected camera, that application will receive a {@link 249 * CameraDevice.ErrorListener#DEVICE_DISCONNECTED disconnection error}. 250 * 251 * @param cameraId The unique identifier of the disconnected camera. 252 */ 253 public void onCameraUnavailable(String cameraId); 254 } 255 256 private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { 257 if (mDeviceIdList == null) { 258 int numCameras = 0; 259 260 try { 261 numCameras = mCameraService.getNumberOfCameras(); 262 } catch(CameraRuntimeException e) { 263 throw e.asChecked(); 264 } catch (RemoteException e) { 265 // impossible 266 return null; 267 } 268 269 mDeviceIdList = new ArrayList<String>(); 270 for (int i = 0; i < numCameras; ++i) { 271 // Non-removable cameras use integers starting at 0 for their 272 // identifiers 273 mDeviceIdList.add(String.valueOf(i)); 274 } 275 276 } 277 return mDeviceIdList; 278 } 279 280 // TODO: this class needs unit tests 281 // TODO: extract class into top level 282 private class CameraServiceListener extends Binder implements ICameraServiceListener { 283 284 // Keep up-to-date with ICameraServiceListener.h 285 286 // Device physically unplugged 287 public static final int STATUS_NOT_PRESENT = 0; 288 // Device physically has been plugged in 289 // and the camera can be used exclusively 290 public static final int STATUS_PRESENT = 1; 291 // Device physically has been plugged in 292 // but it will not be connect-able until enumeration is complete 293 public static final int STATUS_ENUMERATING = 2; 294 // Camera is in use by another app and cannot be used exclusively 295 public static final int STATUS_NOT_AVAILABLE = 0x80000000; 296 297 // Camera ID -> Status map 298 private final HashMap<String, Integer> mDeviceStatus = new HashMap<String, Integer>(); 299 300 private static final String TAG = "CameraServiceListener"; 301 302 @Override 303 public IBinder asBinder() { 304 return this; 305 } 306 307 private boolean isAvailable(int status) { 308 switch (status) { 309 case STATUS_PRESENT: 310 return true; 311 default: 312 return false; 313 } 314 } 315 316 private boolean validStatus(int status) { 317 switch (status) { 318 case STATUS_NOT_PRESENT: 319 case STATUS_PRESENT: 320 case STATUS_ENUMERATING: 321 case STATUS_NOT_AVAILABLE: 322 return true; 323 default: 324 return false; 325 } 326 } 327 328 @Override 329 public void onStatusChanged(int status, int cameraId) throws RemoteException { 330 synchronized(CameraManager.this.mLock) { 331 332 Log.v(TAG, 333 String.format("Camera id %d has status changed to 0x%x", cameraId, status)); 334 335 String id = String.valueOf(cameraId); 336 337 if (!validStatus(status)) { 338 Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId, 339 status)); 340 return; 341 } 342 343 Integer oldStatus = mDeviceStatus.put(id, status); 344 345 if (oldStatus == status) { 346 Log.v(TAG, String.format( 347 "Device status changed to 0x%x, which is what it already was", 348 status)); 349 return; 350 } 351 352 // TODO: consider abstracting out this state minimization + transition 353 // into a separate 354 // more easily testable class 355 // i.e. (new State()).addState(STATE_AVAILABLE) 356 // .addState(STATE_NOT_AVAILABLE) 357 // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), 358 // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) 359 // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); 360 // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); 361 362 // Translate all the statuses to either 'available' or 'not available' 363 // available -> available => no new update 364 // not available -> not available => no new update 365 if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { 366 367 Log.v(TAG, 368 String.format( 369 "Device status was previously available (%d), " + 370 " and is now again available (%d)" + 371 "so no new client visible update will be sent", 372 isAvailable(status), isAvailable(status))); 373 return; 374 } 375 376 for (CameraListener listener : mListenerSet) { 377 if (isAvailable(status)) { 378 listener.onCameraAvailable(id); 379 } else { 380 listener.onCameraUnavailable(id); 381 } 382 } // for 383 } // synchronized 384 } // onStatusChanged 385 } // CameraServiceListener 386} // CameraManager 387