Camera.java revision 542fa8916296196b10d74b439304bd7fc5bf1cef
1/*
2 * Copyright (C) 2007 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.camera;
18
19import android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.hardware.Camera.Parameters;
32import android.hardware.Camera.PictureCallback;
33import android.hardware.Camera.Size;
34import android.location.Location;
35import android.location.LocationManager;
36import android.location.LocationProvider;
37import android.media.AudioManager;
38import android.media.ToneGenerator;
39import android.net.Uri;
40import android.os.Build;
41import android.os.Bundle;
42import android.os.Debug;
43import android.os.Environment;
44import android.os.Handler;
45import android.os.Message;
46import android.os.SystemClock;
47import android.preference.PreferenceManager;
48import android.provider.MediaStore;
49import android.text.format.DateFormat;
50import android.util.AttributeSet;
51import android.util.Log;
52import android.view.Display;
53import android.view.GestureDetector;
54import android.view.KeyEvent;
55import android.view.LayoutInflater;
56import android.view.Menu;
57import android.view.MenuItem;
58import android.view.MotionEvent;
59import android.view.OrientationEventListener;
60import android.view.SurfaceHolder;
61import android.view.SurfaceView;
62import android.view.View;
63import android.view.ViewGroup;
64import android.view.Window;
65import android.view.WindowManager;
66import android.view.MenuItem.OnMenuItemClickListener;
67import android.widget.ImageView;
68import android.widget.ZoomButtonsController;
69
70import com.android.camera.gallery.IImage;
71import com.android.camera.gallery.IImageList;
72
73import java.io.File;
74import java.io.FileNotFoundException;
75import java.io.FileOutputStream;
76import java.io.IOException;
77import java.io.OutputStream;
78import java.util.ArrayList;
79import java.util.Collections;
80import java.util.List;
81
82/**
83 * Activity of the Camera which used to see preview and take pictures.
84 */
85public class Camera extends Activity implements View.OnClickListener,
86        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
87        Switcher.OnSwitchListener, OnScreenSettings.OnVisibilityChangedListener,
88        OnSharedPreferenceChangeListener {
89
90    private static final String TAG = "camera";
91
92    private static final int CROP_MSG = 1;
93    private static final int FIRST_TIME_INIT = 2;
94    private static final int RESTART_PREVIEW = 3;
95    private static final int CLEAR_SCREEN_DELAY = 4;
96
97    private static final String GPS_MODE_ON = "on";
98    private static final String GPS_MODE_OFF = "off";
99
100    private static final int SCREEN_DELAY = 2 * 60 * 1000;
101    private static final int FOCUS_BEEP_VOLUME = 100;
102
103    private static final String SCENE_MODE_ON = "on";
104    private static final String SCENE_MODE_OFF = "off";
105
106    private double mZoomValue;  // The current zoom value.
107    private double mZoomStep;
108    private double mZoomMax;
109    public static final double ZOOM_STEP_MIN = 0.25;
110    public static final String ZOOM_STOP = "stop";
111    public static final String ZOOM_IMMEDIATE = "zoom-immediate";
112    public static final String ZOOM_CONTINUOUS = "zoom-continuous";
113    public static final double ZOOM_MIN = 1.0;
114    public static final String ZOOM_SPEED = "99";
115
116    private Parameters mParameters;
117    private Parameters mInitialParameters;
118
119    // The non-standard parameter strings to communicate with camera driver.
120    // This will be removed in the future.
121    public static final String PARM_ZOOM_STATE = "mot-zoom-state";
122    public static final String PARM_ZOOM_STEP = "mot-zoom-step";
123    public static final String PARM_ZOOM_TO_LEVEL = "mot-zoom-to-level";
124    public static final String PARM_ZOOM_SPEED = "mot-zoom-speed";
125    public static final String PARM_ZOOM_MAX = "mot-max-picture-continuous-zoom";
126
127    private OrientationEventListener mOrientationListener;
128    private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
129    private SharedPreferences mPreferences;
130
131    private static final int IDLE = 1;
132    private static final int SNAPSHOT_IN_PROGRESS = 2;
133
134    private static final boolean SWITCH_CAMERA = true;
135    private static final boolean SWITCH_VIDEO = false;
136
137    private int mStatus = IDLE;
138    private static final String sTempCropFilename = "crop-temp";
139
140    private android.hardware.Camera mCameraDevice;
141    private SurfaceView mSurfaceView;
142    private SurfaceHolder mSurfaceHolder = null;
143    private ShutterButton mShutterButton;
144    private FocusRectangle mFocusRectangle;
145    private IconIndicator mGpsIndicator;
146    private IconIndicator mFlashIndicator;
147    private IconIndicator mFocusIndicator;
148    private IconIndicator mWhitebalanceIndicator;
149    private IconIndicator mSceneModeIndicator;
150    private ToneGenerator mFocusToneGenerator;
151    private ZoomButtonsController mZoomButtons;
152    private GestureDetector mGestureDetector;
153    private Switcher mSwitcher;
154    private boolean mStartPreviewFail = false;
155
156    // mPostCaptureAlert, mLastPictureButton, mThumbController
157    // are non-null only if isImageCaptureIntent() is true.
158    private ImageView mLastPictureButton;
159    private ThumbnailController mThumbController;
160
161    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
162    private String mCropValue;
163    private Uri mSaveUri;
164
165    private ImageCapture mImageCapture = null;
166
167    private boolean mPreviewing;
168    private boolean mPausing;
169    private boolean mFirstTimeInitialized;
170    private boolean mIsImageCaptureIntent;
171    private boolean mRecordLocation;
172
173    private static final int FOCUS_NOT_STARTED = 0;
174    private static final int FOCUSING = 1;
175    private static final int FOCUSING_SNAP_ON_FINISH = 2;
176    private static final int FOCUS_SUCCESS = 3;
177    private static final int FOCUS_FAIL = 4;
178    private int mFocusState = FOCUS_NOT_STARTED;
179
180    private ContentResolver mContentResolver;
181    private boolean mDidRegister = false;
182
183    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
184
185    private LocationManager mLocationManager = null;
186
187    // Use OneShotPreviewCallback to measure the time between
188    // JpegPictureCallback and preview.
189    private final OneShotPreviewCallback mOneShotPreviewCallback =
190            new OneShotPreviewCallback();
191    private final ShutterCallback mShutterCallback = new ShutterCallback();
192    private final RawPictureCallback mRawPictureCallback =
193            new RawPictureCallback();
194    private final AutoFocusCallback mAutoFocusCallback =
195            new AutoFocusCallback();
196    private final ZoomCallback mZoomCallback = new ZoomCallback();
197    // Use the ErrorCallback to capture the crash count
198    // on the mediaserver
199    private final ErrorCallback mErrorCallback = new ErrorCallback();
200
201    private long mFocusStartTime;
202    private long mFocusCallbackTime;
203    private long mCaptureStartTime;
204    private long mShutterCallbackTime;
205    private long mRawPictureCallbackTime;
206    private long mJpegPictureCallbackTime;
207    private int mPicturesRemaining;
208
209    // These latency time are for the CameraLatency test.
210    public long mAutoFocusTime;
211    public long mShutterLag;
212    public long mShutterAndRawPictureCallbackTime;
213    public long mJpegPictureCallbackTimeLag;
214    public long mRawPictureAndJpegPictureCallbackTime;
215
216    // Add the media server tag
217    public static boolean mMediaServerDied = false;
218    // Focus mode. Options are pref_camera_focusmode_entryvalues.
219    private String mFocusMode;
220
221    private final Handler mHandler = new MainHandler();
222    private OnScreenSettings mSettings;
223
224    /**
225     * This Handler is used to post message back onto the main thread of the
226     * application
227     */
228    private class MainHandler extends Handler {
229        @Override
230        public void handleMessage(Message msg) {
231            switch (msg.what) {
232                case RESTART_PREVIEW: {
233                    restartPreview();
234                    break;
235                }
236
237                case CLEAR_SCREEN_DELAY: {
238                    getWindow().clearFlags(
239                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
240                    break;
241                }
242
243                case FIRST_TIME_INIT: {
244                    initializeFirstTime();
245                    break;
246                }
247            }
248        }
249    }
250
251    // Snapshots can only be taken after this is called. It should be called
252    // once only. We could have done these things in onCreate() but we want to
253    // make preview screen appear as soon as possible.
254    private void initializeFirstTime() {
255        if (mFirstTimeInitialized) return;
256
257        // Create orientation listenter. This should be done first because it
258        // takes some time to get first orientation.
259        mOrientationListener =
260                new OrientationEventListener(Camera.this) {
261            @Override
262            public void onOrientationChanged(int orientation) {
263                // We keep the last known orientation. So if the user
264                // first orient the camera then point the camera to
265                // floor/sky, we still have the correct orientation.
266                if (orientation != ORIENTATION_UNKNOWN) {
267                    mLastOrientation = orientation;
268                }
269            }
270        };
271        mOrientationListener.enable();
272
273        // Initialize location sevice.
274        mLocationManager = (LocationManager)
275                getSystemService(Context.LOCATION_SERVICE);
276        readPreference();
277        if (mRecordLocation) startReceivingLocationUpdates();
278
279        checkStorage();
280
281        // Initialize last picture button.
282        mContentResolver = getContentResolver();
283        if (!mIsImageCaptureIntent)  {
284            findViewById(R.id.camera_switch).setOnClickListener(this);
285            mLastPictureButton =
286                    (ImageView) findViewById(R.id.review_thumbnail);
287            mLastPictureButton.setOnClickListener(this);
288            mThumbController = new ThumbnailController(
289                    getResources(), mLastPictureButton, mContentResolver);
290            mThumbController.loadData(ImageManager.getLastImageThumbPath());
291            // Update last image thumbnail.
292            updateThumbnailButton();
293        }
294
295        // Initialize shutter button.
296        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
297        mShutterButton.setOnShutterButtonListener(this);
298        mShutterButton.setVisibility(View.VISIBLE);
299
300        mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
301        updateFocusIndicator();
302
303        // Initialize GPS indicator.
304        mGpsIndicator = (IconIndicator) findViewById(R.id.gps_icon);
305
306        ImageManager.ensureOSXCompatibleFolder();
307
308        installIntentFilter();
309
310        initializeFocusTone();
311
312        initializeZoom();
313
314        mFirstTimeInitialized = true;
315    }
316
317    private void updateThumbnailButton() {
318        // Update last image if URI is invalid and the storage is ready.
319        if (!mThumbController.isUriValid() && mPicturesRemaining >= 0) {
320            updateLastImage();
321        }
322        mThumbController.updateDisplayIfNeeded();
323    }
324
325    // If the activity is paused and resumed, this method will be called in
326    // onResume.
327    private void initializeSecondTime() {
328        // Start orientation listener as soon as possible because it takes
329        // some time to get first orientation.
330        mOrientationListener.enable();
331
332        // Start location update if needed.
333        readPreference();
334        if (mRecordLocation) startReceivingLocationUpdates();
335
336        installIntentFilter();
337
338        initializeFocusTone();
339
340        checkStorage();
341
342        if (mZoomButtons != null) {
343            mZoomValue = Double.parseDouble(
344                    mParameters.get(PARM_ZOOM_TO_LEVEL));
345            mCameraDevice.setZoomCallback(mZoomCallback);
346        }
347
348        if (!mIsImageCaptureIntent) {
349            updateThumbnailButton();
350        }
351    }
352
353    private void initializeZoom() {
354        // Check if the phone has zoom capability.
355        String zoomState = mParameters.get(PARM_ZOOM_STATE);
356        if (zoomState == null) return;
357
358        mZoomValue = Double.parseDouble(mParameters.get(PARM_ZOOM_TO_LEVEL));
359        mZoomMax = Double.parseDouble(mParameters.get(PARM_ZOOM_MAX));
360        mZoomStep = Double.parseDouble(mParameters.get(PARM_ZOOM_STEP));
361        mParameters.set(PARM_ZOOM_SPEED, ZOOM_SPEED);
362        mCameraDevice.setParameters(mParameters);
363
364        mGestureDetector = new GestureDetector(this, new ZoomGestureListener());
365        mCameraDevice.setZoomCallback(mZoomCallback);
366        mZoomButtons = new ZoomButtonsController(mSurfaceView);
367        mZoomButtons.setAutoDismissed(true);
368        mZoomButtons.setZoomSpeed(100);
369        mZoomButtons.setOnZoomListener(
370                new ZoomButtonsController.OnZoomListener() {
371            public void onVisibilityChanged(boolean visible) {
372                if (visible) {
373                    updateZoomButtonsEnabled();
374                }
375            }
376
377            public void onZoom(boolean zoomIn) {
378                if (isZooming()) return;
379
380                if (zoomIn) {
381                    if (mZoomValue < mZoomMax) {
382                        zoomToLevel(ZOOM_CONTINUOUS, mZoomValue + mZoomStep);
383                    }
384                } else {
385                    if (mZoomValue > ZOOM_MIN) {
386                        zoomToLevel(ZOOM_CONTINUOUS, mZoomValue - mZoomStep);
387                    }
388                }
389            }
390        });
391    }
392
393    public void onVisibilityChanged(boolean visible) {
394        // When the on-screen setting is not displayed, we show the gripper.
395        // When the on-screen setting is displayed, we hide the gripper.
396        int reverseVisibility = visible ? View.INVISIBLE : View.VISIBLE;
397        findViewById(R.id.btn_gripper).setVisibility(reverseVisibility);
398        findViewById(R.id.indicator_bar).setVisibility(reverseVisibility);
399
400        if (visible) {
401            mPreferences.registerOnSharedPreferenceChangeListener(this);
402        } else {
403            mPreferences.unregisterOnSharedPreferenceChangeListener(this);
404        }
405    }
406
407    private boolean isZooming() {
408        mParameters = mCameraDevice.getParameters();
409        return "continuous".equals(mParameters.get(PARM_ZOOM_STATE));
410    }
411
412    private void zoomToLevel(String type, double zoomValue) {
413        if (zoomValue > mZoomMax) zoomValue = mZoomMax;
414        if (zoomValue < ZOOM_MIN) zoomValue = ZOOM_MIN;
415
416        // If the application sets a unchanged zoom value, the driver will stuck
417        // at the zoom state. This is a work-around to ensure the state is at
418        // "stop".
419        mParameters.set(PARM_ZOOM_STATE, ZOOM_STOP);
420        mCameraDevice.setParameters(mParameters);
421
422        mParameters.set(PARM_ZOOM_TO_LEVEL, Double.toString(zoomValue));
423        mParameters.set(PARM_ZOOM_STATE, type);
424        mCameraDevice.setParameters(mParameters);
425
426        if (ZOOM_IMMEDIATE.equals(type)) mZoomValue = zoomValue;
427    }
428
429    private void updateZoomButtonsEnabled() {
430        mZoomButtons.setZoomInEnabled(mZoomValue < mZoomMax);
431        mZoomButtons.setZoomOutEnabled(mZoomValue > ZOOM_MIN);
432    }
433
434    private class ZoomGestureListener extends
435            GestureDetector.SimpleOnGestureListener {
436        @Override
437        public boolean onDown(MotionEvent e) {
438            // Show zoom buttons only when preview is started and snapshot
439            // is not in progress. mZoomButtons may be null if it is not
440            // initialized.
441            if (!mPausing && isCameraIdle() && mPreviewing
442                    && mZoomButtons != null) {
443                mZoomButtons.setVisible(true);
444            }
445            return true;
446        }
447
448        @Override
449        public boolean onDoubleTap(MotionEvent e) {
450            // Perform zoom only when preview is started and snapshot is not in
451            // progress.
452            if (mPausing || !isCameraIdle() || !mPreviewing
453                    || mZoomButtons == null || isZooming()) {
454                return false;
455            }
456
457            if (mZoomValue < mZoomMax) {
458                // Zoom in to the maximum.
459                while (mZoomValue < mZoomMax) {
460                    zoomToLevel(ZOOM_IMMEDIATE, mZoomValue + ZOOM_STEP_MIN);
461                    // Wait for a while so we are not changing zoom too fast.
462                    try {
463                        Thread.sleep(5);
464                    } catch (InterruptedException ex) {
465                    }
466                }
467            } else {
468                // Zoom out to the minimum.
469                while (mZoomValue > ZOOM_MIN) {
470                    zoomToLevel(ZOOM_IMMEDIATE, mZoomValue - ZOOM_STEP_MIN);
471                    // Wait for a while so we are not changing zoom too fast.
472                    try {
473                        Thread.sleep(5);
474                    } catch (InterruptedException ex) {
475                    }
476                }
477            }
478            updateZoomButtonsEnabled();
479            return true;
480        }
481    }
482
483    @Override
484    public boolean dispatchTouchEvent(MotionEvent m) {
485        if (!super.dispatchTouchEvent(m) && mGestureDetector != null) {
486            return mGestureDetector.onTouchEvent(m);
487        }
488        return true;
489    }
490
491    LocationListener [] mLocationListeners = new LocationListener[] {
492            new LocationListener(LocationManager.GPS_PROVIDER),
493            new LocationListener(LocationManager.NETWORK_PROVIDER)
494    };
495
496    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
497        @Override
498        public void onReceive(Context context, Intent intent) {
499            String action = intent.getAction();
500            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
501                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
502                    || action.equals(Intent.ACTION_MEDIA_CHECKING)
503                    || action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
504                checkStorage();
505            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
506                checkStorage();
507                if (!mIsImageCaptureIntent)  {
508                    updateThumbnailButton();
509                }
510            }
511        }
512    };
513
514    private class LocationListener
515            implements android.location.LocationListener {
516        Location mLastLocation;
517        boolean mValid = false;
518        String mProvider;
519
520        public LocationListener(String provider) {
521            mProvider = provider;
522            mLastLocation = new Location(mProvider);
523        }
524
525        public void onLocationChanged(Location newLocation) {
526            if (newLocation.getLatitude() == 0.0
527                    && newLocation.getLongitude() == 0.0) {
528                // Hack to filter out 0.0,0.0 locations
529                return;
530            }
531            // If GPS is available before start camera, we won't get status
532            // update so update GPS indicator when we receive data.
533            if (mRecordLocation
534                    && LocationManager.GPS_PROVIDER.equals(mProvider)) {
535                mGpsIndicator.setMode(GPS_MODE_ON);
536            }
537            mLastLocation.set(newLocation);
538            mValid = true;
539        }
540
541        public void onProviderEnabled(String provider) {
542        }
543
544        public void onProviderDisabled(String provider) {
545            mValid = false;
546        }
547
548        public void onStatusChanged(
549                String provider, int status, Bundle extras) {
550            switch(status) {
551                case LocationProvider.OUT_OF_SERVICE:
552                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
553                    mValid = false;
554                    if (mRecordLocation &&
555                            LocationManager.GPS_PROVIDER.equals(provider)) {
556                        mGpsIndicator.setMode(GPS_MODE_OFF);
557                    }
558                    break;
559                }
560            }
561        }
562
563        public Location current() {
564            return mValid ? mLastLocation : null;
565        }
566    }
567
568    private final class OneShotPreviewCallback
569            implements android.hardware.Camera.PreviewCallback {
570        public void onPreviewFrame(byte[] data,
571                                   android.hardware.Camera camera) {
572            long now = System.currentTimeMillis();
573            if (mJpegPictureCallbackTime != 0) {
574                mJpegPictureCallbackTimeLag = now - mJpegPictureCallbackTime;
575                Log.v(TAG, "mJpegPictureCallbackTimeLag = "
576                        + mJpegPictureCallbackTimeLag + "ms");
577                mJpegPictureCallbackTime = 0;
578            } else {
579                Log.v(TAG, "Got first frame");
580            }
581        }
582    }
583
584    private final class ShutterCallback
585            implements android.hardware.Camera.ShutterCallback {
586        public void onShutter() {
587            mShutterCallbackTime = System.currentTimeMillis();
588            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
589            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
590            clearFocusState();
591        }
592    }
593
594    private final class RawPictureCallback implements PictureCallback {
595        public void onPictureTaken(
596                byte [] rawData, android.hardware.Camera camera) {
597            mRawPictureCallbackTime = System.currentTimeMillis();
598            mShutterAndRawPictureCallbackTime =
599                mRawPictureCallbackTime - mShutterCallbackTime;
600            Log.v(TAG, "mShutterAndRawPictureCallbackTime = "
601                    + mShutterAndRawPictureCallbackTime + "ms");
602        }
603    }
604
605    private final class JpegPictureCallback implements PictureCallback {
606        Location mLocation;
607
608        public JpegPictureCallback(Location loc) {
609            mLocation = loc;
610        }
611
612        public void onPictureTaken(
613                final byte [] jpegData, final android.hardware.Camera camera) {
614            if (mPausing) {
615                return;
616            }
617
618            mJpegPictureCallbackTime = System.currentTimeMillis();
619            mRawPictureAndJpegPictureCallbackTime =
620                mJpegPictureCallbackTime - mRawPictureCallbackTime;
621            Log.v(TAG, "mRawPictureAndJpegPictureCallbackTime = "
622                    + mRawPictureAndJpegPictureCallbackTime + "ms");
623            mImageCapture.storeImage(jpegData, camera, mLocation);
624
625            if (!mIsImageCaptureIntent) {
626                long delay = 1200 - (
627                        System.currentTimeMillis() - mRawPictureCallbackTime);
628                mHandler.sendEmptyMessageDelayed(
629                        RESTART_PREVIEW, Math.max(delay, 0));
630            }
631        }
632    }
633
634    private final class AutoFocusCallback
635            implements android.hardware.Camera.AutoFocusCallback {
636        public void onAutoFocus(
637                boolean focused, android.hardware.Camera camera) {
638            mFocusCallbackTime = System.currentTimeMillis();
639            mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
640            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
641            if (mFocusState == FOCUSING_SNAP_ON_FINISH) {
642                // Take the picture no matter focus succeeds or fails. No need
643                // to play the AF sound if we're about to play the shutter
644                // sound.
645                if (focused) {
646                    mFocusState = FOCUS_SUCCESS;
647                } else {
648                    mFocusState = FOCUS_FAIL;
649                }
650                mImageCapture.onSnap();
651            } else if (mFocusState == FOCUSING) {
652                // User is half-pressing the focus key. Play the focus tone.
653                // Do not take the picture now.
654                ToneGenerator tg = mFocusToneGenerator;
655                if (tg != null) {
656                    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
657                }
658                if (focused) {
659                    mFocusState = FOCUS_SUCCESS;
660                } else {
661                    mFocusState = FOCUS_FAIL;
662                }
663            } else if (mFocusState == FOCUS_NOT_STARTED) {
664                // User has released the focus key before focus completes.
665                // Do nothing.
666            }
667            updateFocusIndicator();
668        }
669    }
670
671    private final class ErrorCallback
672        implements android.hardware.Camera.ErrorCallback {
673        public void onError(int error, android.hardware.Camera camera) {
674            if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
675                 mMediaServerDied = true;
676                 Log.v(TAG, "media server died");
677            }
678        }
679    }
680
681    private final class ZoomCallback
682        implements android.hardware.Camera.ZoomCallback {
683        public void onZoomUpdate(int zoomLevel,
684                                 android.hardware.Camera camera) {
685            mZoomValue = (double) zoomLevel / 1000;
686            Log.v(TAG, "ZoomCallback: zoom level=" + zoomLevel);
687            updateZoomButtonsEnabled();
688        }
689    }
690
691    private class ImageCapture {
692
693        private boolean mCancel = false;
694
695        private Uri mLastContentUri;
696
697        byte[] mCaptureOnlyData;
698
699        // Returns the rotation degree in the jpeg header.
700        private int storeImage(byte[] data, Location loc) {
701            try {
702                long dateTaken = System.currentTimeMillis();
703                String name = createName(dateTaken) + ".jpg";
704                int[] degree = new int[1];
705                mLastContentUri = ImageManager.addImage(
706                        mContentResolver,
707                        name,
708                        dateTaken,
709                        loc, // location from gps/network
710                        ImageManager.CAMERA_IMAGE_BUCKET_NAME, name,
711                        null, data,
712                        degree);
713                if (mLastContentUri == null) {
714                    // this means we got an error
715                    mCancel = true;
716                }
717                if (!mCancel) {
718                    ImageManager.setImageSize(mContentResolver, mLastContentUri,
719                            new File(ImageManager.CAMERA_IMAGE_BUCKET_NAME,
720                            name).length());
721                }
722                return degree[0];
723            } catch (Exception ex) {
724                Log.e(TAG, "Exception while compressing image.", ex);
725                return 0;
726            }
727        }
728
729        public void storeImage(final byte[] data,
730                android.hardware.Camera camera, Location loc) {
731            if (!mIsImageCaptureIntent) {
732                int degree = storeImage(data, loc);
733                sendBroadcast(new Intent(
734                        "com.android.camera.NEW_PICTURE", mLastContentUri));
735                setLastPictureThumb(data, degree,
736                        mImageCapture.getLastCaptureUri());
737                mThumbController.updateDisplayIfNeeded();
738            } else {
739                mCaptureOnlyData = data;
740                showPostCaptureAlert();
741            }
742        }
743
744        /**
745         * Initiate the capture of an image.
746         */
747        public void initiate() {
748            if (mCameraDevice == null) {
749                return;
750            }
751
752            mCancel = false;
753
754            capture();
755        }
756
757        public Uri getLastCaptureUri() {
758            return mLastContentUri;
759        }
760
761        public byte[] getLastCaptureData() {
762            return mCaptureOnlyData;
763        }
764
765        private void capture() {
766            mCaptureOnlyData = null;
767
768            // Set rotation.
769            int orientation = mLastOrientation;
770            if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
771                orientation += 90;
772            }
773            orientation = ImageManager.roundOrientation(orientation);
774            Log.v(TAG, "mLastOrientation = " + mLastOrientation
775                    + ", orientation = " + orientation);
776            mParameters.setRotation(orientation);
777
778            // Clear previous GPS location from the parameters.
779            mParameters.removeGpsData();
780
781            // Set GPS location.
782            Location loc = mRecordLocation ? getCurrentLocation() : null;
783            if (loc != null) {
784                double lat = loc.getLatitude();
785                double lon = loc.getLongitude();
786                boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
787
788                if (hasLatLon) {
789                    mParameters.setGpsLatitude(lat);
790                    mParameters.setGpsLongitude(lon);
791                    if (loc.hasAltitude()) {
792                        mParameters.setGpsAltitude(loc.getAltitude());
793                    } else {
794                        // for NETWORK_PROVIDER location provider, we may have
795                        // no altitude information, but the driver needs it, so
796                        // we fake one.
797                        mParameters.setGpsAltitude(0);
798                    }
799                    if (loc.getTime() != 0) {
800                        // Location.getTime() is UTC in milliseconds.
801                        // gps-timestamp is UTC in seconds.
802                        long utcTimeSeconds = loc.getTime() / 1000;
803                        mParameters.setGpsTimestamp(utcTimeSeconds);
804                    }
805                } else {
806                    loc = null;
807                }
808            }
809
810            mCameraDevice.setParameters(mParameters);
811
812            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
813                    new JpegPictureCallback(loc));
814            mPreviewing = false;
815        }
816
817        public void onSnap() {
818            // If we are already in the middle of taking a snapshot then ignore.
819            if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS) {
820                return;
821            }
822            mCaptureStartTime = System.currentTimeMillis();
823
824            // Don't check the filesystem here, we can't afford the latency.
825            // Instead, check the cached value which was calculated when the
826            // preview was restarted.
827            if (mPicturesRemaining < 1) {
828                updateStorageHint(mPicturesRemaining);
829                return;
830            }
831
832            mStatus = SNAPSHOT_IN_PROGRESS;
833
834            mImageCapture.initiate();
835        }
836
837        private void clearLastData() {
838            mCaptureOnlyData = null;
839        }
840    }
841
842    public boolean saveDataToFile(String filePath, byte[] data) {
843        FileOutputStream f = null;
844        try {
845            f = new FileOutputStream(filePath);
846            f.write(data);
847        } catch (IOException e) {
848            return false;
849        } finally {
850            MenuHelper.closeSilently(f);
851        }
852        return true;
853    }
854
855    private void setLastPictureThumb(byte[] data, int degree, Uri uri) {
856        BitmapFactory.Options options = new BitmapFactory.Options();
857        options.inSampleSize = 16;
858        Bitmap lastPictureThumb =
859                BitmapFactory.decodeByteArray(data, 0, data.length, options);
860        lastPictureThumb = Util.rotate(lastPictureThumb, degree);
861        mThumbController.setData(uri, lastPictureThumb);
862    }
863
864    private static String createName(long dateTaken) {
865        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
866    }
867
868    @Override
869    public void onCreate(Bundle icicle) {
870        super.onCreate(icicle);
871
872        Window win = getWindow();
873        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
874        setContentView(R.layout.camera);
875        mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview);
876
877        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
878
879        /*
880         * To reduce startup time, we start the preview in another thread.
881         * We make sure the preview is started at the end of onCreate.
882         */
883        Thread startPreviewThread = new Thread(new Runnable() {
884            public void run() {
885                try {
886                    mStartPreviewFail = false;
887                    startPreview();
888                } catch (CameraHardwareException e) {
889                    // In eng build, we throw the exception so that test tool
890                    // can detect it and report it
891                    if ("eng".equals(Build.TYPE)) {
892                        throw new RuntimeException(e);
893                    }
894                    mStartPreviewFail = true;
895                }
896            }
897        });
898        startPreviewThread.start();
899
900        // don't set mSurfaceHolder here. We have it set ONLY within
901        // surfaceChanged / surfaceDestroyed, other parts of the code
902        // assume that when it is set, the surface is also set.
903        SurfaceHolder holder = mSurfaceView.getHolder();
904        holder.addCallback(this);
905        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
906
907        mIsImageCaptureIntent = isImageCaptureIntent();
908        if (mIsImageCaptureIntent) {
909            setupCaptureParams();
910        }
911
912        LayoutInflater inflater = getLayoutInflater();
913
914        ViewGroup rootView = (ViewGroup) findViewById(R.id.camera);
915        if (mIsImageCaptureIntent) {
916            View controlBar = inflater.inflate(
917                    R.layout.attach_camera_control, rootView);
918            controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this);
919            controlBar.findViewById(R.id.btn_retake).setOnClickListener(this);
920            controlBar.findViewById(R.id.btn_done).setOnClickListener(this);
921        } else {
922            inflater.inflate(R.layout.camera_control, rootView);
923            mSwitcher = ((Switcher) findViewById(R.id.camera_switch));
924            mSwitcher.setOnSwitchListener(this);
925            mSwitcher.addTouchView(findViewById(R.id.camera_switch_set));
926        }
927        findViewById(R.id.btn_gripper)
928                .setOnTouchListener(new GripperTouchListener());
929
930        mFlashIndicator = (IconIndicator) findViewById(R.id.flash_icon);
931        mFocusIndicator = (IconIndicator) findViewById(R.id.focus_icon);
932        mSceneModeIndicator = (IconIndicator) findViewById(R.id.scenemode_icon);
933        mWhitebalanceIndicator =
934                (IconIndicator) findViewById(R.id.whitebalance_icon);
935
936        // Make sure preview is started.
937        try {
938            startPreviewThread.join();
939            if (mStartPreviewFail) {
940                showCameraErrorAndFinish();
941                return;
942            }
943        } catch (InterruptedException ex) {
944            // ignore
945        }
946    }
947
948    private class GripperTouchListener implements View.OnTouchListener {
949        public boolean onTouch(View view, MotionEvent event) {
950            switch (event.getAction()) {
951                case MotionEvent.ACTION_DOWN:
952                    return true;
953                case MotionEvent.ACTION_UP:
954                    showOnScreenSettings();
955                    return true;
956            }
957            return false;
958        }
959    }
960
961    @Override
962    public void onStart() {
963        super.onStart();
964        if (!mIsImageCaptureIntent) {
965            mSwitcher.setSwitch(SWITCH_CAMERA);
966        }
967    }
968
969    private void checkStorage() {
970        if (ImageManager.isMediaScannerScanning(getContentResolver())) {
971            mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR;
972        } else {
973            calculatePicturesRemaining();
974        }
975        updateStorageHint(mPicturesRemaining);
976    }
977
978
979    private void showOnScreenSettings() {
980        if (mSettings == null) {
981            mSettings = new OnScreenSettings(
982                    findViewById(R.id.camera_preview));
983            CameraSettings helper =
984                    new CameraSettings(this, mInitialParameters);
985            mSettings.setPreferenceScreen(helper
986                    .getPreferenceScreen(R.xml.camera_preferences));
987            mSettings.setOnVisibilityChangedListener(this);
988
989            String sceneMode = mParameters.getSceneMode();
990            if (sceneMode == null
991                    || Parameters.SCENE_MODE_AUTO.equals(sceneMode)) {
992                // If scene mode is auto, cancel override in settings
993                mSettings.overrideSettings(CameraSettings.KEY_FLASH_MODE, null);
994                mSettings.overrideSettings(CameraSettings.KEY_FOCUS_MODE, null);
995                mSettings.overrideSettings(
996                        CameraSettings.KEY_WHITE_BALANCE, null);
997            } else {
998                // If scene mode is not auto, override the value in settings
999                mSettings.overrideSettings(CameraSettings.KEY_FLASH_MODE,
1000                        mParameters.getFlashMode());
1001                mSettings.overrideSettings(CameraSettings.KEY_FOCUS_MODE,
1002                        mParameters.getFocusMode());
1003                mSettings.overrideSettings(CameraSettings.KEY_WHITE_BALANCE,
1004                        mParameters.getWhiteBalance());
1005            }
1006        }
1007
1008        mSettings.setVisible(true);
1009    }
1010
1011    public void onClick(View v) {
1012        switch (v.getId()) {
1013            case R.id.btn_retake:
1014                hidePostCaptureAlert();
1015                restartPreview();
1016                break;
1017            case R.id.review_thumbnail:
1018                if (isCameraIdle()) {
1019                    viewLastImage();
1020                }
1021                break;
1022            case R.id.btn_done:
1023                doAttach();
1024                break;
1025            case R.id.btn_cancel:
1026                doCancel();
1027        }
1028    }
1029
1030    private Bitmap createCaptureBitmap(byte[] data) {
1031        // This is really stupid...we just want to read the orientation in
1032        // the jpeg header.
1033        String filepath = ImageManager.getTempJpegPath();
1034        int degree = 0;
1035        if (saveDataToFile(filepath, data)) {
1036            degree = ImageManager.getExifOrientation(filepath);
1037            new File(filepath).delete();
1038        }
1039
1040        // Limit to 50k pixels so we can return it in the intent.
1041        Bitmap bitmap = Util.makeBitmap(data, 50*1024);
1042        bitmap = Util.rotate(bitmap, degree);
1043        return bitmap;
1044    }
1045
1046    private void doAttach() {
1047        if (mPausing) {
1048            return;
1049        }
1050
1051        byte[] data = mImageCapture.getLastCaptureData();
1052
1053        if (mCropValue == null) {
1054            // First handle the no crop case -- just return the value.  If the
1055            // caller specifies a "save uri" then write the data to it's
1056            // stream. Otherwise, pass back a scaled down version of the bitmap
1057            // directly in the extras.
1058            if (mSaveUri != null) {
1059                OutputStream outputStream = null;
1060                try {
1061                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1062                    outputStream.write(data);
1063                    outputStream.close();
1064
1065                    setResult(RESULT_OK);
1066                    finish();
1067                } catch (IOException ex) {
1068                    // ignore exception
1069                } finally {
1070                    Util.closeSilently(outputStream);
1071                }
1072            } else {
1073                Bitmap bitmap = createCaptureBitmap(data);
1074                setResult(RESULT_OK,
1075                        new Intent("inline-data").putExtra("data", bitmap));
1076                finish();
1077            }
1078        } else {
1079            // Save the image to a temp file and invoke the cropper
1080            Uri tempUri = null;
1081            FileOutputStream tempStream = null;
1082            try {
1083                File path = getFileStreamPath(sTempCropFilename);
1084                path.delete();
1085                tempStream = openFileOutput(sTempCropFilename, 0);
1086                tempStream.write(data);
1087                tempStream.close();
1088                tempUri = Uri.fromFile(path);
1089            } catch (FileNotFoundException ex) {
1090                setResult(Activity.RESULT_CANCELED);
1091                finish();
1092                return;
1093            } catch (IOException ex) {
1094                setResult(Activity.RESULT_CANCELED);
1095                finish();
1096                return;
1097            } finally {
1098                Util.closeSilently(tempStream);
1099            }
1100
1101            Bundle newExtras = new Bundle();
1102            if (mCropValue.equals("circle")) {
1103                newExtras.putString("circleCrop", "true");
1104            }
1105            if (mSaveUri != null) {
1106                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1107            } else {
1108                newExtras.putBoolean("return-data", true);
1109            }
1110
1111            Intent cropIntent = new Intent();
1112            cropIntent.setClass(this, CropImage.class);
1113            cropIntent.setData(tempUri);
1114            cropIntent.putExtras(newExtras);
1115
1116            startActivityForResult(cropIntent, CROP_MSG);
1117        }
1118    }
1119
1120    private void doCancel() {
1121        setResult(RESULT_CANCELED, new Intent());
1122        finish();
1123    }
1124
1125    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
1126        if (mPausing) {
1127            return;
1128        }
1129        switch (button.getId()) {
1130            case R.id.shutter_button:
1131                doFocus(pressed);
1132                break;
1133        }
1134    }
1135
1136    public void onShutterButtonClick(ShutterButton button) {
1137        if (mPausing) {
1138            return;
1139        }
1140        switch (button.getId()) {
1141            case R.id.shutter_button:
1142                doSnap();
1143                break;
1144        }
1145    }
1146
1147    private OnScreenHint mStorageHint;
1148
1149    private void updateStorageHint(int remaining) {
1150        String noStorageText = null;
1151
1152        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
1153            String state = Environment.getExternalStorageState();
1154            if (state == Environment.MEDIA_CHECKING ||
1155                    ImageManager.isMediaScannerScanning(getContentResolver())) {
1156                noStorageText = getString(R.string.preparing_sd);
1157            } else {
1158                noStorageText = getString(R.string.no_storage);
1159            }
1160        } else if (remaining < 1) {
1161            noStorageText = getString(R.string.not_enough_space);
1162        }
1163
1164        if (noStorageText != null) {
1165            if (mStorageHint == null) {
1166                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1167            } else {
1168                mStorageHint.setText(noStorageText);
1169            }
1170            mStorageHint.show();
1171        } else if (mStorageHint != null) {
1172            mStorageHint.cancel();
1173            mStorageHint = null;
1174        }
1175    }
1176
1177    private void installIntentFilter() {
1178        // install an intent filter to receive SD card related events.
1179        IntentFilter intentFilter =
1180                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1181        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1182        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
1183        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1184        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1185        intentFilter.addDataScheme("file");
1186        registerReceiver(mReceiver, intentFilter);
1187        mDidRegister = true;
1188    }
1189
1190    private void initializeFocusTone() {
1191        // Initialize focus tone generator.
1192        try {
1193            mFocusToneGenerator = new ToneGenerator(
1194                    AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1195        } catch (Throwable ex) {
1196            Log.w(TAG, "Exception caught while creating tone generator: ", ex);
1197            mFocusToneGenerator = null;
1198        }
1199    }
1200
1201    private void readPreference() {
1202        mRecordLocation = mPreferences.getBoolean(
1203                "pref_camera_recordlocation_key", false);
1204        mFocusMode = mPreferences.getString(
1205                CameraSettings.KEY_FOCUS_MODE,
1206                getString(R.string.pref_camera_focusmode_default));
1207    }
1208
1209    @Override
1210    public void onResume() {
1211        super.onResume();
1212
1213        mPausing = false;
1214        mJpegPictureCallbackTime = 0;
1215        mImageCapture = new ImageCapture();
1216
1217        // Start the preview if it is not started.
1218        if (!mPreviewing && !mStartPreviewFail) {
1219            try {
1220                startPreview();
1221            } catch (CameraHardwareException e) {
1222                showCameraErrorAndFinish();
1223                return;
1224            }
1225        }
1226
1227        if (mSurfaceHolder != null) {
1228            // If first time initialization is not finished, put it in the
1229            // message queue.
1230            if (!mFirstTimeInitialized) {
1231                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1232            } else {
1233                initializeSecondTime();
1234            }
1235        }
1236        keepScreenOnAwhile();
1237    }
1238
1239    private static ImageManager.DataLocation dataLocation() {
1240        return ImageManager.DataLocation.EXTERNAL;
1241    }
1242
1243    @Override
1244    protected void onPause() {
1245        mPausing = true;
1246        stopPreview();
1247        // Close the camera now because other activities may need to use it.
1248        closeCamera();
1249        resetScreenOn();
1250
1251        if (mSettings != null && mSettings.isVisible()) {
1252            mSettings.setVisible(false);
1253        }
1254
1255        if (mFirstTimeInitialized) {
1256            mOrientationListener.disable();
1257            mGpsIndicator.setMode(GPS_MODE_OFF);
1258            if (!mIsImageCaptureIntent) {
1259                mThumbController.storeData(
1260                        ImageManager.getLastImageThumbPath());
1261            }
1262            hidePostCaptureAlert();
1263        }
1264
1265        if (mDidRegister) {
1266            unregisterReceiver(mReceiver);
1267            mDidRegister = false;
1268        }
1269        stopReceivingLocationUpdates();
1270
1271        if (mFocusToneGenerator != null) {
1272            mFocusToneGenerator.release();
1273            mFocusToneGenerator = null;
1274        }
1275
1276        if (mStorageHint != null) {
1277            mStorageHint.cancel();
1278            mStorageHint = null;
1279        }
1280
1281        // If we are in an image capture intent and has taken
1282        // a picture, we just clear it in onPause.
1283        mImageCapture.clearLastData();
1284        mImageCapture = null;
1285
1286        // This is necessary to make the ZoomButtonsController unregister
1287        // its configuration change receiver.
1288        if (mZoomButtons != null) {
1289            mZoomButtons.setVisible(false);
1290        }
1291
1292        // Remove the messages in the event queue.
1293        mHandler.removeMessages(RESTART_PREVIEW);
1294        mHandler.removeMessages(FIRST_TIME_INIT);
1295
1296        super.onPause();
1297    }
1298
1299    @Override
1300    protected void onActivityResult(
1301            int requestCode, int resultCode, Intent data) {
1302        switch (requestCode) {
1303            case CROP_MSG: {
1304                Intent intent = new Intent();
1305                if (data != null) {
1306                    Bundle extras = data.getExtras();
1307                    if (extras != null) {
1308                        intent.putExtras(extras);
1309                    }
1310                }
1311                setResult(resultCode, intent);
1312                finish();
1313
1314                File path = getFileStreamPath(sTempCropFilename);
1315                path.delete();
1316
1317                break;
1318            }
1319        }
1320    }
1321
1322    private boolean canTakePicture() {
1323        return isCameraIdle() && mPreviewing && (mPicturesRemaining > 0);
1324    }
1325
1326    private void autoFocus() {
1327        // Initiate autofocus only when preview is started and snapshot is not
1328        // in progress.
1329        if (canTakePicture()) {
1330            Log.v(TAG, "Start autofocus.");
1331            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1332            mFocusStartTime = System.currentTimeMillis();
1333            mFocusState = FOCUSING;
1334            updateFocusIndicator();
1335            mCameraDevice.autoFocus(mAutoFocusCallback);
1336        }
1337    }
1338
1339    private void cancelAutoFocus() {
1340        // User releases half-pressed focus key.
1341        if (mFocusState == FOCUSING || mFocusState == FOCUS_SUCCESS
1342                || mFocusState == FOCUS_FAIL) {
1343            Log.v(TAG, "Cancel autofocus.");
1344            mCameraDevice.cancelAutoFocus();
1345        }
1346        if (mFocusState != FOCUSING_SNAP_ON_FINISH) {
1347            clearFocusState();
1348        }
1349    }
1350
1351    private void clearFocusState() {
1352        mFocusState = FOCUS_NOT_STARTED;
1353        updateFocusIndicator();
1354    }
1355
1356    private void updateFocusIndicator() {
1357        if (mFocusRectangle == null) return;
1358
1359        if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) {
1360            mFocusRectangle.showStart();
1361        } else if (mFocusState == FOCUS_SUCCESS) {
1362            mFocusRectangle.showSuccess();
1363        } else if (mFocusState == FOCUS_FAIL) {
1364            mFocusRectangle.showFail();
1365        } else {
1366            mFocusRectangle.clear();
1367        }
1368    }
1369
1370    @Override
1371    public void onBackPressed() {
1372        if (!isCameraIdle()) {
1373            // ignore backs while we're taking a picture
1374            return;
1375        }
1376        super.onBackPressed();
1377    }
1378
1379    @Override
1380    public boolean onKeyDown(int keyCode, KeyEvent event) {
1381        switch (keyCode) {
1382            case KeyEvent.KEYCODE_FOCUS:
1383                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1384                    doFocus(true);
1385                }
1386                return true;
1387            case KeyEvent.KEYCODE_CAMERA:
1388                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1389                    doSnap();
1390                }
1391                return true;
1392            case KeyEvent.KEYCODE_DPAD_CENTER:
1393                // If we get a dpad center event without any focused view, move
1394                // the focus to the shutter button and press it.
1395                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1396                    // Start auto-focus immediately to reduce shutter lag. After
1397                    // the shutter button gets the focus, doFocus() will be
1398                    // called again but it is fine.
1399                    doFocus(true);
1400                    if (mShutterButton.isInTouchMode()) {
1401                        mShutterButton.requestFocusFromTouch();
1402                    } else {
1403                        mShutterButton.requestFocus();
1404                    }
1405                    mShutterButton.setPressed(true);
1406                }
1407                return true;
1408        }
1409
1410        return super.onKeyDown(keyCode, event);
1411    }
1412
1413    @Override
1414    public boolean onKeyUp(int keyCode, KeyEvent event) {
1415        switch (keyCode) {
1416            case KeyEvent.KEYCODE_FOCUS:
1417                if (mFirstTimeInitialized) {
1418                    doFocus(false);
1419                }
1420                return true;
1421            case KeyEvent.KEYCODE_MENU:
1422                if (mIsImageCaptureIntent) {
1423                    showOnScreenSettings();
1424                    return true;
1425                }
1426                break;
1427        }
1428        return super.onKeyUp(keyCode, event);
1429    }
1430
1431    private void doSnap() {
1432        Log.v(TAG, "doSnap: mFocusState=" + mFocusState);
1433        // If the user has half-pressed the shutter and focus is completed, we
1434        // can take the photo right away. If the focus mode is infinity, we can
1435        // also take the photo.
1436        if (mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)
1437                || (mFocusState == FOCUS_SUCCESS
1438                || mFocusState == FOCUS_FAIL)) {
1439            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1440            mImageCapture.onSnap();
1441        } else if (mFocusState == FOCUSING) {
1442            // Half pressing the shutter (i.e. the focus button event) will
1443            // already have requested AF for us, so just request capture on
1444            // focus here.
1445            mFocusState = FOCUSING_SNAP_ON_FINISH;
1446        } else if (mFocusState == FOCUS_NOT_STARTED) {
1447            // Focus key down event is dropped for some reasons. Just ignore.
1448        }
1449    }
1450
1451    private void doFocus(boolean pressed) {
1452        // Do the focus if the mode is not infinity.
1453        if (!mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)) {
1454            if (pressed) {  // Focus key down.
1455                autoFocus();
1456            } else {  // Focus key up.
1457                cancelAutoFocus();
1458            }
1459        }
1460    }
1461
1462    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1463        // Make sure we have a surface in the holder before proceeding.
1464        if (holder.getSurface() == null) {
1465            Log.d(TAG, "holder.getSurface() == null");
1466            return;
1467        }
1468
1469        // The mCameraDevice will be null if it fails to connect to the camera
1470        // hardware. In this case we will show a dialog and then finish the
1471        // activity, so it's OK to ignore it.
1472        if (mCameraDevice == null) return;
1473
1474        mSurfaceHolder = holder;
1475
1476        // Sometimes surfaceChanged is called after onPause. Ignore it.
1477        if (mPausing || isFinishing()) return;
1478
1479        // Set preview display if the surface is being created. Preview was
1480        // already started.
1481        if (holder.isCreating()) {
1482            setPreviewDisplay(holder);
1483        }
1484
1485        // If first time initialization is not finished, send a message to do
1486        // it later. We want to finish surfaceChanged as soon as possible to let
1487        // user see preview first.
1488        if (!mFirstTimeInitialized) {
1489            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1490        } else {
1491            initializeSecondTime();
1492        }
1493    }
1494
1495    public void surfaceCreated(SurfaceHolder holder) {
1496    }
1497
1498    public void surfaceDestroyed(SurfaceHolder holder) {
1499        stopPreview();
1500        mSurfaceHolder = null;
1501    }
1502
1503    private void closeCamera() {
1504        if (mCameraDevice != null) {
1505            CameraHolder.instance().release();
1506            mCameraDevice = null;
1507            mPreviewing = false;
1508        }
1509    }
1510
1511    private void ensureCameraDevice() throws CameraHardwareException {
1512        if (mCameraDevice == null) {
1513            mCameraDevice = CameraHolder.instance().open();
1514            mInitialParameters = mCameraDevice.getParameters();
1515        }
1516    }
1517
1518    private void updateLastImage() {
1519        IImageList list = ImageManager.makeImageList(
1520            mContentResolver,
1521            dataLocation(),
1522            ImageManager.INCLUDE_IMAGES,
1523            ImageManager.SORT_ASCENDING,
1524            ImageManager.CAMERA_IMAGE_BUCKET_ID);
1525        int count = list.getCount();
1526        if (count > 0) {
1527            IImage image = list.getImageAt(count - 1);
1528            Uri uri = image.fullSizeImageUri();
1529            mThumbController.setData(uri, image.miniThumbBitmap());
1530        } else {
1531            mThumbController.setData(null, null);
1532        }
1533        list.close();
1534    }
1535
1536    private void showCameraErrorAndFinish() {
1537        Resources ress = getResources();
1538        Util.showFatalErrorAndFinish(Camera.this,
1539                ress.getString(R.string.camera_error_title),
1540                ress.getString(R.string.cannot_connect_camera));
1541    }
1542
1543    private void restartPreview() {
1544        // make sure the surfaceview fills the whole screen when previewing
1545        try {
1546            startPreview();
1547        } catch (CameraHardwareException e) {
1548            showCameraErrorAndFinish();
1549            return;
1550        }
1551
1552        // Calculate this in advance of each shot so we don't add to shutter
1553        // latency. It's true that someone else could write to the SD card in
1554        // the mean time and fill it, but that could have happened between the
1555        // shutter press and saving the JPEG too.
1556        calculatePicturesRemaining();
1557    }
1558
1559    private void setPreviewDisplay(SurfaceHolder holder) {
1560        try {
1561            mCameraDevice.setPreviewDisplay(holder);
1562        } catch (Throwable ex) {
1563            closeCamera();
1564            throw new RuntimeException("setPreviewDisplay failed", ex);
1565        }
1566    }
1567
1568    private void startPreview() throws CameraHardwareException {
1569        if (mPausing || isFinishing()) return;
1570
1571        ensureCameraDevice();
1572
1573        // If we're previewing already, stop the preview first (this will blank
1574        // the screen).
1575        if (mPreviewing) stopPreview();
1576
1577        setPreviewDisplay(mSurfaceHolder);
1578        setCameraParameters();
1579
1580        final long wallTimeStart = SystemClock.elapsedRealtime();
1581        final long threadTimeStart = Debug.threadCpuTimeNanos();
1582
1583        // Set one shot preview callback for latency measurement.
1584        mCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
1585        mCameraDevice.setErrorCallback(mErrorCallback);
1586
1587        try {
1588            Log.v(TAG, "startPreview");
1589            mCameraDevice.startPreview();
1590        } catch (Throwable ex) {
1591            closeCamera();
1592            throw new RuntimeException("startPreview failed", ex);
1593        }
1594        mPreviewing = true;
1595        mStatus = IDLE;
1596
1597        long threadTimeEnd = Debug.threadCpuTimeNanos();
1598        long wallTimeEnd = SystemClock.elapsedRealtime();
1599        if ((wallTimeEnd - wallTimeStart) > 3000) {
1600            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart)
1601                    + " ms. Thread time was"
1602                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
1603        }
1604    }
1605
1606    private void stopPreview() {
1607        if (mCameraDevice != null && mPreviewing) {
1608            Log.v(TAG, "stopPreview");
1609            mCameraDevice.stopPreview();
1610        }
1611        mPreviewing = false;
1612        // If auto focus was in progress, it would have been canceled.
1613        clearFocusState();
1614    }
1615
1616    private Size getOptimalPreviewSize(List<Size> sizes, double targetRatio) {
1617        final double ASPECT_TOLERANCE = 0.05;
1618        if (sizes == null) return null;
1619
1620        Size optimalSize = null;
1621        double minDiff = Double.MAX_VALUE;
1622
1623        // Because of bugs of overlay and layout, we sometimes will try to
1624        // layout the viewfinder in the portrait orientation and thus get the
1625        // wrong size of mSurfaceView. When we change the preview size, the
1626        // new overlay will be created before the old one closed, which causes
1627        // an exception. For now, just get the screen size
1628
1629        Display display = getWindowManager().getDefaultDisplay();
1630        int targetHeight = Math.min(display.getHeight(), display.getWidth());
1631
1632        if (targetHeight <= 0) {
1633            // We don't know the size of SurefaceView, use screen height
1634            WindowManager windowManager = (WindowManager)
1635                    getSystemService(Context.WINDOW_SERVICE);
1636            targetHeight = windowManager.getDefaultDisplay().getHeight();
1637        }
1638
1639        // Try to find an size match aspect ratio and size
1640        for (Size size : sizes) {
1641            double ratio = (double) size.width / size.height;
1642            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
1643            if (Math.abs(size.height - targetHeight) < minDiff) {
1644                optimalSize = size;
1645                minDiff = Math.abs(size.height - targetHeight);
1646            }
1647        }
1648
1649        // Cannot find the one match the aspect ratio, ignore the requirement
1650        if (optimalSize == null) {
1651            Log.v(TAG, "No preview size match the aspect ratio");
1652            minDiff = Double.MAX_VALUE;
1653            for (Size size : sizes) {
1654                if (Math.abs(size.height - targetHeight) < minDiff) {
1655                    optimalSize = size;
1656                    minDiff = Math.abs(size.height - targetHeight);
1657                }
1658            }
1659        }
1660        Log.v(TAG, String.format(
1661                "Optimal preview size is %sx%s",
1662                optimalSize.width, optimalSize.height));
1663        return optimalSize;
1664    }
1665
1666    private static boolean isSupported(String value, List<String> supported) {
1667        return supported == null ? false : supported.indexOf(value) >= 0;
1668    }
1669
1670    private void setCameraParameters() {
1671        mParameters = mCameraDevice.getParameters();
1672
1673        // Reset preview frame rate to the maximum because it may be lowered by
1674        // video camera application.
1675        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1676        if (frameRates != null) {
1677            Integer max = Collections.max(frameRates);
1678            mParameters.setPreviewFrameRate(max);
1679        }
1680
1681        // Set picture size.
1682        String pictureSize = mPreferences.getString(
1683                CameraSettings.KEY_PICTURE_SIZE, null);
1684        if (pictureSize == null) {
1685            CameraSettings.initialCameraPictureSize(this, mParameters);
1686        } else {
1687            List<Size> supported = mParameters.getSupportedPictureSizes();
1688            CameraSettings.setCameraPictureSize(
1689                    pictureSize, supported, mParameters);
1690        }
1691
1692        // Set the preview frame aspect ratio according to the picture size.
1693        Size size = mParameters.getPictureSize();
1694        PreviewFrameLayout frameLayout =
1695                (PreviewFrameLayout) findViewById(R.id.frame_layout);
1696        frameLayout.setAspectRatio((double) size.width / size.height);
1697
1698        // Set a preview size that is closest to the viewfinder height and has
1699        // the right aspect ratio.
1700        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1701        Size optimalSize = getOptimalPreviewSize(
1702                sizes, (double) size.width / size.height);
1703        if (optimalSize != null) {
1704            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1705        }
1706
1707        // Set JPEG quality.
1708        String jpegQuality = mPreferences.getString(
1709                CameraSettings.KEY_JPEG_QUALITY,
1710                getString(R.string.pref_camera_jpegquality_default));
1711        mParameters.setJpegQuality(Integer.parseInt(jpegQuality));
1712
1713        // For the following settings, we need to check if the settings are
1714        // still supported by latest driver, if not, ignore the settings.
1715
1716        // Set color effect parameter.
1717        String colorEffect = mPreferences.getString(
1718                CameraSettings.KEY_COLOR_EFFECT,
1719                getString(R.string.pref_camera_coloreffect_default));
1720        if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
1721            mParameters.setColorEffect(colorEffect);
1722        }
1723
1724        // Set scene mode.
1725        String sceneMode = mPreferences.getString(
1726                CameraSettings.KEY_SCENE_MODE,
1727                getString(R.string.pref_camera_scenemode_default));
1728        if (isSupported(sceneMode, mParameters.getSupportedSceneModes())) {
1729            mParameters.setSceneMode(sceneMode);
1730        } else {
1731            sceneMode = mParameters.getSceneMode();
1732            if (sceneMode == null) {
1733                sceneMode = Parameters.SCENE_MODE_AUTO;
1734            }
1735        }
1736
1737        // If scene mode is set, we cannot set flash mode, white balance, and
1738        // focus mode, instead, we read it from driver
1739        String flashMode;
1740        String whiteBalance;
1741
1742        if (!Parameters.SCENE_MODE_AUTO.equals(sceneMode)) {
1743            mCameraDevice.setParameters(mParameters);
1744
1745            // Setting scene mode will change the settings of flash mode, white
1746            // balance, and focus mode. So read back here, so that we know
1747            // what's the settings
1748            mParameters = mCameraDevice.getParameters();
1749            flashMode = mParameters.getFlashMode();
1750            whiteBalance = mParameters.getWhiteBalance();
1751            mFocusMode = mParameters.getFocusMode();
1752            if (mSettings != null) {
1753                mSettings.overrideSettings(
1754                        CameraSettings.KEY_FLASH_MODE, flashMode);
1755                mSettings.overrideSettings(
1756                        CameraSettings.KEY_WHITE_BALANCE, whiteBalance);
1757                mSettings.overrideSettings(
1758                        CameraSettings.KEY_FOCUS_MODE, mFocusMode);
1759            }
1760        } else {
1761            if (mSettings != null) {
1762                mSettings.overrideSettings(CameraSettings.KEY_FLASH_MODE, null);
1763                mSettings.overrideSettings(CameraSettings.KEY_FOCUS_MODE, null);
1764                mSettings.overrideSettings(
1765                        CameraSettings.KEY_WHITE_BALANCE, null);
1766            }
1767
1768            // Set flash mode.
1769            flashMode = mPreferences.getString(
1770                    CameraSettings.KEY_FLASH_MODE,
1771                    getString(R.string.pref_camera_flashmode_default));
1772            List<String> supportedFlash = mParameters.getSupportedFlashModes();
1773            if (isSupported(flashMode, supportedFlash)) {
1774                mParameters.setFlashMode(flashMode);
1775            } else {
1776                flashMode = mParameters.getFlashMode();
1777                if (flashMode == null) {
1778                    flashMode = Parameters.FLASH_MODE_OFF;
1779                }
1780            }
1781
1782            // Set white balance parameter.
1783            whiteBalance = mPreferences.getString(
1784                    CameraSettings.KEY_WHITE_BALANCE,
1785                    getString(R.string.pref_camera_whitebalance_default));
1786            if (isSupported(whiteBalance, mParameters.getSupportedWhiteBalance())) {
1787                mParameters.setWhiteBalance(whiteBalance);
1788            } else {
1789                whiteBalance = mParameters.getWhiteBalance();
1790                if (whiteBalance == null) {
1791                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
1792                }
1793            }
1794
1795            // Set focus mode.
1796            mFocusMode = mPreferences.getString(
1797                    CameraSettings.KEY_FOCUS_MODE,
1798                    getString(R.string.pref_camera_focusmode_default));
1799            if (isSupported(mFocusMode, mParameters.getSupportedFocusModes())) {
1800                mParameters.setFocusMode(mFocusMode);
1801            } else {
1802                mFocusMode = mParameters.getFocusMode();
1803                if (mFocusMode == null) {
1804                    mFocusMode = Parameters.FOCUS_MODE_AUTO;
1805                }
1806            }
1807
1808            mCameraDevice.setParameters(mParameters);
1809        }
1810
1811        // We post the runner because this function can be called from
1812        // non-UI thread (i.e., startPreviewThread).
1813        final String finalWhiteBalance = whiteBalance;
1814        final String finalFlashMode = flashMode;
1815        final String finalSceneMode =
1816                Parameters.SCENE_MODE_AUTO.equals(sceneMode)
1817                ? SCENE_MODE_OFF
1818                : SCENE_MODE_ON;
1819
1820        mHandler.post(new Runnable() {
1821            public void run() {
1822                mFocusIndicator.setMode(mFocusMode);
1823                mWhitebalanceIndicator.setMode(finalWhiteBalance);
1824                mSceneModeIndicator.setMode(finalSceneMode);
1825                mFlashIndicator.setMode(finalFlashMode);
1826            }
1827        });
1828    }
1829
1830    private void gotoGallery() {
1831        MenuHelper.gotoCameraImageGallery(this);
1832    }
1833
1834    private void viewLastImage() {
1835        if (mThumbController.isUriValid()) {
1836            Uri targetUri = mThumbController.getUri();
1837            targetUri = targetUri.buildUpon().appendQueryParameter(
1838                    "bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
1839            Intent intent = new Intent(this, ReviewImage.class);
1840            intent.setData(targetUri);
1841            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
1842            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
1843            intent.putExtra("com.android.camera.ReviewMode", true);
1844            try {
1845                startActivity(intent);
1846            } catch (ActivityNotFoundException ex) {
1847                Log.e(TAG, "review image fail", ex);
1848            }
1849        } else {
1850            Log.e(TAG, "Can't view last image.");
1851        }
1852    }
1853
1854    private void startReceivingLocationUpdates() {
1855        if (mLocationManager != null) {
1856            try {
1857                mLocationManager.requestLocationUpdates(
1858                        LocationManager.NETWORK_PROVIDER,
1859                        1000,
1860                        0F,
1861                        mLocationListeners[1]);
1862            } catch (java.lang.SecurityException ex) {
1863                Log.i(TAG, "fail to request location update, ignore", ex);
1864            } catch (IllegalArgumentException ex) {
1865                Log.d(TAG, "provider does not exist " + ex.getMessage());
1866            }
1867            try {
1868                mLocationManager.requestLocationUpdates(
1869                        LocationManager.GPS_PROVIDER,
1870                        1000,
1871                        0F,
1872                        mLocationListeners[0]);
1873            } catch (java.lang.SecurityException ex) {
1874                Log.i(TAG, "fail to request location update, ignore", ex);
1875            } catch (IllegalArgumentException ex) {
1876                Log.d(TAG, "provider does not exist " + ex.getMessage());
1877            }
1878        }
1879    }
1880
1881    private void stopReceivingLocationUpdates() {
1882        if (mLocationManager != null) {
1883            for (int i = 0; i < mLocationListeners.length; i++) {
1884                try {
1885                    mLocationManager.removeUpdates(mLocationListeners[i]);
1886                } catch (Exception ex) {
1887                    Log.i(TAG, "fail to remove location listners, ignore", ex);
1888                }
1889            }
1890        }
1891    }
1892
1893    private Location getCurrentLocation() {
1894        // go in best to worst order
1895        for (int i = 0; i < mLocationListeners.length; i++) {
1896            Location l = mLocationListeners[i].current();
1897            if (l != null) return l;
1898        }
1899        return null;
1900    }
1901
1902    private boolean isCameraIdle() {
1903        return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED;
1904    }
1905
1906    private boolean isImageCaptureIntent() {
1907        String action = getIntent().getAction();
1908        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1909    }
1910
1911    private void setupCaptureParams() {
1912        Bundle myExtras = getIntent().getExtras();
1913        if (myExtras != null) {
1914            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1915            mCropValue = myExtras.getString("crop");
1916        }
1917    }
1918
1919    private void showPostCaptureAlert() {
1920        if (mIsImageCaptureIntent) {
1921            findViewById(R.id.shutter_button).setVisibility(View.INVISIBLE);
1922            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1923            for (int id : pickIds) {
1924                View button = findViewById(id);
1925                ((View) button.getParent()).setVisibility(View.VISIBLE);
1926            }
1927        }
1928    }
1929
1930    private void hidePostCaptureAlert() {
1931        if (mIsImageCaptureIntent) {
1932            findViewById(R.id.shutter_button).setVisibility(View.VISIBLE);
1933            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1934            for (int id : pickIds) {
1935                View button = findViewById(id);
1936                ((View) button.getParent()).setVisibility(View.GONE);
1937            }
1938        }
1939    }
1940
1941    private int calculatePicturesRemaining() {
1942        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
1943        return mPicturesRemaining;
1944    }
1945
1946    @Override
1947    public boolean onPrepareOptionsMenu(Menu menu) {
1948        super.onPrepareOptionsMenu(menu);
1949        // Only show the menu when camera is idle.
1950        for (int i = 0; i < menu.size(); i++) {
1951            menu.getItem(i).setVisible(isCameraIdle());
1952        }
1953
1954        return true;
1955    }
1956
1957    @Override
1958    public boolean onCreateOptionsMenu(Menu menu) {
1959        super.onCreateOptionsMenu(menu);
1960
1961        if (mIsImageCaptureIntent) {
1962            // No options menu for attach mode.
1963            return false;
1964        } else {
1965            addBaseMenuItems(menu);
1966        }
1967        return true;
1968    }
1969
1970    private void addBaseMenuItems(Menu menu) {
1971        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1972                MenuHelper.POSITION_GOTO_GALLERY,
1973                R.string.camera_gallery_photos_text)
1974                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1975            public boolean onMenuItemClick(MenuItem item) {
1976                gotoGallery();
1977                return true;
1978            }
1979        });
1980        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1981        mGalleryItems.add(gallery);
1982
1983        MenuItem item = menu.add(Menu.NONE, Menu.NONE,
1984                MenuHelper.POSITION_CAMERA_SETTING, R.string.settings)
1985                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1986            public boolean onMenuItemClick(MenuItem item) {
1987                showOnScreenSettings();
1988                return true;
1989            }
1990        });
1991        item.setIcon(android.R.drawable.ic_menu_preferences);
1992    }
1993
1994    public boolean onSwitchChanged(Switcher source, boolean onOff) {
1995        if (onOff == SWITCH_VIDEO) {
1996            if (!isCameraIdle()) return false;
1997            MenuHelper.gotoVideoMode(this);
1998            finish();
1999        }
2000        return true;
2001    }
2002
2003    public void onSharedPreferenceChanged(
2004            SharedPreferences preferences, String key) {
2005        // ignore the events after "onPause()"
2006        if (mPausing) return;
2007
2008        if (CameraSettings.KEY_RECORD_LOCATION.equals(key)) {
2009            mRecordLocation = preferences.getBoolean(key, false);
2010            if (mRecordLocation) {
2011                startReceivingLocationUpdates();
2012            } else {
2013                stopReceivingLocationUpdates();
2014            }
2015        } else {
2016            // All preferences except RECORD_LOCATION are camera parameters.
2017            // Call setCameraParameters to take effect now.
2018            setCameraParameters();
2019        }
2020    }
2021
2022    @Override
2023    public void onUserInteraction() {
2024        super.onUserInteraction();
2025        keepScreenOnAwhile();
2026    }
2027
2028    private void resetScreenOn() {
2029        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2030        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2031    }
2032
2033    private void keepScreenOnAwhile() {
2034        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2035        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2036        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2037    }
2038}
2039
2040class FocusRectangle extends View {
2041
2042    @SuppressWarnings("unused")
2043    private static final String TAG = "FocusRectangle";
2044
2045    public FocusRectangle(Context context, AttributeSet attrs) {
2046        super(context, attrs);
2047    }
2048
2049    private void setDrawable(int resid) {
2050        setBackgroundDrawable(getResources().getDrawable(resid));
2051    }
2052
2053    public void showStart() {
2054        setDrawable(R.drawable.focus_focusing);
2055    }
2056
2057    public void showSuccess() {
2058        setDrawable(R.drawable.focus_focused);
2059    }
2060
2061    public void showFail() {
2062        setDrawable(R.drawable.focus_focus_failed);
2063    }
2064
2065    public void clear() {
2066        setBackgroundDrawable(null);
2067    }
2068}
2069