Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 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 | |
Eino-Ville Talvala | 2f1a2e4 | 2013-07-25 17:12:05 -0700 | [diff] [blame] | 17 | package android.hardware.camera2; |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 18 | |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 19 | import android.content.Context; |
| 20 | import android.hardware.ICameraService; |
| 21 | import android.hardware.ICameraServiceListener; |
Eino-Ville Talvala | 70c2207 | 2013-08-27 12:09:04 -0700 | [diff] [blame] | 22 | import android.hardware.camera2.impl.CameraMetadataNative; |
Eino-Ville Talvala | 2f1a2e4 | 2013-07-25 17:12:05 -0700 | [diff] [blame] | 23 | import android.hardware.camera2.utils.CameraBinderDecorator; |
| 24 | import android.hardware.camera2.utils.CameraRuntimeException; |
Ruben Brunk | 66ef645 | 2013-08-08 13:05:30 -0700 | [diff] [blame] | 25 | import android.hardware.camera2.utils.BinderHolder; |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 26 | import android.os.IBinder; |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 27 | import android.os.Handler; |
| 28 | import android.os.Looper; |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 29 | import android.os.RemoteException; |
| 30 | import android.os.ServiceManager; |
| 31 | import android.util.Log; |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 32 | import android.util.ArrayMap; |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 33 | |
| 34 | import java.util.ArrayList; |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 35 | |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 36 | /** |
| 37 | * <p>An interface for iterating, listing, and connecting to |
| 38 | * {@link CameraDevice CameraDevices}.</p> |
| 39 | * |
| 40 | * <p>You can get an instance of this class by calling |
| 41 | * {@link android.content.Context#getSystemService(String) Context.getSystemService()}.</p> |
| 42 | * |
| 43 | * <pre>CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);</pre> |
| 44 | * |
| 45 | * <p>For more details about communicating with camera devices, read the Camera |
Eino-Ville Talvala | 2f1a2e4 | 2013-07-25 17:12:05 -0700 | [diff] [blame] | 46 | * developer guide or the {@link android.hardware.camera2 camera2} |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 47 | * package documentation.</p> |
| 48 | */ |
| 49 | public final class CameraManager { |
| 50 | |
| 51 | /** |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 52 | * This should match the ICameraService definition |
| 53 | */ |
| 54 | private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; |
| 55 | private static final int USE_CALLING_UID = -1; |
| 56 | |
| 57 | private final ICameraService mCameraService; |
| 58 | private ArrayList<String> mDeviceIdList; |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 59 | |
Igor Murashkin | 5c9eaf6 | 2013-09-10 19:35:24 -0700 | [diff] [blame] | 60 | private final ArrayMap<AvailabilityListener, Handler> mListenerMap = |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 61 | new ArrayMap<AvailabilityListener, Handler>(); |
| 62 | |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 63 | private final Context mContext; |
| 64 | private final Object mLock = new Object(); |
| 65 | |
| 66 | /** |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 67 | * @hide |
| 68 | */ |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 69 | public CameraManager(Context context) { |
| 70 | mContext = context; |
| 71 | |
| 72 | IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); |
| 73 | ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); |
| 74 | |
| 75 | /** |
| 76 | * Wrap the camera service in a decorator which automatically translates return codes |
| 77 | * into exceptions, and RemoteExceptions into other exceptions. |
| 78 | */ |
| 79 | mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw); |
| 80 | |
| 81 | try { |
| 82 | mCameraService.addListener(new CameraServiceListener()); |
| 83 | } catch(CameraRuntimeException e) { |
| 84 | throw new IllegalStateException("Failed to register a camera service listener", |
| 85 | e.asChecked()); |
| 86 | } catch (RemoteException e) { |
| 87 | // impossible |
| 88 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 89 | } |
| 90 | |
| 91 | /** |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 92 | * Return the list of currently connected camera devices by |
| 93 | * identifier. |
| 94 | * |
| 95 | * <p>Non-removable cameras use integers starting at 0 for their |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 96 | * identifiers, while removable cameras have a unique identifier for each |
| 97 | * individual device, even if they are the same model.</p> |
| 98 | * |
| 99 | * @return The list of currently connected camera devices. |
| 100 | */ |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 101 | public String[] getCameraIdList() throws CameraAccessException { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 102 | synchronized (mLock) { |
Igor Murashkin | 7072550 | 2013-06-25 20:27:06 +0000 | [diff] [blame] | 103 | try { |
| 104 | return getOrCreateDeviceIdListLocked().toArray(new String[0]); |
| 105 | } catch(CameraAccessException e) { |
| 106 | // this should almost never happen, except if mediaserver crashes |
| 107 | throw new IllegalStateException( |
| 108 | "Failed to query camera service for device ID list", e); |
| 109 | } |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 110 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 111 | } |
| 112 | |
| 113 | /** |
| 114 | * Register a listener to be notified about camera device availability. |
| 115 | * |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 116 | * <p>Registering the same listener again will replace the handler with the |
| 117 | * new one provided.</p> |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 118 | * |
Benjamin Hendricks | 24eb8a3 | 2013-08-15 12:46:22 -0700 | [diff] [blame] | 119 | * @param listener The new listener to send camera availability notices to |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 120 | * @param handler The handler on which the listener should be invoked, or |
| 121 | * {@code null} to use the current thread's {@link android.os.Looper looper}. |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 122 | */ |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 123 | public void addAvailabilityListener(AvailabilityListener listener, Handler handler) { |
| 124 | if (handler == null) { |
| 125 | Looper looper = Looper.myLooper(); |
| 126 | if (looper == null) { |
| 127 | throw new IllegalArgumentException( |
| 128 | "No handler given, and current thread has no looper!"); |
| 129 | } |
| 130 | handler = new Handler(looper); |
| 131 | } |
| 132 | |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 133 | synchronized (mLock) { |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 134 | mListenerMap.put(listener, handler); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 135 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Remove a previously-added listener; the listener will no longer receive |
| 140 | * connection and disconnection callbacks. |
| 141 | * |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 142 | * <p>Removing a listener that isn't registered has no effect.</p> |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 143 | * |
Benjamin Hendricks | 24eb8a3 | 2013-08-15 12:46:22 -0700 | [diff] [blame] | 144 | * @param listener The listener to remove from the notification list |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 145 | */ |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 146 | public void removeAvailabilityListener(AvailabilityListener listener) { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 147 | synchronized (mLock) { |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 148 | mListenerMap.remove(listener); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 149 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 150 | } |
| 151 | |
| 152 | /** |
| 153 | * <p>Query the capabilities of a camera device. These capabilities are |
| 154 | * immutable for a given camera.</p> |
| 155 | * |
| 156 | * @param cameraId The id of the camera device to query |
| 157 | * @return The properties of the given camera |
| 158 | * |
| 159 | * @throws IllegalArgumentException if the cameraId does not match any |
| 160 | * currently connected camera device. |
| 161 | * @throws CameraAccessException if the camera is disabled by device policy. |
| 162 | * @throws SecurityException if the application does not have permission to |
| 163 | * access the camera |
| 164 | * |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 165 | * @see #getCameraIdList |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 166 | * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| 167 | */ |
Igor Murashkin | 68f4006 | 2013-09-10 12:15:54 -0700 | [diff] [blame] | 168 | public CameraCharacteristics getCameraCharacteristics(String cameraId) |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 169 | throws CameraAccessException { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 170 | |
| 171 | synchronized (mLock) { |
| 172 | if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { |
| 173 | throw new IllegalArgumentException(String.format("Camera id %s does not match any" + |
| 174 | " currently connected camera device", cameraId)); |
| 175 | } |
| 176 | } |
| 177 | |
Zhijun He | 2001188 | 2013-09-25 10:05:59 -0700 | [diff] [blame] | 178 | CameraMetadataNative info = new CameraMetadataNative(); |
| 179 | try { |
| 180 | mCameraService.getCameraCharacteristics(Integer.valueOf(cameraId), info); |
| 181 | } catch(CameraRuntimeException e) { |
| 182 | throw e.asChecked(); |
| 183 | } catch(RemoteException e) { |
| 184 | // impossible |
| 185 | return null; |
| 186 | } |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 187 | |
Zhijun He | 2001188 | 2013-09-25 10:05:59 -0700 | [diff] [blame] | 188 | return new CameraCharacteristics(info); |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Open a connection to a camera with the given ID. Use |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 193 | * {@link #getCameraIdList} to get the list of available camera |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 194 | * devices. Note that even if an id is listed, open may fail if the device |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 195 | * is disconnected between the calls to {@link #getCameraIdList} and |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 196 | * {@link #openCamera}. |
| 197 | * |
| 198 | * @param cameraId The unique identifier of the camera device to open |
Eino-Ville Talvala | 868d904 | 2013-10-03 11:15:21 -0700 | [diff] [blame] | 199 | * @param listener The listener for the camera. Must not be null. |
| 200 | * @param handler The handler to call the listener on. Must not be null. |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 201 | * |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 202 | * @throws CameraAccessException if the camera is disabled by device policy, |
Ruben Brunk | 66ef645 | 2013-08-08 13:05:30 -0700 | [diff] [blame] | 203 | * or too many camera devices are already open, or the cameraId does not match |
| 204 | * any currently available camera device. |
| 205 | * |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 206 | * @throws SecurityException if the application does not have permission to |
| 207 | * access the camera |
Eino-Ville Talvala | 868d904 | 2013-10-03 11:15:21 -0700 | [diff] [blame] | 208 | * @throws IllegalArgumentException if listener or handler is null. |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 209 | * |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 210 | * @see #getCameraIdList |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 211 | * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| 212 | */ |
Eino-Ville Talvala | 868d904 | 2013-10-03 11:15:21 -0700 | [diff] [blame] | 213 | private void openCameraDeviceUserAsync(String cameraId, |
| 214 | CameraDevice.StateListener listener, Handler handler) |
| 215 | throws CameraAccessException { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 216 | try { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 217 | |
| 218 | synchronized (mLock) { |
Igor Murashkin | 7072550 | 2013-06-25 20:27:06 +0000 | [diff] [blame] | 219 | |
| 220 | ICameraDeviceUser cameraUser; |
| 221 | |
Eino-Ville Talvala | 2f1a2e4 | 2013-07-25 17:12:05 -0700 | [diff] [blame] | 222 | android.hardware.camera2.impl.CameraDevice device = |
Eino-Ville Talvala | 868d904 | 2013-10-03 11:15:21 -0700 | [diff] [blame] | 223 | new android.hardware.camera2.impl.CameraDevice( |
| 224 | cameraId, |
| 225 | listener, |
| 226 | handler); |
Igor Murashkin | 7072550 | 2013-06-25 20:27:06 +0000 | [diff] [blame] | 227 | |
Ruben Brunk | 66ef645 | 2013-08-08 13:05:30 -0700 | [diff] [blame] | 228 | BinderHolder holder = new BinderHolder(); |
| 229 | mCameraService.connectDevice(device.getCallbacks(), |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 230 | Integer.parseInt(cameraId), |
Ruben Brunk | 66ef645 | 2013-08-08 13:05:30 -0700 | [diff] [blame] | 231 | mContext.getPackageName(), USE_CALLING_UID, holder); |
| 232 | cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); |
Igor Murashkin | 7072550 | 2013-06-25 20:27:06 +0000 | [diff] [blame] | 233 | |
| 234 | // TODO: factor out listener to be non-nested, then move setter to constructor |
Eino-Ville Talvala | 868d904 | 2013-10-03 11:15:21 -0700 | [diff] [blame] | 235 | // For now, calling setRemoteDevice will fire initial |
| 236 | // onOpened/onUnconfigured callbacks. |
Igor Murashkin | 7072550 | 2013-06-25 20:27:06 +0000 | [diff] [blame] | 237 | device.setRemoteDevice(cameraUser); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 238 | } |
| 239 | |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 240 | } catch (NumberFormatException e) { |
| 241 | throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: " |
| 242 | + cameraId); |
| 243 | } catch (CameraRuntimeException e) { |
Ruben Brunk | 66ef645 | 2013-08-08 13:05:30 -0700 | [diff] [blame] | 244 | throw e.asChecked(); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 245 | } catch (RemoteException e) { |
| 246 | // impossible |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 247 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 248 | } |
| 249 | |
| 250 | /** |
Igor Murashkin | 5c9eaf6 | 2013-09-10 19:35:24 -0700 | [diff] [blame] | 251 | * Open a connection to a camera with the given ID. |
| 252 | * |
| 253 | * <p>Use {@link #getCameraIdList} to get the list of available camera |
| 254 | * devices. Note that even if an id is listed, open may fail if the device |
| 255 | * is disconnected between the calls to {@link #getCameraIdList} and |
| 256 | * {@link #openCamera}.</p> |
| 257 | * |
| 258 | * <p>If the camera successfully opens after this function call returns, |
| 259 | * {@link CameraDevice.StateListener#onOpened} will be invoked with the |
| 260 | * newly opened {@link CameraDevice} in the unconfigured state.</p> |
| 261 | * |
| 262 | * <p>If the camera becomes disconnected during initialization |
| 263 | * after this function call returns, |
| 264 | * {@link CameraDevice.StateListener#onDisconnected} with a |
| 265 | * {@link CameraDevice} in the disconnected state (and |
| 266 | * {@link CameraDevice.StateListener#onOpened} will be skipped).</p> |
| 267 | * |
| 268 | * <p>If the camera fails to initialize after this function call returns, |
| 269 | * {@link CameraDevice.StateListener#onError} will be invoked with a |
| 270 | * {@link CameraDevice} in the error state (and |
| 271 | * {@link CameraDevice.StateListener#onOpened} will be skipped).</p> |
| 272 | * |
| 273 | * @param cameraId |
| 274 | * The unique identifier of the camera device to open |
| 275 | * @param listener |
| 276 | * The listener which is invoked once the camera is opened |
| 277 | * @param handler |
| 278 | * The handler on which the listener should be invoked, or |
| 279 | * {@code null} to use the current thread's {@link android.os.Looper looper}. |
| 280 | * |
| 281 | * @throws CameraAccessException if the camera is disabled by device policy, |
| 282 | * or the camera has become or was disconnected. |
| 283 | * |
| 284 | * @throws IllegalArgumentException if cameraId or the listener was null, |
| 285 | * or the cameraId does not match any currently or previously available |
| 286 | * camera device. |
| 287 | * |
| 288 | * @throws SecurityException if the application does not have permission to |
| 289 | * access the camera |
| 290 | * |
| 291 | * @see #getCameraIdList |
| 292 | * @see android.app.admin.DevicePolicyManager#setCameraDisabled |
| 293 | */ |
| 294 | public void openCamera(String cameraId, final CameraDevice.StateListener listener, |
| 295 | Handler handler) |
| 296 | throws CameraAccessException { |
| 297 | |
| 298 | if (cameraId == null) { |
| 299 | throw new IllegalArgumentException("cameraId was null"); |
| 300 | } else if (listener == null) { |
| 301 | throw new IllegalArgumentException("listener was null"); |
| 302 | } else if (handler == null) { |
| 303 | if (Looper.myLooper() != null) { |
| 304 | handler = new Handler(); |
| 305 | } else { |
| 306 | throw new IllegalArgumentException( |
| 307 | "Looper doesn't exist in the calling thread"); |
| 308 | } |
| 309 | } |
| 310 | |
Eino-Ville Talvala | 868d904 | 2013-10-03 11:15:21 -0700 | [diff] [blame] | 311 | openCameraDeviceUserAsync(cameraId, listener, handler); |
Igor Murashkin | 5c9eaf6 | 2013-09-10 19:35:24 -0700 | [diff] [blame] | 312 | } |
| 313 | |
| 314 | /** |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 315 | * Interface for listening to camera devices becoming available or |
| 316 | * unavailable. |
| 317 | * |
| 318 | * <p>Cameras become available when they are no longer in use, or when a new |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 319 | * removable camera is connected. They become unavailable when some |
| 320 | * application or service starts using a camera, or when a removable camera |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 321 | * is disconnected.</p> |
| 322 | * |
| 323 | * @see addAvailabilityListener |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 324 | */ |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 325 | public static abstract class AvailabilityListener { |
| 326 | |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 327 | /** |
| 328 | * A new camera has become available to use. |
| 329 | * |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 330 | * <p>The default implementation of this method does nothing.</p> |
| 331 | * |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 332 | * @param cameraId The unique identifier of the new camera. |
| 333 | */ |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 334 | public void onCameraAvailable(String cameraId) { |
| 335 | // default empty implementation |
| 336 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 337 | |
| 338 | /** |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 339 | * A previously-available camera has become unavailable for use. |
| 340 | * |
| 341 | * <p>If an application had an active CameraDevice instance for the |
| 342 | * now-disconnected camera, that application will receive a |
Igor Murashkin | 5c9eaf6 | 2013-09-10 19:35:24 -0700 | [diff] [blame] | 343 | * {@link CameraDevice.StateListener#onDisconnected disconnection error}.</p> |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 344 | * |
| 345 | * <p>The default implementation of this method does nothing.</p> |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 346 | * |
| 347 | * @param cameraId The unique identifier of the disconnected camera. |
| 348 | */ |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 349 | public void onCameraUnavailable(String cameraId) { |
| 350 | // default empty implementation |
| 351 | } |
Eino-Ville Talvala | b267554 | 2012-12-12 13:29:45 -0800 | [diff] [blame] | 352 | } |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 353 | |
| 354 | private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { |
| 355 | if (mDeviceIdList == null) { |
| 356 | int numCameras = 0; |
| 357 | |
| 358 | try { |
| 359 | numCameras = mCameraService.getNumberOfCameras(); |
| 360 | } catch(CameraRuntimeException e) { |
| 361 | throw e.asChecked(); |
| 362 | } catch (RemoteException e) { |
| 363 | // impossible |
| 364 | return null; |
| 365 | } |
| 366 | |
| 367 | mDeviceIdList = new ArrayList<String>(); |
Zhijun He | 18fe0ae | 2013-10-01 11:09:28 -0700 | [diff] [blame] | 368 | CameraMetadataNative info = new CameraMetadataNative(); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 369 | for (int i = 0; i < numCameras; ++i) { |
| 370 | // Non-removable cameras use integers starting at 0 for their |
| 371 | // identifiers |
Zhijun He | 18fe0ae | 2013-10-01 11:09:28 -0700 | [diff] [blame] | 372 | boolean isDeviceSupported = false; |
| 373 | try { |
| 374 | mCameraService.getCameraCharacteristics(i, info); |
| 375 | if (!info.isEmpty()) { |
| 376 | isDeviceSupported = true; |
| 377 | } else { |
| 378 | throw new AssertionError("Expected to get non-empty characteristics"); |
| 379 | } |
| 380 | } catch(IllegalArgumentException e) { |
| 381 | // Got a BAD_VALUE from service, meaning that this |
| 382 | // device is not supported. |
| 383 | } catch(CameraRuntimeException e) { |
| 384 | throw e.asChecked(); |
| 385 | } catch(RemoteException e) { |
| 386 | // impossible |
| 387 | } |
| 388 | |
| 389 | if (isDeviceSupported) { |
| 390 | mDeviceIdList.add(String.valueOf(i)); |
| 391 | } |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 392 | } |
| 393 | |
| 394 | } |
| 395 | return mDeviceIdList; |
| 396 | } |
| 397 | |
| 398 | // TODO: this class needs unit tests |
| 399 | // TODO: extract class into top level |
Zhijun He | ecb323e | 2013-07-31 09:40:27 -0700 | [diff] [blame] | 400 | private class CameraServiceListener extends ICameraServiceListener.Stub { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 401 | |
| 402 | // Keep up-to-date with ICameraServiceListener.h |
| 403 | |
| 404 | // Device physically unplugged |
| 405 | public static final int STATUS_NOT_PRESENT = 0; |
| 406 | // Device physically has been plugged in |
| 407 | // and the camera can be used exclusively |
| 408 | public static final int STATUS_PRESENT = 1; |
| 409 | // Device physically has been plugged in |
| 410 | // but it will not be connect-able until enumeration is complete |
| 411 | public static final int STATUS_ENUMERATING = 2; |
| 412 | // Camera is in use by another app and cannot be used exclusively |
| 413 | public static final int STATUS_NOT_AVAILABLE = 0x80000000; |
| 414 | |
| 415 | // Camera ID -> Status map |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 416 | private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>(); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 417 | |
| 418 | private static final String TAG = "CameraServiceListener"; |
| 419 | |
| 420 | @Override |
| 421 | public IBinder asBinder() { |
| 422 | return this; |
| 423 | } |
| 424 | |
| 425 | private boolean isAvailable(int status) { |
| 426 | switch (status) { |
| 427 | case STATUS_PRESENT: |
| 428 | return true; |
| 429 | default: |
| 430 | return false; |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | private boolean validStatus(int status) { |
| 435 | switch (status) { |
| 436 | case STATUS_NOT_PRESENT: |
| 437 | case STATUS_PRESENT: |
| 438 | case STATUS_ENUMERATING: |
| 439 | case STATUS_NOT_AVAILABLE: |
| 440 | return true; |
| 441 | default: |
| 442 | return false; |
| 443 | } |
| 444 | } |
| 445 | |
| 446 | @Override |
| 447 | public void onStatusChanged(int status, int cameraId) throws RemoteException { |
Igor Murashkin | 7072550 | 2013-06-25 20:27:06 +0000 | [diff] [blame] | 448 | synchronized(CameraManager.this.mLock) { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 449 | |
| 450 | Log.v(TAG, |
| 451 | String.format("Camera id %d has status changed to 0x%x", cameraId, status)); |
| 452 | |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 453 | final String id = String.valueOf(cameraId); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 454 | |
| 455 | if (!validStatus(status)) { |
| 456 | Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId, |
| 457 | status)); |
| 458 | return; |
| 459 | } |
| 460 | |
| 461 | Integer oldStatus = mDeviceStatus.put(id, status); |
| 462 | |
Igor Murashkin | 7441695 | 2013-09-06 16:03:29 -0700 | [diff] [blame] | 463 | if (oldStatus != null && oldStatus == status) { |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 464 | Log.v(TAG, String.format( |
| 465 | "Device status changed to 0x%x, which is what it already was", |
| 466 | status)); |
| 467 | return; |
| 468 | } |
| 469 | |
| 470 | // TODO: consider abstracting out this state minimization + transition |
| 471 | // into a separate |
| 472 | // more easily testable class |
| 473 | // i.e. (new State()).addState(STATE_AVAILABLE) |
| 474 | // .addState(STATE_NOT_AVAILABLE) |
| 475 | // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), |
| 476 | // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) |
| 477 | // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); |
| 478 | // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); |
| 479 | |
| 480 | // Translate all the statuses to either 'available' or 'not available' |
| 481 | // available -> available => no new update |
| 482 | // not available -> not available => no new update |
| 483 | if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { |
| 484 | |
| 485 | Log.v(TAG, |
| 486 | String.format( |
| 487 | "Device status was previously available (%d), " + |
| 488 | " and is now again available (%d)" + |
| 489 | "so no new client visible update will be sent", |
| 490 | isAvailable(status), isAvailable(status))); |
| 491 | return; |
| 492 | } |
| 493 | |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 494 | final int listenerCount = mListenerMap.size(); |
| 495 | for (int i = 0; i < listenerCount; i++) { |
| 496 | Handler handler = mListenerMap.valueAt(i); |
| 497 | final AvailabilityListener listener = mListenerMap.keyAt(i); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 498 | if (isAvailable(status)) { |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 499 | handler.post( |
| 500 | new Runnable() { |
Igor Murashkin | 5c9eaf6 | 2013-09-10 19:35:24 -0700 | [diff] [blame] | 501 | @Override |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 502 | public void run() { |
| 503 | listener.onCameraAvailable(id); |
| 504 | } |
| 505 | }); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 506 | } else { |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 507 | handler.post( |
| 508 | new Runnable() { |
Igor Murashkin | 5c9eaf6 | 2013-09-10 19:35:24 -0700 | [diff] [blame] | 509 | @Override |
Eino-Ville Talvala | 4af73c2 | 2013-08-14 10:35:46 -0700 | [diff] [blame] | 510 | public void run() { |
| 511 | listener.onCameraUnavailable(id); |
| 512 | } |
| 513 | }); |
Igor Murashkin | e363fbb | 2013-06-25 20:26:06 +0000 | [diff] [blame] | 514 | } |
| 515 | } // for |
| 516 | } // synchronized |
| 517 | } // onStatusChanged |
| 518 | } // CameraServiceListener |
| 519 | } // CameraManager |