1/*
2 * Copyright (C) 2016 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 */
16package com.android.devcamera;
17
18import android.Manifest;
19import android.content.Intent;
20import android.content.pm.PackageManager;
21import android.graphics.Color;
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CaptureResult;
24import android.hardware.SensorManager;
25import android.os.Bundle;
26import android.app.Activity;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.SystemClock;
30import android.util.DisplayMetrics;
31import android.util.Log;
32import android.util.Size;
33import android.view.Gravity;
34import android.view.SurfaceHolder;
35import android.view.SurfaceView;
36import android.view.View;
37import android.view.WindowManager;
38import android.widget.Button;
39import android.widget.FrameLayout;
40import android.widget.LinearLayout;
41import android.widget.TextView;
42import android.widget.Toast;
43import android.widget.ToggleButton;
44
45
46/**
47 * A minimum camera app.
48 * To keep it simple: portrait mode only.
49 */
50public class DevCameraActivity extends Activity implements CameraInterface.MyCameraCallback, SurfaceHolder.Callback {
51    private static final String TAG = "DevCamera_UI";
52
53    private static final boolean LOG_FRAME_DATA = false;
54    private static final int AF_TRIGGER_HOLD_MILLIS = 4000;
55    private static final boolean STARTUP_FULL_YUV_ON = true;
56    private static final boolean START_WITH_FRONT_CAMERA = false;
57
58    private static final int PERMISSIONS_REQUEST_CAMERA = 1;
59    private boolean mPermissionCheckActive = false;
60
61    private SurfaceView mPreviewView;
62    private SurfaceHolder mPreviewHolder;
63    private PreviewOverlay mPreviewOverlay;
64    private FrameLayout mPreviewFrame;
65
66    private TextView mLabel1;
67    private TextView mLabel2;
68    private ToggleButton mToggleFrontCam; // Use front camera
69    private ToggleButton mToggleYuvFull; // full YUV
70    private ToggleButton mToggleYuvVga; // VGA YUV
71    private ToggleButton mToggleRaw; // raw10
72    private Button mButtonNoiseMode; // Noise reduction mode
73    private Button mButtonEdgeModeReprocess; // Edge mode
74    private Button mButtonNoiseModeReprocess; // Noise reduction mode for reprocessing
75    private Button mButtonEdgeMode; // Edge mode for reprocessing
76    private ToggleButton mToggleFace; // Face detection
77    private ToggleButton mToggleShow3A; // 3A info
78    private ToggleButton mToggleGyro; // Gyro
79    private ToggleButton mToggleBurstJpeg;
80    private ToggleButton mToggleSaveSdCard;
81    private LinearLayout mReprocessingGroup;
82    private Handler mMainHandler;
83    private CameraInterface mCamera;
84
85    // Used for saving JPEGs.
86    private HandlerThread mUtilityThread;
87    private Handler mUtilityHandler;
88
89    // send null for initialization
90    View.OnClickListener mTransferUiStateToCameraState = new View.OnClickListener() {
91        @Override
92        public void onClick(View view) {
93            // set capture flow.
94            if (view == mToggleYuvFull || view == mToggleYuvVga || view == mToggleRaw ||
95                    view == mButtonNoiseMode || view == mButtonEdgeMode || view == mToggleFace || view == null)
96                mCamera.setCaptureFlow(
97                    mToggleYuvFull.isChecked(),
98                    mToggleYuvVga.isChecked(),
99                    mToggleRaw.isChecked(),
100                    view == mButtonNoiseMode, /* cycle noise reduction mode */
101                    view == mButtonEdgeMode, /* cycle edge mode */
102                    mToggleFace.isChecked()
103            );
104            // set reprocessing flow.
105            if (view == mButtonNoiseModeReprocess || view == mButtonEdgeModeReprocess || view == null) {
106                mCamera.setReprocessingFlow(view == mButtonNoiseModeReprocess, view == mButtonEdgeModeReprocess);
107            }
108            // set visibility of cluster of reprocessing controls.
109            int reprocessingViz = mToggleYuvFull.isChecked() && mCamera.isReprocessingAvailable() ? View.VISIBLE : View.GONE;
110            mReprocessingGroup.setVisibility(reprocessingViz);
111
112            // if just turned off YUV1 stream, end burst.
113            if (view == mToggleYuvFull && !mToggleYuvFull.isChecked()) {
114                mToggleBurstJpeg.setChecked(false);
115                mCamera.setBurst(false);
116            }
117
118            if (view == mToggleBurstJpeg) {
119                mCamera.setBurst(mToggleBurstJpeg.isChecked());
120            }
121
122            if (view == mToggleShow3A || view == null) {
123                mPreviewOverlay.show3AInfo(mToggleShow3A.isChecked());
124            }
125            if (view == mToggleGyro || view == null) {
126                if (mToggleGyro.isChecked()) {
127                    startGyroDisplay();
128                } else {
129                    stopGyroDisplay();
130                }
131            }
132        }
133    };
134
135    @Override
136    protected void onCreate(Bundle savedInstanceState) {
137        Log.v(TAG, "onCreate");
138        CameraTimer.t0 = SystemClock.elapsedRealtime();
139
140        if (checkPermissions()) {
141            // Go speed racer.
142            openCamera(START_WITH_FRONT_CAMERA);
143        }
144
145        // Initialize UI.
146        setContentView(R.layout.activity_main);
147        mLabel1 = (TextView) findViewById(R.id.label1);
148        mLabel1.setText("Snappy initializing.");
149        mLabel2 = (TextView) findViewById(R.id.label2);
150        mLabel2.setText(" ...");
151        Button mAfTriggerButton = (Button) findViewById(R.id.af_trigger);
152        mToggleFrontCam = (ToggleButton) findViewById(R.id.toggle_front_cam);
153        mToggleFrontCam.setChecked(START_WITH_FRONT_CAMERA);
154        mToggleYuvFull = (ToggleButton) findViewById(R.id.toggle_yuv_full);
155        mToggleYuvVga = (ToggleButton) findViewById(R.id.toggle_yuv_vga);
156        mToggleRaw = (ToggleButton) findViewById(R.id.toggle_raw);
157        mButtonNoiseMode = (Button) findViewById(R.id.button_noise);
158        mButtonEdgeMode = (Button) findViewById(R.id.button_edge);
159        mButtonNoiseModeReprocess = (Button) findViewById(R.id.button_noise_reprocess);
160        mButtonEdgeModeReprocess = (Button) findViewById(R.id.button_edge_reprocess);
161
162        mToggleFace = (ToggleButton) findViewById(R.id.toggle_face);
163        mToggleShow3A = (ToggleButton) findViewById(R.id.toggle_show_3A);
164        mToggleGyro = (ToggleButton) findViewById(R.id.toggle_show_gyro);
165        Button mGetJpegButton = (Button) findViewById(R.id.jpeg_capture);
166        Button mGalleryButton = (Button) findViewById(R.id.gallery);
167
168        mToggleBurstJpeg = (ToggleButton) findViewById(R.id.toggle_burst_jpeg);
169        mToggleSaveSdCard = (ToggleButton) findViewById(R.id.toggle_save_sdcard);
170        mReprocessingGroup = (LinearLayout) findViewById(R.id.reprocessing_controls);
171        mPreviewView = (SurfaceView) findViewById(R.id.preview_view);
172        mPreviewHolder = mPreviewView.getHolder();
173        mPreviewHolder.addCallback(this);
174        mPreviewOverlay = (PreviewOverlay) findViewById(R.id.preview_overlay_view);
175        mPreviewFrame = (FrameLayout) findViewById(R.id.preview_frame);
176
177        // Set UI listeners.
178        mAfTriggerButton.setOnClickListener(new View.OnClickListener() {
179            @Override
180            public void onClick(View view) {
181                doAFScan();
182            }
183        });
184        mGetJpegButton.setOnClickListener(new View.OnClickListener() {
185            @Override
186            public void onClick(View view) {
187                hitCaptureButton();
188            }
189        });
190        mGalleryButton.setOnClickListener(new View.OnClickListener() {
191            @Override
192            public void onClick(View view) {
193                launchPhotosViewer();
194            }
195        });
196        mToggleFrontCam.setOnClickListener(new View.OnClickListener() {
197            @Override
198            public void onClick(View view) {
199                Log.v(TAG, "switchCamera()");
200                CameraTimer.t0 = SystemClock.elapsedRealtime();
201                // ToggleButton isChecked state will determine which camera is started.
202                openCamera(mToggleFrontCam.isChecked());
203                startCamera();
204            }
205        });
206        mToggleYuvFull.setOnClickListener(mTransferUiStateToCameraState);
207        mToggleYuvVga.setOnClickListener(mTransferUiStateToCameraState);
208        mToggleRaw.setOnClickListener(mTransferUiStateToCameraState);
209        mButtonNoiseMode.setOnClickListener(mTransferUiStateToCameraState);
210        mButtonEdgeMode.setOnClickListener(mTransferUiStateToCameraState);
211        mButtonNoiseModeReprocess.setOnClickListener(mTransferUiStateToCameraState);
212        mButtonEdgeModeReprocess.setOnClickListener(mTransferUiStateToCameraState);
213        mToggleFace.setOnClickListener(mTransferUiStateToCameraState);
214        mToggleShow3A.setOnClickListener(mTransferUiStateToCameraState);
215        mToggleGyro.setOnClickListener(mTransferUiStateToCameraState);
216        mToggleBurstJpeg.setOnClickListener(mTransferUiStateToCameraState);
217        mToggleSaveSdCard.setOnClickListener(mTransferUiStateToCameraState);
218        mToggleSaveSdCard.setChecked(true);
219
220        mMainHandler = new Handler(this.getApplicationContext().getMainLooper());
221
222        // General utility thread for e.g. saving JPEGs.
223        mUtilityThread = new HandlerThread("UtilityThread");
224        mUtilityThread.start();
225        mUtilityHandler = new Handler(mUtilityThread.getLooper());
226
227        // --- PRINT REPORT ---
228        //CameraDeviceReport.printReport(this, false);
229        super.onCreate(savedInstanceState);
230    }
231
232    // Open camera. No UI required.
233    private void openCamera(boolean frontCamera) {
234        // Close previous camera if required.
235        if (mCamera != null) {
236            mCamera.closeCamera();
237        }
238        // --- SET UP CAMERA ---
239        mCamera = new Api2Camera(this, frontCamera);
240        mCamera.setCallback(this);
241        mCamera.openCamera();
242    }
243
244    // Initialize camera related UI and start camera; call openCamera first.
245    private void startCamera() {
246        // --- SET UP USER INTERFACE ---
247        mToggleYuvFull.setChecked(STARTUP_FULL_YUV_ON);
248        mToggleFace.setChecked(true);
249        mToggleRaw.setVisibility(mCamera.isRawAvailable() ? View.VISIBLE : View.GONE);
250        mToggleShow3A.setChecked(true);
251        mTransferUiStateToCameraState.onClick(null);
252
253        // --- SET UP PREVIEW AND OPEN CAMERA ---
254
255        if (mPreviewSurfaceValid) {
256            mCamera.startPreview(mPreviewHolder.getSurface());
257        } else {
258            // Note that preview is rotated 90 degrees from camera. We just hard code this now.
259            Size previewSize = mCamera.getPreviewSize();
260            // Render in top 12 x 9 of 16 x 9 display.
261            int renderHeight = 3 * displayHeight() / 4;
262            int renderWidth = renderHeight * previewSize.getHeight() / previewSize.getWidth();
263            int renderPad = (displayWidth() - renderWidth) / 2;
264
265            mPreviewFrame.setPadding(renderPad, 0, 0, 0);
266            mPreviewFrame.setLayoutParams(new LinearLayout.LayoutParams(renderWidth + renderPad, renderHeight));
267            // setFixedSize() will trigger surfaceChanged() callback below, which will start preview.
268            mPreviewHolder.setFixedSize(previewSize.getHeight(), previewSize.getWidth());
269        }
270    }
271
272    boolean mPreviewSurfaceValid = false;
273
274    @Override
275    public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
276        Log.v(TAG, String.format("surfaceChanged: format=%x w=%d h=%d", format, width, height));
277        if (checkPermissions()) {
278            mPreviewSurfaceValid = true;
279            mCamera.startPreview(mPreviewHolder.getSurface());
280        }
281    }
282
283    Runnable mReturnToCafRunnable = new Runnable() {
284        @Override
285        public void run() {
286            mCamera.setCAF();
287        }
288    };
289
290    private void doAFScan() {
291        mCamera.triggerAFScan();
292        mMainHandler.removeCallbacks(mReturnToCafRunnable);
293        mMainHandler.postDelayed(mReturnToCafRunnable, AF_TRIGGER_HOLD_MILLIS);
294    }
295
296    private int displayWidth() {
297        DisplayMetrics metrics = new DisplayMetrics();
298        this.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
299        return metrics.widthPixels;
300    }
301
302    private int displayHeight() {
303        DisplayMetrics metrics = new DisplayMetrics();
304        this.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
305        return metrics.heightPixels;
306    }
307
308    @Override
309    public void onStart() {
310        Log.v(TAG, "onStart");
311        super.onStart();
312        // Leave screen on.
313        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
314
315        if (!checkPermissions()) return;
316
317        // Can start camera now that we have the above initialized.
318        if (mCamera == null) {
319            openCamera(mToggleFrontCam.isChecked());
320        }
321        startCamera();
322    }
323
324    private boolean checkPermissions() {
325        if (mPermissionCheckActive) return false;
326
327        // Check for all runtime permissions
328        if ((checkSelfPermission(Manifest.permission.CAMERA)
329                != PackageManager.PERMISSION_GRANTED )
330            || (checkSelfPermission(Manifest.permission.RECORD_AUDIO)
331                != PackageManager.PERMISSION_GRANTED)
332            || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
333                != PackageManager.PERMISSION_GRANTED)) {
334            Log.i(TAG, "Requested camera/video permissions");
335            requestPermissions(new String[] {
336                        Manifest.permission.CAMERA,
337                        Manifest.permission.RECORD_AUDIO,
338                        Manifest.permission.WRITE_EXTERNAL_STORAGE},
339                    PERMISSIONS_REQUEST_CAMERA);
340            mPermissionCheckActive = true;
341            return false;
342        }
343
344        return true;
345    }
346
347    @Override
348    public void onRequestPermissionsResult(int requestCode, String[] permissions,
349            int[] grantResults) {
350        mPermissionCheckActive = false;
351        if (requestCode == PERMISSIONS_REQUEST_CAMERA) {
352            for (int i = 0; i < grantResults.length; i++) {
353                if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
354                    Log.i(TAG, "At least one permission denied, can't continue: " + permissions[i]);
355                    finish();
356                    return;
357                }
358            }
359
360            Log.i(TAG, "All permissions granted");
361            openCamera(mToggleFrontCam.isChecked());
362            startCamera();
363        }
364    }
365
366    @Override
367    public void onStop() {
368        Log.v(TAG, "onStop");
369        if (mCamera != null) {
370            mCamera.closeCamera();
371            mCamera = null;
372        }
373
374        // Cancel any pending AF operations.
375        mMainHandler.removeCallbacks(mReturnToCafRunnable);
376        stopGyroDisplay(); // No-op if not running.
377        super.onStop();
378    }
379
380    public void noCamera2Full() {
381        Toast toast = Toast.makeText(this, "WARNING: this camera does not support camera2 HARDWARE_LEVEL_FULL.", Toast.LENGTH_LONG);
382        toast.setGravity(Gravity.TOP, 0, 0);
383        toast.show();
384    }
385
386    @Override
387    public void setNoiseEdgeText(final String nrMode, final String edgeMode) {
388        mMainHandler.post(new Runnable() {
389            @Override
390            public void run() {
391                mButtonNoiseMode.setText(nrMode);
392                mButtonEdgeMode.setText(edgeMode);
393            }
394        });
395    }
396
397    @Override
398    public void setNoiseEdgeTextForReprocessing(final String nrMode, final String edgeMode) {
399        mMainHandler.post(new Runnable() {
400            @Override
401            public void run() {
402                mButtonNoiseModeReprocess.setText(nrMode);
403                mButtonEdgeModeReprocess.setText(edgeMode);
404            }
405        });
406    }
407
408    int mJpegCounter = 0;
409    long mJpegMillis = 0;
410
411    @Override
412    public void jpegAvailable(final byte[] jpegData, final int x, final int y) {
413        Log.v(TAG, "JPEG returned, size = " + jpegData.length);
414        long now = SystemClock.elapsedRealtime();
415        final long dt = mJpegMillis > 0 ? now - mJpegMillis : 0;
416        mJpegMillis = now;
417
418        if (mToggleSaveSdCard.isChecked()) {
419            mUtilityHandler.post(new Runnable() {
420                @Override
421                public void run() {
422                    final String result = MediaSaver.saveJpeg(getApplicationContext(), jpegData, getContentResolver());
423                    mMainHandler.post(new Runnable() {
424                        @Override
425                        public void run() {
426                            fileNameToast(String.format("Saved %dx%d and %d bytes JPEG to %s in %d ms.", x, y, jpegData.length, result, dt));
427                        }
428                    });
429
430                }
431            });
432        } else {
433            mMainHandler.post(new Runnable() {
434                @Override
435                public void run() {
436                    fileNameToast(String.format("Processing JPEG #%d %dx%d and %d bytes in %d ms.", ++mJpegCounter, x, y, jpegData.length, dt));
437                }
438            });
439        }
440    }
441
442    @Override
443    public void receivedFirstFrame() {
444        mMainHandler.post(new Runnable() {
445            @Override
446            public void run() {
447                mPreviewView.setBackgroundColor(Color.TRANSPARENT);
448            }
449        });
450    }
451
452    Toast mToast;
453
454    public void fileNameToast(String s) {
455        if (mToast != null) {
456            mToast.cancel();
457        }
458        mToast = Toast.makeText(this, s, Toast.LENGTH_SHORT);
459        mToast.setGravity(Gravity.TOP, 0, 0);
460        mToast.show();
461    }
462
463    @Override
464    public void frameDataAvailable(final NormalizedFace[] faces, final float normExposure, final float normLens, float fps, int iso, final int afState, int aeState, int awbState) {
465        mMainHandler.post(new Runnable() {
466            @Override
467            public void run() {
468                mPreviewOverlay.setFrameData(faces, normExposure, normLens, afState);
469            }
470        });
471        // Build info string.
472        String ae = aeStateToString(aeState);
473        String af = afStateToString(afState);
474        String awb = awbStateToString(awbState);
475        final String info = String.format(" %2.0f FPS%5d ISO  AF:%s AE:%s AWB:%s", fps, iso, af, ae, awb);
476        mLastInfo = info;
477
478        if (LOG_FRAME_DATA && faces != null) {
479            Log.v(TAG, "normExposure: " + normExposure);
480            Log.v(TAG, "normLens: " + normLens);
481            for (int i = 0; i < faces.length; ++i) {
482                Log.v(TAG, "Face getBounds: " + faces[i].bounds);
483                Log.v(TAG, "Face left eye: " + faces[i].leftEye);
484                Log.v(TAG, "Face right eye: " + faces[i].rightEye);
485                Log.v(TAG, "Face mouth: " + faces[i].mouth);
486            }
487        }
488
489        // Status line
490        mMainHandler.post(new Runnable() {
491            @Override
492            public void run() {
493                mLabel1.setText(info);
494            }
495        });
496    }
497
498    Integer mTimeToFirstFrame = 0;
499    Integer mHalWaitTime = 0;
500    Float mDroppedFrameCount = 0f;
501    String mLastInfo;
502
503    @Override
504    public void performanceDataAvailable(Integer timeToFirstFrame, Integer halWaitTime, Float droppedFrameCount) {
505        if (timeToFirstFrame != null) {
506            mTimeToFirstFrame = timeToFirstFrame;
507        }
508        if (halWaitTime != null) {
509            mHalWaitTime = halWaitTime;
510        }
511        if (droppedFrameCount != null) {
512            mDroppedFrameCount += droppedFrameCount;
513        }
514        mMainHandler.post(new Runnable() {
515            @Override
516            public void run() {
517                mLabel2.setText(String.format("TTP %dms  HAL %dms  Framedrops:%.2f", mTimeToFirstFrame, mHalWaitTime, mDroppedFrameCount));
518            }
519        });
520    }
521
522    // Hit capture button.
523    private void hitCaptureButton() {
524        Log.v(TAG, "hitCaptureButton");
525        mCamera.takePicture();
526    }
527
528    // Hit Photos button.
529    private void launchPhotosViewer() {
530        Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
531        intent.setType("image/*");
532        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
533        startActivity(intent);
534    }
535
536    /*********************************
537     * Gyro graphics overlay update. *
538     *********************************/
539    GyroOperations mGyroOperations;
540
541    private void startGyroDisplay() {
542
543        float[] fovs = mCamera.getFieldOfView();
544        mPreviewOverlay.setFieldOfView(fovs[0], fovs[1]);
545        mPreviewOverlay.setFacingAndOrientation(mToggleFrontCam.isChecked() ?
546                CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK,
547                mCamera.getOrientation());
548        if (mGyroOperations == null) {
549            SensorManager sensorManager = (SensorManager) getSystemService(this.SENSOR_SERVICE);
550            mGyroOperations = new GyroOperations(sensorManager);
551        }
552        mGyroOperations.startListening(
553                new GyroListener() {
554                    @Override
555                    public void updateGyroAngles(float[] gyroAngles) {
556                        mPreviewOverlay.setGyroAngles(gyroAngles);
557                    }
558                }
559        );
560
561        mPreviewOverlay.showGyroGrid(true);
562    }
563
564    private void stopGyroDisplay() {
565        if (mGyroOperations != null) {
566            mGyroOperations.stopListening();
567        }
568        mPreviewOverlay.showGyroGrid(false);
569    }
570
571
572    /*******************************************
573     * SurfaceView callbacks just for logging. *
574     *******************************************/
575
576    @Override
577    public void surfaceCreated(SurfaceHolder holder) {
578        Log.v(TAG, "surfaceCreated");
579    }
580
581    @Override
582    public void surfaceDestroyed(SurfaceHolder holder) {
583        Log.v(TAG, "surfaceDestroyed");
584    }
585
586    /*********************
587     * UTILITY FUNCTIONS *
588     *********************/
589
590    private static String awbStateToString(int mode) {
591        switch (mode) {
592            case CaptureResult.CONTROL_AWB_STATE_INACTIVE:
593                return "inactive";
594            case CaptureResult.CONTROL_AWB_STATE_SEARCHING:
595                return "searching";
596            case CaptureResult.CONTROL_AWB_STATE_CONVERGED:
597                return "converged";
598            case CaptureResult.CONTROL_AWB_STATE_LOCKED:
599                return "lock";
600            default:
601                return "unknown " + Integer.toString(mode);
602        }
603    }
604
605    private static String aeStateToString(int mode) {
606        switch (mode) {
607            case CaptureResult.CONTROL_AE_STATE_INACTIVE:
608                return "inactive";
609            case CaptureResult.CONTROL_AE_STATE_SEARCHING:
610                return "searching";
611            case CaptureResult.CONTROL_AE_STATE_PRECAPTURE:
612                return "precapture";
613            case CaptureResult.CONTROL_AE_STATE_CONVERGED:
614                return "converged";
615            case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED:
616                return "flashReq";
617            case CaptureResult.CONTROL_AE_STATE_LOCKED:
618                return "lock";
619            default:
620                return "unknown " + Integer.toString(mode);
621        }
622    }
623
624    private static String afStateToString(int mode) {
625        switch (mode) {
626            case CaptureResult.CONTROL_AF_STATE_INACTIVE:
627                return "inactive";
628            case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
629                return "passiveScan";
630            case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
631                return "passiveFocused";
632            case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
633                return "passiveUnfocused";
634            case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
635                return "activeScan";
636            case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
637                return "focusedLock";
638            case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
639                return "notFocusedLock";
640            default:
641                return "unknown" + Integer.toString(mode);
642        }
643    }
644
645}
646