blob: 71eb0e9f2883426c5c2546b8aaada4dba895b3e4 [file] [log] [blame]
Igor Murashkin70725502013-06-25 20:27:06 +00001/*
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 Talvala2f1a2e42013-07-25 17:12:05 -070017package android.hardware.camera2.impl;
Igor Murashkin70725502013-06-25 20:27:06 +000018
Igor Murashkin57ea59b2013-08-23 16:55:57 -070019import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
20
Eino-Ville Talvala2f1a2e42013-07-25 17:12:05 -070021import android.hardware.camera2.CameraAccessException;
Eino-Ville Talvalacca00c62014-05-14 10:53:20 -070022import android.hardware.camera2.CameraCaptureSession;
Ruben Brunk57493682014-05-27 18:58:08 -070023import android.hardware.camera2.CameraCharacteristics;
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -070024import android.hardware.camera2.CameraDevice;
Eino-Ville Talvala2f1a2e42013-07-25 17:12:05 -070025import android.hardware.camera2.CaptureRequest;
Igor Murashkin57ea59b2013-08-23 16:55:57 -070026import android.hardware.camera2.CaptureResult;
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -070027import android.hardware.camera2.CaptureFailure;
Igor Murashkin57ea59b2013-08-23 16:55:57 -070028import android.hardware.camera2.ICameraDeviceCallbacks;
29import android.hardware.camera2.ICameraDeviceUser;
Igor Murashkindb075af2014-05-21 10:07:08 -070030import android.hardware.camera2.TotalCaptureResult;
Eino-Ville Talvala5afd3e92013-08-21 10:37:04 -070031import android.hardware.camera2.utils.CameraBinderDecorator;
Igor Murashkin57ea59b2013-08-23 16:55:57 -070032import android.hardware.camera2.utils.CameraRuntimeException;
Igor Murashkin9c595172014-05-12 13:56:20 -070033import android.hardware.camera2.utils.LongParcelable;
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -070034import android.os.Handler;
Ruben Brunkdecfe952013-10-29 11:00:32 -070035import android.os.IBinder;
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -070036import android.os.Looper;
Ruben Brunkdecfe952013-10-29 11:00:32 -070037import android.os.RemoteException;
Igor Murashkin70725502013-06-25 20:27:06 +000038import android.util.Log;
Igor Murashkin57ea59b2013-08-23 16:55:57 -070039import android.util.SparseArray;
Igor Murashkin70725502013-06-25 20:27:06 +000040import android.view.Surface;
41
Jianing Weid2c3a822014-03-27 18:27:43 -070042import java.util.AbstractMap.SimpleEntry;
Igor Murashkin57ea59b2013-08-23 16:55:57 -070043import java.util.ArrayList;
Igor Murashkin57ea59b2013-08-23 16:55:57 -070044import java.util.HashSet;
Ruben Brunkdecfe952013-10-29 11:00:32 -070045import java.util.Iterator;
Igor Murashkin70725502013-06-25 20:27:06 +000046import java.util.List;
Jianing Weid2c3a822014-03-27 18:27:43 -070047import java.util.TreeSet;
Igor Murashkin70725502013-06-25 20:27:06 +000048
49/**
Eino-Ville Talvala70c22072013-08-27 12:09:04 -070050 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
Igor Murashkin70725502013-06-25 20:27:06 +000051 */
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -070052public class CameraDeviceImpl extends CameraDevice {
Igor Murashkin70725502013-06-25 20:27:06 +000053
54 private final String TAG;
Zhijun Heecb323e2013-07-31 09:40:27 -070055 private final boolean DEBUG;
Igor Murashkin70725502013-06-25 20:27:06 +000056
Ruben Brunkdecfe952013-10-29 11:00:32 -070057 private static final int REQUEST_ID_NONE = -1;
58
Igor Murashkin70725502013-06-25 20:27:06 +000059 // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
60 private ICameraDeviceUser mRemoteDevice;
61
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -070062 // Lock to synchronize cross-thread access to device public interface
63 private final Object mInterfaceLock = new Object();
Igor Murashkin70725502013-06-25 20:27:06 +000064 private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
65
Eino-Ville Talvala868d9042013-10-03 11:15:21 -070066 private final StateListener mDeviceListener;
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -070067 private volatile StateListenerKK mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -070068 private final Handler mDeviceHandler;
69
Igor Murashkin49b2b132014-06-18 19:03:00 -070070 private volatile boolean mClosing = false;
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -070071 private boolean mInError = false;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -070072 private boolean mIdle = true;
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -070073
Igor Murashkin21547d62014-06-04 15:21:42 -070074 /** map request IDs to listener/request data */
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -070075 private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
76 new SparseArray<CaptureListenerHolder>();
Igor Murashkin70725502013-06-25 20:27:06 +000077
Ruben Brunkdecfe952013-10-29 11:00:32 -070078 private int mRepeatingRequestId = REQUEST_ID_NONE;
79 private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>();
Igor Murashkin57ea59b2013-08-23 16:55:57 -070080 // Map stream IDs to Surfaces
81 private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
Igor Murashkin70725502013-06-25 20:27:06 +000082
83 private final String mCameraId;
Ruben Brunk57493682014-05-27 18:58:08 -070084 private final CameraCharacteristics mCharacteristics;
Zhijun He83159152014-07-16 11:32:59 -070085 private final int mTotalPartialCount;
Igor Murashkin70725502013-06-25 20:27:06 +000086
Jianing Weid2c3a822014-03-27 18:27:43 -070087 /**
88 * A list tracking request and its expected last frame.
89 * Updated when calling ICameraDeviceUser methods.
90 */
91 private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>>
92 mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>();
93
94 /**
95 * An object tracking received frame numbers.
96 * Updated when receiving callbacks from ICameraDeviceCallbacks.
97 */
98 private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
99
Igor Murashkin0b27d342014-05-30 09:45:05 -0700100 private CameraCaptureSessionImpl mCurrentSession;
101
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700102 // Runnables for all state transitions, except error, which needs the
103 // error code argument
104
105 private final Runnable mCallOnOpened = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700106 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700107 public void run() {
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700108 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700109 synchronized(mInterfaceLock) {
110 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -0700111
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700112 sessionListener = mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700113 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700114 if (sessionListener != null) {
115 sessionListener.onOpened(CameraDeviceImpl.this);
116 }
117 mDeviceListener.onOpened(CameraDeviceImpl.this);
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700118 }
119 };
120
121 private final Runnable mCallOnUnconfigured = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700122 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700123 public void run() {
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700124 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700125 synchronized(mInterfaceLock) {
126 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -0700127
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700128 sessionListener = mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700129 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700130 if (sessionListener != null) {
131 sessionListener.onUnconfigured(CameraDeviceImpl.this);
132 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700133 }
134 };
135
136 private final Runnable mCallOnActive = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700137 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700138 public void run() {
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700139 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700140 synchronized(mInterfaceLock) {
141 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -0700142
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700143 sessionListener = mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700144 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700145 if (sessionListener != null) {
146 sessionListener.onActive(CameraDeviceImpl.this);
147 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700148 }
149 };
150
151 private final Runnable mCallOnBusy = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700152 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700153 public void run() {
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700154 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700155 synchronized(mInterfaceLock) {
156 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -0700157
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700158 sessionListener = mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700159 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700160 if (sessionListener != null) {
161 sessionListener.onBusy(CameraDeviceImpl.this);
162 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700163 }
164 };
165
166 private final Runnable mCallOnClosed = new Runnable() {
Igor Murashkin49b2b132014-06-18 19:03:00 -0700167 private boolean mClosedOnce = false;
168
Jianing Weid2c3a822014-03-27 18:27:43 -0700169 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700170 public void run() {
Igor Murashkin49b2b132014-06-18 19:03:00 -0700171 if (mClosedOnce) {
172 throw new AssertionError("Don't post #onClosed more than once");
173 }
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700174 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700175 synchronized(mInterfaceLock) {
176 sessionListener = mSessionStateListener;
177 }
Igor Murashkin0b27d342014-05-30 09:45:05 -0700178 if (sessionListener != null) {
Igor Murashkin21547d62014-06-04 15:21:42 -0700179 sessionListener.onClosed(CameraDeviceImpl.this);
Igor Murashkin0b27d342014-05-30 09:45:05 -0700180 }
Sol Boucher4b3f8002014-06-05 13:47:24 -0700181 mDeviceListener.onClosed(CameraDeviceImpl.this);
Igor Murashkin49b2b132014-06-18 19:03:00 -0700182 mClosedOnce = true;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700183 }
184 };
185
186 private final Runnable mCallOnIdle = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700187 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700188 public void run() {
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700189 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700190 synchronized(mInterfaceLock) {
191 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -0700192
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700193 sessionListener = mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700194 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700195 if (sessionListener != null) {
196 sessionListener.onIdle(CameraDeviceImpl.this);
197 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700198 }
199 };
200
201 private final Runnable mCallOnDisconnected = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700202 @Override
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700203 public void run() {
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700204 StateListenerKK sessionListener = null;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700205 synchronized(mInterfaceLock) {
206 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -0700207
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700208 sessionListener = mSessionStateListener;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700209 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700210 if (sessionListener != null) {
211 sessionListener.onDisconnected(CameraDeviceImpl.this);
212 }
213 mDeviceListener.onDisconnected(CameraDeviceImpl.this);
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700214 }
215 };
216
Igor Murashkin21547d62014-06-04 15:21:42 -0700217 public CameraDeviceImpl(String cameraId, StateListener listener, Handler handler,
Ruben Brunk57493682014-05-27 18:58:08 -0700218 CameraCharacteristics characteristics) {
Zhijun He83159152014-07-16 11:32:59 -0700219 if (cameraId == null || listener == null || handler == null || characteristics == null) {
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700220 throw new IllegalArgumentException("Null argument given");
221 }
Igor Murashkin70725502013-06-25 20:27:06 +0000222 mCameraId = cameraId;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700223 mDeviceListener = listener;
224 mDeviceHandler = handler;
Ruben Brunk57493682014-05-27 18:58:08 -0700225 mCharacteristics = characteristics;
Eino-Ville Talvalab72d1ef2014-04-28 13:21:18 -0700226
227 final int MAX_TAG_LEN = 23;
228 String tag = String.format("CameraDevice-JV-%s", mCameraId);
229 if (tag.length() > MAX_TAG_LEN) {
230 tag = tag.substring(0, MAX_TAG_LEN);
231 }
232 TAG = tag;
Zhijun Heecb323e2013-07-31 09:40:27 -0700233 DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Zhijun He83159152014-07-16 11:32:59 -0700234
235 Integer partialCount =
236 mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
237 if (partialCount == null) {
238 // 1 means partial result is not supported.
239 mTotalPartialCount = 1;
240 } else {
241 mTotalPartialCount = partialCount;
242 }
Igor Murashkin70725502013-06-25 20:27:06 +0000243 }
244
245 public CameraDeviceCallbacks getCallbacks() {
246 return mCallbacks;
247 }
248
Igor Murashkin70725502013-06-25 20:27:06 +0000249 public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700250 synchronized(mInterfaceLock) {
251 // TODO: Move from decorator to direct binder-mediated exceptions
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700252 // If setRemoteFailure already called, do nothing
253 if (mInError) return;
254
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700255 mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
256
257 mDeviceHandler.post(mCallOnOpened);
258 mDeviceHandler.post(mCallOnUnconfigured);
259 }
Igor Murashkin70725502013-06-25 20:27:06 +0000260 }
261
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700262 /**
263 * Call to indicate failed connection to a remote camera device.
264 *
265 * <p>This places the camera device in the error state and informs the listener.
266 * Use in place of setRemoteDevice() when startup fails.</p>
267 */
268 public void setRemoteFailure(final CameraRuntimeException failure) {
269 int failureCode = StateListener.ERROR_CAMERA_DEVICE;
270 boolean failureIsError = true;
271
272 switch (failure.getReason()) {
273 case CameraAccessException.CAMERA_IN_USE:
274 failureCode = StateListener.ERROR_CAMERA_IN_USE;
275 break;
276 case CameraAccessException.MAX_CAMERAS_IN_USE:
277 failureCode = StateListener.ERROR_MAX_CAMERAS_IN_USE;
278 break;
279 case CameraAccessException.CAMERA_DISABLED:
280 failureCode = StateListener.ERROR_CAMERA_DISABLED;
281 break;
282 case CameraAccessException.CAMERA_DISCONNECTED:
283 failureIsError = false;
284 break;
285 case CameraAccessException.CAMERA_ERROR:
286 failureCode = StateListener.ERROR_CAMERA_DEVICE;
287 break;
288 default:
289 Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
290 break;
291 }
292 final int code = failureCode;
293 final boolean isError = failureIsError;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700294 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700295 mInError = true;
296 mDeviceHandler.post(new Runnable() {
Igor Murashkine1442202014-06-09 17:51:24 -0700297 @Override
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700298 public void run() {
299 if (isError) {
300 mDeviceListener.onError(CameraDeviceImpl.this, code);
301 } else {
302 mDeviceListener.onDisconnected(CameraDeviceImpl.this);
303 }
304 }
305 });
306 }
307 }
308
Igor Murashkin70725502013-06-25 20:27:06 +0000309 @Override
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700310 public String getId() {
311 return mCameraId;
312 }
313
Igor Murashkin70725502013-06-25 20:27:06 +0000314 public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700315 // Treat a null input the same an empty list
316 if (outputs == null) {
317 outputs = new ArrayList<Surface>();
318 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700319 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700320 checkIfCameraClosedOrInError();
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700321
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700322 HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
323 List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete
324
325 // Determine which streams need to be created, which to be deleted
326 for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
327 int streamId = mConfiguredOutputs.keyAt(i);
328 Surface s = mConfiguredOutputs.valueAt(i);
329
330 if (!outputs.contains(s)) {
331 deleteList.add(streamId);
332 } else {
333 addSet.remove(s); // Don't create a stream previously created
334 }
335 }
336
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700337 mDeviceHandler.post(mCallOnBusy);
338 stopRepeating();
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700339
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700340 try {
Ruben Brunkdecfe952013-10-29 11:00:32 -0700341 waitUntilIdle();
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700342
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700343 mRemoteDevice.beginConfigure();
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700344 // Delete all streams first (to free up HW resources)
345 for (Integer streamId : deleteList) {
346 mRemoteDevice.deleteStream(streamId);
347 mConfiguredOutputs.delete(streamId);
348 }
349
350 // Add all new streams
351 for (Surface s : addSet) {
Igor Murashkin70725502013-06-25 20:27:06 +0000352 // TODO: remove width,height,format since we are ignoring
353 // it.
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700354 int streamId = mRemoteDevice.createStream(0, 0, 0, s);
355 mConfiguredOutputs.put(streamId, s);
Igor Murashkin70725502013-06-25 20:27:06 +0000356 }
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700357
Ruben Brunkfeb50af2014-05-09 19:58:49 -0700358 mRemoteDevice.endConfigure();
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700359 } catch (CameraRuntimeException e) {
360 if (e.getReason() == CAMERA_IN_USE) {
361 throw new IllegalStateException("The camera is currently busy." +
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700362 " You must wait until the previous operation completes.");
Igor Murashkin57ea59b2013-08-23 16:55:57 -0700363 }
364
365 throw e.asChecked();
366 } catch (RemoteException e) {
367 // impossible
368 return;
Igor Murashkin70725502013-06-25 20:27:06 +0000369 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700370
371 if (outputs.size() > 0) {
372 mDeviceHandler.post(mCallOnIdle);
373 } else {
374 mDeviceHandler.post(mCallOnUnconfigured);
375 }
Igor Murashkin70725502013-06-25 20:27:06 +0000376 }
377 }
378
379 @Override
Eino-Ville Talvalacca00c62014-05-14 10:53:20 -0700380 public void createCaptureSession(List<Surface> outputs,
381 CameraCaptureSession.StateListener listener, Handler handler)
382 throws CameraAccessException {
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700383 synchronized(mInterfaceLock) {
Igor Murashkin0b27d342014-05-30 09:45:05 -0700384 if (DEBUG) {
385 Log.d(TAG, "createCaptureSession");
386 }
387
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700388 checkIfCameraClosedOrInError();
Igor Murashkin0b27d342014-05-30 09:45:05 -0700389
Eino-Ville Talvalad3b85f62014-08-05 15:02:31 -0700390 // Notify current session that it's going away, before starting camera operations
391 // After this call completes, the session is not allowed to call into CameraDeviceImpl
392 if (mCurrentSession != null) {
393 mCurrentSession.replaceSessionClose();
394 }
Igor Murashkin0b27d342014-05-30 09:45:05 -0700395
396 // TODO: dont block for this
397 boolean configureSuccess = true;
398 CameraAccessException pendingException = null;
399 try {
400 configureOutputs(outputs); // and then block until IDLE
401 } catch (CameraAccessException e) {
402 configureSuccess = false;
403 pendingException = e;
Igor Murashkine1442202014-06-09 17:51:24 -0700404 if (DEBUG) {
405 Log.v(TAG, "createCaptureSession - failed with exception ", e);
406 }
Igor Murashkin0b27d342014-05-30 09:45:05 -0700407 }
408
409 // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
410 CameraCaptureSessionImpl newSession =
411 new CameraCaptureSessionImpl(outputs, listener, handler, this, mDeviceHandler,
412 configureSuccess);
413
Igor Murashkin0b27d342014-05-30 09:45:05 -0700414 // TODO: wait until current session closes, then create the new session
415 mCurrentSession = newSession;
416
417 if (pendingException != null) {
418 throw pendingException;
419 }
420
421 mSessionStateListener = mCurrentSession.getDeviceStateListener();
422 }
Eino-Ville Talvalacca00c62014-05-14 10:53:20 -0700423 }
424
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700425 /**
426 * For use by backwards-compatibility code only.
427 */
428 public void setSessionListener(StateListenerKK sessionListener) {
429 synchronized(mInterfaceLock) {
430 mSessionStateListener = sessionListener;
431 }
432 }
433
Eino-Ville Talvalacca00c62014-05-14 10:53:20 -0700434 @Override
Eino-Ville Talvala70c22072013-08-27 12:09:04 -0700435 public CaptureRequest.Builder createCaptureRequest(int templateType)
436 throws CameraAccessException {
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700437 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700438 checkIfCameraClosedOrInError();
Igor Murashkin70725502013-06-25 20:27:06 +0000439
Eino-Ville Talvala70c22072013-08-27 12:09:04 -0700440 CameraMetadataNative templatedRequest = new CameraMetadataNative();
Igor Murashkin70725502013-06-25 20:27:06 +0000441
442 try {
Igor Murashkin0b27d342014-05-30 09:45:05 -0700443 mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
Igor Murashkin70725502013-06-25 20:27:06 +0000444 } catch (CameraRuntimeException e) {
445 throw e.asChecked();
446 } catch (RemoteException e) {
447 // impossible
448 return null;
449 }
450
Eino-Ville Talvala70c22072013-08-27 12:09:04 -0700451 CaptureRequest.Builder builder =
452 new CaptureRequest.Builder(templatedRequest);
Igor Murashkin70725502013-06-25 20:27:06 +0000453
Eino-Ville Talvala70c22072013-08-27 12:09:04 -0700454 return builder;
Igor Murashkin70725502013-06-25 20:27:06 +0000455 }
456 }
457
Igor Murashkin6bbf9dc2013-09-05 12:22:00 -0700458 public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
Igor Murashkin70725502013-06-25 20:27:06 +0000459 throws CameraAccessException {
Jianing Weid2c3a822014-03-27 18:27:43 -0700460 if (DEBUG) {
461 Log.d(TAG, "calling capture");
462 }
463 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
464 requestList.add(request);
465 return submitCaptureRequest(requestList, listener, handler, /*streaming*/false);
Igor Murashkin70725502013-06-25 20:27:06 +0000466 }
467
Igor Murashkin6bbf9dc2013-09-05 12:22:00 -0700468 public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700469 Handler handler) throws CameraAccessException {
Igor Murashkin0b27d342014-05-30 09:45:05 -0700470 if (requests == null || requests.isEmpty()) {
471 throw new IllegalArgumentException("At least one request must be given");
Zhijun Hefc19e2c2013-08-22 14:43:07 -0700472 }
Jianing Weid2c3a822014-03-27 18:27:43 -0700473 return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
Igor Murashkin70725502013-06-25 20:27:06 +0000474 }
475
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700476 /**
477 * This method checks lastFrameNumber returned from ICameraDeviceUser methods for
478 * starting and stopping repeating request and flushing.
479 *
480 * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
Eino-Ville Talvalaee37e302014-06-10 15:24:26 -0700481 * sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700482 * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
483 * is added to the list mFrameNumberRequestPairs.</p>
484 *
485 * @param requestId the request ID of the current repeating request.
486 *
487 * @param lastFrameNumber last frame number returned from binder.
488 */
489 private void checkEarlyTriggerSequenceComplete(
490 final int requestId, final long lastFrameNumber) {
491 // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
Eino-Ville Talvalaee37e302014-06-10 15:24:26 -0700492 // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700493 if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
494 final CaptureListenerHolder holder;
495 int index = mCaptureListenerMap.indexOfKey(requestId);
496 holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null;
497 if (holder != null) {
498 mCaptureListenerMap.removeAt(index);
Jianing Weibaf0c652014-04-18 17:35:00 -0700499 if (DEBUG) {
500 Log.v(TAG, String.format(
501 "remove holder for requestId %d, "
502 + "because lastFrame is %d.",
503 requestId, lastFrameNumber));
504 }
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700505 }
506
507 if (holder != null) {
508 if (DEBUG) {
Eino-Ville Talvalaee37e302014-06-10 15:24:26 -0700509 Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700510 + " request did not reach HAL");
511 }
512
513 Runnable resultDispatch = new Runnable() {
514 @Override
515 public void run() {
Igor Murashkin21547d62014-06-04 15:21:42 -0700516 if (!CameraDeviceImpl.this.isClosed()) {
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700517 if (DEBUG) {
518 Log.d(TAG, String.format(
519 "early trigger sequence complete for request %d",
520 requestId));
521 }
522 if (lastFrameNumber < Integer.MIN_VALUE
523 || lastFrameNumber > Integer.MAX_VALUE) {
524 throw new AssertionError(lastFrameNumber + " cannot be cast to int");
525 }
Eino-Ville Talvalaee37e302014-06-10 15:24:26 -0700526 holder.getListener().onCaptureSequenceAborted(
Igor Murashkin21547d62014-06-04 15:21:42 -0700527 CameraDeviceImpl.this,
Eino-Ville Talvalaee37e302014-06-10 15:24:26 -0700528 requestId);
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700529 }
530 }
531 };
532 holder.getHandler().post(resultDispatch);
533 } else {
534 Log.w(TAG, String.format(
535 "did not register listener to request %d",
536 requestId));
537 }
538 } else {
539 mFrameNumberRequestPairs.add(
540 new SimpleEntry<Long, Integer>(lastFrameNumber,
541 requestId));
542 }
543 }
544
Jianing Weid2c3a822014-03-27 18:27:43 -0700545 private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureListener listener,
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700546 Handler handler, boolean repeating) throws CameraAccessException {
547
548 // Need a valid handler, or current thread needs to have a looper, if
549 // listener is valid
Eino-Ville Talvala7875a882014-07-31 12:47:07 -0700550 handler = checkHandler(handler, listener);
Igor Murashkin70725502013-06-25 20:27:06 +0000551
Ruben Brunk7f2372b2014-07-02 11:05:08 -0700552 // Make sure that there all requests have at least 1 surface; all surfaces are non-null
553 for (CaptureRequest request : requestList) {
554 if (request.getTargets().isEmpty()) {
555 throw new IllegalArgumentException(
556 "Each request must have at least one Surface target");
557 }
558
559 for (Surface surface : request.getTargets()) {
560 if (surface == null) {
561 throw new IllegalArgumentException("Null Surface targets are not allowed");
562 }
563 }
564 }
565
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700566 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700567 checkIfCameraClosedOrInError();
Igor Murashkin70725502013-06-25 20:27:06 +0000568 int requestId;
569
Ruben Brunke73b41b2013-11-07 19:30:43 -0800570 if (repeating) {
571 stopRepeating();
572 }
573
Jianing Weid2c3a822014-03-27 18:27:43 -0700574 LongParcelable lastFrameNumberRef = new LongParcelable();
Igor Murashkin70725502013-06-25 20:27:06 +0000575 try {
Jianing Weid2c3a822014-03-27 18:27:43 -0700576 requestId = mRemoteDevice.submitRequestList(requestList, repeating,
577 /*out*/lastFrameNumberRef);
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700578 if (DEBUG) {
Jianing Weid2c3a822014-03-27 18:27:43 -0700579 Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
580 }
Igor Murashkin70725502013-06-25 20:27:06 +0000581 } catch (CameraRuntimeException e) {
582 throw e.asChecked();
583 } catch (RemoteException e) {
584 // impossible
Igor Murashkin6bbf9dc2013-09-05 12:22:00 -0700585 return -1;
Igor Murashkin70725502013-06-25 20:27:06 +0000586 }
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700587
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700588 if (listener != null) {
Jianing Weid2c3a822014-03-27 18:27:43 -0700589 mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener,
590 requestList, handler, repeating));
Jianing Weibaf0c652014-04-18 17:35:00 -0700591 } else {
592 if (DEBUG) {
593 Log.d(TAG, "Listen for request " + requestId + " is null");
594 }
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700595 }
Igor Murashkin70725502013-06-25 20:27:06 +0000596
Jianing Weid2c3a822014-03-27 18:27:43 -0700597 long lastFrameNumber = lastFrameNumberRef.getNumber();
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700598
Igor Murashkin70725502013-06-25 20:27:06 +0000599 if (repeating) {
Jianing Weid2c3a822014-03-27 18:27:43 -0700600 if (mRepeatingRequestId != REQUEST_ID_NONE) {
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700601 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
Jianing Weid2c3a822014-03-27 18:27:43 -0700602 }
Ruben Brunkdecfe952013-10-29 11:00:32 -0700603 mRepeatingRequestId = requestId;
Jianing Weid2c3a822014-03-27 18:27:43 -0700604 } else {
605 mFrameNumberRequestPairs.add(
606 new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
Igor Murashkin70725502013-06-25 20:27:06 +0000607 }
608
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700609 if (mIdle) {
610 mDeviceHandler.post(mCallOnActive);
611 }
612 mIdle = false;
613
Igor Murashkin6bbf9dc2013-09-05 12:22:00 -0700614 return requestId;
Igor Murashkin70725502013-06-25 20:27:06 +0000615 }
616 }
617
Igor Murashkin6bbf9dc2013-09-05 12:22:00 -0700618 public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700619 Handler handler) throws CameraAccessException {
Jianing Weid2c3a822014-03-27 18:27:43 -0700620 List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
621 requestList.add(request);
622 return submitCaptureRequest(requestList, listener, handler, /*streaming*/true);
Igor Murashkin70725502013-06-25 20:27:06 +0000623 }
624
Igor Murashkin6bbf9dc2013-09-05 12:22:00 -0700625 public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700626 Handler handler) throws CameraAccessException {
Igor Murashkin0b27d342014-05-30 09:45:05 -0700627 if (requests == null || requests.isEmpty()) {
628 throw new IllegalArgumentException("At least one request must be given");
Zhijun Hefc19e2c2013-08-22 14:43:07 -0700629 }
Jianing Weid2c3a822014-03-27 18:27:43 -0700630 return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
Igor Murashkin70725502013-06-25 20:27:06 +0000631 }
632
Igor Murashkin70725502013-06-25 20:27:06 +0000633 public void stopRepeating() throws CameraAccessException {
634
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700635 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700636 checkIfCameraClosedOrInError();
Ruben Brunkdecfe952013-10-29 11:00:32 -0700637 if (mRepeatingRequestId != REQUEST_ID_NONE) {
638
639 int requestId = mRepeatingRequestId;
640 mRepeatingRequestId = REQUEST_ID_NONE;
641
642 // Queue for deletion after in-flight requests finish
Zhijun He1a9b6462014-03-31 16:11:33 -0700643 if (mCaptureListenerMap.get(requestId) != null) {
644 mRepeatingRequestIdDeletedList.add(requestId);
645 }
Igor Murashkin70725502013-06-25 20:27:06 +0000646
647 try {
Jianing Weid2c3a822014-03-27 18:27:43 -0700648 LongParcelable lastFrameNumberRef = new LongParcelable();
649 mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
650 long lastFrameNumber = lastFrameNumberRef.getNumber();
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700651
652 checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
653
Igor Murashkin70725502013-06-25 20:27:06 +0000654 } catch (CameraRuntimeException e) {
655 throw e.asChecked();
656 } catch (RemoteException e) {
657 // impossible
658 return;
659 }
660 }
661 }
662 }
663
Zhijun Hed842fcd2013-12-26 14:14:04 -0800664 private void waitUntilIdle() throws CameraAccessException {
Zhijun He7f4d3142013-07-23 07:54:38 -0700665
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700666 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700667 checkIfCameraClosedOrInError();
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700668
Ruben Brunkdecfe952013-10-29 11:00:32 -0700669 if (mRepeatingRequestId != REQUEST_ID_NONE) {
Zhijun He7f4d3142013-07-23 07:54:38 -0700670 throw new IllegalStateException("Active repeating request ongoing");
671 }
Zhijun He7f4d3142013-07-23 07:54:38 -0700672 try {
673 mRemoteDevice.waitUntilIdle();
674 } catch (CameraRuntimeException e) {
675 throw e.asChecked();
676 } catch (RemoteException e) {
677 // impossible
678 return;
679 }
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -0700680 }
Igor Murashkin70725502013-06-25 20:27:06 +0000681 }
682
Eino-Ville Talvala8ebd52b2013-08-13 12:09:44 -0700683 public void flush() throws CameraAccessException {
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700684 synchronized(mInterfaceLock) {
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700685 checkIfCameraClosedOrInError();
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700686
687 mDeviceHandler.post(mCallOnBusy);
Eino-Ville Talvala8ebd52b2013-08-13 12:09:44 -0700688 try {
Jianing Weid2c3a822014-03-27 18:27:43 -0700689 LongParcelable lastFrameNumberRef = new LongParcelable();
690 mRemoteDevice.flush(/*out*/lastFrameNumberRef);
691 if (mRepeatingRequestId != REQUEST_ID_NONE) {
692 long lastFrameNumber = lastFrameNumberRef.getNumber();
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700693 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
Jianing Weid2c3a822014-03-27 18:27:43 -0700694 mRepeatingRequestId = REQUEST_ID_NONE;
695 }
Eino-Ville Talvala8ebd52b2013-08-13 12:09:44 -0700696 } catch (CameraRuntimeException e) {
697 throw e.asChecked();
698 } catch (RemoteException e) {
699 // impossible
700 return;
701 }
702 }
703 }
704
705 @Override
Igor Murashkin5c9eaf62013-09-10 19:35:24 -0700706 public void close() {
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700707 synchronized (mInterfaceLock) {
Igor Murashkin70725502013-06-25 20:27:06 +0000708 try {
Igor Murashkin2a3eced2013-08-28 17:35:10 -0700709 if (mRemoteDevice != null) {
710 mRemoteDevice.disconnect();
711 }
Igor Murashkin70725502013-06-25 20:27:06 +0000712 } catch (CameraRuntimeException e) {
Igor Murashkin5c9eaf62013-09-10 19:35:24 -0700713 Log.e(TAG, "Exception while closing: ", e.asChecked());
Igor Murashkin70725502013-06-25 20:27:06 +0000714 } catch (RemoteException e) {
715 // impossible
716 }
717
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700718 // Only want to fire the onClosed callback once;
719 // either a normal close where the remote device is valid
720 // or a close after a startup error (no remote device but in error state)
721 if (mRemoteDevice != null || mInError) {
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700722 mDeviceHandler.post(mCallOnClosed);
723 }
Igor Murashkin70725502013-06-25 20:27:06 +0000724
Eino-Ville Talvala868d9042013-10-03 11:15:21 -0700725 mRemoteDevice = null;
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -0700726 mInError = false;
Igor Murashkin70725502013-06-25 20:27:06 +0000727 }
728 }
729
730 @Override
731 protected void finalize() throws Throwable {
732 try {
733 close();
Igor Murashkin70725502013-06-25 20:27:06 +0000734 }
735 finally {
736 super.finalize();
737 }
738 }
739
Eino-Ville Talvalaa6b5ba52014-07-02 16:30:53 -0700740 /**
741 * <p>A listener for tracking the progress of a {@link CaptureRequest}
742 * submitted to the camera device.</p>
743 *
744 */
745 public static abstract class CaptureListener {
746
747 /**
748 * This constant is used to indicate that no images were captured for
749 * the request.
750 *
751 * @hide
752 */
753 public static final int NO_FRAMES_CAPTURED = -1;
754
755 /**
756 * This method is called when the camera device has started capturing
757 * the output image for the request, at the beginning of image exposure.
758 *
759 * @see android.media.MediaActionSound
760 */
761 public void onCaptureStarted(CameraDevice camera,
762 CaptureRequest request, long timestamp) {
763 // default empty implementation
764 }
765
766 /**
767 * This method is called when some results from an image capture are
768 * available.
769 *
770 * @hide
771 */
772 public void onCapturePartial(CameraDevice camera,
773 CaptureRequest request, CaptureResult result) {
774 // default empty implementation
775 }
776
777 /**
778 * This method is called when an image capture makes partial forward progress; some
779 * (but not all) results from an image capture are available.
780 *
781 */
782 public void onCaptureProgressed(CameraDevice camera,
783 CaptureRequest request, CaptureResult partialResult) {
784 // default empty implementation
785 }
786
787 /**
788 * This method is called when an image capture has fully completed and all the
789 * result metadata is available.
790 */
791 public void onCaptureCompleted(CameraDevice camera,
792 CaptureRequest request, TotalCaptureResult result) {
793 // default empty implementation
794 }
795
796 /**
797 * This method is called instead of {@link #onCaptureCompleted} when the
798 * camera device failed to produce a {@link CaptureResult} for the
799 * request.
800 */
801 public void onCaptureFailed(CameraDevice camera,
802 CaptureRequest request, CaptureFailure failure) {
803 // default empty implementation
804 }
805
806 /**
807 * This method is called independently of the others in CaptureListener,
808 * when a capture sequence finishes and all {@link CaptureResult}
809 * or {@link CaptureFailure} for it have been returned via this listener.
810 */
811 public void onCaptureSequenceCompleted(CameraDevice camera,
812 int sequenceId, long frameNumber) {
813 // default empty implementation
814 }
815
816 /**
817 * This method is called independently of the others in CaptureListener,
818 * when a capture sequence aborts before any {@link CaptureResult}
819 * or {@link CaptureFailure} for it have been returned via this listener.
820 */
821 public void onCaptureSequenceAborted(CameraDevice camera,
822 int sequenceId) {
823 // default empty implementation
824 }
825 }
826
827 /**
828 * A listener for notifications about the state of a camera device, adding in the callbacks that
829 * were part of the earlier KK API design, but now only used internally.
830 */
831 public static abstract class StateListenerKK extends StateListener {
832 /**
833 * The method called when a camera device has no outputs configured.
834 *
835 */
836 public void onUnconfigured(CameraDevice camera) {
837 // Default empty implementation
838 }
839
840 /**
841 * The method called when a camera device begins processing
842 * {@link CaptureRequest capture requests}.
843 *
844 */
845 public void onActive(CameraDevice camera) {
846 // Default empty implementation
847 }
848
849 /**
850 * The method called when a camera device is busy.
851 *
852 */
853 public void onBusy(CameraDevice camera) {
854 // Default empty implementation
855 }
856
857 /**
858 * The method called when a camera device has finished processing all
859 * submitted capture requests and has reached an idle state.
860 *
861 */
862 public void onIdle(CameraDevice camera) {
863 // Default empty implementation
864 }
865 }
866
Igor Murashkin70725502013-06-25 20:27:06 +0000867 static class CaptureListenerHolder {
868
869 private final boolean mRepeating;
870 private final CaptureListener mListener;
Jianing Weid2c3a822014-03-27 18:27:43 -0700871 private final List<CaptureRequest> mRequestList;
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700872 private final Handler mHandler;
Igor Murashkin70725502013-06-25 20:27:06 +0000873
Jianing Weid2c3a822014-03-27 18:27:43 -0700874 CaptureListenerHolder(CaptureListener listener, List<CaptureRequest> requestList,
875 Handler handler, boolean repeating) {
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700876 if (listener == null || handler == null) {
877 throw new UnsupportedOperationException(
878 "Must have a valid handler and a valid listener");
879 }
Igor Murashkin70725502013-06-25 20:27:06 +0000880 mRepeating = repeating;
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700881 mHandler = handler;
Jianing Weid2c3a822014-03-27 18:27:43 -0700882 mRequestList = new ArrayList<CaptureRequest>(requestList);
Igor Murashkin70725502013-06-25 20:27:06 +0000883 mListener = listener;
884 }
885
886 public boolean isRepeating() {
887 return mRepeating;
888 }
889
890 public CaptureListener getListener() {
891 return mListener;
892 }
893
Jianing Weid2c3a822014-03-27 18:27:43 -0700894 public CaptureRequest getRequest(int subsequenceId) {
895 if (subsequenceId >= mRequestList.size()) {
896 throw new IllegalArgumentException(
897 String.format(
898 "Requested subsequenceId %d is larger than request list size %d.",
899 subsequenceId, mRequestList.size()));
900 } else {
901 if (subsequenceId < 0) {
902 throw new IllegalArgumentException(String.format(
903 "Requested subsequenceId %d is negative", subsequenceId));
904 } else {
905 return mRequestList.get(subsequenceId);
906 }
907 }
908 }
909
Igor Murashkin70725502013-06-25 20:27:06 +0000910 public CaptureRequest getRequest() {
Jianing Weid2c3a822014-03-27 18:27:43 -0700911 return getRequest(0);
Igor Murashkin70725502013-06-25 20:27:06 +0000912 }
Eino-Ville Talvala4af73c22013-08-14 10:35:46 -0700913
914 public Handler getHandler() {
915 return mHandler;
916 }
917
Igor Murashkin70725502013-06-25 20:27:06 +0000918 }
919
Jianing Weid2c3a822014-03-27 18:27:43 -0700920 /**
921 * This class tracks the last frame number for submitted requests.
922 */
923 public class FrameNumberTracker {
924
925 private long mCompletedFrameNumber = -1;
926 private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>();
927
928 private void update() {
929 Iterator<Long> iter = mFutureErrorSet.iterator();
930 while (iter.hasNext()) {
931 long errorFrameNumber = iter.next();
932 if (errorFrameNumber == mCompletedFrameNumber + 1) {
933 mCompletedFrameNumber++;
934 iter.remove();
935 } else {
936 break;
937 }
938 }
939 }
940
941 /**
942 * This function is called every time when a result or an error is received.
943 * @param frameNumber: the frame number corresponding to the result or error
944 * @param isError: true if it is an error, false if it is not an error
945 */
946 public void updateTracker(long frameNumber, boolean isError) {
947 if (isError) {
948 mFutureErrorSet.add(frameNumber);
949 } else {
950 /**
951 * HAL cannot send an OnResultReceived for frame N unless it knows for
952 * sure that all frames prior to N have either errored out or completed.
953 * So if the current frame is not an error, then all previous frames
954 * should have arrived. The following line checks whether this holds.
955 */
956 if (frameNumber != mCompletedFrameNumber + 1) {
Zhijun He22589b42014-05-01 10:37:36 -0700957 Log.e(TAG, String.format(
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700958 "result frame number %d comes out of order, should be %d + 1",
959 frameNumber, mCompletedFrameNumber));
Jianing Weid2c3a822014-03-27 18:27:43 -0700960 }
961 mCompletedFrameNumber++;
962 }
963 update();
964 }
965
966 public long getCompletedFrameNumber() {
967 return mCompletedFrameNumber;
968 }
969
970 }
971
972 private void checkAndFireSequenceComplete() {
973 long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
974 Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator();
975 while (iter.hasNext()) {
976 final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next();
977 if (frameNumberRequestPair.getKey() <= completedFrameNumber) {
978
979 // remove request from mCaptureListenerMap
980 final int requestId = frameNumberRequestPair.getValue();
981 final CaptureListenerHolder holder;
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -0700982 synchronized(mInterfaceLock) {
983 if (mRemoteDevice == null) {
Igor Murashkin49b2b132014-06-18 19:03:00 -0700984 Log.w(TAG, "Camera closed while checking sequences");
985 return;
986 }
987
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700988 int index = mCaptureListenerMap.indexOfKey(requestId);
989 holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
Jianing Weid2c3a822014-03-27 18:27:43 -0700990 : null;
991 if (holder != null) {
Jianing Wei5a4b02b2014-04-11 09:59:24 -0700992 mCaptureListenerMap.removeAt(index);
993 if (DEBUG) {
994 Log.v(TAG, String.format(
995 "remove holder for requestId %d, "
996 + "because lastFrame %d is <= %d",
997 requestId, frameNumberRequestPair.getKey(),
998 completedFrameNumber));
999 }
Jianing Weid2c3a822014-03-27 18:27:43 -07001000 }
1001 }
1002 iter.remove();
1003
1004 // Call onCaptureSequenceCompleted
1005 if (holder != null) {
1006 Runnable resultDispatch = new Runnable() {
1007 @Override
1008 public void run() {
Igor Murashkin21547d62014-06-04 15:21:42 -07001009 if (!CameraDeviceImpl.this.isClosed()){
Jianing Weid2c3a822014-03-27 18:27:43 -07001010 if (DEBUG) {
1011 Log.d(TAG, String.format(
1012 "fire sequence complete for request %d",
1013 requestId));
1014 }
1015
Jianing Wei5a4b02b2014-04-11 09:59:24 -07001016 long lastFrameNumber = frameNumberRequestPair.getKey();
1017 if (lastFrameNumber < Integer.MIN_VALUE
1018 || lastFrameNumber > Integer.MAX_VALUE) {
1019 throw new AssertionError(lastFrameNumber
1020 + " cannot be cast to int");
1021 }
Jianing Weid2c3a822014-03-27 18:27:43 -07001022 holder.getListener().onCaptureSequenceCompleted(
Igor Murashkin21547d62014-06-04 15:21:42 -07001023 CameraDeviceImpl.this,
Jianing Weid2c3a822014-03-27 18:27:43 -07001024 requestId,
Igor Murashkindb075af2014-05-21 10:07:08 -07001025 lastFrameNumber);
Jianing Weid2c3a822014-03-27 18:27:43 -07001026 }
1027 }
1028 };
1029 holder.getHandler().post(resultDispatch);
1030 }
1031
1032 }
1033 }
1034 }
1035
Zhijun Heecb323e2013-07-31 09:40:27 -07001036 public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
Igor Murashkin70725502013-06-25 20:27:06 +00001037
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001038 //
1039 // Constants below need to be kept up-to-date with
1040 // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
1041 //
1042
1043 //
1044 // Error codes for onCameraError
1045 //
1046
1047 /**
1048 * Camera has been disconnected
1049 */
1050 static final int ERROR_CAMERA_DISCONNECTED = 0;
1051
1052 /**
1053 * Camera has encountered a device-level error
1054 * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
1055 */
1056 static final int ERROR_CAMERA_DEVICE = 1;
1057
1058 /**
1059 * Camera has encountered a service-level error
1060 * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
1061 */
1062 static final int ERROR_CAMERA_SERVICE = 2;
1063
Igor Murashkin70725502013-06-25 20:27:06 +00001064 @Override
1065 public IBinder asBinder() {
1066 return this;
1067 }
1068
Igor Murashkin70725502013-06-25 20:27:06 +00001069 @Override
Jianing Weid2c3a822014-03-27 18:27:43 -07001070 public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001071 Runnable r = null;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001072
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -07001073 synchronized(mInterfaceLock) {
1074 if (mRemoteDevice == null) {
Igor Murashkin49b2b132014-06-18 19:03:00 -07001075 return; // Camera already closed
1076 }
1077
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -07001078 mInError = true;
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001079 switch (errorCode) {
1080 case ERROR_CAMERA_DISCONNECTED:
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001081 r = mCallOnDisconnected;
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001082 break;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001083 default:
1084 Log.e(TAG, "Unknown error from camera device: " + errorCode);
1085 // no break
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001086 case ERROR_CAMERA_DEVICE:
1087 case ERROR_CAMERA_SERVICE:
1088 r = new Runnable() {
Jianing Weid2c3a822014-03-27 18:27:43 -07001089 @Override
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001090 public void run() {
Igor Murashkin21547d62014-06-04 15:21:42 -07001091 if (!CameraDeviceImpl.this.isClosed()) {
1092 mDeviceListener.onError(CameraDeviceImpl.this, errorCode);
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001093 }
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001094 }
1095 };
1096 break;
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001097 }
Igor Murashkin21547d62014-06-04 15:21:42 -07001098 CameraDeviceImpl.this.mDeviceHandler.post(r);
Jianing Weid2c3a822014-03-27 18:27:43 -07001099
Igor Murashkin49b2b132014-06-18 19:03:00 -07001100 // Fire onCaptureSequenceCompleted
1101 if (DEBUG) {
1102 Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
1103 }
1104 mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
1105 checkAndFireSequenceComplete();
Jianing Wei5a4b02b2014-04-11 09:59:24 -07001106 }
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001107 }
1108
1109 @Override
1110 public void onCameraIdle() {
1111 if (DEBUG) {
1112 Log.d(TAG, "Camera now idle");
1113 }
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -07001114 synchronized(mInterfaceLock) {
1115 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -07001116
Igor Murashkin21547d62014-06-04 15:21:42 -07001117 if (!CameraDeviceImpl.this.mIdle) {
1118 CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001119 }
Igor Murashkin21547d62014-06-04 15:21:42 -07001120 CameraDeviceImpl.this.mIdle = true;
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001121 }
1122 }
1123
1124 @Override
Jianing Weid2c3a822014-03-27 18:27:43 -07001125 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
1126 int requestId = resultExtras.getRequestId();
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001127 if (DEBUG) {
1128 Log.d(TAG, "Capture started for id " + requestId);
1129 }
1130 final CaptureListenerHolder holder;
1131
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -07001132 synchronized(mInterfaceLock) {
1133 if (mRemoteDevice == null) return; // Camera already closed
Igor Murashkin49b2b132014-06-18 19:03:00 -07001134
1135 // Get the listener for this frame ID, if there is one
Igor Murashkin21547d62014-06-04 15:21:42 -07001136 holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001137
Igor Murashkin49b2b132014-06-18 19:03:00 -07001138 if (holder == null) {
1139 return;
1140 }
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001141
Igor Murashkin49b2b132014-06-18 19:03:00 -07001142 if (isClosed()) return;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001143
Igor Murashkin49b2b132014-06-18 19:03:00 -07001144 // Dispatch capture start notice
1145 holder.getHandler().post(
1146 new Runnable() {
1147 @Override
1148 public void run() {
1149 if (!CameraDeviceImpl.this.isClosed()) {
1150 holder.getListener().onCaptureStarted(
1151 CameraDeviceImpl.this,
1152 holder.getRequest(resultExtras.getSubsequenceId()),
1153 timestamp);
1154 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001155 }
Igor Murashkin49b2b132014-06-18 19:03:00 -07001156 });
1157
1158 }
Igor Murashkin70725502013-06-25 20:27:06 +00001159 }
1160
1161 @Override
Jianing Weid2c3a822014-03-27 18:27:43 -07001162 public void onResultReceived(CameraMetadataNative result,
1163 CaptureResultExtras resultExtras) throws RemoteException {
Ruben Brunk57493682014-05-27 18:58:08 -07001164
Jianing Weid2c3a822014-03-27 18:27:43 -07001165 int requestId = resultExtras.getRequestId();
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001166 long frameNumber = resultExtras.getFrameNumber();
1167
Zhijun Heecb323e2013-07-31 09:40:27 -07001168 if (DEBUG) {
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001169 Log.v(TAG, "Received result frame " + frameNumber + " for id "
Jianing Weibaf0c652014-04-18 17:35:00 -07001170 + requestId);
Zhijun Heecb323e2013-07-31 09:40:27 -07001171 }
Ruben Brunk57493682014-05-27 18:58:08 -07001172
Eino-Ville Talvala2f75e7d2014-07-15 10:31:54 -07001173 synchronized(mInterfaceLock) {
1174 if (mRemoteDevice == null) return; // Camera already closed
Ruben Brunk57493682014-05-27 18:58:08 -07001175
Igor Murashkin49b2b132014-06-18 19:03:00 -07001176 // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
1177 result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
1178 getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
Ruben Brunk57493682014-05-27 18:58:08 -07001179
Igor Murashkin49b2b132014-06-18 19:03:00 -07001180 final CaptureListenerHolder holder =
1181 CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
Igor Murashkin70725502013-06-25 20:27:06 +00001182
Zhijun He83159152014-07-16 11:32:59 -07001183 boolean isPartialResult =
1184 (resultExtras.getPartialResultCount() < mTotalPartialCount);
Eino-Ville Talvala7a313102013-11-07 14:45:06 -08001185
Igor Murashkin49b2b132014-06-18 19:03:00 -07001186 // Update tracker (increment counter) when it's not a partial result.
Zhijun He83159152014-07-16 11:32:59 -07001187 if (!isPartialResult) {
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001188 mFrameNumberTracker.updateTracker(frameNumber,
Igor Murashkin49b2b132014-06-18 19:03:00 -07001189 /*error*/false);
Jianing Wei5a4b02b2014-04-11 09:59:24 -07001190 }
Igor Murashkin70725502013-06-25 20:27:06 +00001191
Igor Murashkin49b2b132014-06-18 19:03:00 -07001192 // Check if we have a listener for this
1193 if (holder == null) {
1194 if (DEBUG) {
1195 Log.d(TAG,
1196 "holder is null, early return at frame "
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001197 + frameNumber);
Igor Murashkin49b2b132014-06-18 19:03:00 -07001198 }
1199 return;
Jianing Wei5a4b02b2014-04-11 09:59:24 -07001200 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001201
Igor Murashkin49b2b132014-06-18 19:03:00 -07001202 if (isClosed()) {
1203 if (DEBUG) {
1204 Log.d(TAG,
1205 "camera is closed, early return at frame "
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001206 + frameNumber);
Eino-Ville Talvala7a313102013-11-07 14:45:06 -08001207 }
Igor Murashkin49b2b132014-06-18 19:03:00 -07001208 return;
1209 }
Igor Murashkindb075af2014-05-21 10:07:08 -07001210
Igor Murashkin49b2b132014-06-18 19:03:00 -07001211 final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
1212
1213
1214 Runnable resultDispatch = null;
1215
1216 // Either send a partial result or the final capture completed result
Zhijun He83159152014-07-16 11:32:59 -07001217 if (isPartialResult) {
Igor Murashkin49b2b132014-06-18 19:03:00 -07001218 final CaptureResult resultAsCapture =
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001219 new CaptureResult(result, request, resultExtras);
Igor Murashkin49b2b132014-06-18 19:03:00 -07001220
1221 // Partial result
1222 resultDispatch = new Runnable() {
1223 @Override
1224 public void run() {
1225 if (!CameraDeviceImpl.this.isClosed()){
Zhijun Hefac77c42014-07-18 14:20:48 -07001226 holder.getListener().onCaptureProgressed(
Igor Murashkin49b2b132014-06-18 19:03:00 -07001227 CameraDeviceImpl.this,
1228 request,
1229 resultAsCapture);
1230 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001231 }
Igor Murashkin49b2b132014-06-18 19:03:00 -07001232 };
1233 } else {
1234 final TotalCaptureResult resultAsCapture =
Igor Murashkinbdf366c2014-07-25 16:54:20 -07001235 new TotalCaptureResult(result, request, resultExtras);
Eino-Ville Talvala7a313102013-11-07 14:45:06 -08001236
Igor Murashkin49b2b132014-06-18 19:03:00 -07001237 // Final capture result
1238 resultDispatch = new Runnable() {
1239 @Override
1240 public void run() {
1241 if (!CameraDeviceImpl.this.isClosed()){
1242 holder.getListener().onCaptureCompleted(
1243 CameraDeviceImpl.this,
1244 request,
1245 resultAsCapture);
1246 }
1247 }
1248 };
1249 }
Jianing Weid2c3a822014-03-27 18:27:43 -07001250
Igor Murashkin49b2b132014-06-18 19:03:00 -07001251 holder.getHandler().post(resultDispatch);
1252
1253 // Fire onCaptureSequenceCompleted
Zhijun He83159152014-07-16 11:32:59 -07001254 if (!isPartialResult) {
Igor Murashkin49b2b132014-06-18 19:03:00 -07001255 checkAndFireSequenceComplete();
1256 }
1257
Jianing Weid2c3a822014-03-27 18:27:43 -07001258 }
Igor Murashkin70725502013-06-25 20:27:06 +00001259 }
1260
1261 }
1262
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001263 /**
Igor Murashkin0b27d342014-05-30 09:45:05 -07001264 * Default handler management.
1265 *
1266 * <p>
1267 * If handler is null, get the current thread's
1268 * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
1269 * </p>
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001270 */
Igor Murashkin0b27d342014-05-30 09:45:05 -07001271 static Handler checkHandler(Handler handler) {
Eino-Ville Talvalae841d4e2013-09-05 09:04:08 -07001272 if (handler == null) {
1273 Looper looper = Looper.myLooper();
1274 if (looper == null) {
1275 throw new IllegalArgumentException(
1276 "No handler given, and current thread has no looper!");
1277 }
1278 handler = new Handler(looper);
1279 }
1280 return handler;
1281 }
1282
Eino-Ville Talvala7875a882014-07-31 12:47:07 -07001283 /**
1284 * Default handler management, conditional on there being a listener.
1285 *
1286 * <p>If the listener isn't null, check the handler, otherwise pass it through.</p>
1287 */
1288 static <T> Handler checkHandler(Handler handler, T listener) {
1289 if (listener != null) {
1290 return checkHandler(handler);
1291 }
1292 return handler;
1293 }
1294
Eino-Ville Talvala7fcb35782014-06-03 18:30:27 -07001295 private void checkIfCameraClosedOrInError() throws CameraAccessException {
1296 if (mInError) {
1297 throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
1298 "The camera device has encountered a serious error");
1299 }
Zhijun He7f4d3142013-07-23 07:54:38 -07001300 if (mRemoteDevice == null) {
1301 throw new IllegalStateException("CameraDevice was already closed");
1302 }
1303 }
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001304
Igor Murashkin49b2b132014-06-18 19:03:00 -07001305 /** Whether the camera device has started to close (may not yet have finished) */
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001306 private boolean isClosed() {
Igor Murashkin49b2b132014-06-18 19:03:00 -07001307 return mClosing;
Eino-Ville Talvala868d9042013-10-03 11:15:21 -07001308 }
Ruben Brunk57493682014-05-27 18:58:08 -07001309
1310 private CameraCharacteristics getCharacteristics() {
1311 return mCharacteristics;
1312 }
Igor Murashkin70725502013-06-25 20:27:06 +00001313}