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