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