Home | History | Annotate | Download | only in v1
      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
    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
    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
    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 
    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
    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
    261     protected void onDestroy() {
    262         mOrientationEventListener.disable();
    263         super.onDestroy();
    264     }
    265 
    266     @Override
    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 
    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 
    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
    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
    351     public void surfaceCreated(SurfaceHolder holder) {
    352 
    353     }
    354 
    355     @Override
    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      */
    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 
    496     private void logException(String msg, Throwable e) {
    497         Log.e(TAG, msg + Log.getStackTraceString(e));
    498     }
    499 
    500     private RadioGroup getRadioFmt() {
    501       return (RadioGroup)findViewById(R.id.radio_fmt);
    502     }
    503 
    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