blob: c1c9435d76dbd31062c4be8eeb7198e26a7f6c3a [file] [log] [blame]
Eino-Ville Talvalab2675542012-12-12 13:29:45 -08001/*
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.photography;
18
Igor Murashkine363fbb2013-06-25 20:26:06 +000019import android.content.Context;
20import android.hardware.ICameraService;
21import android.hardware.ICameraServiceListener;
22import android.hardware.IProCameraUser;
23import android.hardware.photography.utils.CameraBinderDecorator;
24import android.hardware.photography.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
Eino-Ville Talvalab2675542012-12-12 13:29:45 -080035/**
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.photography photography}
46 * package documentation.</p>
47 */
48public final class CameraManager {
49
50 /**
Igor Murashkine363fbb2013-06-25 20:26:06 +000051 * 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 /**
Eino-Ville Talvalab2675542012-12-12 13:29:45 -080063 * @hide
64 */
Igor Murashkine363fbb2013-06-25 20:26:06 +000065 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 }
Eino-Ville Talvalab2675542012-12-12 13:29:45 -080085 }
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 */
Igor Murashkine363fbb2013-06-25 20:26:06 +000095 public String[] getDeviceIdList() throws CameraAccessException {
96 synchronized (mLock) {
Igor Murashkin70725502013-06-25 20:27:06 +000097 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 }
Igor Murashkine363fbb2013-06-25 20:26:06 +0000104 }
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800105 }
106
107 /**
108 * Register a listener to be notified about camera device availability.
109 *
Igor Murashkine363fbb2013-06-25 20:26:06 +0000110 * Registering a listener more than once has no effect.
111 *
112 * @param listener the new listener to send camera availability notices to.
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800113 */
114 public void registerCameraListener(CameraListener listener) {
Igor Murashkine363fbb2013-06-25 20:26:06 +0000115 synchronized (mLock) {
116 mListenerSet.add(listener);
117 }
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800118 }
119
120 /**
121 * Remove a previously-added listener; the listener will no longer receive
122 * connection and disconnection callbacks.
123 *
Igor Murashkine363fbb2013-06-25 20:26:06 +0000124 * Removing a listener that isn't registered has no effect.
125 *
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800126 * @param listener the listener to remove from the notification list
127 */
128 public void unregisterCameraListener(CameraListener listener) {
Igor Murashkine363fbb2013-06-25 20:26:06 +0000129 synchronized (mLock) {
130 mListenerSet.remove(listener);
131 }
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800132 }
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 {
Igor Murashkine363fbb2013-06-25 20:26:06 +0000152
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();
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800164 }
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 {
Igor Murashkine363fbb2013-06-25 20:26:06 +0000186
187 try {
Igor Murashkine363fbb2013-06-25 20:26:06 +0000188
189 synchronized (mLock) {
Igor Murashkin70725502013-06-25 20:27:06 +0000190
191 ICameraDeviceUser cameraUser;
192
193 android.hardware.photography.impl.CameraDevice device =
194 new android.hardware.photography.impl.CameraDevice(cameraId);
195
196 cameraUser = mCameraService.connectDevice(device.getCallbacks(),
Igor Murashkine363fbb2013-06-25 20:26:06 +0000197 Integer.parseInt(cameraId),
198 mContext.getPackageName(), USE_CALLING_UID);
199
Igor Murashkin70725502013-06-25 20:27:06 +0000200 // 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
Igor Murashkine363fbb2013-06-25 20:26:06 +0000212 }
213
Igor Murashkine363fbb2013-06-25 20:26:06 +0000214 } 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 }
Eino-Ville Talvalab2675542012-12-12 13:29:45 -0800228 }
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 }
Igor Murashkine363fbb2013-06-25 20:26:06 +0000255
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 {
Igor Murashkin70725502013-06-25 20:27:06 +0000330 synchronized(CameraManager.this.mLock) {
Igor Murashkine363fbb2013-06-25 20:26:06 +0000331
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