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