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 com.android.testingcamera2; 18 19 import java.util.ArrayList; 20 import java.util.HashSet; 21 import java.util.LinkedList; 22 import java.util.List; 23 import java.util.Locale; 24 import java.util.Set; 25 26 import android.content.Context; 27 import android.util.AttributeSet; 28 import android.view.LayoutInflater; 29 import android.view.Surface; 30 import android.view.View; 31 import android.widget.AdapterView; 32 import android.widget.AdapterView.OnItemSelectedListener; 33 import android.widget.ArrayAdapter; 34 import android.widget.Button; 35 import android.widget.CompoundButton; 36 import android.widget.Spinner; 37 import android.widget.TextView; 38 import android.widget.ToggleButton; 39 import android.hardware.camera2.CameraAccessException; 40 import android.hardware.camera2.CameraCaptureSession; 41 import android.hardware.camera2.CameraCaptureSession.CaptureCallback; 42 import android.hardware.camera2.CameraCharacteristics; 43 import android.hardware.camera2.CameraDevice; 44 import android.hardware.camera2.CameraManager; 45 import android.hardware.camera2.CaptureRequest; 46 import android.hardware.camera2.CaptureResult; 47 import android.hardware.camera2.TotalCaptureResult; 48 49 import org.xmlpull.v1.XmlPullParser; 50 import org.xmlpull.v1.XmlPullParserException; 51 52 import com.android.testingcamera2.PaneTracker.PaneEvent; 53 54 import java.io.IOException; 55 56 /** 57 * 58 * Basic control pane block for the control list 59 * 60 */ 61 public class CameraControlPane extends ControlPane { 62 63 // XML attributes 64 65 /** Name of pane tag */ 66 private static final String PANE_NAME = "camera_pane"; 67 68 /** Attribute: ID for pane (integer) */ 69 private static final String PANE_ID = "id"; 70 /** Attribute: ID for camera to select (String) */ 71 private static final String CAMERA_ID = "camera_id"; 72 73 // End XML attributes 74 75 private static final int MAX_CACHED_RESULTS = 100; 76 77 private static int mCameraPaneIdCounter = 0; 78 79 /** 80 * These correspond to the callbacks from 81 * android.hardware.camera2.CameraDevice.StateCallback, plus UNAVAILABLE for 82 * when there's not a valid camera selected. 83 */ 84 private enum CameraState { 85 UNAVAILABLE, 86 CLOSED, 87 OPENED, 88 DISCONNECTED, 89 ERROR 90 } 91 92 /** 93 * These correspond to the callbacks from {@link CameraCaptureSession.StateCallback}, plus 94 * {@code CONFIGURING} for before a session is returned and {@code NONE} for when there 95 * is no session created. 96 */ 97 private enum SessionState { 98 NONE, 99 CONFIGURED, 100 CONFIGURE_FAILED, 101 READY, 102 ACTIVE, 103 CLOSED 104 } 105 106 private enum CameraCall { 107 NONE, 108 CONFIGURE 109 } 110 111 private final int mPaneId; 112 113 private CameraOps2 mCameraOps; 114 private InfoDisplayer mInfoDisplayer; 115 116 private Spinner mCameraSpinner; 117 private ToggleButton mOpenButton; 118 private Button mInfoButton; 119 private TextView mStatusText; 120 private Button mConfigureButton; 121 private Button mStopButton; 122 private Button mFlushButton; 123 124 /** 125 * All controls that should be enabled when there's a valid camera ID 126 * selected 127 */ 128 private final Set<View> mBaseControls = new HashSet<View>(); 129 /** 130 * All controls that should be enabled when camera is at least in the OPEN 131 * state 132 */ 133 private final Set<View> mOpenControls = new HashSet<View>(); 134 /** 135 * All controls that should be enabled when camera is at least in the IDLE 136 * state 137 */ 138 private final Set<View> mConfiguredControls = new HashSet<View>(); 139 140 private String[] mCameraIds; 141 private String mCurrentCameraId; 142 143 private CameraState mCameraState; 144 private CameraDevice mCurrentCamera; 145 private CameraCaptureSession mCurrentCaptureSession; 146 private SessionState mSessionState = SessionState.NONE; 147 private CameraCall mActiveCameraCall; 148 private LinkedList<TotalCaptureResult> mRecentResults = new LinkedList<>(); 149 150 private List<Surface> mConfiguredSurfaces; 151 private List<TargetControlPane> mConfiguredTargetPanes; 152 153 /** 154 * Constructor for tooling only 155 */ CameraControlPane(Context context, AttributeSet attrs)156 public CameraControlPane(Context context, AttributeSet attrs) { 157 super(context, attrs, null, null); 158 159 mPaneId = 0; 160 setUpUI(context); 161 } 162 CameraControlPane(TestingCamera21 tc, AttributeSet attrs, StatusListener listener)163 public CameraControlPane(TestingCamera21 tc, AttributeSet attrs, StatusListener listener) { 164 165 super(tc, attrs, listener, tc.getPaneTracker()); 166 167 mPaneId = mCameraPaneIdCounter++; 168 setUpUI(tc); 169 initializeCameras(tc); 170 171 if (mCameraIds != null) { 172 switchToCamera(mCameraIds[0]); 173 } 174 } 175 CameraControlPane(TestingCamera21 tc, XmlPullParser configParser, StatusListener listener)176 public CameraControlPane(TestingCamera21 tc, XmlPullParser configParser, StatusListener listener) 177 throws XmlPullParserException, IOException { 178 super(tc, null, listener, tc.getPaneTracker()); 179 180 configParser.require(XmlPullParser.START_TAG, XmlPullParser.NO_NAMESPACE, PANE_NAME); 181 182 int paneId = getAttributeInt(configParser, PANE_ID, -1); 183 if (paneId == -1) { 184 mPaneId = mCameraPaneIdCounter++; 185 } else { 186 mPaneId = paneId; 187 if (mPaneId >= mCameraPaneIdCounter) { 188 mCameraPaneIdCounter = mPaneId + 1; 189 } 190 } 191 192 String cameraId = getAttributeString(configParser, CAMERA_ID, null); 193 194 configParser.next(); 195 configParser.require(XmlPullParser.END_TAG, XmlPullParser.NO_NAMESPACE, PANE_NAME); 196 197 setUpUI(tc); 198 initializeCameras(tc); 199 200 boolean gotCamera = false; 201 if (mCameraIds != null && cameraId != null) { 202 for (int i = 0; i < mCameraIds.length; i++) { 203 if (cameraId.equals(mCameraIds[i])) { 204 switchToCamera(mCameraIds[i]); 205 mCameraSpinner.setSelection(i); 206 gotCamera = true; 207 } 208 } 209 } 210 211 if (!gotCamera && mCameraIds != null) { 212 switchToCamera(mCameraIds[0]); 213 } 214 } 215 216 @Override remove()217 public void remove() { 218 closeCurrentCamera(); 219 super.remove(); 220 } 221 222 /** 223 * Get list of target panes that are currently actively configured for this 224 * camera 225 */ getCurrentConfiguredTargets()226 public List<TargetControlPane> getCurrentConfiguredTargets() { 227 return mConfiguredTargetPanes; 228 } 229 230 /** 231 * Interface to be implemented by an application service for displaying a 232 * camera's information. 233 */ 234 public interface InfoDisplayer { showCameraInfo(String cameraId)235 public void showCameraInfo(String cameraId); 236 } 237 getCharacteristics()238 public CameraCharacteristics getCharacteristics() { 239 if (mCurrentCameraId != null) { 240 return mCameraOps.getCameraInfo(mCurrentCameraId); 241 } 242 return null; 243 } 244 getRequestBuilder(int template)245 public CaptureRequest.Builder getRequestBuilder(int template) { 246 CaptureRequest.Builder request = null; 247 if (mCurrentCamera != null) { 248 try { 249 request = mCurrentCamera.createCaptureRequest(template); 250 // Workaround for b/15748139 251 request.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE, 252 CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON); 253 } catch (CameraAccessException e) { 254 TLog.e("Unable to build request for camera %s with template %d.", e, 255 mCurrentCameraId, template); 256 } 257 } 258 return request; 259 } 260 261 /** 262 * Send single capture to camera device. 263 * 264 * @param request 265 * @return true if capture sent successfully 266 */ capture(CaptureRequest request)267 public boolean capture(CaptureRequest request) { 268 if (mCurrentCaptureSession != null) { 269 try { 270 mCurrentCaptureSession.capture(request, mResultListener, null); 271 return true; 272 } catch (CameraAccessException e) { 273 TLog.e("Unable to capture for camera %s.", e, mCurrentCameraId); 274 } 275 } 276 return false; 277 } 278 repeat(CaptureRequest request)279 public boolean repeat(CaptureRequest request) { 280 if (mCurrentCaptureSession != null) { 281 try { 282 mCurrentCaptureSession.setRepeatingRequest(request, mResultListener, null); 283 return true; 284 } catch (CameraAccessException e) { 285 TLog.e("Unable to set repeating request for camera %s.", e, mCurrentCameraId); 286 } 287 } 288 return false; 289 } 290 getResultAt(long timestamp)291 public TotalCaptureResult getResultAt(long timestamp) { 292 for (TotalCaptureResult result : mRecentResults) { 293 long resultTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); 294 if (resultTimestamp == timestamp) return result; 295 if (resultTimestamp > timestamp) return null; 296 } 297 return null; 298 } 299 prepareSurface(Surface target)300 public void prepareSurface(Surface target) { 301 if (mCurrentCaptureSession != null) { 302 try { 303 TLog.i("Preparing Surface " + target); 304 mCurrentCaptureSession.prepare(target); 305 } catch (CameraAccessException e) { 306 TLog.e("Unable to prepare surface for camera %s.", e, mCurrentCameraId); 307 } catch (IllegalArgumentException e) { 308 TLog.e("Bad Surface passed to prepare", e); 309 } 310 } 311 } 312 313 private CaptureCallback mResultListener = new CaptureCallback() { 314 public void onCaptureCompleted( 315 CameraCaptureSession session, 316 CaptureRequest request, 317 TotalCaptureResult result) { 318 mRecentResults.add(result); 319 if (mRecentResults.size() > MAX_CACHED_RESULTS) { 320 mRecentResults.remove(); 321 } 322 } 323 }; 324 setUpUI(Context context)325 private void setUpUI(Context context) { 326 String paneName = 327 String.format(Locale.US, "%s %c", 328 context.getResources().getString(R.string.camera_pane_title), 329 (char) ('A' + mPaneId)); 330 this.setName(paneName); 331 332 LayoutInflater inflater = 333 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 334 335 inflater.inflate(R.layout.camera_pane, this); 336 337 mCameraSpinner = (Spinner) findViewById(R.id.camera_pane_camera_spinner); 338 mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); 339 340 mOpenButton = (ToggleButton) findViewById(R.id.camera_pane_open_button); 341 mOpenButton.setOnCheckedChangeListener(mOpenButtonListener); 342 mBaseControls.add(mOpenButton); 343 344 mInfoButton = (Button) findViewById(R.id.camera_pane_info_button); 345 mInfoButton.setOnClickListener(mInfoButtonListener); 346 mBaseControls.add(mInfoButton); 347 348 mStatusText = (TextView) findViewById(R.id.camera_pane_status_text); 349 350 mConfigureButton = (Button) findViewById(R.id.camera_pane_configure_button); 351 mConfigureButton.setOnClickListener(mConfigureButtonListener); 352 mOpenControls.add(mConfigureButton); 353 354 mStopButton = (Button) findViewById(R.id.camera_pane_stop_button); 355 mStopButton.setOnClickListener(mStopButtonListener); 356 mConfiguredControls.add(mStopButton); 357 mFlushButton = (Button) findViewById(R.id.camera_pane_flush_button); 358 mFlushButton.setOnClickListener(mFlushButtonListener); 359 mConfiguredControls.add(mFlushButton); 360 } 361 initializeCameras(TestingCamera21 tc)362 private void initializeCameras(TestingCamera21 tc) { 363 mCameraOps = tc.getCameraOps(); 364 mInfoDisplayer = tc; 365 366 updateCameraList(); 367 } 368 updateCameraList()369 private void updateCameraList() { 370 mCameraIds = null; 371 try { 372 mCameraIds = mCameraOps.getCamerasAndListen(mCameraAvailabilityCallback); 373 String[] cameraSpinnerItems = new String[mCameraIds.length]; 374 for (int i = 0; i < mCameraIds.length; i++) { 375 cameraSpinnerItems[i] = String.format("Camera %s", mCameraIds[i]); 376 } 377 mCameraSpinner.setAdapter(new ArrayAdapter<String>(getContext(), R.layout.spinner_item, 378 cameraSpinnerItems)); 379 380 } catch (CameraAccessException e) { 381 TLog.e("Exception trying to get list of cameras: " + e); 382 } 383 } 384 385 private final CompoundButton.OnCheckedChangeListener mOpenButtonListener = 386 new CompoundButton.OnCheckedChangeListener() { 387 @Override 388 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 389 if (isChecked) { 390 // Open camera 391 mCurrentCamera = null; 392 mCameraOps.openCamera(mCurrentCameraId, mCameraListener); 393 } else { 394 // Close camera 395 closeCurrentCamera(); 396 } 397 } 398 }; 399 400 private final OnClickListener mInfoButtonListener = new OnClickListener() { 401 @Override 402 public void onClick(View v) { 403 mInfoDisplayer.showCameraInfo(mCurrentCameraId); 404 } 405 }; 406 407 private final OnClickListener mStopButtonListener = new OnClickListener() { 408 @Override 409 public void onClick(View v) { 410 if (mCurrentCaptureSession != null) { 411 try { 412 mCurrentCaptureSession.stopRepeating(); 413 } catch (CameraAccessException e) { 414 TLog.e("Unable to stop repeating request for camera %s.", e, mCurrentCameraId); 415 } 416 } 417 } 418 }; 419 420 private final OnClickListener mFlushButtonListener = new OnClickListener() { 421 @Override 422 public void onClick(View v) { 423 if (mCurrentCaptureSession != null) { 424 try { 425 mCurrentCaptureSession.abortCaptures(); 426 } catch (CameraAccessException e) { 427 TLog.e("Unable to flush camera %s.", e, mCurrentCameraId); 428 } 429 } 430 } 431 }; 432 433 private final OnClickListener mConfigureButtonListener = new OnClickListener() { 434 @Override 435 public void onClick(View v) { 436 List<Surface> targetSurfaces = new ArrayList<Surface>(); 437 List<TargetControlPane> targetPanes = new ArrayList<TargetControlPane>(); 438 for (TargetControlPane targetPane : mPaneTracker.getPanes(TargetControlPane.class)) { 439 Surface target = targetPane.getTargetSurfaceForCameraPane(getPaneName()); 440 if (target != null) { 441 targetSurfaces.add(target); 442 targetPanes.add(targetPane); 443 } 444 } 445 try { 446 TLog.i("Configuring camera %s with %d surfaces", mCurrentCamera.getId(), 447 targetSurfaces.size()); 448 mActiveCameraCall = CameraCall.CONFIGURE; 449 if (targetSurfaces.size() > 0) { 450 mCurrentCamera.createCaptureSession(targetSurfaces, mSessionListener, /*handler*/null); 451 } else if (mCurrentCaptureSession != null) { 452 mCurrentCaptureSession.close(); 453 mCurrentCaptureSession = null; 454 } 455 mConfiguredSurfaces = targetSurfaces; 456 mConfiguredTargetPanes = targetPanes; 457 } catch (CameraAccessException e) { 458 mActiveCameraCall = CameraCall.NONE; 459 TLog.e("Unable to configure camera %s.", e, mCurrentCamera.getId()); 460 } catch (IllegalArgumentException e) { 461 mActiveCameraCall = CameraCall.NONE; 462 TLog.e("Unable to configure camera %s.", e, mCurrentCamera.getId()); 463 } catch (IllegalStateException e) { 464 mActiveCameraCall = CameraCall.NONE; 465 TLog.e("Unable to configure camera %s.", e, mCurrentCamera.getId()); 466 } 467 } 468 }; 469 470 private final CameraCaptureSession.StateCallback mSessionListener = 471 new CameraCaptureSession.StateCallback() { 472 473 @Override 474 public void onConfigured(CameraCaptureSession session) { 475 mCurrentCaptureSession = session; 476 TLog.i("Configuration completed for camera %s.", mCurrentCamera.getId()); 477 478 setSessionState(SessionState.CONFIGURED); 479 } 480 481 @Override 482 public void onConfigureFailed(CameraCaptureSession session) { 483 mActiveCameraCall = CameraCall.NONE; 484 TLog.e("Configuration failed for camera %s.", mCurrentCamera.getId()); 485 486 setSessionState(SessionState.CONFIGURE_FAILED); 487 } 488 489 @Override 490 public void onReady(CameraCaptureSession session) { 491 setSessionState(SessionState.READY); 492 } 493 494 /** 495 * This method is called when the session starts actively processing capture requests. 496 * 497 * <p>If capture requests are submitted prior to {@link #onConfigured} being called, 498 * then the session will start processing those requests immediately after the callback, 499 * and this method will be immediately called after {@link #onConfigured}. 500 * 501 * <p>If the session runs out of capture requests to process and calls {@link #onReady}, 502 * then this callback will be invoked again once new requests are submitted for capture.</p> 503 */ 504 @Override 505 public void onActive(CameraCaptureSession session) { 506 setSessionState(SessionState.ACTIVE); 507 } 508 509 /** 510 * This method is called when the session is closed. 511 * 512 * <p>A session is closed when a new session is created by the parent camera device, 513 * or when the parent camera device is closed (either by the user closing the device, 514 * or due to a camera device disconnection or fatal error).</p> 515 * 516 * <p>Once a session is closed, all methods on it will throw an IllegalStateException, and 517 * any repeating requests or bursts are stopped (as if {@link #stopRepeating()} was called). 518 * However, any in-progress capture requests submitted to the session will be completed 519 * as normal.</p> 520 */ 521 @Override 522 public void onClosed(CameraCaptureSession session) { 523 // Ignore closes if the session has been replaced 524 if (mCurrentCaptureSession != null && session != mCurrentCaptureSession) { 525 return; 526 } 527 setSessionState(SessionState.CLOSED); 528 } 529 530 @Override 531 public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { 532 TLog.i("Surface preparation complete for Surface " + surface); 533 } 534 535 }; 536 537 private final CameraDevice.StateCallback mCameraListener = new CameraDevice.StateCallback() { 538 @Override 539 public void onClosed(CameraDevice camera) { 540 // Don't change state on close, tracked by callers of close() 541 mOpenButton.setChecked(false); 542 } 543 544 @Override 545 public void onDisconnected(CameraDevice camera) { 546 setCameraState(CameraState.DISCONNECTED); 547 } 548 549 @Override 550 public void onError(CameraDevice camera, int error) { 551 setCameraState(CameraState.ERROR); 552 } 553 554 @Override 555 public void onOpened(CameraDevice camera) { 556 mCurrentCamera = camera; 557 setCameraState(CameraState.OPENED); 558 } 559 }; 560 switchToCamera(String newCameraId)561 private void switchToCamera(String newCameraId) { 562 closeCurrentCamera(); 563 564 mCurrentCameraId = newCameraId; 565 566 if (mCurrentCameraId == null) { 567 setCameraState(CameraState.UNAVAILABLE); 568 } else { 569 setCameraState(CameraState.CLOSED); 570 } 571 572 mPaneTracker.notifyOtherPanes(this, PaneTracker.PaneEvent.NEW_CAMERA_SELECTED); 573 } 574 closeCurrentCamera()575 private void closeCurrentCamera() { 576 if (mCurrentCamera != null) { 577 mCurrentCamera.close(); 578 mCurrentCamera = null; 579 setCameraState(CameraState.CLOSED); 580 } 581 } 582 setSessionState(SessionState newState)583 private void setSessionState(SessionState newState) { 584 mSessionState = newState; 585 mStatusText.setText("S." + mSessionState.toString()); 586 587 switch (mSessionState) { 588 case CONFIGURE_FAILED: 589 mActiveCameraCall = CameraCall.NONE; 590 // fall-through 591 case CLOSED: 592 enableBaseControls(true); 593 enableOpenControls(true); 594 enableConfiguredControls(false); 595 mConfiguredTargetPanes = null; 596 break; 597 case NONE: 598 enableBaseControls(true); 599 enableOpenControls(true); 600 enableConfiguredControls(false); 601 mConfiguredTargetPanes = null; 602 break; 603 case CONFIGURED: 604 if (mActiveCameraCall != CameraCall.CONFIGURE) { 605 throw new AssertionError(); 606 } 607 mPaneTracker.notifyOtherPanes(this, PaneEvent.CAMERA_CONFIGURED); 608 mActiveCameraCall = CameraCall.NONE; 609 // fall-through 610 case READY: 611 case ACTIVE: 612 enableBaseControls(true); 613 enableOpenControls(true); 614 enableConfiguredControls(true); 615 break; 616 default: 617 throw new AssertionError("Unhandled case " + mSessionState); 618 } 619 } 620 setCameraState(CameraState newState)621 private void setCameraState(CameraState newState) { 622 mCameraState = newState; 623 mStatusText.setText("C." + mCameraState.toString()); 624 switch (mCameraState) { 625 case UNAVAILABLE: 626 enableBaseControls(false); 627 enableOpenControls(false); 628 enableConfiguredControls(false); 629 mConfiguredTargetPanes = null; 630 break; 631 case CLOSED: 632 case DISCONNECTED: 633 case ERROR: 634 enableBaseControls(true); 635 enableOpenControls(false); 636 enableConfiguredControls(false); 637 mConfiguredTargetPanes = null; 638 break; 639 case OPENED: 640 enableBaseControls(true); 641 enableOpenControls(true); 642 enableConfiguredControls(false); 643 mConfiguredTargetPanes = null; 644 break; 645 } 646 } 647 enableBaseControls(boolean enabled)648 private void enableBaseControls(boolean enabled) { 649 for (View v : mBaseControls) { 650 v.setEnabled(enabled); 651 } 652 if (!enabled) { 653 mOpenButton.setChecked(false); 654 } 655 } 656 enableOpenControls(boolean enabled)657 private void enableOpenControls(boolean enabled) { 658 for (View v : mOpenControls) { 659 v.setEnabled(enabled); 660 } 661 } 662 enableConfiguredControls(boolean enabled)663 private void enableConfiguredControls(boolean enabled) { 664 for (View v : mConfiguredControls) { 665 v.setEnabled(enabled); 666 } 667 } 668 669 private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback = 670 new CameraManager.AvailabilityCallback() { 671 @Override 672 public void onCameraAvailable(String cameraId) { 673 // TODO: Update camera list in an intelligent fashion 674 // (can't just call updateCameraList or the selected camera may change) 675 } 676 677 @Override 678 public void onCameraUnavailable(String cameraId) { 679 // TODO: Update camera list in an intelligent fashion 680 // (can't just call updateCameraList or the selected camera may change) 681 } 682 }; 683 684 private final OnItemSelectedListener mCameraSpinnerListener = new OnItemSelectedListener() { 685 @Override 686 public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { 687 String newCameraId = mCameraIds[pos]; 688 if (newCameraId != mCurrentCameraId) { 689 switchToCamera(newCameraId); 690 } 691 } 692 693 @Override 694 public void onNothingSelected(AdapterView<?> parent) { 695 switchToCamera(null); 696 } 697 }; 698 } 699