1/*
2 * Copyright (C) 2015 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
17package com.android.example.cannylive;
18
19import android.content.Context;
20import android.graphics.ImageFormat;
21import android.hardware.camera2.CameraAccessException;
22import android.hardware.camera2.CameraCaptureSession;
23import android.hardware.camera2.CameraCharacteristics;
24import android.hardware.camera2.CameraDevice;
25import android.hardware.camera2.CameraManager;
26import android.hardware.camera2.CaptureRequest;
27import android.hardware.camera2.TotalCaptureResult;
28import android.hardware.camera2.params.StreamConfigurationMap;
29import android.media.Image;
30import android.media.ImageReader;
31import android.os.ConditionVariable;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.Looper;
35import android.util.Log;
36import android.util.Range;
37import android.util.Size;
38import android.view.Surface;
39import android.view.SurfaceHolder;
40
41import java.io.IOException;
42import java.io.OutputStream;
43import java.nio.ByteBuffer;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collections;
47import java.util.Comparator;
48import java.util.List;
49
50/**
51 * Simple interface for operating the camera, with major camera operations
52 * all performed on a background handler thread.
53 */
54public class CameraOps {
55
56    private static final String TAG = "CameraOps";
57    private static final long ONE_SECOND = 1000000000;
58    public static final long CAMERA_CLOSE_TIMEOUT = 2000; // ms
59
60    private final CameraManager mCameraManager;
61    private CameraDevice mCameraDevice;
62    private CameraCaptureSession mCameraSession;
63    private List<Surface> mSurfaces;
64
65    private final ConditionVariable mCloseWaiter = new ConditionVariable();
66
67    private HandlerThread mCameraThread;
68    private Handler mCameraHandler;
69
70    private final ErrorDisplayer mErrorDisplayer;
71
72    private final CameraReadyListener mReadyListener;
73    private final Handler mReadyHandler;
74
75    private int mISOmax;
76    private int mISOmin;
77    private long mExpMax;
78    private long mExpMin;
79    private float mFocusMin;
80    private float mFocusDist = 0;
81    private int mIso;
82    boolean mAutoExposure = true;
83    boolean mAutoFocus = true;
84    private long mExposure = ONE_SECOND / 33;
85
86    private Object mAutoExposureTag = new Object();
87
88    private ImageReader mImageReader;
89    private Handler mBackgroundHandler;
90    private CameraCharacteristics mCameraInfo;
91    private HandlerThread mBackgroundThread;
92    CaptureRequest.Builder mHdrBuilder;
93    private Surface mProcessingNormalSurface;
94    CaptureRequest mPreviewRequest;
95    private String mSaveFileName;
96    private Context mContext;
97    private int mCaptureMode;
98
99    public String resume() {
100        String errorMessage = "Unknown error";
101        boolean foundCamera = false;
102        try {
103            // Find first back-facing camera that has necessary capability
104            String[] cameraIds = mCameraManager.getCameraIdList();
105            for (String id : cameraIds) {
106                CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
107                int facing = info.get(CameraCharacteristics.LENS_FACING);
108
109                int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
110                boolean hasFullLevel
111                        = (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
112
113                int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
114                int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
115                boolean hasManualControl = hasCapability(capabilities,
116                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
117                boolean hasEnoughCapability = hasManualControl &&
118                        syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
119                Range<Integer> irange;
120                Range<Long> lrange;
121
122                irange = info.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
123                if (irange != null) {
124                    mISOmax = irange.getUpper();
125                    mISOmin = irange.getLower();
126                    lrange = info.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
127                    mExpMax = lrange.getUpper();
128                    mExpMin = lrange.getLower();
129                    mFocusMin = info.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
130                } else {
131                    mISOmax = 200;
132                    mISOmin = 100;
133                    mExpMax = 1000;
134                }
135                mFocusDist = mFocusMin;
136                StreamConfigurationMap map = info.get(
137                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
138                Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
139                List<Size> sizeList = Arrays.asList(sizes);
140                Collections.sort(sizeList, new Comparator<Size>() {
141                    @Override
142                    public int compare(Size lhs, Size rhs) {
143                        int leftArea = lhs.getHeight() * lhs.getWidth();
144                        int rightArea = lhs.getHeight() * lhs.getWidth();
145                        return Integer.compare(leftArea, rightArea);
146                    }
147                });
148                Size max = sizeList.get(0);
149                int check = 1;
150                Size big = sizeList.get(check);
151                float aspect = 16/9f;
152                Log.v(TAG,"max big "+max.getWidth()+" x "+max.getHeight());
153                for (int i = 0; i < sizeList.size(); i++) {
154                    Size s = sizeList.get(i);
155                    if (s.getHeight() == 720) {
156                        big = s;
157                        break;
158                    }
159                }
160                Log.v(TAG,"BIG wil be "+big.getWidth()+" x "+big.getHeight());
161                mImageReader = ImageReader.newInstance(big.getWidth(), big.getHeight(),
162                        ImageFormat.JPEG, /*maxImages*/2);
163                mImageReader.setOnImageAvailableListener(
164                        mOnImageAvailableListener, mBackgroundHandler);
165
166                if (facing == CameraCharacteristics.LENS_FACING_BACK &&
167                        (hasFullLevel || hasEnoughCapability)) {
168                    // Found suitable camera - get info, open, and set up outputs
169                    mCameraInfo = info;
170                    openCamera(id);
171                    foundCamera = true;
172                    break;
173                }
174            }
175            if (!foundCamera) {
176                errorMessage = "no back camera";
177            }
178        } catch (CameraAccessException e) {
179            errorMessage = e.getMessage();
180        }
181        // startBackgroundThread
182        mBackgroundThread = new HandlerThread("CameraBackground");
183        mBackgroundThread.start();
184        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
185        return (foundCamera) ? null : errorMessage;
186    }
187
188
189    private boolean hasCapability(int[] capabilities, int capability) {
190        for (int c : capabilities) {
191            if (c == capability) return true;
192        }
193        return false;
194    }
195
196    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
197            = new ImageReader.OnImageAvailableListener() {
198
199        @Override
200        public void onImageAvailable(ImageReader reader) {
201            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(),
202                    mSaveFileName, mContext,mCaptureMode));
203        }
204
205    };
206
207    /**
208     * Saves a JPEG {@link android.media.Image} into the specified {@link java.io.File}.
209     */
210    private static class ImageSaver implements Runnable {
211        private final Image mImage;
212        private final String mName;
213        Context mContext;
214        private int mMode;
215
216        public ImageSaver(Image image, String fileName, Context context,int mode) {
217            mImage = image;
218            mName = fileName;
219            mContext = context;
220            mMode = mode;
221        }
222
223        @Override
224        public void run() {
225            Log.v(TAG, "S>> SAVING...");
226            String url = MediaStoreSaver.insertImage(mContext.getContentResolver(),
227                    new MediaStoreSaver.StreamWriter() {
228                        @Override
229                        public void write(OutputStream imageOut) throws IOException {
230                            try {
231                                ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
232                                byte[] bytes = new byte[buffer.remaining()];
233                                Log.v(TAG, "S>> size=" + mImage.getWidth() +
234                                        "," + mImage.getHeight());
235                                Log.v(TAG, "S>> bytes " + bytes.length +
236                                        " (" + bytes.length / (1024 * 1024) + "MB");
237                                Log.v(TAG, "S>> bytes out " + bytes.length / mImage.getWidth());
238                                buffer.get(bytes);
239                                imageOut.write(bytes);
240                            } finally {
241                                mImage.close();
242                            }
243                        }
244                    }, mName, "Saved from Simple Camera Demo");
245            ViewfinderProcessor.reProcessImage(mContext, url, mMode);
246        }
247    }
248
249    /**
250     * Create a new camera ops thread.
251     *
252     * @param errorDisplayer listener for displaying error messages
253     * @param readyListener  listener for notifying when camera is ready for requests
254     */
255    CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer,
256              CameraReadyListener readyListener) {
257        mReadyHandler = new Handler(Looper.getMainLooper());
258
259        mCameraThread = new HandlerThread("CameraOpsThread");
260        mCameraThread.start();
261
262        if (manager == null || errorDisplayer == null ||
263                readyListener == null || mReadyHandler == null) {
264            throw new IllegalArgumentException("Need valid displayer, listener, handler");
265        }
266
267        mCameraManager = manager;
268        mErrorDisplayer = errorDisplayer;
269        mReadyListener = readyListener;
270
271    }
272
273    /**
274     * Open the first backfacing camera listed by the camera manager.
275     * Displays a dialog if it cannot open a camera.
276     */
277    public void openCamera(final String cameraId) {
278        mCameraHandler = new Handler(mCameraThread.getLooper());
279
280        mCameraHandler.post(new Runnable() {
281            public void run() {
282                if (mCameraDevice != null) {
283                    throw new IllegalStateException("Camera already open");
284                }
285                try {
286
287                    mCameraManager.openCamera(cameraId, mCameraDeviceListener, mCameraHandler);
288                } catch (CameraAccessException e) {
289                    String errorMessage = mErrorDisplayer.getErrorString(e);
290                    mErrorDisplayer.showErrorDialog(errorMessage);
291                }
292            }
293        });
294    }
295
296    public void pause() {
297
298        closeCameraAndWait();
299        mBackgroundThread.quitSafely();
300        try {
301            mBackgroundThread.join();
302            mBackgroundThread = null;
303            mBackgroundHandler = null;
304        } catch (InterruptedException e) {
305            e.printStackTrace();
306        }
307    }
308
309    public Size getBestSize() {
310        // Find a good size for output - largest 16:9 aspect ratio that's less than 720p
311        final int MAX_WIDTH = 640;
312        final float TARGET_ASPECT = 16.f / 9.f;
313
314
315        StreamConfigurationMap configs =
316                mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
317
318        Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
319
320        Size outputSize = null;
321        ArrayList<Size> smallEnough = new ArrayList<Size>();
322        for (Size candidateSize : outputSizes) {
323            if (candidateSize.getWidth() <= MAX_WIDTH) {
324                Log.v(TAG, "consider " + candidateSize);
325                smallEnough.add(candidateSize);
326            }
327        }
328        if (smallEnough.size() == 0) {
329            return outputSizes[outputSizes.length - 1]; //pick the smallest
330        }
331        Size maxSize = smallEnough.get(0);
332        double aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
333        for (Size candidateSize : smallEnough) {
334            if (maxSize.getWidth() < candidateSize.getWidth()) {
335                maxSize = candidateSize;
336                aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
337            }
338            if (maxSize.getWidth() == candidateSize.getWidth()) {
339                if (aspectDelta > Math.abs(candidateSize.getWidth() / candidateSize.getHeight() - TARGET_ASPECT)) {
340                    maxSize = candidateSize;
341                    aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
342                }
343            }
344        }
345
346        return maxSize;
347    }
348
349    /**
350     * Close the camera and wait for the close callback to be called in the camera thread.
351     * Times out after @{value CAMERA_CLOSE_TIMEOUT} ms.
352     */
353    public void closeCameraAndWait() {
354        mCloseWaiter.close();
355        mCameraHandler.post(mCloseCameraRunnable);
356        boolean closed = mCloseWaiter.block(CAMERA_CLOSE_TIMEOUT);
357        if (!closed) {
358            Log.e(TAG, "Timeout closing camera");
359        }
360    }
361
362    private Runnable mCloseCameraRunnable = new Runnable() {
363        public void run() {
364            if (mCameraDevice != null) {
365                mCameraDevice.close();
366            }
367            mCameraDevice = null;
368            mCameraSession = null;
369            mSurfaces = null;
370        }
371    };
372
373    /**
374     * Set the output Surfaces, and finish configuration if otherwise ready.
375     */
376    public void setSurface(Surface surface) {
377        final List<Surface> surfaceList = new ArrayList<Surface>();
378        surfaceList.add(surface);
379        surfaceList.add(mImageReader.getSurface());
380
381        mCameraHandler.post(new Runnable() {
382            public void run() {
383                mSurfaces = surfaceList;
384                startCameraSession();
385            }
386        });
387    }
388
389    /**
390     * Get a request builder for the current camera.
391     */
392    public CaptureRequest.Builder createCaptureRequest(int template) throws CameraAccessException {
393        CameraDevice device = mCameraDevice;
394        if (device == null) {
395            throw new IllegalStateException("Can't get requests when no camera is open");
396        }
397        return device.createCaptureRequest(template);
398    }
399
400    /**
401     * Set a repeating request.
402     */
403    public void setRepeatingRequest(final CaptureRequest request,
404                                    final CameraCaptureSession.CaptureCallback listener,
405                                    final Handler handler) {
406        mCameraHandler.post(new Runnable() {
407            public void run() {
408                try {
409                    mCameraSession.setRepeatingRequest(request, listener, handler);
410                } catch (CameraAccessException e) {
411                    String errorMessage = mErrorDisplayer.getErrorString(e);
412                    mErrorDisplayer.showErrorDialog(errorMessage);
413                }
414            }
415        });
416    }
417
418    /**
419     * Set a repeating request.
420     */
421    public void setRepeatingBurst(final List<CaptureRequest> requests,
422                                  final CameraCaptureSession.CaptureCallback listener,
423                                  final Handler handler) {
424        mCameraHandler.post(new Runnable() {
425            public void run() {
426                try {
427                    mCameraSession.setRepeatingBurst(requests, listener, handler);
428
429                } catch (CameraAccessException e) {
430                    String errorMessage = mErrorDisplayer.getErrorString(e);
431                    mErrorDisplayer.showErrorDialog(errorMessage);
432                }
433            }
434        });
435    }
436
437    /**
438     * Configure the camera session.
439     */
440    private void startCameraSession() {
441        // Wait until both the camera device is open and the SurfaceView is ready
442        if (mCameraDevice == null || mSurfaces == null) return;
443
444        try {
445
446            mCameraDevice.createCaptureSession(
447                    mSurfaces, mCameraSessionListener, mCameraHandler);
448        } catch (CameraAccessException e) {
449            String errorMessage = mErrorDisplayer.getErrorString(e);
450            mErrorDisplayer.showErrorDialog(errorMessage);
451            mCameraDevice.close();
452            mCameraDevice = null;
453        }
454    }
455
456    /**
457     * Main listener for camera session events
458     * Invoked on mCameraThread
459     */
460    private CameraCaptureSession.StateCallback mCameraSessionListener =
461            new CameraCaptureSession.StateCallback() {
462
463                @Override
464                public void onConfigured(CameraCaptureSession session) {
465                    mCameraSession = session;
466                    mReadyHandler.post(new Runnable() {
467                        public void run() {
468                            // This can happen when the screen is turned off and turned back on.
469                            if (null == mCameraDevice) {
470                                return;
471                            }
472
473                            mReadyListener.onCameraReady();
474                        }
475                    });
476
477                }
478
479                @Override
480                public void onConfigureFailed(CameraCaptureSession session) {
481                    mErrorDisplayer.showErrorDialog("Unable to configure the capture session");
482                    mCameraDevice.close();
483                    mCameraDevice = null;
484                }
485            };
486
487    /**
488     * Main listener for camera device events.
489     * Invoked on mCameraThread
490     */
491    private CameraDevice.StateCallback mCameraDeviceListener = new CameraDevice.StateCallback() {
492
493        @Override
494        public void onOpened(CameraDevice camera) {
495            mCameraDevice = camera;
496            startCameraSession();
497        }
498
499        @Override
500        public void onClosed(CameraDevice camera) {
501            mCloseWaiter.open();
502        }
503
504        @Override
505        public void onDisconnected(CameraDevice camera) {
506            mErrorDisplayer.showErrorDialog("The camera device has been disconnected.");
507            camera.close();
508            mCameraDevice = null;
509        }
510
511        @Override
512        public void onError(CameraDevice camera, int error) {
513            mErrorDisplayer.showErrorDialog("The camera encountered an error:" + error);
514            camera.close();
515            mCameraDevice = null;
516        }
517
518    };
519
520    public void captureStillPicture(int currentJpegRotation, String name, Context context, int mode) {
521        mSaveFileName = name;
522        mContext = context;
523        mCaptureMode = mode;
524        try {
525            // TODO call lock focus if we are in "AF-S(One-Shot AF) mode"
526            // TODO call precapture if we are using flash
527            // This is the CaptureRequest.Builder that we use to take a picture.
528            final CaptureRequest.Builder captureBuilder =
529                    createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
530            Log.v(TAG, "S>>  Target " + mImageReader.getWidth() + "," + mImageReader.getHeight());
531
532            captureBuilder.addTarget(mImageReader.getSurface());
533
534            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, currentJpegRotation);
535
536            CameraCaptureSession.CaptureCallback captureCallback
537                    = new CameraCaptureSession.CaptureCallback() {
538
539                @Override
540                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
541                                               TotalCaptureResult result) {
542                    Log.v(TAG, "S>>  onCaptureCompleted");
543                    setParameters();
544                }
545            };
546
547
548            setRequest(captureBuilder.build(), captureCallback, null);
549        } catch (CameraAccessException e) {
550            e.printStackTrace();
551        }
552    }
553
554    /**
555     * Set a repeating request.
556     */
557    private void setRequest(final CaptureRequest request,
558                            final CameraCaptureSession.CaptureCallback listener,
559                            final Handler handler) {
560        mCameraHandler.post(new Runnable() {
561            public void run() {
562                try {
563                    mCameraSession.stopRepeating();
564                    mCameraSession.capture(request, listener, handler);
565                } catch (CameraAccessException e) {
566                    String errorMessage = mErrorDisplayer.getErrorString(e);
567                    mErrorDisplayer.showErrorDialog(errorMessage);
568                }
569            }
570        });
571    }
572
573    public void setUpCamera(Surface processingNormalSurface) {
574        mProcessingNormalSurface = processingNormalSurface;
575        // Ready to send requests in, so set them up
576        try {
577            CaptureRequest.Builder previewBuilder =
578                    createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
579            previewBuilder.addTarget(mProcessingNormalSurface);
580            previewBuilder.setTag(mAutoExposureTag);
581            mPreviewRequest = previewBuilder.build();
582            mHdrBuilder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
583            mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE,
584                    CaptureRequest.CONTROL_AE_MODE_OFF);
585            mHdrBuilder.addTarget(mProcessingNormalSurface);
586            setParameters();
587
588        } catch (CameraAccessException e) {
589            String errorMessage = e.getMessage();
590            // MessageDialogFragment.newInstance(errorMessage).show(getFragmentManager(), FRAGMENT_DIALOG);
591        }
592    }
593
594    /**
595     * Start running an HDR burst on a configured camera session
596     */
597    public void setParameters() {
598        if (mHdrBuilder == null) {
599            Log.v(TAG, " Camera not set up");
600            return;
601        }
602        if (mAutoExposure) {
603            mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
604            mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure());
605            mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
606        } else {
607            mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
608            mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
609            mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure());
610            mHdrBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, getIso());
611        }
612        if (mAutoFocus) {
613            mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
614            mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
615        } else {
616            mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
617            mHdrBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, getFocusDistance());
618            mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
619        }
620
621        setRepeatingRequest(mHdrBuilder.build(), mCaptureCallback, mReadyHandler);
622    }
623
624    private CameraCaptureSession.CaptureCallback mCaptureCallback
625            = new CameraCaptureSession.CaptureCallback() {
626
627        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
628                                       TotalCaptureResult result) {
629        }
630    };
631
632    /**
633     * Simple listener for main code to know the camera is ready for requests, or failed to
634     * start.
635     */
636    public interface CameraReadyListener {
637        public void onCameraReady();
638    }
639
640    /**
641     * Simple listener for displaying error messages
642     */
643    public interface ErrorDisplayer {
644        public void showErrorDialog(String errorMessage);
645
646        public String getErrorString(CameraAccessException e);
647    }
648
649    public float getFocusDistance() {
650        return mFocusDist;
651    }
652
653    public void setFocusDistance(float focusDistance) {
654        mFocusDist = focusDistance;
655    }
656
657    public void setIso(int iso) {
658        mIso = iso;
659    }
660
661    public boolean isAutoExposure() {
662        return mAutoExposure;
663    }
664
665    public void setAutoExposure(boolean autoExposure) {
666        mAutoExposure = autoExposure;
667    }
668
669    public boolean isAutoFocus() {
670        return mAutoFocus;
671    }
672
673    public void setAutoFocus(boolean autoFocus) {
674        mAutoFocus = autoFocus;
675    }
676
677    public int getIso() {
678        return mIso;
679    }
680
681    public long getExposure() {
682        return mExposure;
683    }
684
685    public void setExposure(long exposure) {
686        mExposure = exposure;
687    }
688
689    public int getIsoMax() {
690        return mISOmax;
691    }
692
693    public int getIsoMin() {
694        return mISOmin;
695    }
696
697    public long getExpMax() {
698        return mExpMax;
699    }
700
701    public long getExpMin() {
702        return mExpMin;
703    }
704
705    public float getFocusMin() {
706        return mFocusMin;
707    }
708}
709