Home | History | Annotate | Download | only in testingcamera2
      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      */
    156     public CameraControlPane(Context context, AttributeSet attrs) {
    157         super(context, attrs, null, null);
    158 
    159         mPaneId = 0;
    160         setUpUI(context);
    161     }
    162 
    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 
    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
    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      */
    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 {
    235         public void showCameraInfo(String cameraId);
    236     }
    237 
    238     public CameraCharacteristics getCharacteristics() {
    239         if (mCurrentCameraId != null) {
    240             return mCameraOps.getCameraInfo(mCurrentCameraId);
    241         }
    242         return null;
    243     }
    244 
    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      */
    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 
    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 
    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 
    300     private CaptureCallback mResultListener = new CaptureCallback() {
    301         public void onCaptureCompleted(
    302                 CameraCaptureSession session,
    303                 CaptureRequest request,
    304                 TotalCaptureResult result) {
    305             mRecentResults.add(result);
    306             if (mRecentResults.size() > MAX_CACHED_RESULTS) {
    307                 mRecentResults.remove();
    308             }
    309         }
    310     };
    311 
    312     private void setUpUI(Context context) {
    313         String paneName =
    314                 String.format(Locale.US, "%s %c",
    315                         context.getResources().getString(R.string.camera_pane_title),
    316                         (char) ('A' + mPaneId));
    317         this.setName(paneName);
    318 
    319         LayoutInflater inflater =
    320                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    321 
    322         inflater.inflate(R.layout.camera_pane, this);
    323 
    324         mCameraSpinner = (Spinner) findViewById(R.id.camera_pane_camera_spinner);
    325         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
    326 
    327         mOpenButton = (ToggleButton) findViewById(R.id.camera_pane_open_button);
    328         mOpenButton.setOnCheckedChangeListener(mOpenButtonListener);
    329         mBaseControls.add(mOpenButton);
    330 
    331         mInfoButton = (Button) findViewById(R.id.camera_pane_info_button);
    332         mInfoButton.setOnClickListener(mInfoButtonListener);
    333         mBaseControls.add(mInfoButton);
    334 
    335         mStatusText = (TextView) findViewById(R.id.camera_pane_status_text);
    336 
    337         mConfigureButton = (Button) findViewById(R.id.camera_pane_configure_button);
    338         mConfigureButton.setOnClickListener(mConfigureButtonListener);
    339         mOpenControls.add(mConfigureButton);
    340 
    341         mStopButton = (Button) findViewById(R.id.camera_pane_stop_button);
    342         mStopButton.setOnClickListener(mStopButtonListener);
    343         mConfiguredControls.add(mStopButton);
    344         mFlushButton = (Button) findViewById(R.id.camera_pane_flush_button);
    345         mFlushButton.setOnClickListener(mFlushButtonListener);
    346         mConfiguredControls.add(mFlushButton);
    347     }
    348 
    349     private void initializeCameras(TestingCamera21 tc) {
    350         mCameraOps = tc.getCameraOps();
    351         mInfoDisplayer = tc;
    352 
    353         updateCameraList();
    354     }
    355 
    356     private void updateCameraList() {
    357         mCameraIds = null;
    358         try {
    359             mCameraIds = mCameraOps.getCamerasAndListen(mCameraAvailabilityCallback);
    360             String[] cameraSpinnerItems = new String[mCameraIds.length];
    361             for (int i = 0; i < mCameraIds.length; i++) {
    362                 cameraSpinnerItems[i] = String.format("Camera %s", mCameraIds[i]);
    363             }
    364             mCameraSpinner.setAdapter(new ArrayAdapter<String>(getContext(), R.layout.spinner_item,
    365                     cameraSpinnerItems));
    366 
    367         } catch (CameraAccessException e) {
    368             TLog.e("Exception trying to get list of cameras: " + e);
    369         }
    370     }
    371 
    372     private final CompoundButton.OnCheckedChangeListener mOpenButtonListener =
    373             new CompoundButton.OnCheckedChangeListener() {
    374                 @Override
    375                 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    376                     if (isChecked) {
    377                         // Open camera
    378                         mCurrentCamera = null;
    379                         boolean success = mCameraOps.openCamera(mCurrentCameraId, mCameraListener);
    380                         buttonView.setChecked(success);
    381                     } else {
    382                         // Close camera
    383                         closeCurrentCamera();
    384                     }
    385                 }
    386             };
    387 
    388     private final OnClickListener mInfoButtonListener = new OnClickListener() {
    389         @Override
    390         public void onClick(View v) {
    391             mInfoDisplayer.showCameraInfo(mCurrentCameraId);
    392         }
    393     };
    394 
    395     private final OnClickListener mStopButtonListener = new OnClickListener() {
    396         @Override
    397         public void onClick(View v) {
    398             if (mCurrentCaptureSession != null) {
    399                 try {
    400                     mCurrentCaptureSession.stopRepeating();
    401                 } catch (CameraAccessException e) {
    402                     TLog.e("Unable to stop repeating request for camera %s.", e, mCurrentCameraId);
    403                 }
    404             }
    405         }
    406     };
    407 
    408     private final OnClickListener mFlushButtonListener = new OnClickListener() {
    409         @Override
    410         public void onClick(View v) {
    411             if (mCurrentCaptureSession != null) {
    412                 try {
    413                     mCurrentCaptureSession.abortCaptures();
    414                 } catch (CameraAccessException e) {
    415                     TLog.e("Unable to flush camera %s.", e, mCurrentCameraId);
    416                 }
    417             }
    418         }
    419     };
    420 
    421     private final OnClickListener mConfigureButtonListener = new OnClickListener() {
    422         @Override
    423         public void onClick(View v) {
    424             List<Surface> targetSurfaces = new ArrayList<Surface>();
    425             List<TargetControlPane> targetPanes = new ArrayList<TargetControlPane>();
    426             for (TargetControlPane targetPane : mPaneTracker.getPanes(TargetControlPane.class)) {
    427                 Surface target = targetPane.getTargetSurfaceForCameraPane(getPaneName());
    428                 if (target != null) {
    429                     targetSurfaces.add(target);
    430                     targetPanes.add(targetPane);
    431                 }
    432             }
    433             try {
    434                 TLog.i("Configuring camera %s with %d surfaces", mCurrentCamera.getId(),
    435                         targetSurfaces.size());
    436                 mActiveCameraCall = CameraCall.CONFIGURE;
    437                 if (targetSurfaces.size() > 0) {
    438                     mCurrentCamera.createCaptureSession(targetSurfaces, mSessionListener, /*handler*/null);
    439                 } else if (mCurrentCaptureSession != null) {
    440                     mCurrentCaptureSession.close();
    441                     mCurrentCaptureSession = null;
    442                 }
    443                 mConfiguredSurfaces = targetSurfaces;
    444                 mConfiguredTargetPanes = targetPanes;
    445             } catch (CameraAccessException e) {
    446                 mActiveCameraCall = CameraCall.NONE;
    447                 TLog.e("Unable to configure camera %s.", e, mCurrentCamera.getId());
    448             } catch (IllegalArgumentException e) {
    449                 mActiveCameraCall = CameraCall.NONE;
    450                 TLog.e("Unable to configure camera %s.", e, mCurrentCamera.getId());
    451             } catch (IllegalStateException e) {
    452                 mActiveCameraCall = CameraCall.NONE;
    453                 TLog.e("Unable to configure camera %s.", e, mCurrentCamera.getId());
    454             }
    455         }
    456     };
    457 
    458     private final CameraCaptureSession.StateCallback mSessionListener =
    459             new CameraCaptureSession.StateCallback() {
    460 
    461         @Override
    462         public void onConfigured(CameraCaptureSession session) {
    463             mCurrentCaptureSession = session;
    464             TLog.i("Configuration completed for camera %s.", mCurrentCamera.getId());
    465 
    466             setSessionState(SessionState.CONFIGURED);
    467         }
    468 
    469         @Override
    470         public void onConfigureFailed(CameraCaptureSession session) {
    471             mActiveCameraCall = CameraCall.NONE;
    472             TLog.e("Configuration failed for camera %s.", mCurrentCamera.getId());
    473 
    474             setSessionState(SessionState.CONFIGURE_FAILED);
    475         }
    476 
    477         @Override
    478         public void onReady(CameraCaptureSession session) {
    479             setSessionState(SessionState.READY);
    480         }
    481 
    482         /**
    483          * This method is called when the session starts actively processing capture requests.
    484          *
    485          * <p>If capture requests are submitted prior to {@link #onConfigured} being called,
    486          * then the session will start processing those requests immediately after the callback,
    487          * and this method will be immediately called after {@link #onConfigured}.
    488          *
    489          * <p>If the session runs out of capture requests to process and calls {@link #onReady},
    490          * then this callback will be invoked again once new requests are submitted for capture.</p>
    491          */
    492         @Override
    493         public void onActive(CameraCaptureSession session) {
    494             setSessionState(SessionState.ACTIVE);
    495         }
    496 
    497         /**
    498          * This method is called when the session is closed.
    499          *
    500          * <p>A session is closed when a new session is created by the parent camera device,
    501          * or when the parent camera device is closed (either by the user closing the device,
    502          * or due to a camera device disconnection or fatal error).</p>
    503          *
    504          * <p>Once a session is closed, all methods on it will throw an IllegalStateException, and
    505          * any repeating requests or bursts are stopped (as if {@link #stopRepeating()} was called).
    506          * However, any in-progress capture requests submitted to the session will be completed
    507          * as normal.</p>
    508          */
    509         @Override
    510         public void onClosed(CameraCaptureSession session) {
    511             // Ignore closes if the session has been replaced
    512             if (mCurrentCaptureSession != null && session != mCurrentCaptureSession) {
    513                 return;
    514             }
    515             setSessionState(SessionState.CLOSED);
    516         }
    517     };
    518 
    519     private final CameraDevice.StateCallback mCameraListener = new CameraDevice.StateCallback() {
    520         @Override
    521         public void onClosed(CameraDevice camera) {
    522             // Don't change state on close, tracked by callers of close()
    523         }
    524 
    525         @Override
    526         public void onDisconnected(CameraDevice camera) {
    527             setCameraState(CameraState.DISCONNECTED);
    528         }
    529 
    530         @Override
    531         public void onError(CameraDevice camera, int error) {
    532             setCameraState(CameraState.ERROR);
    533         }
    534 
    535         @Override
    536         public void onOpened(CameraDevice camera) {
    537             mCurrentCamera = camera;
    538             setCameraState(CameraState.OPENED);
    539         }
    540     };
    541 
    542     private void switchToCamera(String newCameraId) {
    543         closeCurrentCamera();
    544 
    545         mCurrentCameraId = newCameraId;
    546 
    547         if (mCurrentCameraId == null) {
    548             setCameraState(CameraState.UNAVAILABLE);
    549         } else {
    550             setCameraState(CameraState.CLOSED);
    551         }
    552 
    553         mPaneTracker.notifyOtherPanes(this, PaneTracker.PaneEvent.NEW_CAMERA_SELECTED);
    554     }
    555 
    556     private void closeCurrentCamera() {
    557         if (mCurrentCamera != null) {
    558             mCurrentCamera.close();
    559             mCurrentCamera = null;
    560             setCameraState(CameraState.CLOSED);
    561             mOpenButton.setChecked(false);
    562         }
    563     }
    564 
    565     private void setSessionState(SessionState newState) {
    566         mSessionState = newState;
    567         mStatusText.setText("S." + mSessionState.toString());
    568 
    569         switch (mSessionState) {
    570             case CONFIGURE_FAILED:
    571                 mActiveCameraCall = CameraCall.NONE;
    572                 // fall-through
    573             case CLOSED:
    574                 enableBaseControls(true);
    575                 enableOpenControls(true);
    576                 enableConfiguredControls(false);
    577                 mConfiguredTargetPanes = null;
    578                 break;
    579             case NONE:
    580                 enableBaseControls(true);
    581                 enableOpenControls(true);
    582                 enableConfiguredControls(false);
    583                 mConfiguredTargetPanes = null;
    584                 break;
    585             case CONFIGURED:
    586                 if (mActiveCameraCall != CameraCall.CONFIGURE) {
    587                     throw new AssertionError();
    588                 }
    589                 mPaneTracker.notifyOtherPanes(this, PaneEvent.CAMERA_CONFIGURED);
    590                 mActiveCameraCall = CameraCall.NONE;
    591                 // fall-through
    592             case READY:
    593             case ACTIVE:
    594                 enableBaseControls(true);
    595                 enableOpenControls(true);
    596                 enableConfiguredControls(true);
    597                 break;
    598             default:
    599                 throw new AssertionError("Unhandled case " + mSessionState);
    600         }
    601     }
    602 
    603     private void setCameraState(CameraState newState) {
    604         mCameraState = newState;
    605         mStatusText.setText("C." + mCameraState.toString());
    606         switch (mCameraState) {
    607             case UNAVAILABLE:
    608                 enableBaseControls(false);
    609                 enableOpenControls(false);
    610                 enableConfiguredControls(false);
    611                 mConfiguredTargetPanes = null;
    612                 break;
    613             case CLOSED:
    614             case DISCONNECTED:
    615             case ERROR:
    616                 enableBaseControls(true);
    617                 enableOpenControls(false);
    618                 enableConfiguredControls(false);
    619                 mConfiguredTargetPanes = null;
    620                 break;
    621             case OPENED:
    622                 enableBaseControls(true);
    623                 enableOpenControls(true);
    624                 enableConfiguredControls(false);
    625                 mConfiguredTargetPanes = null;
    626                 break;
    627         }
    628     }
    629 
    630     private void enableBaseControls(boolean enabled) {
    631         for (View v : mBaseControls) {
    632             v.setEnabled(enabled);
    633         }
    634         if (!enabled) {
    635             mOpenButton.setChecked(false);
    636         }
    637     }
    638 
    639     private void enableOpenControls(boolean enabled) {
    640         for (View v : mOpenControls) {
    641             v.setEnabled(enabled);
    642         }
    643     }
    644 
    645     private void enableConfiguredControls(boolean enabled) {
    646         for (View v : mConfiguredControls) {
    647             v.setEnabled(enabled);
    648         }
    649     }
    650 
    651     private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
    652             new CameraManager.AvailabilityCallback() {
    653         @Override
    654         public void onCameraAvailable(String cameraId) {
    655             // TODO: Update camera list in an intelligent fashion
    656             // (can't just call updateCameraList or the selected camera may change)
    657         }
    658 
    659         @Override
    660         public void onCameraUnavailable(String cameraId) {
    661             // TODO: Update camera list in an intelligent fashion
    662             // (can't just call updateCameraList or the selected camera may change)
    663         }
    664     };
    665 
    666     private final OnItemSelectedListener mCameraSpinnerListener = new OnItemSelectedListener() {
    667         @Override
    668         public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
    669             String newCameraId = mCameraIds[pos];
    670             if (newCameraId != mCurrentCameraId) {
    671                 switchToCamera(newCameraId);
    672             }
    673         }
    674 
    675         @Override
    676         public void onNothingSelected(AdapterView<?> parent) {
    677             switchToCamera(null);
    678         }
    679     };
    680 }
    681