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