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