CameraManager.java revision 85e5f85f2e963f5093023050084ef6fbbe20a7ce
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            handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
89        }
90
91        try {
92            mCameraService.addListener(new CameraServiceListener());
93        } catch(CameraRuntimeException e) {
94            throw new IllegalStateException("Failed to register a camera service listener",
95                    e.asChecked());
96        } catch (RemoteException e) {
97            // impossible
98        }
99    }
100
101    /**
102     * Return the list of currently connected camera devices by
103     * identifier.
104     *
105     * <p>Non-removable cameras use integers starting at 0 for their
106     * identifiers, while removable cameras have a unique identifier for each
107     * individual device, even if they are the same model.</p>
108     *
109     * @return The list of currently connected camera devices.
110     */
111    public String[] getCameraIdList() throws CameraAccessException {
112        synchronized (mLock) {
113            try {
114                return getOrCreateDeviceIdListLocked().toArray(new String[0]);
115            } catch(CameraAccessException e) {
116                // this should almost never happen, except if mediaserver crashes
117                throw new IllegalStateException(
118                        "Failed to query camera service for device ID list", e);
119            }
120        }
121    }
122
123    /**
124     * Register a listener to be notified about camera device availability.
125     *
126     * <p>Registering the same listener again will replace the handler with the
127     * new one provided.</p>
128     *
129     * @param listener The new listener to send camera availability notices to
130     * @param handler The handler on which the listener should be invoked, or
131     * {@code null} to use the current thread's {@link android.os.Looper looper}.
132     */
133    public void addAvailabilityListener(AvailabilityListener listener, Handler handler) {
134        if (handler == null) {
135            Looper looper = Looper.myLooper();
136            if (looper == null) {
137                throw new IllegalArgumentException(
138                        "No handler given, and current thread has no looper!");
139            }
140            handler = new Handler(looper);
141        }
142
143        synchronized (mLock) {
144            mListenerMap.put(listener, handler);
145        }
146    }
147
148    /**
149     * Remove a previously-added listener; the listener will no longer receive
150     * connection and disconnection callbacks.
151     *
152     * <p>Removing a listener that isn't registered has no effect.</p>
153     *
154     * @param listener The listener to remove from the notification list
155     */
156    public void removeAvailabilityListener(AvailabilityListener listener) {
157        synchronized (mLock) {
158            mListenerMap.remove(listener);
159        }
160    }
161
162    /**
163     * <p>Query the capabilities of a camera device. These capabilities are
164     * immutable for a given camera.</p>
165     *
166     * @param cameraId The id of the camera device to query
167     * @return The properties of the given camera
168     *
169     * @throws IllegalArgumentException if the cameraId does not match any
170     * currently connected camera device.
171     * @throws CameraAccessException if the camera is disabled by device policy.
172     * @throws SecurityException if the application does not have permission to
173     * access the camera
174     *
175     * @see #getCameraIdList
176     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
177     */
178    public CameraCharacteristics getCameraCharacteristics(String cameraId)
179            throws CameraAccessException {
180
181        synchronized (mLock) {
182            if (!getOrCreateDeviceIdListLocked().contains(cameraId)) {
183                throw new IllegalArgumentException(String.format("Camera id %s does not match any" +
184                        " currently connected camera device", cameraId));
185            }
186        }
187
188        CameraMetadataNative info = new CameraMetadataNative();
189        try {
190            mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info);
191        } catch(CameraRuntimeException e) {
192            throw e.asChecked();
193        } catch(RemoteException e) {
194            // impossible
195            return null;
196        }
197        return new CameraCharacteristics(info);
198    }
199
200    /**
201     * Helper for openning a connection to a camera with the given ID.
202     *
203     * @param cameraId The unique identifier of the camera device to open
204     * @param listener The listener for the camera. Must not be null.
205     * @param handler  The handler to call the listener on. Must not be null.
206     *
207     * @throws CameraAccessException if the camera is disabled by device policy,
208     * or too many camera devices are already open, or the cameraId does not match
209     * any currently available camera device.
210     *
211     * @throws SecurityException if the application does not have permission to
212     * access the camera
213     * @throws IllegalArgumentException if listener or handler is null.
214     * @return A handle to the newly-created camera device.
215     *
216     * @see #getCameraIdList
217     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
218     */
219    private CameraDevice openCameraDeviceUserAsync(String cameraId,
220            CameraDevice.StateListener listener, Handler handler)
221            throws CameraAccessException {
222        CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
223        CameraDevice device = null;
224        try {
225
226            synchronized (mLock) {
227
228                ICameraDeviceUser cameraUser = null;
229
230                android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
231                        new android.hardware.camera2.impl.CameraDeviceImpl(
232                                cameraId,
233                                listener,
234                                handler,
235                                characteristics);
236
237                BinderHolder holder = new BinderHolder();
238
239                ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
240                int id = Integer.parseInt(cameraId);
241                try {
242                    mCameraService.connectDevice(callbacks, id, mContext.getPackageName(),
243                            USE_CALLING_UID, holder);
244                    cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
245                } catch (CameraRuntimeException e) {
246                    if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
247                        // Use legacy camera implementation for HAL1 devices
248                        Log.i(TAG, "Using legacy camera HAL.");
249                        cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
250                    } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
251                            e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
252                            e.getReason() == CameraAccessException.CAMERA_DISABLED ||
253                            e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
254                            e.getReason() == CameraAccessException.CAMERA_ERROR) {
255                        // Received one of the known connection errors
256                        // The remote camera device cannot be connected to, so
257                        // set the local camera to the startup error state
258                        deviceImpl.setRemoteFailure(e);
259
260                        if (e.getReason() == CameraAccessException.CAMERA_DISABLED ||
261                                e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) {
262                            // Per API docs, these failures call onError and throw
263                            throw e;
264                        }
265                    } else {
266                        // Unexpected failure - rethrow
267                        throw e;
268                    }
269                }
270
271                // TODO: factor out listener to be non-nested, then move setter to constructor
272                // For now, calling setRemoteDevice will fire initial
273                // onOpened/onUnconfigured callbacks.
274                deviceImpl.setRemoteDevice(cameraUser);
275                device = deviceImpl;
276            }
277
278        } catch (NumberFormatException e) {
279            throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
280                    + cameraId);
281        } catch (CameraRuntimeException e) {
282            throw e.asChecked();
283        } catch (RemoteException e) {
284            // impossible
285        }
286        return device;
287    }
288
289    /**
290     * Open a connection to a camera with the given ID.
291     *
292     * <p>Use {@link #getCameraIdList} to get the list of available camera
293     * devices. Note that even if an id is listed, open may fail if the device
294     * is disconnected between the calls to {@link #getCameraIdList} and
295     * {@link #openCamera}.</p>
296     *
297     * <p>Once the camera is successfully opened, {@link CameraDevice.StateListener#onOpened} will
298     * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
299     * for operation by calling {@link CameraDevice#createCaptureSession} and
300     * {@link CameraDevice#createCaptureRequest}</p>
301     *
302     * <!--
303     * <p>Since the camera device will be opened asynchronously, any asynchronous operations done
304     * on the returned CameraDevice instance will be queued up until the device startup has
305     * completed and the listener's {@link CameraDevice.StateListener#onOpened onOpened} method is
306     * called. The pending operations are then processed in order.</p>
307     * -->
308     * <p>If the camera becomes disconnected during initialization
309     * after this function call returns,
310     * {@link CameraDevice.StateListener#onDisconnected} with a
311     * {@link CameraDevice} in the disconnected state (and
312     * {@link CameraDevice.StateListener#onOpened} will be skipped).</p>
313     *
314     * <p>If opening the camera device fails, then the device listener's
315     * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent
316     * calls on the camera device will throw a {@link CameraAccessException}.</p>
317     *
318     * @param cameraId
319     *             The unique identifier of the camera device to open
320     * @param listener
321     *             The listener which is invoked once the camera is opened
322     * @param handler
323     *             The handler on which the listener should be invoked, or
324     *             {@code null} to use the current thread's {@link android.os.Looper looper}.
325     *
326     * @throws CameraAccessException if the camera is disabled by device policy,
327     * or the camera has become or was disconnected.
328     *
329     * @throws IllegalArgumentException if cameraId or the listener was null,
330     * or the cameraId does not match any currently or previously available
331     * camera device.
332     *
333     * @throws SecurityException if the application does not have permission to
334     * access the camera
335     *
336     * @see #getCameraIdList
337     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
338     */
339    public void openCamera(String cameraId, final CameraDevice.StateListener listener,
340            Handler handler)
341            throws CameraAccessException {
342
343        if (cameraId == null) {
344            throw new IllegalArgumentException("cameraId was null");
345        } else if (listener == null) {
346            throw new IllegalArgumentException("listener was null");
347        } else if (handler == null) {
348            if (Looper.myLooper() != null) {
349                handler = new Handler();
350            } else {
351                throw new IllegalArgumentException(
352                        "Looper doesn't exist in the calling thread");
353            }
354        }
355
356        openCameraDeviceUserAsync(cameraId, listener, handler);
357    }
358
359    /**
360     * Interface for listening to camera devices becoming available or
361     * unavailable.
362     *
363     * <p>Cameras become available when they are no longer in use, or when a new
364     * removable camera is connected. They become unavailable when some
365     * application or service starts using a camera, or when a removable camera
366     * is disconnected.</p>
367     *
368     * @see addAvailabilityListener
369     */
370    public static abstract class AvailabilityListener {
371
372        /**
373         * A new camera has become available to use.
374         *
375         * <p>The default implementation of this method does nothing.</p>
376         *
377         * @param cameraId The unique identifier of the new camera.
378         */
379        public void onCameraAvailable(String cameraId) {
380            // default empty implementation
381        }
382
383        /**
384         * A previously-available camera has become unavailable for use.
385         *
386         * <p>If an application had an active CameraDevice instance for the
387         * now-disconnected camera, that application will receive a
388         * {@link CameraDevice.StateListener#onDisconnected disconnection error}.</p>
389         *
390         * <p>The default implementation of this method does nothing.</p>
391         *
392         * @param cameraId The unique identifier of the disconnected camera.
393         */
394        public void onCameraUnavailable(String cameraId) {
395            // default empty implementation
396        }
397    }
398
399    private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
400        if (mDeviceIdList == null) {
401            int numCameras = 0;
402
403            try {
404                numCameras = mCameraService.getNumberOfCameras();
405            } catch(CameraRuntimeException e) {
406                throw e.asChecked();
407            } catch (RemoteException e) {
408                // impossible
409                return null;
410            }
411
412            mDeviceIdList = new ArrayList<String>();
413            CameraMetadataNative info = new CameraMetadataNative();
414            for (int i = 0; i < numCameras; ++i) {
415                // Non-removable cameras use integers starting at 0 for their
416                // identifiers
417                boolean isDeviceSupported = false;
418                try {
419                    mCameraService.getCameraCharacteristics(i, info);
420                    if (!info.isEmpty()) {
421                        isDeviceSupported = true;
422                    } else {
423                        throw new AssertionError("Expected to get non-empty characteristics");
424                    }
425                } catch(IllegalArgumentException  e) {
426                    // Got a BAD_VALUE from service, meaning that this
427                    // device is not supported.
428                } catch(CameraRuntimeException e) {
429                    throw e.asChecked();
430                } catch(RemoteException e) {
431                    // impossible
432                }
433
434                if (isDeviceSupported) {
435                    mDeviceIdList.add(String.valueOf(i));
436                }
437            }
438
439        }
440        return mDeviceIdList;
441    }
442
443    private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
444        int problem = e.getReason();
445        switch (problem) {
446            case CameraAccessException.CAMERA_DISCONNECTED:
447                String errorMsg = CameraAccessException.getDefaultMessage(problem);
448                Log.w(TAG, msg + ": " + errorMsg);
449                break;
450            default:
451                throw new IllegalStateException(msg, e.asChecked());
452        }
453    }
454
455    // TODO: this class needs unit tests
456    // TODO: extract class into top level
457    private class CameraServiceListener extends ICameraServiceListener.Stub {
458
459        // Keep up-to-date with ICameraServiceListener.h
460
461        // Device physically unplugged
462        public static final int STATUS_NOT_PRESENT = 0;
463        // Device physically has been plugged in
464        // and the camera can be used exclusively
465        public static final int STATUS_PRESENT = 1;
466        // Device physically has been plugged in
467        // but it will not be connect-able until enumeration is complete
468        public static final int STATUS_ENUMERATING = 2;
469        // Camera is in use by another app and cannot be used exclusively
470        public static final int STATUS_NOT_AVAILABLE = 0x80000000;
471
472        // Camera ID -> Status map
473        private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
474
475        private static final String TAG = "CameraServiceListener";
476
477        @Override
478        public IBinder asBinder() {
479            return this;
480        }
481
482        private boolean isAvailable(int status) {
483            switch (status) {
484                case STATUS_PRESENT:
485                    return true;
486                default:
487                    return false;
488            }
489        }
490
491        private boolean validStatus(int status) {
492            switch (status) {
493                case STATUS_NOT_PRESENT:
494                case STATUS_PRESENT:
495                case STATUS_ENUMERATING:
496                case STATUS_NOT_AVAILABLE:
497                    return true;
498                default:
499                    return false;
500            }
501        }
502
503        @Override
504        public void onStatusChanged(int status, int cameraId) throws RemoteException {
505            synchronized(CameraManager.this.mLock) {
506
507                Log.v(TAG,
508                        String.format("Camera id %d has status changed to 0x%x", cameraId, status));
509
510                final String id = String.valueOf(cameraId);
511
512                if (!validStatus(status)) {
513                    Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId,
514                            status));
515                    return;
516                }
517
518                Integer oldStatus = mDeviceStatus.put(id, status);
519
520                if (oldStatus != null && oldStatus == status) {
521                    Log.v(TAG, String.format(
522                            "Device status changed to 0x%x, which is what it already was",
523                            status));
524                    return;
525                }
526
527                // TODO: consider abstracting out this state minimization + transition
528                // into a separate
529                // more easily testable class
530                // i.e. (new State()).addState(STATE_AVAILABLE)
531                //                   .addState(STATE_NOT_AVAILABLE)
532                //                   .addTransition(STATUS_PRESENT, STATE_AVAILABLE),
533                //                   .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE)
534                //                   .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE);
535                //                   .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE);
536
537                // Translate all the statuses to either 'available' or 'not available'
538                //  available -> available         => no new update
539                //  not available -> not available => no new update
540                if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) {
541
542                    Log.v(TAG,
543                            String.format(
544                                    "Device status was previously available (%d), " +
545                                            " and is now again available (%d)" +
546                                            "so no new client visible update will be sent",
547                                    isAvailable(status), isAvailable(status)));
548                    return;
549                }
550
551                final int listenerCount = mListenerMap.size();
552                for (int i = 0; i < listenerCount; i++) {
553                    Handler handler = mListenerMap.valueAt(i);
554                    final AvailabilityListener listener = mListenerMap.keyAt(i);
555                    if (isAvailable(status)) {
556                        handler.post(
557                            new Runnable() {
558                                @Override
559                                public void run() {
560                                    listener.onCameraAvailable(id);
561                                }
562                            });
563                    } else {
564                        handler.post(
565                            new Runnable() {
566                                @Override
567                                public void run() {
568                                    listener.onCameraUnavailable(id);
569                                }
570                            });
571                    }
572                } // for
573            } // synchronized
574        } // onStatusChanged
575    } // CameraServiceListener
576} // CameraManager
577