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