CameraManager.java revision d8c21e8bd09658f5843712f0176db3517ba44c51
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_IN_USE ||
248                            e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
249                            e.getReason() == CameraAccessException.CAMERA_DISABLED ||
250                            e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
251                            e.getReason() == CameraAccessException.CAMERA_ERROR) {
252                        // Received one of the known connection errors
253                        // The remote camera device cannot be connected to, so
254                        // set the local camera to the startup error state
255                        deviceImpl.setRemoteFailure(e);
256
257                        if (e.getReason() == CameraAccessException.CAMERA_DISABLED ||
258                                e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) {
259                            // Per API docs, these failures call onError and throw
260                            throw e;
261                        }
262                    } else {
263                        // Unexpected failure - rethrow
264                        throw e;
265                    }
266                }
267
268                // TODO: factor out listener to be non-nested, then move setter to constructor
269                // For now, calling setRemoteDevice will fire initial
270                // onOpened/onUnconfigured callbacks.
271                deviceImpl.setRemoteDevice(cameraUser);
272                device = deviceImpl;
273            }
274
275        } catch (NumberFormatException e) {
276            throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
277                    + cameraId);
278        } catch (CameraRuntimeException e) {
279            throw e.asChecked();
280        } catch (RemoteException e) {
281            // impossible
282        }
283        return device;
284    }
285
286    /**
287     * Open a connection to a camera with the given ID.
288     *
289     * <p>Use {@link #getCameraIdList} to get the list of available camera
290     * devices. Note that even if an id is listed, open may fail if the device
291     * is disconnected between the calls to {@link #getCameraIdList} and
292     * {@link #openCamera}.</p>
293     *
294     * <p>Once the camera is successfully opened, {@link CameraDevice.StateListener#onOpened} will
295     * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
296     * for operation by calling {@link CameraDevice#createCaptureSession} and
297     * {@link CameraDevice#createCaptureRequest}</p>
298     *
299     * <!--
300     * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
301     * on the returned CameraDevice instance will be queued up until the device startup has
302     * completed and the listener's {@link CameraDevice.StateListener#onOpened onOpened} method is
303     * called. The pending operations are then processed in order.</p>
304     * -->
305     * <p>If the camera becomes disconnected during initialization
306     * after this function call returns,
307     * {@link CameraDevice.StateListener#onDisconnected} with a
308     * {@link CameraDevice} in the disconnected state (and
309     * {@link CameraDevice.StateListener#onOpened} will be skipped).</p>
310     *
311     * <p>If opening the camera device fails, then the device listener's
312     * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent
313     * calls on the camera device will throw a {@link CameraAccessException}.</p>
314     *
315     * @param cameraId
316     *             The unique identifier of the camera device to open
317     * @param listener
318     *             The listener which is invoked once the camera is opened
319     * @param handler
320     *             The handler on which the listener should be invoked, or
321     *             {@code null} to use the current thread's {@link android.os.Looper looper}.
322     *
323     * @throws CameraAccessException if the camera is disabled by device policy,
324     * or the camera has become or was disconnected.
325     *
326     * @throws IllegalArgumentException if cameraId or the listener was null,
327     * or the cameraId does not match any currently or previously available
328     * camera device.
329     *
330     * @throws SecurityException if the application does not have permission to
331     * access the camera
332     *
333     * @see #getCameraIdList
334     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
335     */
336    public void openCamera(String cameraId, final CameraDevice.StateListener listener,
337            Handler handler)
338            throws CameraAccessException {
339
340        if (cameraId == null) {
341            throw new IllegalArgumentException("cameraId was null");
342        } else if (listener == null) {
343            throw new IllegalArgumentException("listener was null");
344        } else if (handler == null) {
345            if (Looper.myLooper() != null) {
346                handler = new Handler();
347            } else {
348                throw new IllegalArgumentException(
349                        "Looper doesn't exist in the calling thread");
350            }
351        }
352
353        openCameraDeviceUserAsync(cameraId, listener, handler);
354    }
355
356    /**
357     * Interface for listening to camera devices becoming available or
358     * unavailable.
359     *
360     * <p>Cameras become available when they are no longer in use, or when a new
361     * removable camera is connected. They become unavailable when some
362     * application or service starts using a camera, or when a removable camera
363     * is disconnected.</p>
364     *
365     * @see addAvailabilityListener
366     */
367    public static abstract class AvailabilityListener {
368
369        /**
370         * A new camera has become available to use.
371         *
372         * <p>The default implementation of this method does nothing.</p>
373         *
374         * @param cameraId The unique identifier of the new camera.
375         */
376        public void onCameraAvailable(String cameraId) {
377            // default empty implementation
378        }
379
380        /**
381         * A previously-available camera has become unavailable for use.
382         *
383         * <p>If an application had an active CameraDevice instance for the
384         * now-disconnected camera, that application will receive a
385         * {@link CameraDevice.StateListener#onDisconnected disconnection error}.</p>
386         *
387         * <p>The default implementation of this method does nothing.</p>
388         *
389         * @param cameraId The unique identifier of the disconnected camera.
390         */
391        public void onCameraUnavailable(String cameraId) {
392            // default empty implementation
393        }
394    }
395
396    private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
397        if (mDeviceIdList == null) {
398            int numCameras = 0;
399
400            try {
401                numCameras = mCameraService.getNumberOfCameras();
402            } catch(CameraRuntimeException e) {
403                throw e.asChecked();
404            } catch (RemoteException e) {
405                // impossible
406                return null;
407            }
408
409            mDeviceIdList = new ArrayList<String>();
410            CameraMetadataNative info = new CameraMetadataNative();
411            for (int i = 0; i < numCameras; ++i) {
412                // Non-removable cameras use integers starting at 0 for their
413                // identifiers
414                boolean isDeviceSupported = false;
415                try {
416                    mCameraService.getCameraCharacteristics(i, info);
417                    if (!info.isEmpty()) {
418                        isDeviceSupported = true;
419                    } else {
420                        throw new AssertionError("Expected to get non-empty characteristics");
421                    }
422                } catch(IllegalArgumentException  e) {
423                    // Got a BAD_VALUE from service, meaning that this
424                    // device is not supported.
425                } catch(CameraRuntimeException e) {
426                    throw e.asChecked();
427                } catch(RemoteException e) {
428                    // impossible
429                }
430
431                if (isDeviceSupported) {
432                    mDeviceIdList.add(String.valueOf(i));
433                }
434            }
435
436        }
437        return mDeviceIdList;
438    }
439
440    // TODO: this class needs unit tests
441    // TODO: extract class into top level
442    private class CameraServiceListener extends ICameraServiceListener.Stub {
443
444        // Keep up-to-date with ICameraServiceListener.h
445
446        // Device physically unplugged
447        public static final int STATUS_NOT_PRESENT = 0;
448        // Device physically has been plugged in
449        // and the camera can be used exclusively
450        public static final int STATUS_PRESENT = 1;
451        // Device physically has been plugged in
452        // but it will not be connect-able until enumeration is complete
453        public static final int STATUS_ENUMERATING = 2;
454        // Camera is in use by another app and cannot be used exclusively
455        public static final int STATUS_NOT_AVAILABLE = 0x80000000;
456
457        // Camera ID -> Status map
458        private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
459
460        private static final String TAG = "CameraServiceListener";
461
462        @Override
463        public IBinder asBinder() {
464            return this;
465        }
466
467        private boolean isAvailable(int status) {
468            switch (status) {
469                case STATUS_PRESENT:
470                    return true;
471                default:
472                    return false;
473            }
474        }
475
476        private boolean validStatus(int status) {
477            switch (status) {
478                case STATUS_NOT_PRESENT:
479                case STATUS_PRESENT:
480                case STATUS_ENUMERATING:
481                case STATUS_NOT_AVAILABLE:
482                    return true;
483                default:
484                    return false;
485            }
486        }
487
488        @Override
489        public void onStatusChanged(int status, int cameraId) throws RemoteException {
490            synchronized(CameraManager.this.mLock) {
491
492                Log.v(TAG,
493                        String.format("Camera id %d has status changed to 0x%x", cameraId, status));
494
495                final String id = String.valueOf(cameraId);
496
497                if (!validStatus(status)) {
498                    Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId,
499                            status));
500                    return;
501                }
502
503                Integer oldStatus = mDeviceStatus.put(id, status);
504
505                if (oldStatus != null && oldStatus == status) {
506                    Log.v(TAG, String.format(
507                            "Device status changed to 0x%x, which is what it already was",
508                            status));
509                    return;
510                }
511
512                // TODO: consider abstracting out this state minimization + transition
513                // into a separate
514                // more easily testable class
515                // i.e. (new State()).addState(STATE_AVAILABLE)
516                //                   .addState(STATE_NOT_AVAILABLE)
517                //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
518                //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
519                //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
520                //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);
521
522                // Translate all the statuses to either 'available' or 'not available'
523                //  available -> available         => no new update
524                //  not available -> not available => no new update
525                if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {
526
527                    Log.v(TAG,
528                            String.format(
529                                    "Device status was previously available (%d), " +
530                                            " and is now again available (%d)" +
531                                            "so no new client visible update will be sent",
532                                    isAvailable(status), isAvailable(status)));
533                    return;
534                }
535
536                final int listenerCount = mListenerMap.size();
537                for (int i = 0; i < listenerCount; i++) {
538                    Handler handler = mListenerMap.valueAt(i);
539                    final AvailabilityListener listener = mListenerMap.keyAt(i);
540                    if (isAvailable(status)) {
541                        handler.post(
542                            new Runnable() {
543                                @Override
544                                public void run() {
545                                    listener.onCameraAvailable(id);
546                                }
547                            });
548                    } else {
549                        handler.post(
550                            new Runnable() {
551                                @Override
552                                public void run() {
553                                    listener.onCameraUnavailable(id);
554                                }
555                            });
556                    }
557                } // for
558            } // synchronized
559        } // onStatusChanged
560    } // CameraServiceListener
561} // CameraManager
562