• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
CameraControlPane(Context context, AttributeSet attrs)156     public CameraControlPane(Context context, AttributeSet attrs) {
157         super(context, attrs, null, null);
158 
159         mPaneId = 0;
160         setUpUI(context);
161     }
162 
CameraControlPane(TestingCamera21 tc, AttributeSet attrs, StatusListener listener)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 
CameraControlPane(TestingCamera21 tc, XmlPullParser configParser, StatusListener listener)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
remove()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      */
getCurrentConfiguredTargets()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 {
showCameraInfo(String cameraId)235         public void showCameraInfo(String cameraId);
236     }
237 
getCharacteristics()238     public CameraCharacteristics getCharacteristics() {
239         if (mCurrentCameraId != null) {
240             return mCameraOps.getCameraInfo(mCurrentCameraId);
241         }
242         return null;
243     }
244 
getRequestBuilder(int template)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      */
capture(CaptureRequest request)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 
repeat(CaptureRequest request)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 
getResultAt(long timestamp)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 
setUpUI(Context context)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 
initializeCameras(TestingCamera21 tc)349     private void initializeCameras(TestingCamera21 tc) {
350         mCameraOps = tc.getCameraOps();
351         mInfoDisplayer = tc;
352 
353         updateCameraList();
354     }
355 
updateCameraList()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 
switchToCamera(String newCameraId)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 
closeCurrentCamera()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 
setSessionState(SessionState newState)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 
setCameraState(CameraState newState)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 
enableBaseControls(boolean enabled)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 
enableOpenControls(boolean enabled)639     private void enableOpenControls(boolean enabled) {
640         for (View v : mOpenControls) {
641             v.setEnabled(enabled);
642         }
643     }
644 
enableConfiguredControls(boolean enabled)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