Home | History | Annotate | Download | only in hdrviewfinder
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.hdrviewfinder;
     18 
     19 import android.Manifest;
     20 import android.annotation.SuppressLint;
     21 import android.content.Intent;
     22 import android.content.pm.PackageManager;
     23 import android.hardware.camera2.CameraAccessException;
     24 import android.hardware.camera2.CameraCaptureSession;
     25 import android.hardware.camera2.CameraCharacteristics;
     26 import android.hardware.camera2.CameraDevice;
     27 import android.hardware.camera2.CameraManager;
     28 import android.hardware.camera2.CaptureRequest;
     29 import android.hardware.camera2.CaptureResult;
     30 import android.hardware.camera2.TotalCaptureResult;
     31 import android.hardware.camera2.params.StreamConfigurationMap;
     32 import android.net.Uri;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.Looper;
     36 import android.provider.Settings;
     37 import android.renderscript.RenderScript;
     38 import android.support.annotation.NonNull;
     39 import android.support.design.widget.Snackbar;
     40 import android.support.v4.app.ActivityCompat;
     41 import android.support.v7.app.AppCompatActivity;
     42 import android.util.Log;
     43 import android.util.Size;
     44 import android.view.GestureDetector;
     45 import android.view.Menu;
     46 import android.view.MenuItem;
     47 import android.view.MotionEvent;
     48 import android.view.Surface;
     49 import android.view.SurfaceHolder;
     50 import android.view.View;
     51 import android.widget.Button;
     52 import android.widget.TextView;
     53 
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 import java.util.Locale;
     57 import java.util.Objects;
     58 
     59 /**
     60  * A small demo of advanced camera functionality with the Android camera2 API.
     61  *
     62  * <p>This demo implements a real-time high-dynamic-range camera viewfinder,
     63  * by alternating the sensor's exposure time between two exposure values on even and odd
     64  * frames, and then compositing together the latest two frames whenever a new frame is
     65  * captured.</p>
     66  *
     67  * <p>The demo has three modes: Regular auto-exposure viewfinder, split-screen manual exposure,
     68  * and the fused HDR viewfinder.  The latter two use manual exposure controlled by the user,
     69  * by swiping up/down on the right and left halves of the viewfinder.  The left half controls
     70  * the exposure time of even frames, and the right half controls the exposure time of odd frames.
     71  * </p>
     72  *
     73  * <p>In split-screen mode, the even frames are shown on the left and the odd frames on the right,
     74  * so the user can see two different exposures of the scene simultaneously.  In fused HDR mode,
     75  * the even/odd frames are merged together into a single image.  By selecting different exposure
     76  * values for the even/odd frames, the fused image has a higher dynamic range than the regular
     77  * viewfinder.</p>
     78  *
     79  * <p>The HDR fusion and the split-screen viewfinder processing is done with RenderScript; as is the
     80  * necessary YUV->RGB conversion. The camera subsystem outputs YUV images naturally, while the GPU
     81  * and display subsystems generally only accept RGB data.  Therefore, after the images are
     82  * fused/composited, a standard YUV->RGB color transform is applied before the the data is written
     83  * to the output Allocation. The HDR fusion algorithm is very simple, and tends to result in
     84  * lower-contrast scenes, but has very few artifacts and can run very fast.</p>
     85  *
     86  * <p>Data is passed between the subsystems (camera, RenderScript, and display) using the
     87  * Android {@link android.view.Surface} class, which allows for zero-copy transport of large
     88  * buffers between processes and subsystems.</p>
     89  */
     90 public class HdrViewfinderActivity extends AppCompatActivity implements
     91         SurfaceHolder.Callback, CameraOps.ErrorDisplayer, CameraOps.CameraReadyListener {
     92 
     93     private static final String TAG = "HdrViewfinderDemo";
     94 
     95     private static final String FRAGMENT_DIALOG = "dialog";
     96 
     97     private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
     98 
     99     /**
    100      * View for the camera preview.
    101      */
    102     private FixedAspectSurfaceView mPreviewView;
    103 
    104     /**
    105      * Root view of this activity.
    106      */
    107     private View rootView;
    108 
    109     /**
    110      * This shows the current mode of the app.
    111      */
    112     private TextView mModeText;
    113 
    114     // These show lengths of exposure for even frames, exposure for odd frames, and auto exposure.
    115     private TextView mEvenExposureText, mOddExposureText, mAutoExposureText;
    116 
    117     private Handler mUiHandler;
    118 
    119     private CameraCharacteristics mCameraInfo;
    120 
    121     private Surface mPreviewSurface;
    122     private Surface mProcessingHdrSurface;
    123     private Surface mProcessingNormalSurface;
    124     CaptureRequest.Builder mHdrBuilder;
    125     ArrayList<CaptureRequest> mHdrRequests = new ArrayList<>(2);
    126 
    127     CaptureRequest mPreviewRequest;
    128 
    129     RenderScript mRS;
    130     ViewfinderProcessor mProcessor;
    131     CameraManager mCameraManager;
    132     CameraOps mCameraOps;
    133 
    134     private int mRenderMode = ViewfinderProcessor.MODE_NORMAL;
    135 
    136     // Durations in nanoseconds
    137     private static final long MICRO_SECOND = 1000;
    138     private static final long MILLI_SECOND = MICRO_SECOND * 1000;
    139     private static final long ONE_SECOND = MILLI_SECOND * 1000;
    140 
    141     private long mOddExposure = ONE_SECOND / 33;
    142     private long mEvenExposure = ONE_SECOND / 33;
    143 
    144     private Object mOddExposureTag = new Object();
    145     private Object mEvenExposureTag = new Object();
    146     private Object mAutoExposureTag = new Object();
    147 
    148     @Override
    149     protected void onCreate(Bundle savedInstanceState) {
    150         super.onCreate(savedInstanceState);
    151         setContentView(R.layout.main);
    152 
    153         rootView = findViewById(R.id.panels);
    154 
    155         mPreviewView = (FixedAspectSurfaceView) findViewById(R.id.preview);
    156         mPreviewView.getHolder().addCallback(this);
    157         mPreviewView.setGestureListener(this, mViewListener);
    158 
    159         Button helpButton = (Button) findViewById(R.id.help_button);
    160         helpButton.setOnClickListener(mHelpButtonListener);
    161 
    162         mModeText = (TextView) findViewById(R.id.mode_label);
    163         mEvenExposureText = (TextView) findViewById(R.id.even_exposure);
    164         mOddExposureText = (TextView) findViewById(R.id.odd_exposure);
    165         mAutoExposureText = (TextView) findViewById(R.id.auto_exposure);
    166 
    167         mUiHandler = new Handler(Looper.getMainLooper());
    168 
    169         mRS = RenderScript.create(this);
    170 
    171         // When permissions are revoked the app is restarted so onCreate is sufficient to check for
    172         // permissions core to the Activity's functionality.
    173         if (!checkCameraPermissions()) {
    174             requestCameraPermissions();
    175         } else {
    176             findAndOpenCamera();
    177         }
    178     }
    179 
    180     @Override
    181     protected void onResume() {
    182         super.onResume();
    183     }
    184 
    185     @Override
    186     protected void onPause() {
    187         super.onPause();
    188 
    189         // Wait until camera is closed to ensure the next application can open it
    190         if (mCameraOps != null) {
    191             mCameraOps.closeCameraAndWait();
    192             mCameraOps = null;
    193         }
    194     }
    195 
    196     @Override
    197     public boolean onCreateOptionsMenu(Menu menu) {
    198         getMenuInflater().inflate(R.menu.main, menu);
    199         return super.onCreateOptionsMenu(menu);
    200     }
    201 
    202     @Override
    203     public boolean onOptionsItemSelected(MenuItem item) {
    204         switch (item.getItemId()) {
    205             case R.id.info: {
    206                 MessageDialogFragment.newInstance(R.string.intro_message)
    207                         .show(getSupportFragmentManager(), FRAGMENT_DIALOG);
    208                 break;
    209             }
    210         }
    211         return super.onOptionsItemSelected(item);
    212     }
    213 
    214     private GestureDetector.OnGestureListener mViewListener
    215             = new GestureDetector.SimpleOnGestureListener() {
    216 
    217         @Override
    218         public boolean onDown(MotionEvent e) {
    219             return true;
    220         }
    221 
    222         @Override
    223         public boolean onSingleTapUp(MotionEvent e) {
    224             switchRenderMode(1);
    225             return true;
    226         }
    227 
    228         @Override
    229         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    230             if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) return false;
    231 
    232             float xPosition = e1.getAxisValue(MotionEvent.AXIS_X);
    233             float width = mPreviewView.getWidth();
    234             float height = mPreviewView.getHeight();
    235 
    236             float xPosNorm = xPosition / width;
    237             float yDistNorm = distanceY / height;
    238 
    239             final float ACCELERATION_FACTOR = 8;
    240             double scaleFactor = Math.pow(2.f, yDistNorm * ACCELERATION_FACTOR);
    241 
    242             // Even on left, odd on right
    243             if (xPosNorm > 0.5) {
    244                 mOddExposure *= scaleFactor;
    245             } else {
    246                 mEvenExposure *= scaleFactor;
    247             }
    248 
    249             setHdrBurst();
    250 
    251             return true;
    252         }
    253     };
    254 
    255     /**
    256      * Show help dialogs.
    257      */
    258     private View.OnClickListener mHelpButtonListener = new View.OnClickListener() {
    259         public void onClick(View v) {
    260             MessageDialogFragment.newInstance(R.string.help_text)
    261                     .show(getSupportFragmentManager(), FRAGMENT_DIALOG);
    262         }
    263     };
    264 
    265     /**
    266      * Return the current state of the camera permissions.
    267      */
    268     private boolean checkCameraPermissions() {
    269         int permissionState = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
    270 
    271         // Check if the Camera permission is already available.
    272         if (permissionState != PackageManager.PERMISSION_GRANTED) {
    273             // Camera permission has not been granted.
    274             Log.i(TAG, "CAMERA permission has NOT been granted.");
    275             return false;
    276         } else {
    277             // Camera permissions are available.
    278             Log.i(TAG, "CAMERA permission has already been granted.");
    279             return true;
    280         }
    281     }
    282 
    283     /**
    284      * Attempt to initialize the camera.
    285      */
    286     private void initializeCamera() {
    287         mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
    288         if (mCameraManager != null) {
    289             mCameraOps = new CameraOps(mCameraManager,
    290                 /*errorDisplayer*/ this,
    291                 /*readyListener*/ this,
    292                 /*readyHandler*/ mUiHandler);
    293 
    294             mHdrRequests.add(null);
    295             mHdrRequests.add(null);
    296         } else {
    297             Log.e(TAG, "Couldn't initialize the camera");
    298         }
    299     }
    300 
    301     private void requestCameraPermissions() {
    302         // Provide an additional rationale to the user. This would happen if the user denied the
    303         // request previously, but didn't check the "Don't ask again" checkbox.
    304         if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
    305             Log.i(TAG, "Displaying camera permission rationale to provide additional context.");
    306             Snackbar.make(rootView, R.string.camera_permission_rationale, Snackbar
    307                     .LENGTH_INDEFINITE)
    308                     .setAction(R.string.ok, new View.OnClickListener() {
    309                         @Override
    310                         public void onClick(View view) {
    311                             // Request Camera permission
    312                             ActivityCompat.requestPermissions(HdrViewfinderActivity.this,
    313                                     new String[]{Manifest.permission.CAMERA},
    314                                     REQUEST_PERMISSIONS_REQUEST_CODE);
    315                         }
    316                     })
    317                     .show();
    318         } else {
    319             Log.i(TAG, "Requesting camera permission");
    320             // Request Camera permission. It's possible this can be auto answered if device policy
    321             // sets the permission in a given state or the user denied the permission
    322             // previously and checked "Never ask again".
    323             ActivityCompat.requestPermissions(HdrViewfinderActivity.this,
    324                     new String[]{Manifest.permission.CAMERA},
    325                     REQUEST_PERMISSIONS_REQUEST_CODE);
    326         }
    327     }
    328 
    329     /**
    330      * Callback received when a permissions request has been completed.
    331      */
    332     @Override
    333     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
    334             @NonNull int[] grantResults) {
    335         Log.i(TAG, "onRequestPermissionResult");
    336         if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
    337             if (grantResults.length <= 0) {
    338                 // If user interaction was interrupted, the permission request is cancelled and you
    339                 // receive empty arrays.
    340                 Log.i(TAG, "User interaction was cancelled.");
    341             } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    342                 // Permission was granted.
    343                 findAndOpenCamera();
    344             } else {
    345                 // Permission denied.
    346 
    347                 // In this Activity we've chosen to notify the user that they
    348                 // have rejected a core permission for the app since it makes the Activity useless.
    349                 // We're communicating this message in a Snackbar since this is a sample app, but
    350                 // core permissions would typically be best requested during a welcome-screen flow.
    351 
    352                 // Additionally, it is important to remember that a permission might have been
    353                 // rejected without asking the user for permission (device policy or "Never ask
    354                 // again" prompts). Therefore, a user interface affordance is typically implemented
    355                 // when permissions are denied. Otherwise, your app could appear unresponsive to
    356                 // touches or interactions which have required permissions.
    357                 Snackbar.make(rootView, R.string.camera_permission_denied_explanation, Snackbar
    358                         .LENGTH_INDEFINITE)
    359                         .setAction(R.string.settings, new View.OnClickListener() {
    360                             @Override
    361                             public void onClick(View view) {
    362                                 // Build intent that displays the App settings screen.
    363                                 Intent intent = new Intent();
    364                                 intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    365                                 Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null);
    366                                 intent.setData(uri);
    367                                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    368                                 startActivity(intent);
    369                             }
    370                         })
    371                         .show();
    372             }
    373         }
    374     }
    375 
    376     private void findAndOpenCamera() {
    377         boolean cameraPermissions = checkCameraPermissions();
    378         if (!cameraPermissions) {
    379             return;
    380         }
    381         String errorMessage = "Unknown error";
    382         boolean foundCamera = false;
    383         initializeCamera();
    384         if (mCameraOps != null) {
    385             try {
    386                 // Find first back-facing camera that has necessary capability.
    387                 String[] cameraIds = mCameraManager.getCameraIdList();
    388                 for (String id : cameraIds) {
    389                     CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
    390                     Integer facing = info.get(CameraCharacteristics.LENS_FACING);
    391                     Integer level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
    392                     boolean hasFullLevel = Objects.equals(level,
    393                             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
    394 
    395                     int[] capabilities = info
    396                             .get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
    397                     Integer syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
    398                     boolean hasManualControl = hasCapability(capabilities,
    399                             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
    400                     boolean hasEnoughCapability = hasManualControl && Objects.equals(syncLatency,
    401                             CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL);
    402 
    403                     // All these are guaranteed by
    404                     // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking
    405                     // for only the things we care about expands range of devices we can run on.
    406                     // We want:
    407                     //  - Back-facing camera
    408                     //  - Manual sensor control
    409                     //  - Per-frame synchronization (so that exposure can be changed every frame)
    410                     if (Objects.equals(facing, CameraCharacteristics.LENS_FACING_BACK) &&
    411                             (hasFullLevel || hasEnoughCapability)) {
    412                         // Found suitable camera - get info, open, and set up outputs
    413                         mCameraInfo = info;
    414                         mCameraOps.openCamera(id);
    415                         configureSurfaces();
    416                         foundCamera = true;
    417                         break;
    418                     }
    419                 }
    420                 if (!foundCamera) {
    421                     errorMessage = getString(R.string.camera_no_good);
    422                 }
    423             } catch (CameraAccessException e) {
    424                 errorMessage = getErrorString(e);
    425             }
    426             if (!foundCamera) {
    427                 showErrorDialog(errorMessage);
    428             }
    429         }
    430     }
    431 
    432     private boolean hasCapability(int[] capabilities, int capability) {
    433         for (int c : capabilities) {
    434             if (c == capability) return true;
    435         }
    436         return false;
    437     }
    438 
    439     private void switchRenderMode(int direction) {
    440         if (mCameraOps != null) {
    441             mRenderMode = (mRenderMode + direction) % 3;
    442 
    443             mModeText.setText(getResources().getStringArray(R.array.mode_label_array)[mRenderMode]);
    444 
    445             if (mProcessor != null) {
    446                 mProcessor.setRenderMode(mRenderMode);
    447             }
    448             if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) {
    449                 mCameraOps.setRepeatingRequest(mPreviewRequest,
    450                         mCaptureCallback, mUiHandler);
    451             } else {
    452                 setHdrBurst();
    453             }
    454         }
    455     }
    456 
    457     /**
    458      * Configure the surfaceview and RS processing.
    459      */
    460     private void configureSurfaces() {
    461         // Find a good size for output - largest 16:9 aspect ratio that's less than 720p
    462         final int MAX_WIDTH = 1280;
    463         final float TARGET_ASPECT = 16.f / 9.f;
    464         final float ASPECT_TOLERANCE = 0.1f;
    465 
    466         StreamConfigurationMap configs =
    467                 mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    468         if (configs == null) {
    469             throw new RuntimeException("Cannot get available picture/preview sizes.");
    470         }
    471         Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
    472 
    473         Size outputSize = outputSizes[0];
    474         float outputAspect = (float) outputSize.getWidth() / outputSize.getHeight();
    475         for (Size candidateSize : outputSizes) {
    476             if (candidateSize.getWidth() > MAX_WIDTH) continue;
    477             float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight();
    478             boolean goodCandidateAspect =
    479                     Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
    480             boolean goodOutputAspect =
    481                     Math.abs(outputAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
    482             if ((goodCandidateAspect && !goodOutputAspect) ||
    483                     candidateSize.getWidth() > outputSize.getWidth()) {
    484                 outputSize = candidateSize;
    485                 outputAspect = candidateAspect;
    486             }
    487         }
    488         Log.i(TAG, "Resolution chosen: " + outputSize);
    489 
    490         // Configure processing
    491         mProcessor = new ViewfinderProcessor(mRS, outputSize);
    492         setupProcessor();
    493 
    494         // Configure the output view - this will fire surfaceChanged
    495         mPreviewView.setAspectRatio(outputAspect);
    496         mPreviewView.getHolder().setFixedSize(outputSize.getWidth(), outputSize.getHeight());
    497     }
    498 
    499     /**
    500      * Once camera is open and output surfaces are ready, configure the RS processing
    501      * and the camera device inputs/outputs.
    502      */
    503     private void setupProcessor() {
    504         if (mProcessor == null || mPreviewSurface == null) return;
    505 
    506         mProcessor.setOutputSurface(mPreviewSurface);
    507         mProcessingHdrSurface = mProcessor.getInputHdrSurface();
    508         mProcessingNormalSurface = mProcessor.getInputNormalSurface();
    509 
    510         List<Surface> cameraOutputSurfaces = new ArrayList<>();
    511         cameraOutputSurfaces.add(mProcessingHdrSurface);
    512         cameraOutputSurfaces.add(mProcessingNormalSurface);
    513 
    514         mCameraOps.setSurfaces(cameraOutputSurfaces);
    515     }
    516 
    517     /**
    518      * Start running an HDR burst on a configured camera session
    519      */
    520     public void setHdrBurst() {
    521 
    522         mHdrBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, 1600);
    523         mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
    524 
    525         mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, mEvenExposure);
    526         mHdrBuilder.setTag(mEvenExposureTag);
    527         mHdrRequests.set(0, mHdrBuilder.build());
    528 
    529         mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, mOddExposure);
    530         mHdrBuilder.setTag(mOddExposureTag);
    531         mHdrRequests.set(1, mHdrBuilder.build());
    532 
    533         mCameraOps.setRepeatingBurst(mHdrRequests, mCaptureCallback, mUiHandler);
    534     }
    535 
    536     /**
    537      * Listener for completed captures
    538      * Invoked on UI thread
    539      */
    540     private CameraCaptureSession.CaptureCallback mCaptureCallback
    541             = new CameraCaptureSession.CaptureCallback() {
    542 
    543         public void onCaptureCompleted(@NonNull CameraCaptureSession session,
    544                                        @NonNull CaptureRequest request,
    545                                        @NonNull TotalCaptureResult result) {
    546 
    547             // Only update UI every so many frames
    548             // Use an odd number here to ensure both even and odd exposures get an occasional update
    549             long frameNumber = result.getFrameNumber();
    550             if (frameNumber % 3 != 0) return;
    551 
    552             final Long exposureTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
    553             if (exposureTime == null) {
    554                 throw new RuntimeException("Cannot get exposure time.");
    555             }
    556 
    557             // Format exposure time nicely
    558             String exposureText;
    559             if (exposureTime > ONE_SECOND) {
    560                 exposureText = String.format(Locale.US, "%.2f s", exposureTime / 1e9);
    561             } else if (exposureTime > MILLI_SECOND) {
    562                 exposureText = String.format(Locale.US, "%.2f ms", exposureTime / 1e6);
    563             } else if (exposureTime > MICRO_SECOND) {
    564                 exposureText = String.format(Locale.US, "%.2f us", exposureTime / 1e3);
    565             } else {
    566                 exposureText = String.format(Locale.US, "%d ns", exposureTime);
    567             }
    568 
    569             Object tag = request.getTag();
    570             Log.i(TAG, "Exposure: " + exposureText);
    571 
    572             if (tag == mEvenExposureTag) {
    573                 mEvenExposureText.setText(exposureText);
    574 
    575                 mEvenExposureText.setEnabled(true);
    576                 mOddExposureText.setEnabled(true);
    577                 mAutoExposureText.setEnabled(false);
    578             } else if (tag == mOddExposureTag) {
    579                 mOddExposureText.setText(exposureText);
    580 
    581                 mEvenExposureText.setEnabled(true);
    582                 mOddExposureText.setEnabled(true);
    583                 mAutoExposureText.setEnabled(false);
    584             } else {
    585                 mAutoExposureText.setText(exposureText);
    586 
    587                 mEvenExposureText.setEnabled(false);
    588                 mOddExposureText.setEnabled(false);
    589                 mAutoExposureText.setEnabled(true);
    590             }
    591         }
    592     };
    593 
    594     /**
    595      * Callbacks for the FixedAspectSurfaceView
    596      */
    597 
    598     @Override
    599     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    600         mPreviewSurface = holder.getSurface();
    601 
    602         setupProcessor();
    603     }
    604 
    605     @Override
    606     public void surfaceCreated(SurfaceHolder holder) {
    607         // ignored
    608     }
    609 
    610     @Override
    611     public void surfaceDestroyed(SurfaceHolder holder) {
    612         mPreviewSurface = null;
    613     }
    614 
    615     /**
    616      * Callbacks for CameraOps
    617      */
    618     @Override
    619     public void onCameraReady() {
    620         // Ready to send requests in, so set them up
    621         try {
    622             CaptureRequest.Builder previewBuilder =
    623                     mCameraOps.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    624             previewBuilder.addTarget(mProcessingNormalSurface);
    625             previewBuilder.setTag(mAutoExposureTag);
    626             mPreviewRequest = previewBuilder.build();
    627 
    628             mHdrBuilder =
    629                     mCameraOps.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    630             mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE,
    631                     CaptureRequest.CONTROL_AE_MODE_OFF);
    632             mHdrBuilder.addTarget(mProcessingHdrSurface);
    633 
    634             switchRenderMode(0);
    635 
    636         } catch (CameraAccessException e) {
    637             String errorMessage = getErrorString(e);
    638             showErrorDialog(errorMessage);
    639         }
    640     }
    641 
    642     /**
    643      * Utility methods
    644      */
    645     @Override
    646     public void showErrorDialog(String errorMessage) {
    647         MessageDialogFragment.newInstance(errorMessage)
    648                 .show(getSupportFragmentManager(), FRAGMENT_DIALOG);
    649     }
    650 
    651     @SuppressLint("SwitchIntDef")
    652     @Override
    653     public String getErrorString(CameraAccessException e) {
    654         String errorMessage;
    655         switch (e.getReason()) {
    656             case CameraAccessException.CAMERA_DISABLED:
    657                 errorMessage = getString(R.string.camera_disabled);
    658                 break;
    659             case CameraAccessException.CAMERA_DISCONNECTED:
    660                 errorMessage = getString(R.string.camera_disconnected);
    661                 break;
    662             case CameraAccessException.CAMERA_ERROR:
    663                 errorMessage = getString(R.string.camera_error);
    664                 break;
    665             default:
    666                 errorMessage = getString(R.string.camera_unknown, e.getReason());
    667                 break;
    668         }
    669         return errorMessage;
    670     }
    671 
    672 }
    673