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