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