1 /* 2 * Copyright (C) 2014 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 17 package android.hardware.camera2.legacy; 18 19 import android.hardware.ICameraService; 20 import android.hardware.Camera; 21 import android.hardware.Camera.CameraInfo; 22 import android.hardware.camera2.CameraAccessException; 23 import android.hardware.camera2.CameraCharacteristics; 24 import android.hardware.camera2.CaptureRequest; 25 import android.hardware.camera2.ICameraDeviceCallbacks; 26 import android.hardware.camera2.ICameraDeviceUser; 27 import android.hardware.camera2.impl.CameraMetadataNative; 28 import android.hardware.camera2.impl.CaptureResultExtras; 29 import android.hardware.camera2.params.OutputConfiguration; 30 import android.hardware.camera2.utils.SubmitInfo; 31 import android.os.ConditionVariable; 32 import android.os.IBinder; 33 import android.os.Looper; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.os.Message; 37 import android.os.RemoteException; 38 import android.os.ServiceSpecificException; 39 import android.util.Log; 40 import android.util.SparseArray; 41 import android.view.Surface; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 46 import static android.system.OsConstants.EACCES; 47 import static android.system.OsConstants.ENODEV; 48 49 /** 50 * Compatibility implementation of the Camera2 API binder interface. 51 * 52 * <p> 53 * This is intended to be called from the same process as client 54 * {@link android.hardware.camera2.CameraDevice}, and wraps a 55 * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using 56 * the Camera1 API. 57 * </p> 58 * 59 * <p> 60 * Keep up to date with ICameraDeviceUser.aidl. 61 * </p> 62 */ 63 @SuppressWarnings("deprecation") 64 public class CameraDeviceUserShim implements ICameraDeviceUser { 65 private static final String TAG = "CameraDeviceUserShim"; 66 67 private static final boolean DEBUG = false; 68 private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout) 69 70 private final LegacyCameraDevice mLegacyDevice; 71 72 private final Object mConfigureLock = new Object(); 73 private int mSurfaceIdCounter; 74 private boolean mConfiguring; 75 private final SparseArray<Surface> mSurfaces; 76 private final CameraCharacteristics mCameraCharacteristics; 77 private final CameraLooper mCameraInit; 78 private final CameraCallbackThread mCameraCallbacks; 79 80 81 protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera, 82 CameraCharacteristics characteristics, CameraLooper cameraInit, 83 CameraCallbackThread cameraCallbacks) { 84 mLegacyDevice = legacyCamera; 85 mConfiguring = false; 86 mSurfaces = new SparseArray<Surface>(); 87 mCameraCharacteristics = characteristics; 88 mCameraInit = cameraInit; 89 mCameraCallbacks = cameraCallbacks; 90 91 mSurfaceIdCounter = 0; 92 } 93 94 private static int translateErrorsFromCamera1(int errorCode) { 95 if (errorCode == -EACCES) { 96 return ICameraService.ERROR_PERMISSION_DENIED; 97 } 98 99 return errorCode; 100 } 101 102 /** 103 * Create a separate looper/thread for the camera to run on; open the camera. 104 * 105 * <p>Since the camera automatically latches on to the current thread's looper, 106 * it's important that we have our own thread with our own looper to guarantee 107 * that the camera callbacks get correctly posted to our own thread.</p> 108 */ 109 private static class CameraLooper implements Runnable, AutoCloseable { 110 private final int mCameraId; 111 private Looper mLooper; 112 private volatile int mInitErrors; 113 private final Camera mCamera = Camera.openUninitialized(); 114 private final ConditionVariable mStartDone = new ConditionVariable(); 115 private final Thread mThread; 116 117 /** 118 * Spin up a new thread, immediately open the camera in the background. 119 * 120 * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p> 121 * 122 * @param cameraId numeric camera Id 123 * 124 * @see #waitForOpen 125 */ 126 public CameraLooper(int cameraId) { 127 mCameraId = cameraId; 128 129 mThread = new Thread(this); 130 mThread.start(); 131 } 132 133 public Camera getCamera() { 134 return mCamera; 135 } 136 137 @Override 138 public void run() { 139 // Set up a looper to be used by camera. 140 Looper.prepare(); 141 142 // Save the looper so that we can terminate this thread 143 // after we are done with it. 144 mLooper = Looper.myLooper(); 145 mInitErrors = mCamera.cameraInitUnspecified(mCameraId); 146 mStartDone.open(); 147 Looper.loop(); // Blocks forever until #close is called. 148 } 149 150 /** 151 * Quit the looper safely; then join until the thread shuts down. 152 */ 153 @Override 154 public void close() { 155 if (mLooper == null) { 156 return; 157 } 158 159 mLooper.quitSafely(); 160 try { 161 mThread.join(); 162 } catch (InterruptedException e) { 163 throw new AssertionError(e); 164 } 165 166 mLooper = null; 167 } 168 169 /** 170 * Block until the camera opens; then return its initialization error code (if any). 171 * 172 * @param timeoutMs timeout in milliseconds 173 * 174 * @return int error code 175 * 176 * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR}) 177 */ 178 public int waitForOpen(int timeoutMs) { 179 // Block until the camera is open asynchronously 180 if (!mStartDone.block(timeoutMs)) { 181 Log.e(TAG, "waitForOpen - Camera failed to open after timeout of " 182 + OPEN_CAMERA_TIMEOUT_MS + " ms"); 183 try { 184 mCamera.release(); 185 } catch (RuntimeException e) { 186 Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e); 187 } 188 189 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION); 190 } 191 192 return mInitErrors; 193 } 194 } 195 196 /** 197 * A thread to process callbacks to send back to the camera client. 198 * 199 * <p>This effectively emulates one-way binder semantics when in the same process as the 200 * callee.</p> 201 */ 202 private static class CameraCallbackThread implements ICameraDeviceCallbacks { 203 private static final int CAMERA_ERROR = 0; 204 private static final int CAMERA_IDLE = 1; 205 private static final int CAPTURE_STARTED = 2; 206 private static final int RESULT_RECEIVED = 3; 207 private static final int PREPARED = 4; 208 private static final int REPEATING_REQUEST_ERROR = 5; 209 210 private final HandlerThread mHandlerThread; 211 private Handler mHandler; 212 213 private final ICameraDeviceCallbacks mCallbacks; 214 215 public CameraCallbackThread(ICameraDeviceCallbacks callbacks) { 216 mCallbacks = callbacks; 217 218 mHandlerThread = new HandlerThread("LegacyCameraCallback"); 219 mHandlerThread.start(); 220 } 221 222 public void close() { 223 mHandlerThread.quitSafely(); 224 } 225 226 @Override 227 public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) { 228 Message msg = getHandler().obtainMessage(CAMERA_ERROR, 229 /*arg1*/ errorCode, /*arg2*/ 0, 230 /*obj*/ resultExtras); 231 getHandler().sendMessage(msg); 232 } 233 234 @Override 235 public void onDeviceIdle() { 236 Message msg = getHandler().obtainMessage(CAMERA_IDLE); 237 getHandler().sendMessage(msg); 238 } 239 240 @Override 241 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { 242 Message msg = getHandler().obtainMessage(CAPTURE_STARTED, 243 /*arg1*/ (int) (timestamp & 0xFFFFFFFFL), 244 /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL), 245 /*obj*/ resultExtras); 246 getHandler().sendMessage(msg); 247 } 248 249 @Override 250 public void onResultReceived(final CameraMetadataNative result, 251 final CaptureResultExtras resultExtras) { 252 Object[] resultArray = new Object[] { result, resultExtras }; 253 Message msg = getHandler().obtainMessage(RESULT_RECEIVED, 254 /*obj*/ resultArray); 255 getHandler().sendMessage(msg); 256 } 257 258 @Override 259 public void onPrepared(int streamId) { 260 Message msg = getHandler().obtainMessage(PREPARED, 261 /*arg1*/ streamId, /*arg2*/ 0); 262 getHandler().sendMessage(msg); 263 } 264 265 266 @Override 267 public void onRepeatingRequestError(long lastFrameNumber) { 268 Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR, 269 /*arg1*/ (int) (lastFrameNumber & 0xFFFFFFFFL), 270 /*arg2*/ (int) ( (lastFrameNumber >> 32) & 0xFFFFFFFFL)); 271 getHandler().sendMessage(msg); 272 } 273 274 @Override 275 public IBinder asBinder() { 276 // This is solely intended to be used for in-process binding. 277 return null; 278 } 279 280 private Handler getHandler() { 281 if (mHandler == null) { 282 mHandler = new CallbackHandler(mHandlerThread.getLooper()); 283 } 284 return mHandler; 285 } 286 287 private class CallbackHandler extends Handler { 288 public CallbackHandler(Looper l) { 289 super(l); 290 } 291 292 @Override 293 public void handleMessage(Message msg) { 294 try { 295 switch (msg.what) { 296 case CAMERA_ERROR: { 297 int errorCode = msg.arg1; 298 CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; 299 mCallbacks.onDeviceError(errorCode, resultExtras); 300 break; 301 } 302 case CAMERA_IDLE: 303 mCallbacks.onDeviceIdle(); 304 break; 305 case CAPTURE_STARTED: { 306 long timestamp = msg.arg2 & 0xFFFFFFFFL; 307 timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL); 308 CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; 309 mCallbacks.onCaptureStarted(resultExtras, timestamp); 310 break; 311 } 312 case RESULT_RECEIVED: { 313 Object[] resultArray = (Object[]) msg.obj; 314 CameraMetadataNative result = (CameraMetadataNative) resultArray[0]; 315 CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1]; 316 mCallbacks.onResultReceived(result, resultExtras); 317 break; 318 } 319 case PREPARED: { 320 int streamId = msg.arg1; 321 mCallbacks.onPrepared(streamId); 322 break; 323 } 324 case REPEATING_REQUEST_ERROR: { 325 long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL; 326 lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL); 327 mCallbacks.onRepeatingRequestError(lastFrameNumber); 328 break; 329 } 330 default: 331 throw new IllegalArgumentException( 332 "Unknown callback message " + msg.what); 333 } 334 } catch (RemoteException e) { 335 throw new IllegalStateException( 336 "Received remote exception during camera callback " + msg.what, e); 337 } 338 } 339 } 340 } 341 342 public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks, 343 int cameraId) { 344 if (DEBUG) { 345 Log.d(TAG, "Opening shim Camera device"); 346 } 347 348 /* 349 * Put the camera open on a separate thread with its own looper; otherwise 350 * if the main thread is used then the callbacks might never get delivered 351 * (e.g. in CTS which run its own default looper only after tests) 352 */ 353 354 CameraLooper init = new CameraLooper(cameraId); 355 356 CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks); 357 358 // TODO: Make this async instead of blocking 359 int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS); 360 Camera legacyCamera = init.getCamera(); 361 362 // Check errors old HAL initialization 363 LegacyExceptionUtils.throwOnServiceError(initErrors); 364 365 // Disable shutter sounds (this will work unconditionally) for api2 clients 366 legacyCamera.disableShutterSound(); 367 368 CameraInfo info = new CameraInfo(); 369 Camera.getCameraInfo(cameraId, info); 370 371 Camera.Parameters legacyParameters = null; 372 try { 373 legacyParameters = legacyCamera.getParameters(); 374 } catch (RuntimeException e) { 375 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, 376 "Unable to get initial parameters: " + e.getMessage()); 377 } 378 379 CameraCharacteristics characteristics = 380 LegacyMetadataMapper.createCharacteristics(legacyParameters, info); 381 LegacyCameraDevice device = new LegacyCameraDevice( 382 cameraId, legacyCamera, characteristics, threadCallbacks); 383 return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks); 384 } 385 386 @Override 387 public void disconnect() { 388 if (DEBUG) { 389 Log.d(TAG, "disconnect called."); 390 } 391 392 if (mLegacyDevice.isClosed()) { 393 Log.w(TAG, "Cannot disconnect, device has already been closed."); 394 } 395 396 try { 397 mLegacyDevice.close(); 398 } finally { 399 mCameraInit.close(); 400 mCameraCallbacks.close(); 401 } 402 } 403 404 @Override 405 public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) { 406 if (DEBUG) { 407 Log.d(TAG, "submitRequest called."); 408 } 409 if (mLegacyDevice.isClosed()) { 410 String err = "Cannot submit request, device has been closed."; 411 Log.e(TAG, err); 412 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 413 } 414 415 synchronized(mConfigureLock) { 416 if (mConfiguring) { 417 String err = "Cannot submit request, configuration change in progress."; 418 Log.e(TAG, err); 419 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 420 } 421 } 422 return mLegacyDevice.submitRequest(request, streaming); 423 } 424 425 @Override 426 public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) { 427 if (DEBUG) { 428 Log.d(TAG, "submitRequestList called."); 429 } 430 if (mLegacyDevice.isClosed()) { 431 String err = "Cannot submit request list, device has been closed."; 432 Log.e(TAG, err); 433 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 434 } 435 436 synchronized(mConfigureLock) { 437 if (mConfiguring) { 438 String err = "Cannot submit request, configuration change in progress."; 439 Log.e(TAG, err); 440 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 441 } 442 } 443 return mLegacyDevice.submitRequestList(request, streaming); 444 } 445 446 @Override 447 public long cancelRequest(int requestId) { 448 if (DEBUG) { 449 Log.d(TAG, "cancelRequest called."); 450 } 451 if (mLegacyDevice.isClosed()) { 452 String err = "Cannot cancel request, device has been closed."; 453 Log.e(TAG, err); 454 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 455 } 456 457 synchronized(mConfigureLock) { 458 if (mConfiguring) { 459 String err = "Cannot cancel request, configuration change in progress."; 460 Log.e(TAG, err); 461 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 462 } 463 } 464 return mLegacyDevice.cancelRequest(requestId); 465 } 466 467 @Override 468 public void beginConfigure() { 469 if (DEBUG) { 470 Log.d(TAG, "beginConfigure called."); 471 } 472 if (mLegacyDevice.isClosed()) { 473 String err = "Cannot begin configure, device has been closed."; 474 Log.e(TAG, err); 475 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 476 } 477 478 synchronized(mConfigureLock) { 479 if (mConfiguring) { 480 String err = "Cannot begin configure, configuration change already in progress."; 481 Log.e(TAG, err); 482 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 483 } 484 mConfiguring = true; 485 } 486 } 487 488 @Override 489 public void endConfigure(boolean isConstrainedHighSpeed) { 490 if (DEBUG) { 491 Log.d(TAG, "endConfigure called."); 492 } 493 if (mLegacyDevice.isClosed()) { 494 String err = "Cannot end configure, device has been closed."; 495 Log.e(TAG, err); 496 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 497 } 498 499 SparseArray<Surface> surfaces = null; 500 synchronized(mConfigureLock) { 501 if (!mConfiguring) { 502 String err = "Cannot end configure, no configuration change in progress."; 503 Log.e(TAG, err); 504 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 505 } 506 if (mSurfaces != null) { 507 surfaces = mSurfaces.clone(); 508 } 509 mConfiguring = false; 510 } 511 mLegacyDevice.configureOutputs(surfaces); 512 } 513 514 @Override 515 public void deleteStream(int streamId) { 516 if (DEBUG) { 517 Log.d(TAG, "deleteStream called."); 518 } 519 if (mLegacyDevice.isClosed()) { 520 String err = "Cannot delete stream, device has been closed."; 521 Log.e(TAG, err); 522 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 523 } 524 525 synchronized(mConfigureLock) { 526 if (!mConfiguring) { 527 String err = "Cannot delete stream, no configuration change in progress."; 528 Log.e(TAG, err); 529 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 530 } 531 int index = mSurfaces.indexOfKey(streamId); 532 if (index < 0) { 533 String err = "Cannot delete stream, stream id " + streamId + " doesn't exist."; 534 Log.e(TAG, err); 535 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); 536 } 537 mSurfaces.removeAt(index); 538 } 539 } 540 541 @Override 542 public int createStream(OutputConfiguration outputConfiguration) { 543 if (DEBUG) { 544 Log.d(TAG, "createStream called."); 545 } 546 if (mLegacyDevice.isClosed()) { 547 String err = "Cannot create stream, device has been closed."; 548 Log.e(TAG, err); 549 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 550 } 551 552 synchronized(mConfigureLock) { 553 if (!mConfiguring) { 554 String err = "Cannot create stream, beginConfigure hasn't been called yet."; 555 Log.e(TAG, err); 556 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 557 } 558 if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) { 559 String err = "Cannot create stream, stream rotation is not supported."; 560 Log.e(TAG, err); 561 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); 562 } 563 int id = ++mSurfaceIdCounter; 564 mSurfaces.put(id, outputConfiguration.getSurface()); 565 return id; 566 } 567 } 568 569 @Override 570 public void setDeferredConfiguration(int steamId, OutputConfiguration config) { 571 String err = "Set deferred configuration is not supported on legacy devices"; 572 Log.e(TAG, err); 573 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 574 } 575 576 @Override 577 public int createInputStream(int width, int height, int format) { 578 String err = "Creating input stream is not supported on legacy devices"; 579 Log.e(TAG, err); 580 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 581 } 582 583 @Override 584 public Surface getInputSurface() { 585 String err = "Getting input surface is not supported on legacy devices"; 586 Log.e(TAG, err); 587 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 588 } 589 590 @Override 591 public CameraMetadataNative createDefaultRequest(int templateId) { 592 if (DEBUG) { 593 Log.d(TAG, "createDefaultRequest called."); 594 } 595 if (mLegacyDevice.isClosed()) { 596 String err = "Cannot create default request, device has been closed."; 597 Log.e(TAG, err); 598 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 599 } 600 601 CameraMetadataNative template; 602 try { 603 template = 604 LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId); 605 } catch (IllegalArgumentException e) { 606 String err = "createDefaultRequest - invalid templateId specified"; 607 Log.e(TAG, err); 608 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); 609 } 610 611 return template; 612 } 613 614 @Override 615 public CameraMetadataNative getCameraInfo() { 616 if (DEBUG) { 617 Log.d(TAG, "getCameraInfo called."); 618 } 619 // TODO: implement getCameraInfo. 620 Log.e(TAG, "getCameraInfo unimplemented."); 621 return null; 622 } 623 624 @Override 625 public void waitUntilIdle() throws RemoteException { 626 if (DEBUG) { 627 Log.d(TAG, "waitUntilIdle called."); 628 } 629 if (mLegacyDevice.isClosed()) { 630 String err = "Cannot wait until idle, device has been closed."; 631 Log.e(TAG, err); 632 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 633 } 634 635 synchronized(mConfigureLock) { 636 if (mConfiguring) { 637 String err = "Cannot wait until idle, configuration change in progress."; 638 Log.e(TAG, err); 639 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 640 } 641 } 642 mLegacyDevice.waitUntilIdle(); 643 } 644 645 @Override 646 public long flush() { 647 if (DEBUG) { 648 Log.d(TAG, "flush called."); 649 } 650 if (mLegacyDevice.isClosed()) { 651 String err = "Cannot flush, device has been closed."; 652 Log.e(TAG, err); 653 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 654 } 655 656 synchronized(mConfigureLock) { 657 if (mConfiguring) { 658 String err = "Cannot flush, configuration change in progress."; 659 Log.e(TAG, err); 660 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); 661 } 662 } 663 return mLegacyDevice.flush(); 664 } 665 666 public void prepare(int streamId) { 667 if (DEBUG) { 668 Log.d(TAG, "prepare called."); 669 } 670 if (mLegacyDevice.isClosed()) { 671 String err = "Cannot prepare stream, device has been closed."; 672 Log.e(TAG, err); 673 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 674 } 675 676 // LEGACY doesn't support actual prepare, just signal success right away 677 mCameraCallbacks.onPrepared(streamId); 678 } 679 680 public void prepare2(int maxCount, int streamId) { 681 // We don't support this in LEGACY mode. 682 prepare(streamId); 683 } 684 685 public void tearDown(int streamId) { 686 if (DEBUG) { 687 Log.d(TAG, "tearDown called."); 688 } 689 if (mLegacyDevice.isClosed()) { 690 String err = "Cannot tear down stream, device has been closed."; 691 Log.e(TAG, err); 692 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); 693 } 694 695 // LEGACY doesn't support actual teardown, so just a no-op 696 } 697 698 @Override 699 public IBinder asBinder() { 700 // This is solely intended to be used for in-process binding. 701 return null; 702 } 703 } 704