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     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 
    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 
    362     private void initializeCameras(TestingCamera21 tc) {
    363         mCameraOps = tc.getCameraOps();
    364         mInfoDisplayer = tc;
    365 
    366         updateCameraList();
    367     }
    368 
    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 
    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 
    575     private void closeCurrentCamera() {
    576         if (mCurrentCamera != null) {
    577             mCurrentCamera.close();
    578             mCurrentCamera = null;
    579             setCameraState(CameraState.CLOSED);
    580         }
    581     }
    582 
    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 
    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 
    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 
    657     private void enableOpenControls(boolean enabled) {
    658         for (View v : mOpenControls) {
    659             v.setEnabled(enabled);
    660         }
    661     }
    662 
    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