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