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.impl; 18 19import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; 20 21import android.hardware.camera2.CameraAccessException; 22import android.hardware.camera2.CameraMetadata; 23import android.hardware.camera2.CameraCharacteristics; 24import android.hardware.camera2.CaptureRequest; 25import android.hardware.camera2.CaptureResult; 26import android.hardware.camera2.ICameraDeviceCallbacks; 27import android.hardware.camera2.ICameraDeviceUser; 28import android.hardware.camera2.utils.CameraBinderDecorator; 29import android.hardware.camera2.utils.CameraRuntimeException; 30import android.os.IBinder; 31import android.os.RemoteException; 32import android.os.Handler; 33import android.os.Looper; 34import android.util.Log; 35import android.util.SparseArray; 36import android.view.Surface; 37 38import java.util.ArrayList; 39import java.util.HashMap; 40import java.util.HashSet; 41import java.util.List; 42import java.util.Stack; 43 44/** 45 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate 46 */ 47public class CameraDevice implements android.hardware.camera2.CameraDevice { 48 49 private final String TAG; 50 private final boolean DEBUG; 51 52 // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) 53 private ICameraDeviceUser mRemoteDevice; 54 55 private final Object mLock = new Object(); 56 private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); 57 58 private final StateListener mDeviceListener; 59 private final Handler mDeviceHandler; 60 61 private boolean mIdle = true; 62 63 private final SparseArray<CaptureListenerHolder> mCaptureListenerMap = 64 new SparseArray<CaptureListenerHolder>(); 65 66 private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>(); 67 // Map stream IDs to Surfaces 68 private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); 69 70 private final String mCameraId; 71 72 // Runnables for all state transitions, except error, which needs the 73 // error code argument 74 75 private final Runnable mCallOnOpened = new Runnable() { 76 public void run() { 77 if (!CameraDevice.this.isClosed()) { 78 mDeviceListener.onOpened(CameraDevice.this); 79 } 80 } 81 }; 82 83 private final Runnable mCallOnUnconfigured = new Runnable() { 84 public void run() { 85 if (!CameraDevice.this.isClosed()) { 86 mDeviceListener.onUnconfigured(CameraDevice.this); 87 } 88 } 89 }; 90 91 private final Runnable mCallOnActive = new Runnable() { 92 public void run() { 93 if (!CameraDevice.this.isClosed()) { 94 mDeviceListener.onActive(CameraDevice.this); 95 } 96 } 97 }; 98 99 private final Runnable mCallOnBusy = new Runnable() { 100 public void run() { 101 if (!CameraDevice.this.isClosed()) { 102 mDeviceListener.onBusy(CameraDevice.this); 103 } 104 } 105 }; 106 107 private final Runnable mCallOnClosed = new Runnable() { 108 public void run() { 109 if (!CameraDevice.this.isClosed()) { 110 mDeviceListener.onClosed(CameraDevice.this); 111 } 112 } 113 }; 114 115 private final Runnable mCallOnIdle = new Runnable() { 116 public void run() { 117 if (!CameraDevice.this.isClosed()) { 118 mDeviceListener.onIdle(CameraDevice.this); 119 } 120 } 121 }; 122 123 private final Runnable mCallOnDisconnected = new Runnable() { 124 public void run() { 125 if (!CameraDevice.this.isClosed()) { 126 mDeviceListener.onDisconnected(CameraDevice.this); 127 } 128 } 129 }; 130 131 public CameraDevice(String cameraId, StateListener listener, Handler handler) { 132 if (cameraId == null || listener == null || handler == null) { 133 throw new IllegalArgumentException("Null argument given"); 134 } 135 mCameraId = cameraId; 136 mDeviceListener = listener; 137 mDeviceHandler = handler; 138 TAG = String.format("CameraDevice-%s-JV", mCameraId); 139 DEBUG = Log.isLoggable(TAG, Log.DEBUG); 140 } 141 142 public CameraDeviceCallbacks getCallbacks() { 143 return mCallbacks; 144 } 145 146 public void setRemoteDevice(ICameraDeviceUser remoteDevice) { 147 // TODO: Move from decorator to direct binder-mediated exceptions 148 synchronized(mLock) { 149 mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); 150 151 mDeviceHandler.post(mCallOnOpened); 152 mDeviceHandler.post(mCallOnUnconfigured); 153 } 154 } 155 156 @Override 157 public String getId() { 158 return mCameraId; 159 } 160 161 @Override 162 public void configureOutputs(List<Surface> outputs) throws CameraAccessException { 163 // Treat a null input the same an empty list 164 if (outputs == null) { 165 outputs = new ArrayList<Surface>(); 166 } 167 synchronized (mLock) { 168 checkIfCameraClosed(); 169 170 HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create 171 List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete 172 173 // Determine which streams need to be created, which to be deleted 174 for (int i = 0; i < mConfiguredOutputs.size(); ++i) { 175 int streamId = mConfiguredOutputs.keyAt(i); 176 Surface s = mConfiguredOutputs.valueAt(i); 177 178 if (!outputs.contains(s)) { 179 deleteList.add(streamId); 180 } else { 181 addSet.remove(s); // Don't create a stream previously created 182 } 183 } 184 185 mDeviceHandler.post(mCallOnBusy); 186 stopRepeating(); 187 188 try { 189 mRemoteDevice.waitUntilIdle(); 190 191 // TODO: mRemoteDevice.beginConfigure 192 // Delete all streams first (to free up HW resources) 193 for (Integer streamId : deleteList) { 194 mRemoteDevice.deleteStream(streamId); 195 mConfiguredOutputs.delete(streamId); 196 } 197 198 // Add all new streams 199 for (Surface s : addSet) { 200 // TODO: remove width,height,format since we are ignoring 201 // it. 202 int streamId = mRemoteDevice.createStream(0, 0, 0, s); 203 mConfiguredOutputs.put(streamId, s); 204 } 205 206 // TODO: mRemoteDevice.endConfigure 207 } catch (CameraRuntimeException e) { 208 if (e.getReason() == CAMERA_IN_USE) { 209 throw new IllegalStateException("The camera is currently busy." + 210 " You must wait until the previous operation completes."); 211 } 212 213 throw e.asChecked(); 214 } catch (RemoteException e) { 215 // impossible 216 return; 217 } 218 219 if (outputs.size() > 0) { 220 mDeviceHandler.post(mCallOnIdle); 221 } else { 222 mDeviceHandler.post(mCallOnUnconfigured); 223 } 224 } 225 } 226 227 @Override 228 public CaptureRequest.Builder createCaptureRequest(int templateType) 229 throws CameraAccessException { 230 synchronized (mLock) { 231 checkIfCameraClosed(); 232 233 CameraMetadataNative templatedRequest = new CameraMetadataNative(); 234 235 try { 236 mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest); 237 } catch (CameraRuntimeException e) { 238 throw e.asChecked(); 239 } catch (RemoteException e) { 240 // impossible 241 return null; 242 } 243 244 CaptureRequest.Builder builder = 245 new CaptureRequest.Builder(templatedRequest); 246 247 return builder; 248 } 249 } 250 251 @Override 252 public int capture(CaptureRequest request, CaptureListener listener, Handler handler) 253 throws CameraAccessException { 254 return submitCaptureRequest(request, listener, handler, /*streaming*/false); 255 } 256 257 @Override 258 public int captureBurst(List<CaptureRequest> requests, CaptureListener listener, 259 Handler handler) throws CameraAccessException { 260 if (requests.isEmpty()) { 261 Log.w(TAG, "Capture burst request list is empty, do nothing!"); 262 return -1; 263 } 264 // TODO 265 throw new UnsupportedOperationException("Burst capture implemented yet"); 266 267 } 268 269 private int submitCaptureRequest(CaptureRequest request, CaptureListener listener, 270 Handler handler, boolean repeating) throws CameraAccessException { 271 272 // Need a valid handler, or current thread needs to have a looper, if 273 // listener is valid 274 if (listener != null) { 275 handler = checkHandler(handler); 276 } 277 278 synchronized (mLock) { 279 checkIfCameraClosed(); 280 int requestId; 281 282 try { 283 requestId = mRemoteDevice.submitRequest(request, repeating); 284 } catch (CameraRuntimeException e) { 285 throw e.asChecked(); 286 } catch (RemoteException e) { 287 // impossible 288 return -1; 289 } 290 if (listener != null) { 291 mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request, 292 handler, repeating)); 293 } 294 295 if (repeating) { 296 mRepeatingRequestIdStack.add(requestId); 297 } 298 299 if (mIdle) { 300 mDeviceHandler.post(mCallOnActive); 301 } 302 mIdle = false; 303 304 return requestId; 305 } 306 } 307 308 @Override 309 public int setRepeatingRequest(CaptureRequest request, CaptureListener listener, 310 Handler handler) throws CameraAccessException { 311 return submitCaptureRequest(request, listener, handler, /*streaming*/true); 312 } 313 314 @Override 315 public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener, 316 Handler handler) throws CameraAccessException { 317 if (requests.isEmpty()) { 318 Log.w(TAG, "Set Repeating burst request list is empty, do nothing!"); 319 return -1; 320 } 321 // TODO 322 throw new UnsupportedOperationException("Burst capture implemented yet"); 323 } 324 325 @Override 326 public void stopRepeating() throws CameraAccessException { 327 328 synchronized (mLock) { 329 checkIfCameraClosed(); 330 while (!mRepeatingRequestIdStack.isEmpty()) { 331 int requestId = mRepeatingRequestIdStack.pop(); 332 333 try { 334 mRemoteDevice.cancelRequest(requestId); 335 } catch (CameraRuntimeException e) { 336 throw e.asChecked(); 337 } catch (RemoteException e) { 338 // impossible 339 return; 340 } 341 } 342 } 343 } 344 345 @Override 346 public void waitUntilIdle() throws CameraAccessException { 347 348 synchronized (mLock) { 349 checkIfCameraClosed(); 350 if (!mRepeatingRequestIdStack.isEmpty()) { 351 throw new IllegalStateException("Active repeating request ongoing"); 352 } 353 354 try { 355 mRemoteDevice.waitUntilIdle(); 356 } catch (CameraRuntimeException e) { 357 throw e.asChecked(); 358 } catch (RemoteException e) { 359 // impossible 360 return; 361 } 362 } 363 } 364 365 @Override 366 public void flush() throws CameraAccessException { 367 synchronized (mLock) { 368 checkIfCameraClosed(); 369 370 mDeviceHandler.post(mCallOnBusy); 371 try { 372 mRemoteDevice.flush(); 373 } catch (CameraRuntimeException e) { 374 throw e.asChecked(); 375 } catch (RemoteException e) { 376 // impossible 377 return; 378 } 379 } 380 } 381 382 @Override 383 public void close() { 384 synchronized (mLock) { 385 386 try { 387 if (mRemoteDevice != null) { 388 mRemoteDevice.disconnect(); 389 } 390 } catch (CameraRuntimeException e) { 391 Log.e(TAG, "Exception while closing: ", e.asChecked()); 392 } catch (RemoteException e) { 393 // impossible 394 } 395 396 if (mRemoteDevice != null) { 397 mDeviceHandler.post(mCallOnClosed); 398 } 399 400 mRemoteDevice = null; 401 } 402 } 403 404 @Override 405 protected void finalize() throws Throwable { 406 try { 407 close(); 408 } 409 finally { 410 super.finalize(); 411 } 412 } 413 414 static class CaptureListenerHolder { 415 416 private final boolean mRepeating; 417 private final CaptureListener mListener; 418 private final CaptureRequest mRequest; 419 private final Handler mHandler; 420 421 CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler, 422 boolean repeating) { 423 if (listener == null || handler == null) { 424 throw new UnsupportedOperationException( 425 "Must have a valid handler and a valid listener"); 426 } 427 mRepeating = repeating; 428 mHandler = handler; 429 mRequest = request; 430 mListener = listener; 431 } 432 433 public boolean isRepeating() { 434 return mRepeating; 435 } 436 437 public CaptureListener getListener() { 438 return mListener; 439 } 440 441 public CaptureRequest getRequest() { 442 return mRequest; 443 } 444 445 public Handler getHandler() { 446 return mHandler; 447 } 448 449 } 450 451 public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub { 452 453 // 454 // Constants below need to be kept up-to-date with 455 // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h 456 // 457 458 // 459 // Error codes for onCameraError 460 // 461 462 /** 463 * Camera has been disconnected 464 */ 465 static final int ERROR_CAMERA_DISCONNECTED = 0; 466 467 /** 468 * Camera has encountered a device-level error 469 * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE 470 */ 471 static final int ERROR_CAMERA_DEVICE = 1; 472 473 /** 474 * Camera has encountered a service-level error 475 * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE 476 */ 477 static final int ERROR_CAMERA_SERVICE = 2; 478 479 @Override 480 public IBinder asBinder() { 481 return this; 482 } 483 484 @Override 485 public void onCameraError(final int errorCode) { 486 Runnable r = null; 487 if (isClosed()) return; 488 489 synchronized(mLock) { 490 switch (errorCode) { 491 case ERROR_CAMERA_DISCONNECTED: 492 r = mCallOnDisconnected; 493 break; 494 default: 495 Log.e(TAG, "Unknown error from camera device: " + errorCode); 496 // no break 497 case ERROR_CAMERA_DEVICE: 498 case ERROR_CAMERA_SERVICE: 499 r = new Runnable() { 500 public void run() { 501 if (!CameraDevice.this.isClosed()) { 502 mDeviceListener.onError(CameraDevice.this, errorCode); 503 } 504 } 505 }; 506 break; 507 } 508 CameraDevice.this.mDeviceHandler.post(r); 509 } 510 } 511 512 @Override 513 public void onCameraIdle() { 514 if (isClosed()) return; 515 516 if (DEBUG) { 517 Log.d(TAG, "Camera now idle"); 518 } 519 synchronized (mLock) { 520 if (!CameraDevice.this.mIdle) { 521 CameraDevice.this.mDeviceHandler.post(mCallOnIdle); 522 } 523 CameraDevice.this.mIdle = true; 524 } 525 } 526 527 @Override 528 public void onCaptureStarted(int requestId, final long timestamp) { 529 if (DEBUG) { 530 Log.d(TAG, "Capture started for id " + requestId); 531 } 532 final CaptureListenerHolder holder; 533 534 // Get the listener for this frame ID, if there is one 535 synchronized (mLock) { 536 holder = CameraDevice.this.mCaptureListenerMap.get(requestId); 537 } 538 539 if (holder == null) { 540 return; 541 } 542 543 if (isClosed()) return; 544 545 // Dispatch capture start notice 546 holder.getHandler().post( 547 new Runnable() { 548 public void run() { 549 if (!CameraDevice.this.isClosed()) { 550 holder.getListener().onCaptureStarted( 551 CameraDevice.this, 552 holder.getRequest(), 553 timestamp); 554 } 555 } 556 }); 557 } 558 559 @Override 560 public void onResultReceived(int requestId, CameraMetadataNative result) 561 throws RemoteException { 562 if (DEBUG) { 563 Log.d(TAG, "Received result for id " + requestId); 564 } 565 final CaptureListenerHolder holder; 566 567 synchronized (mLock) { 568 // TODO: move this whole map into this class to make it more testable, 569 // exposing the methods necessary like subscribeToRequest, unsubscribe.. 570 // TODO: make class static class 571 572 holder = CameraDevice.this.mCaptureListenerMap.get(requestId); 573 574 // Clean up listener once we no longer expect to see it. 575 576 // TODO: how to handle repeating listeners? 577 // we probably want cancelRequest to return # of times it already enqueued and 578 // keep a counter. 579 if (holder != null && !holder.isRepeating()) { 580 CameraDevice.this.mCaptureListenerMap.remove(requestId); 581 } 582 } 583 584 // Check if we have a listener for this 585 if (holder == null) { 586 return; 587 } 588 589 if (isClosed()) return; 590 591 final CaptureRequest request = holder.getRequest(); 592 final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); 593 594 holder.getHandler().post( 595 new Runnable() { 596 @Override 597 public void run() { 598 if (!CameraDevice.this.isClosed()){ 599 holder.getListener().onCaptureCompleted( 600 CameraDevice.this, 601 request, 602 resultAsCapture); 603 } 604 } 605 }); 606 } 607 608 } 609 610 /** 611 * Default handler management. If handler is null, get the current thread's 612 * Looper to create a Handler with. If no looper exists, throw exception. 613 */ 614 private Handler checkHandler(Handler handler) { 615 if (handler == null) { 616 Looper looper = Looper.myLooper(); 617 if (looper == null) { 618 throw new IllegalArgumentException( 619 "No handler given, and current thread has no looper!"); 620 } 621 handler = new Handler(looper); 622 } 623 return handler; 624 } 625 626 private void checkIfCameraClosed() { 627 if (mRemoteDevice == null) { 628 throw new IllegalStateException("CameraDevice was already closed"); 629 } 630 } 631 632 private boolean isClosed() { 633 synchronized(mLock) { 634 return (mRemoteDevice == null); 635 } 636 } 637} 638