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