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