1 /*
2  * Copyright (C) 2013 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.v1;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.content.pm.PackageManager;
22 import android.content.res.Configuration;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.ImageFormat;
26 import android.hardware.camera2.CameraCharacteristics;
27 import android.hardware.camera2.CameraCaptureSession;
28 import android.hardware.camera2.CameraDevice;
29 import android.hardware.camera2.CaptureFailure;
30 import android.hardware.camera2.CaptureRequest;
31 import android.hardware.camera2.CaptureResult;
32 import android.hardware.camera2.TotalCaptureResult;
33 import android.media.Image;
34 import android.media.MediaMuxer;
35 import android.os.AsyncTask;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.util.Log;
39 import android.util.Range;
40 import android.view.OrientationEventListener;
41 import android.view.SurfaceHolder;
42 import android.view.SurfaceView;
43 import android.view.View;
44 import android.view.ViewGroup.LayoutParams;
45 import android.view.WindowManager;
46 import android.widget.AdapterView;
47 import android.widget.AdapterView.OnItemSelectedListener;
48 import android.widget.ArrayAdapter;
49 import android.widget.Button;
50 import android.widget.CompoundButton;
51 import android.widget.CheckBox;
52 import android.widget.ImageView;
53 import android.widget.RadioGroup;
54 import android.widget.SeekBar;
55 import android.widget.SeekBar.OnSeekBarChangeListener;
56 import android.widget.Spinner;
57 import android.widget.TextView;
58 import android.widget.ToggleButton;
59 
60 import java.nio.ByteBuffer;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Set;
66 
67 import com.android.testingcamera2.R;
68 
69 public class TestingCamera2 extends Activity implements SurfaceHolder.Callback {
70 
71     private static final String TAG = "TestingCamera2";
72     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
73     private CameraOps mCameraOps;
74     private static final int mSeekBarMax = 100;
75     private static final long MAX_EXPOSURE = 200000000L; // 200ms
76     private static final long MIN_EXPOSURE = 100000L; // 100us
77     private static final long MAX_FRAME_DURATION = 1000000000L; // 1s
78     // Manual control change step size
79     private static final int STEP_SIZE = 100;
80     // Min and max sensitivity ISO values
81     private static final int MIN_SENSITIVITY = 100;
82     private static final int MAX_SENSITIVITY = 1600;
83     private static final int ORIENTATION_UNINITIALIZED = -1;
84 
85     private int mLastOrientation = ORIENTATION_UNINITIALIZED;
86     private OrientationEventListener mOrientationEventListener;
87     private SurfaceView mPreviewView;
88     private ImageView mStillView;
89 
90     private SurfaceHolder mCurrentPreviewHolder = null;
91 
92     private Button mInfoButton;
93     private Button mFlushButton;
94     private ToggleButton mFocusLockToggle;
95     private Spinner mFocusModeSpinner;
96     private CheckBox mUseMediaCodecCheckBox;
97 
98     private SeekBar mSensitivityBar;
99     private SeekBar mExposureBar;
100     private SeekBar mFrameDurationBar;
101 
102     private TextView mSensitivityInfoView;
103     private TextView mExposureInfoView;
104     private TextView mFrameDurationInfoView;
105     private TextView mCaptureResultView;
106     private ToggleButton mRecordingToggle;
107     private ToggleButton mManualCtrlToggle;
108 
109     private CameraControls mCameraControl = null;
110     private final Set<View> mManualControls = new HashSet<View>();
111     private final Set<View> mAutoControls = new HashSet<View>();
112 
113     private static final int PERMISSIONS_REQUEST_CAMERA = 1;
114 
115     Handler mMainHandler;
116     boolean mUseMediaCodec;
117 
118     @Override
onCreate(Bundle savedInstanceState)119     public void onCreate(Bundle savedInstanceState) {
120         super.onCreate(savedInstanceState);
121 
122         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
123                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
124 
125         setContentView(R.layout.main);
126 
127         mPreviewView = (SurfaceView) findViewById(R.id.preview_view);
128         mPreviewView.getHolder().addCallback(this);
129 
130         mStillView = (ImageView) findViewById(R.id.still_view);
131 
132         mInfoButton  = (Button) findViewById(R.id.info_button);
133         mInfoButton.setOnClickListener(mInfoButtonListener);
134         mFlushButton  = (Button) findViewById(R.id.flush_button);
135         mFlushButton.setOnClickListener(mFlushButtonListener);
136 
137         mFocusLockToggle = (ToggleButton) findViewById(R.id.focus_button);
138         mFocusLockToggle.setOnClickListener(mFocusLockToggleListener);
139         mFocusModeSpinner = (Spinner) findViewById(R.id.focus_mode_spinner);
140         mAutoControls.add(mFocusLockToggle);
141 
142         mRecordingToggle = (ToggleButton) findViewById(R.id.start_recording);
143         mRecordingToggle.setOnClickListener(mRecordingToggleListener);
144         mUseMediaCodecCheckBox = (CheckBox) findViewById(R.id.use_media_codec);
145         mUseMediaCodecCheckBox.setOnCheckedChangeListener(mUseMediaCodecListener);
146         mUseMediaCodecCheckBox.setChecked(mUseMediaCodec);
147 
148         mManualCtrlToggle = (ToggleButton) findViewById(R.id.manual_control);
149         mManualCtrlToggle.setOnClickListener(mControlToggleListener);
150 
151         mSensitivityBar = (SeekBar) findViewById(R.id.sensitivity_seekbar);
152         mSensitivityBar.setOnSeekBarChangeListener(mSensitivitySeekBarListener);
153         mSensitivityBar.setMax(mSeekBarMax);
154         mManualControls.add(mSensitivityBar);
155 
156         mExposureBar = (SeekBar) findViewById(R.id.exposure_time_seekbar);
157         mExposureBar.setOnSeekBarChangeListener(mExposureSeekBarListener);
158         mExposureBar.setMax(mSeekBarMax);
159         mManualControls.add(mExposureBar);
160 
161         mFrameDurationBar = (SeekBar) findViewById(R.id.frame_duration_seekbar);
162         mFrameDurationBar.setOnSeekBarChangeListener(mFrameDurationSeekBarListener);
163         mFrameDurationBar.setMax(mSeekBarMax);
164         mManualControls.add(mFrameDurationBar);
165 
166         mSensitivityInfoView = (TextView) findViewById(R.id.sensitivity_bar_label);
167         mExposureInfoView = (TextView) findViewById(R.id.exposure_time_bar_label);
168         mFrameDurationInfoView = (TextView) findViewById(R.id.frame_duration_bar_label);
169         mCaptureResultView = (TextView) findViewById(R.id.capture_result_info_label);
170 
171         enableManualControls(false);
172         mCameraControl = new CameraControls();
173 
174         // Get UI handler
175         mMainHandler = new Handler();
176 
177         try {
178             mCameraOps = CameraOps.create(this, mCameraOpsListener, mMainHandler);
179         } catch(ApiFailureException e) {
180             logException("Cannot create camera ops!",e);
181         }
182 
183         mOrientationEventListener = new OrientationEventListener(this) {
184             @Override
185             public void onOrientationChanged(int orientation) {
186                 if (orientation == ORIENTATION_UNKNOWN) {
187                     orientation = 0;
188                 }
189                 mCameraOps.updateOrientation(orientation);
190             }
191         };
192         mOrientationEventListener.enable();
193         // Process the initial configuration (for i.e. initial orientation)
194         // We need this because #onConfigurationChanged doesn't get called when the app launches
195         maybeUpdateConfiguration(getResources().getConfiguration());
196     }
197 
198     @Override
onResume()199     public void onResume() {
200         super.onResume();
201         if (VERBOSE) Log.v(TAG, String.format("onResume"));
202 
203         if ((checkSelfPermission(Manifest.permission.CAMERA)
204                 != PackageManager.PERMISSION_GRANTED )
205             || (checkSelfPermission(Manifest.permission.RECORD_AUDIO)
206                 != PackageManager.PERMISSION_GRANTED)
207             || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
208                 != PackageManager.PERMISSION_GRANTED)) {
209             Log.i(TAG, "Requested camera/video permissions");
210             requestPermissions(new String[] {
211                         Manifest.permission.CAMERA,
212                         Manifest.permission.RECORD_AUDIO,
213                         Manifest.permission.WRITE_EXTERNAL_STORAGE},
214                     PERMISSIONS_REQUEST_CAMERA);
215             return;
216         }
217 
218         setUpPreview();
219     }
220 
221     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)222     public void onRequestPermissionsResult(int requestCode, String[] permissions,
223             int[] grantResults) {
224         if (requestCode == PERMISSIONS_REQUEST_CAMERA) {
225             for (int i = 0; i < grantResults.length; i++) {
226                 if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
227                     Log.i(TAG, "At least one permission denied, can't continue: " + permissions[i]);
228                     finish();
229                     return;
230                 }
231             }
232 
233             Log.i(TAG, "All permissions granted");
234             setUpPreview();
235         }
236     }
237 
setUpPreview()238     private void setUpPreview() {
239         try {
240             mCameraOps.minimalPreviewConfig(mPreviewView.getHolder());
241             mCurrentPreviewHolder = mPreviewView.getHolder();
242         } catch (ApiFailureException e) {
243             logException("Can't configure preview surface: ",e);
244         }
245     }
246 
247     @Override
onPause()248     public void onPause() {
249         super.onPause();
250         try {
251             if (VERBOSE) Log.v(TAG, String.format("onPause"));
252 
253             mCameraOps.closeDevice();
254         } catch (ApiFailureException e) {
255             logException("Can't close device: ",e);
256         }
257         mCurrentPreviewHolder = null;
258     }
259 
260     @Override
onDestroy()261     protected void onDestroy() {
262         mOrientationEventListener.disable();
263         super.onDestroy();
264     }
265 
266     @Override
onConfigurationChanged(Configuration newConfig)267     public void onConfigurationChanged(Configuration newConfig) {
268         super.onConfigurationChanged(newConfig);
269 
270         if (VERBOSE) {
271             Log.v(TAG, String.format("onConfiguredChanged: orientation %x",
272                     newConfig.orientation));
273         }
274 
275         maybeUpdateConfiguration(newConfig);
276     }
277 
maybeUpdateConfiguration(Configuration newConfig)278     private void maybeUpdateConfiguration(Configuration newConfig) {
279         if (VERBOSE) {
280             Log.v(TAG, String.format("handleConfiguration: orientation %x",
281                     newConfig.orientation));
282         }
283 
284         if (mLastOrientation != newConfig.orientation) {
285             mLastOrientation = newConfig.orientation;
286             updatePreviewOrientation();
287         }
288     }
289 
updatePreviewOrientation()290     private void updatePreviewOrientation() {
291         LayoutParams params = mPreviewView.getLayoutParams();
292         int width = params.width;
293         int height = params.height;
294 
295         if (VERBOSE) {
296             Log.v(TAG, String.format(
297                     "onConfiguredChanged: current layout is %dx%d", width,
298                     height));
299         }
300         /**
301          * Force wide aspect ratios for landscape
302          * Force narrow aspect ratios for portrait
303          */
304         if (mLastOrientation == Configuration.ORIENTATION_LANDSCAPE) {
305             if (height > width) {
306                 int tmp = width;
307                 width = height;
308                 height = tmp;
309             }
310         } else if (mLastOrientation == Configuration.ORIENTATION_PORTRAIT) {
311             if (width > height) {
312                 int tmp = width;
313                 width = height;
314                 height = tmp;
315             }
316         }
317 
318         if (width != params.width && height != params.height) {
319             if (VERBOSE) {
320                 Log.v(TAG, String.format(
321                         "onConfiguredChanged: updating preview size to %dx%d", width,
322                         height));
323             }
324             params.width = width;
325             params.height = height;
326 
327             mPreviewView.setLayoutParams(params);
328         }
329     }
330 
331     /** SurfaceHolder.Callback methods */
332     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)333     public void surfaceChanged(SurfaceHolder holder,
334             int format,
335             int width,
336             int height) {
337         if (VERBOSE) {
338             Log.v(TAG, String.format("surfaceChanged: format %x, width %d, height %d", format,
339                     width, height));
340         }
341         if (mCurrentPreviewHolder != null && holder == mCurrentPreviewHolder) {
342             try {
343                 mCameraOps.minimalPreview(holder, mCameraControl);
344             } catch (ApiFailureException e) {
345                 logException("Can't start minimal preview: ", e);
346             }
347         }
348     }
349 
350     @Override
surfaceCreated(SurfaceHolder holder)351     public void surfaceCreated(SurfaceHolder holder) {
352 
353     }
354 
355     @Override
surfaceDestroyed(SurfaceHolder holder)356     public void surfaceDestroyed(SurfaceHolder holder) {
357     }
358 
359     private final Button.OnClickListener mInfoButtonListener = new Button.OnClickListener() {
360         @Override
361         public void onClick(View v) {
362             final Handler uiHandler = new Handler();
363             AsyncTask.execute(new Runnable() {
364                 @Override
365                 public void run() {
366                     try {
367                         mCameraOps.minimalJpegCapture(mCaptureCallback, mCaptureResultListener,
368                                 uiHandler, mCameraControl);
369                         if (mCurrentPreviewHolder != null) {
370                             mCameraOps.minimalPreview(mCurrentPreviewHolder, mCameraControl);
371                         }
372                     } catch (ApiFailureException e) {
373                         logException("Can't take a JPEG! ", e);
374                     }
375                 }
376             });
377         }
378     };
379 
380     private final Button.OnClickListener mFlushButtonListener = new Button.OnClickListener() {
381         @Override
382         public void onClick(View v) {
383             AsyncTask.execute(new Runnable() {
384                 @Override
385                 public void run() {
386                     try {
387                         mCameraOps.flush();
388                     } catch (ApiFailureException e) {
389                         logException("Can't flush!", e);
390                     }
391                 }
392             });
393         }
394     };
395 
396     /**
397      * UI controls enable/disable for all manual controls
398      */
enableManualControls(boolean enabled)399     private void enableManualControls(boolean enabled) {
400         for (View v : mManualControls) {
401             v.setEnabled(enabled);
402         }
403 
404         for (View v : mAutoControls) {
405             v.setEnabled(!enabled);
406         }
407     }
408 
409     private final CameraOps.CaptureCallback mCaptureCallback = new CameraOps.CaptureCallback() {
410         @Override
411         public void onCaptureAvailable(Image capture) {
412             if (capture.getFormat() != ImageFormat.JPEG) {
413                 Log.e(TAG, "Unexpected format: " + capture.getFormat());
414                 return;
415             }
416             ByteBuffer jpegBuffer = capture.getPlanes()[0].getBuffer();
417             byte[] jpegData = new byte[jpegBuffer.capacity()];
418             jpegBuffer.get(jpegData);
419 
420             Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
421             mStillView.setImageBitmap(b);
422         }
423     };
424 
425     // TODO: this callback is not called for each capture, need figure out why.
426     private final CameraOps.CaptureResultListener mCaptureResultListener =
427             new CameraOps.CaptureResultListener() {
428 
429                 @Override
430                 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
431                         long timestamp, long frameNumber) {
432                 }
433 
434                 @Override
435                 public void onCaptureCompleted(
436                         CameraCaptureSession session, CaptureRequest request,
437                         TotalCaptureResult result) {
438                     Log.i(TAG, "Capture result is available");
439                     Integer reqCtrlMode;
440                     Integer resCtrlMode;
441                     if (request == null || result ==null) {
442                         Log.e(TAG, "request/result is invalid");
443                         return;
444                     }
445                     Log.i(TAG, "Capture complete");
446                     final StringBuffer info = new StringBuffer("Capture Result:\n");
447 
448                     reqCtrlMode = request.get(CaptureRequest.CONTROL_MODE);
449                     resCtrlMode = result.get(CaptureResult.CONTROL_MODE);
450                     info.append("Control mode: request " + reqCtrlMode + ". result " + resCtrlMode);
451                     info.append("\n");
452 
453                     Integer reqSen = request.get(CaptureRequest.SENSOR_SENSITIVITY);
454                     Integer resSen = result.get(CaptureResult.SENSOR_SENSITIVITY);
455                     info.append("Sensitivity: request " + reqSen + ". result " + resSen);
456                     info.append("\n");
457 
458                     Long reqExp = request.get(CaptureRequest.SENSOR_EXPOSURE_TIME);
459                     Long resExp = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
460                     info.append("Exposure: request " + reqExp + ". result " + resExp);
461                     info.append("\n");
462 
463                     Long reqFD = request.get(CaptureRequest.SENSOR_FRAME_DURATION);
464                     Long resFD = result.get(CaptureResult.SENSOR_FRAME_DURATION);
465                     info.append("Frame duration: request " + reqFD + ". result " + resFD);
466                     info.append("\n");
467 
468                     List<CaptureResult.Key<?>> resultKeys = result.getKeys();
469                     if (VERBOSE) {
470                         CaptureResult.Key<?>[] arrayKeys =
471                                 resultKeys.toArray(new CaptureResult.Key<?>[0]);
472                         Log.v(TAG, "onCaptureCompleted - dumping keys: " +
473                                 Arrays.toString(arrayKeys));
474                     }
475                     info.append("Total keys: " + resultKeys.size());
476                     info.append("\n");
477 
478                     if (mMainHandler != null) {
479                         mMainHandler.post (new Runnable() {
480                             @Override
481                             public void run() {
482                                 // Update UI for capture result
483                                 mCaptureResultView.setText(info);
484                             }
485                         });
486                     }
487                 }
488 
489                 @Override
490                 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
491                         CaptureFailure failure) {
492                     Log.e(TAG, "Capture failed");
493                 }
494     };
495 
logException(String msg, Throwable e)496     private void logException(String msg, Throwable e) {
497         Log.e(TAG, msg + Log.getStackTraceString(e));
498     }
499 
getRadioFmt()500     private RadioGroup getRadioFmt() {
501       return (RadioGroup)findViewById(R.id.radio_fmt);
502     }
503 
getOutputFormat()504     private int getOutputFormat() {
505         RadioGroup fmt = getRadioFmt();
506         switch (fmt.getCheckedRadioButtonId()) {
507             case R.id.radio_mp4:
508                 return MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
509 
510             case R.id.radio_webm:
511                 return MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
512 
513             default:
514                 throw new IllegalStateException("Checked button unrecognized.");
515         }
516     }
517 
518     private final OnSeekBarChangeListener mSensitivitySeekBarListener =
519             new OnSeekBarChangeListener() {
520 
521               @Override
522               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
523                   Range<Integer> defaultRange = new Range<Integer>(MIN_SENSITIVITY,
524                           MAX_SENSITIVITY);
525                   CameraCharacteristics properties = mCameraOps.getCameraCharacteristics();
526                   Range<Integer> sensitivityRange = properties.get(
527                           CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
528                   if (sensitivityRange == null || sensitivityRange.getLower() > MIN_SENSITIVITY ||
529                           sensitivityRange.getUpper() < MAX_SENSITIVITY) {
530                       Log.e(TAG, "unable to get sensitivity range, use default range");
531                       sensitivityRange = defaultRange;
532                   }
533                   int min = sensitivityRange.getLower();
534                   int max = sensitivityRange.getUpper();
535                   float progressFactor = progress / (float)mSeekBarMax;
536                   int curSensitivity = (int) (min + (max - min) * progressFactor);
537                   curSensitivity = (curSensitivity / STEP_SIZE ) * STEP_SIZE;
538                   mCameraControl.getManualControls().setSensitivity(curSensitivity);
539                   // Update the sensitivity info
540                   StringBuffer info = new StringBuffer("Sensitivity(ISO):");
541                   info.append("" + curSensitivity);
542                   mSensitivityInfoView.setText(info);
543                   mCameraOps.updatePreview(mCameraControl);
544               }
545 
546               @Override
547               public void onStartTrackingTouch(SeekBar seekBar) {
548               }
549 
550               @Override
551               public void onStopTrackingTouch(SeekBar seekBar) {
552               }
553     };
554 
555     private final OnSeekBarChangeListener mExposureSeekBarListener =
556             new OnSeekBarChangeListener() {
557 
558               @Override
559               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
560                   Range<Long> defaultRange = new Range<Long>(MIN_EXPOSURE, MAX_EXPOSURE);
561                   CameraCharacteristics properties = mCameraOps.getCameraCharacteristics();
562                   Range<Long> exposureRange = properties.get(
563                           CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
564                   // Not enforce the max value check here, most of the devices don't support
565                   // larger than 30s exposure time
566                   if (exposureRange == null || exposureRange.getLower() > MIN_EXPOSURE ||
567                           exposureRange.getUpper() < 0) {
568                       exposureRange = defaultRange;
569                       Log.e(TAG, "exposure time range is invalid, use default range");
570                   }
571                   long min = exposureRange.getLower();
572                   long max = exposureRange.getUpper();
573                   float progressFactor = progress / (float)mSeekBarMax;
574                   long curExposureTime = (long) (min + (max - min) * progressFactor);
575                   mCameraControl.getManualControls().setExposure(curExposureTime);
576                   // Update the sensitivity info
577                   StringBuffer info = new StringBuffer("Exposure Time:");
578                   info.append("" + curExposureTime / 1000000.0 + "ms");
579                   mExposureInfoView.setText(info);
580                   mCameraOps.updatePreview(mCameraControl);
581               }
582 
583               @Override
584               public void onStartTrackingTouch(SeekBar seekBar) {
585               }
586 
587               @Override
588               public void onStopTrackingTouch(SeekBar seekBar) {
589               }
590     };
591 
592     private final OnSeekBarChangeListener mFrameDurationSeekBarListener =
593             new OnSeekBarChangeListener() {
594 
595               @Override
596               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
597                   CameraCharacteristics properties = mCameraOps.getCameraCharacteristics();
598                   Long frameDurationMax = properties.get(
599                           CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION);
600                   if (frameDurationMax == null || frameDurationMax <= 0) {
601                       frameDurationMax = MAX_FRAME_DURATION;
602                       Log.e(TAG, "max frame duration is invalid, set to " + frameDurationMax);
603                   }
604                   // Need calculate from different resolution, hard code to 10ms for now.
605                   long min = 10000000L;
606                   long max = frameDurationMax;
607                   float progressFactor = progress / (float)mSeekBarMax;
608                   long curFrameDuration = (long) (min + (max - min) * progressFactor);
609                   mCameraControl.getManualControls().setFrameDuration(curFrameDuration);
610                   // Update the sensitivity info
611                   StringBuffer info = new StringBuffer("Frame Duration:");
612                   info.append("" + curFrameDuration / 1000000.0 + "ms");
613                   mFrameDurationInfoView.setText(info);
614                   mCameraOps.updatePreview(mCameraControl);
615               }
616 
617               @Override
618               public void onStartTrackingTouch(SeekBar seekBar) {
619               }
620 
621               @Override
622               public void onStopTrackingTouch(SeekBar seekBar) {
623               }
624     };
625 
626     private final View.OnClickListener mControlToggleListener =
627             new View.OnClickListener() {
628         @Override
629         public void onClick(View v) {
630             boolean enableManual;
631             if (mManualCtrlToggle.isChecked()) {
632                 enableManual = true;
633             } else {
634                 enableManual = false;
635             }
636             mCameraControl.getManualControls().setManualControlEnabled(enableManual);
637             enableManualControls(enableManual);
638             mCameraOps.updatePreview(mCameraControl);
639         }
640     };
641 
642     private final View.OnClickListener mRecordingToggleListener =
643             new View.OnClickListener() {
644         @Override
645         public void onClick(View v) {
646             if (mRecordingToggle.isChecked()) {
647                 try {
648                     Log.i(TAG, "start recording, useMediaCodec = " + mUseMediaCodec);
649                     RadioGroup fmt = getRadioFmt();
650                     fmt.setActivated(false);
651                     mCameraOps.startRecording(
652                             /* applicationContext */ TestingCamera2.this,
653                             /* useMediaCodec */ mUseMediaCodec,
654                             /* outputFormat */ getOutputFormat());
655                 } catch (ApiFailureException e) {
656                     logException("Failed to start recording", e);
657                 }
658             } else {
659                 try {
660                     mCameraOps.stopRecording(TestingCamera2.this);
661                     getRadioFmt().setActivated(true);
662                 } catch (ApiFailureException e) {
663                     logException("Failed to stop recording", e);
664                 }
665             }
666         }
667     };
668 
669     private final View.OnClickListener mFocusLockToggleListener =
670             new View.OnClickListener() {
671         @Override
672         public void onClick(View v) {
673             if (VERBOSE) {
674                 Log.v(TAG, "focus_lock#onClick - start");
675             }
676 
677             CameraAutoFocusControls afControls = mCameraControl.getAfControls();
678 
679             if (mFocusLockToggle.isChecked()) {
680                 Log.i(TAG, "lock focus");
681 
682                 afControls.setPendingTriggerStart();
683             } else {
684                 Log.i(TAG, "unlock focus");
685 
686                 afControls.setPendingTriggerCancel();
687             }
688 
689             mCameraOps.updatePreview(mCameraControl);
690 
691             if (VERBOSE) {
692                 Log.v(TAG, "focus_lock#onClick - end");
693             }
694         }
695     };
696 
697     private final CompoundButton.OnCheckedChangeListener mUseMediaCodecListener =
698             new CompoundButton.OnCheckedChangeListener() {
699         @Override
700         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
701             mUseMediaCodec = isChecked;
702         }
703     };
704 
705     private final CameraOps.Listener mCameraOpsListener = new CameraOps.Listener() {
706         @Override
707         public void onCameraOpened(String cameraId, CameraCharacteristics characteristics) {
708             /*
709              * Populate dynamic per-camera settings
710              */
711 
712             // Map available AF Modes -> AF mode spinner dropdown list of strings
713             int[] availableAfModes =
714                     characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
715 
716             String[] allAfModes = getResources().getStringArray(R.array.focus_mode_spinner_arrays);
717 
718             final List<String> afModeList = new ArrayList<>();
719             final int[] afModePositions = new int[availableAfModes.length];
720 
721             int i = 0;
722             for (int mode : availableAfModes) {
723                 afModeList.add(allAfModes[mode]);
724                 afModePositions[i++] = mode;
725             }
726 
727             ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(TestingCamera2.this,
728                     android.R.layout.simple_spinner_item, afModeList);
729             dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
730             mFocusModeSpinner.setAdapter(dataAdapter);
731 
732             /*
733              * Change the AF mode and update preview when AF spinner's selected item changes
734              */
735             mFocusModeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
736 
737                 @Override
738                 public void onItemSelected(AdapterView<?> parent, View view, int position,
739                         long id) {
740                     int afMode = afModePositions[position];
741 
742                     Log.i(TAG, "Change auto-focus mode to " + afModeList.get(position)
743                             + " " + afMode);
744 
745                     mCameraControl.getAfControls().setAfMode(afMode);
746 
747                     mCameraOps.updatePreview(mCameraControl);
748                 }
749 
750                 @Override
751                 public void onNothingSelected(AdapterView<?> parent) {
752                     // Do nothing
753                 }
754             });
755         }
756     };
757 }
758