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