Camera.java revision cd4aacd29f860ba6c1769372c698ab1a4517179c
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 com.android.camera.ui.CameraHeadUpDisplay;
20import com.android.camera.ui.CameraPicker;
21import com.android.camera.ui.FocusRectangle;
22import com.android.camera.ui.GLRootView;
23import com.android.camera.ui.HeadUpDisplay;
24import com.android.camera.ui.IndicatorWheel;
25import com.android.camera.ui.RotateImageView;
26import com.android.camera.ui.SharePopup;
27import com.android.camera.ui.ZoomControllerListener;
28import com.android.camera.ui.ZoomPicker;
29
30import android.app.Activity;
31import android.content.BroadcastReceiver;
32import android.content.ContentProviderClient;
33import android.content.ContentResolver;
34import android.content.Context;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.content.SharedPreferences.Editor;
38import android.content.res.Configuration;
39import android.graphics.Bitmap;
40import android.graphics.Rect;
41import android.hardware.Camera.Area;
42import android.hardware.Camera.CameraInfo;
43import android.hardware.Camera.Parameters;
44import android.hardware.Camera.PictureCallback;
45import android.hardware.Camera.Size;
46import android.location.Location;
47import android.location.LocationManager;
48import android.location.LocationProvider;
49import android.media.AudioManager;
50import android.media.CameraProfile;
51import android.media.ToneGenerator;
52import android.net.Uri;
53import android.os.Bundle;
54import android.os.Handler;
55import android.os.Looper;
56import android.os.Message;
57import android.os.MessageQueue;
58import android.os.SystemClock;
59import android.provider.MediaStore;
60import android.provider.Settings;
61import android.util.Log;
62import android.view.GestureDetector;
63import android.view.Gravity;
64import android.view.KeyEvent;
65import android.view.Menu;
66import android.view.MenuItem;
67import android.view.MenuItem.OnMenuItemClickListener;
68import android.view.MotionEvent;
69import android.view.OrientationEventListener;
70import android.view.SurfaceHolder;
71import android.view.SurfaceView;
72import android.view.View;
73import android.view.ViewGroup;
74import android.view.Window;
75import android.view.WindowManager;
76import android.widget.Button;
77import android.widget.RelativeLayout;
78import android.widget.TextView;
79import android.widget.Toast;
80
81import java.io.File;
82import java.io.FileNotFoundException;
83import java.io.FileOutputStream;
84import java.io.IOException;
85import java.io.OutputStream;
86import java.text.SimpleDateFormat;
87import java.util.ArrayList;
88import java.util.Collections;
89import java.util.Date;
90import java.util.List;
91
92/** The Camera activity which can preview and take pictures. */
93public class Camera extends ActivityBase implements View.OnClickListener,
94        View.OnTouchListener, ShutterButton.OnShutterButtonListener,
95        SurfaceHolder.Callback, Switcher.OnSwitchListener {
96
97    private static final String TAG = "camera";
98
99    private static final String LAST_THUMB_FILENAME = "image_last_thumb";
100
101    private static final int CROP_MSG = 1;
102    private static final int FIRST_TIME_INIT = 2;
103    private static final int RESTART_PREVIEW = 3;
104    private static final int CLEAR_SCREEN_DELAY = 4;
105    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 5;
106    private static final int CHECK_DISPLAY_ROTATION = 6;
107    private static final int RESET_TOUCH_FOCUS = 7;
108
109    // The subset of parameters we need to update in setCameraParameters().
110    private static final int UPDATE_PARAM_INITIALIZE = 1;
111    private static final int UPDATE_PARAM_ZOOM = 2;
112    private static final int UPDATE_PARAM_PREFERENCE = 4;
113    private static final int UPDATE_PARAM_ALL = -1;
114
115    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
116    // needed to be updated in mUpdateSet.
117    private int mUpdateSet;
118
119    // The brightness settings used when it is set to automatic in the system.
120    // The reason why it is set to 0.7 is just because 1.0 is too bright.
121    private static final float DEFAULT_CAMERA_BRIGHTNESS = 0.7f;
122
123    private static final int SCREEN_DELAY = 2 * 60 * 1000;
124    private static final int FOCUS_BEEP_VOLUME = 100;
125
126    private static final int ZOOM_STOPPED = 0;
127    private static final int ZOOM_START = 1;
128    private static final int ZOOM_STOPPING = 2;
129
130    private int mZoomState = ZOOM_STOPPED;
131    private boolean mSmoothZoomSupported = false;
132    private int mZoomValue;  // The current zoom value.
133    private int mZoomMax;
134    private int mTargetZoomValue;
135    private ZoomPicker mZoomPicker;
136
137    private Parameters mParameters;
138    private Parameters mInitialParams;
139
140    private MyOrientationEventListener mOrientationListener;
141    // The degrees of the device rotated clockwise from its natural orientation.
142    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
143    // The orientation compensation for icons and thumbnails. Ex: if the value
144    // is 90, the UI components should be rotated 90 degrees counter-clockwise.
145    private int mOrientationCompensation = 0;
146    private ComboPreferences mPreferences;
147
148    private static final boolean SWITCH_CAMERA = true;
149    private static final boolean SWITCH_VIDEO = false;
150
151    private static final String sTempCropFilename = "crop-temp";
152
153    private android.hardware.Camera mCameraDevice;
154    private ContentProviderClient mMediaProviderClient;
155    private SurfaceHolder mSurfaceHolder = null;
156    private ShutterButton mShutterButton;
157    private ToneGenerator mFocusToneGenerator;
158    private GestureDetector mPopupGestureDetector;
159    private SwitcherSet mSwitcher;
160    private boolean mOpenCameraFail = false;
161    private boolean mCameraDisabled = false;
162
163    private View mPreviewFrame;  // Preview frame area.
164    private View mPreviewBorder;
165    private FocusRectangle mFocusRectangle;
166    private List<Area> mFocusArea;  // focus area in driver format
167    private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
168
169    private GLRootView mGLRootView;
170
171    // A button showing the last captured picture thumbnail. Clicking on it will
172    // show the share popup window.
173    private RotateImageView mThumbnailButton;
174    // A popup window that contains a bigger thumbnail and a list of apps to share.
175    private SharePopup mSharePopup;
176    // The bitmap of the last captured picture thumbnail and the URI of the
177    // original picture.
178    private Thumbnail mThumbnail;
179    // A button sharing the last picture.
180    private RotateImageView mShareButton;
181    private RotateImageView mCameraSwitchIcon;
182    private RotateImageView mVideoSwitchIcon;
183
184    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
185    private String mCropValue;
186    private Uri mSaveUri;
187
188    // GPS on-screen indicator
189    private View mGpsNoSignalView;
190    private View mGpsHasSignalView;
191
192    // Front/Back camera picker for w1024dp layout
193    private CameraPicker mCameraPicker;
194
195    /**
196     * An unpublished intent flag requesting to return as soon as capturing
197     * is completed.
198     *
199     * TODO: consider publishing by moving into MediaStore.
200     */
201    private final static String EXTRA_QUICK_CAPTURE =
202            "android.intent.extra.quickCapture";
203
204    // The display rotation in degrees. This is only valid when mCameraState is
205    // not PREVIEW_STOPPED.
206    private int mDisplayRotation;
207    private boolean mPausing;
208    private boolean mFirstTimeInitialized;
209    private boolean mIsImageCaptureIntent;
210    private boolean mRecordLocation;
211
212    private static final int PREVIEW_STOPPED = 0;
213    private static final int IDLE = 1;  // preview is active
214    private static final int FOCUSING = 2;
215    private static final int FOCUSING_SNAP_ON_FINISH = 3;
216    private static final int FOCUS_SUCCESS = 4;
217    private static final int FOCUS_FAIL = 5;
218    private static final int SNAPSHOT_IN_PROGRESS = 6;
219    private int mCameraState = PREVIEW_STOPPED;
220
221    private ContentResolver mContentResolver;
222    private boolean mDidRegister = false;
223
224    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
225
226    private LocationManager mLocationManager = null;
227
228    private final ShutterCallback mShutterCallback = new ShutterCallback();
229    private final PostViewPictureCallback mPostViewPictureCallback =
230            new PostViewPictureCallback();
231    private final RawPictureCallback mRawPictureCallback =
232            new RawPictureCallback();
233    private final AutoFocusCallback mAutoFocusCallback =
234            new AutoFocusCallback();
235    private final ZoomListener mZoomListener = new ZoomListener();
236    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
237
238    private long mFocusStartTime;
239    private long mFocusCallbackTime;
240    private long mCaptureStartTime;
241    private long mShutterCallbackTime;
242    private long mPostViewPictureCallbackTime;
243    private long mRawPictureCallbackTime;
244    private long mJpegPictureCallbackTime;
245    private long mOnResumeTime;
246    private long mPicturesRemaining;
247    private byte[] mJpegImageData;
248
249    // These latency time are for the CameraLatency test.
250    public long mAutoFocusTime;
251    public long mShutterLag;
252    public long mShutterToPictureDisplayedTime;
253    public long mPictureDisplayedToJpegCallbackTime;
254    public long mJpegCallbackFinishTime;
255
256    // Focus mode. Options are pref_camera_focusmode_entryvalues.
257    private String mFocusMode;
258    private String mSceneMode;
259    private Toast mNotSelectableToast;
260    private Toast mNoShareToast;
261
262    private final Handler mHandler = new MainHandler();
263    // w1024dp devices use indicator wheel. Other devices use head-up display.
264    private CameraHeadUpDisplay mHeadUpDisplay;
265    private IndicatorWheel mIndicatorWheel;
266    private PreferenceGroup mPreferenceGroup;
267
268    // multiple cameras support
269    private int mNumberOfCameras;
270    private int mCameraId;
271    private int mFrontCameraId;
272    private int mBackCameraId;
273
274    private boolean mQuickCapture;
275
276    /**
277     * This Handler is used to post message back onto the main thread of the
278     * application
279     */
280    private class MainHandler extends Handler {
281        @Override
282        public void handleMessage(Message msg) {
283            switch (msg.what) {
284                case RESTART_PREVIEW: {
285                    startPreview();
286                    if (mJpegPictureCallbackTime != 0) {
287                        long now = System.currentTimeMillis();
288                        mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
289                        Log.v(TAG, "mJpegCallbackFinishTime = "
290                                + mJpegCallbackFinishTime + "ms");
291                        mJpegPictureCallbackTime = 0;
292                    }
293                    break;
294                }
295
296                case CLEAR_SCREEN_DELAY: {
297                    getWindow().clearFlags(
298                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
299                    break;
300                }
301
302                case FIRST_TIME_INIT: {
303                    initializeFirstTime();
304                    break;
305                }
306
307                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
308                    setCameraParametersWhenIdle(0);
309                    break;
310                }
311
312                case CHECK_DISPLAY_ROTATION: {
313                    // Restart the preview if display rotation has changed.
314                    // Sometimes this happens when the device is held upside
315                    // down and camera app is opened. Rotation animation will
316                    // take some time and the rotation value we have got may be
317                    // wrong. Framework does not have a callback for this now.
318                    if (Util.getDisplayRotation(Camera.this) != mDisplayRotation
319                            && isCameraIdle()) {
320                        startPreview();
321                    }
322                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
323                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
324                    }
325                    break;
326                }
327
328                case RESET_TOUCH_FOCUS: {
329                    cancelAutoFocus();
330                    break;
331                }
332            }
333        }
334    }
335
336    private void resetExposureCompensation() {
337        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
338                CameraSettings.EXPOSURE_DEFAULT_VALUE);
339        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
340            Editor editor = mPreferences.edit();
341            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
342            editor.apply();
343            if (mHeadUpDisplay != null) {
344                mHeadUpDisplay.reloadPreferences();
345            }
346            if (mIndicatorWheel != null) {
347                mIndicatorWheel.reloadPreferences();
348            }
349        }
350    }
351
352    private void keepMediaProviderInstance() {
353        // We want to keep a reference to MediaProvider in camera's lifecycle.
354        // TODO: Utilize mMediaProviderClient instance to replace
355        // ContentResolver calls.
356        if (mMediaProviderClient == null) {
357            mMediaProviderClient = getContentResolver()
358                    .acquireContentProviderClient(MediaStore.AUTHORITY);
359        }
360    }
361
362    // Snapshots can only be taken after this is called. It should be called
363    // once only. We could have done these things in onCreate() but we want to
364    // make preview screen appear as soon as possible.
365    private void initializeFirstTime() {
366        if (mFirstTimeInitialized) return;
367
368        // Create orientation listenter. This should be done first because it
369        // takes some time to get first orientation.
370        mOrientationListener = new MyOrientationEventListener(Camera.this);
371        mOrientationListener.enable();
372
373        // Initialize location sevice.
374        mLocationManager = (LocationManager)
375                getSystemService(Context.LOCATION_SERVICE);
376        mRecordLocation = RecordLocationPreference.get(
377                mPreferences, getContentResolver());
378        initGpsOnScreenIndicator();
379        if (mRecordLocation) startReceivingLocationUpdates();
380
381        keepMediaProviderInstance();
382        checkStorage();
383
384        // Initialize last picture button.
385        mContentResolver = getContentResolver();
386        if (!mIsImageCaptureIntent) {  // no thumbnail in image capture intent
387            findViewById(R.id.camera_switch).setOnClickListener(this);
388            initThumbnailButton();
389            if (mShareButton != null) mShareButton.setOnClickListener(this);
390        }
391
392        // Initialize shutter button.
393        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
394        mShutterButton.setOnShutterButtonListener(this);
395        mShutterButton.setVisibility(View.VISIBLE);
396
397        // Initialize focus UI.
398        mPreviewFrame = findViewById(R.id.camera_preview);
399        mPreviewFrame.setOnTouchListener(this);
400        mPreviewBorder = findViewById(R.id.preview_border);
401        // Set the length of focus rectangle according to preview frame size.
402        int len = Math.min(mPreviewFrame.getWidth(), mPreviewFrame.getHeight()) / 4;
403        ViewGroup.LayoutParams layout = mFocusRectangle.getLayoutParams();
404        layout.width = len;
405        layout.height = len;
406
407        initializeScreenBrightness();
408        installIntentFilter();
409        initializeFocusTone();
410        initializeZoom();
411        // w1024dp devices use indicator wheel. Other devices use head-up display.
412        if (mIndicatorWheel == null) {
413            mHeadUpDisplay = new CameraHeadUpDisplay(this);
414            mHeadUpDisplay.setListener(new MyHeadUpDisplayListener());
415            initializeHeadUpDisplay();
416        }
417        mFirstTimeInitialized = true;
418        changeHeadUpDisplayState();
419        addIdleHandler();
420    }
421
422    private void addIdleHandler() {
423        MessageQueue queue = Looper.myQueue();
424        queue.addIdleHandler(new MessageQueue.IdleHandler() {
425            public boolean queueIdle() {
426                Storage.ensureOSXCompatible();
427                return false;
428            }
429        });
430    }
431
432    private void initThumbnailButton() {
433        mThumbnailButton.setOnClickListener(this);
434        // Load the thumbnail from the disk.
435        mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), LAST_THUMB_FILENAME));
436        updateThumbnailButton();
437    }
438
439    private void updateThumbnailButton() {
440        // Update last image if URI is invalid and the storage is ready.
441        if ((mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver))
442                && mPicturesRemaining >= 0) {
443            mThumbnail = Thumbnail.getLastImageThumbnail(mContentResolver);
444        }
445        if (mThumbnail != null) {
446            mThumbnailButton.setBitmap(mThumbnail.getBitmap());
447        } else {
448            mThumbnailButton.setBitmap(null);
449        }
450    }
451
452    // If the activity is paused and resumed, this method will be called in
453    // onResume.
454    private void initializeSecondTime() {
455        // Start orientation listener as soon as possible because it takes
456        // some time to get first orientation.
457        mOrientationListener.enable();
458
459        // Start location update if needed.
460        mRecordLocation = RecordLocationPreference.get(
461                mPreferences, getContentResolver());
462        if (mRecordLocation) startReceivingLocationUpdates();
463
464        installIntentFilter();
465        initializeFocusTone();
466        initializeZoom();
467        changeHeadUpDisplayState();
468
469        keepMediaProviderInstance();
470        checkStorage();
471
472        if (!mIsImageCaptureIntent) {
473            updateThumbnailButton();
474            mSwitcher.setSwitch(SWITCH_CAMERA);
475        }
476    }
477
478    private void initializeZoom() {
479        if (!mParameters.isZoomSupported()) return;
480
481        mZoomMax = mParameters.getMaxZoom();
482        mSmoothZoomSupported = mParameters.isSmoothZoomSupported();
483        if (mZoomPicker != null) {
484            mZoomPicker.setZoomRatios(Util.convertZoomRatios(mParameters.getZoomRatios()));
485            mZoomPicker.setZoomIndex(mParameters.getZoom());
486            mZoomPicker.setSmoothZoomSupported(mSmoothZoomSupported);
487            mZoomPicker.setOnZoomChangeListener(
488                    new ZoomPicker.OnZoomChangedListener() {
489                // only for immediate zoom
490                @Override
491                public void onZoomValueChanged(int index) {
492                    Camera.this.onZoomValueChanged(index);
493                }
494
495                // only for smooth zoom
496                @Override
497                public void onZoomStateChanged(int state) {
498                    if (mPausing) return;
499
500                    Log.v(TAG, "zoom picker state=" + state);
501                    if (state == ZoomPicker.ZOOM_IN) {
502                        Camera.this.onZoomValueChanged(mZoomMax);
503                    } else if (state == ZoomPicker.ZOOM_OUT){
504                        Camera.this.onZoomValueChanged(0);
505                    } else {
506                        mTargetZoomValue = -1;
507                        if (mZoomState == ZOOM_START) {
508                            mZoomState = ZOOM_STOPPING;
509                            mCameraDevice.stopSmoothZoom();
510                        }
511                    }
512                }
513            });
514        }
515
516        mCameraDevice.setZoomChangeListener(mZoomListener);
517    }
518
519    private void onZoomValueChanged(int index) {
520        // Not useful to change zoom value when the activity is paused.
521        if (mPausing) return;
522
523        if (mSmoothZoomSupported) {
524            if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
525                mTargetZoomValue = index;
526                if (mZoomState == ZOOM_START) {
527                    mZoomState = ZOOM_STOPPING;
528                    mCameraDevice.stopSmoothZoom();
529                }
530            } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
531                mTargetZoomValue = index;
532                mCameraDevice.startSmoothZoom(index);
533                mZoomState = ZOOM_START;
534            }
535        } else {
536            mZoomValue = index;
537            setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
538        }
539    }
540
541    private class PopupGestureListener
542            extends GestureDetector.SimpleOnGestureListener {
543        public boolean onDown(MotionEvent e) {
544            // Check if the popup window is visible.
545            View popup = mIndicatorWheel.getActivePopupWindow();
546            if (popup == null) return false;
547
548
549            // Let popup window, indicator wheel or preview frame handle the
550            // event by themselves. Dismiss the popup window if users touch on
551            // other areas.
552            if (!Util.pointInView(e.getX(), e.getY(), popup)
553                    && !Util.pointInView(e.getX(), e.getY(), mIndicatorWheel)
554                    && !Util.pointInView(e.getX(), e.getY(), mPreviewFrame)) {
555                mIndicatorWheel.dismissSettingPopup();
556                // Let event fall through.
557            }
558            return false;
559        }
560    }
561
562    @Override
563    public boolean dispatchTouchEvent(MotionEvent m) {
564        // Check if the popup window should be dismissed first.
565        if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
566            return true;
567        }
568
569        return super.dispatchTouchEvent(m);
570    }
571
572    LocationListener [] mLocationListeners = new LocationListener[] {
573            new LocationListener(LocationManager.GPS_PROVIDER),
574            new LocationListener(LocationManager.NETWORK_PROVIDER)
575    };
576
577    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
578        @Override
579        public void onReceive(Context context, Intent intent) {
580            String action = intent.getAction();
581            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
582                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
583                    || action.equals(Intent.ACTION_MEDIA_CHECKING)) {
584                checkStorage();
585            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
586                checkStorage();
587                if (!mIsImageCaptureIntent)  {
588                    updateThumbnailButton();
589                }
590            }
591        }
592    };
593
594    private void initializeCameraPicker() {
595        mCameraPicker = (CameraPicker) findViewById(R.id.camera_picker);
596        if (mCameraPicker != null) {
597            mCameraPicker.setImageResource(R.drawable.camera_toggle);
598            ListPreference pref = mPreferenceGroup.findPreference(
599                    CameraSettings.KEY_CAMERA_ID);
600            if (pref != null) {
601                mCameraPicker.initialize(pref);
602                mCameraPicker.setListener(new MyCameraPickerListener());
603            }
604        }
605    }
606
607    private void initializeZoomPicker() {
608        Button zoomIncrement = (Button) findViewById(R.id.zoom_increment);
609        Button zoomDecrement = (Button) findViewById(R.id.zoom_decrement);
610        TextView zoomRatio = (TextView) findViewById(R.id.zoom_ratio);
611        if (zoomIncrement != null && zoomDecrement != null && mParameters.isZoomSupported()) {
612            mZoomPicker = new ZoomPicker(this, zoomIncrement, zoomDecrement, zoomRatio);
613        }
614    }
615
616    private void initGpsOnScreenIndicator() {
617        mGpsNoSignalView = findViewById(R.id.onscreen_gps_indicator_no_signal);
618        mGpsHasSignalView = findViewById(R.id.onscreen_gps_indicator_on);
619    }
620
621    private void showGpsOnScreenIndicator(boolean hasSignal) {
622        if (hasSignal) {
623            if (mGpsNoSignalView != null) mGpsNoSignalView.setVisibility(View.INVISIBLE);
624            if (mGpsHasSignalView != null) mGpsHasSignalView.setVisibility(View.VISIBLE);
625        } else {
626            if (mGpsNoSignalView != null) mGpsNoSignalView.setVisibility(View.VISIBLE);
627            if (mGpsHasSignalView != null) mGpsHasSignalView.setVisibility(View.INVISIBLE);
628        }
629    }
630
631    private void hideGpsOnScreenIndicator() {
632        if (mGpsNoSignalView != null) mGpsNoSignalView.setVisibility(View.INVISIBLE);
633        if (mGpsHasSignalView != null) mGpsHasSignalView.setVisibility(View.INVISIBLE);
634    }
635
636    private class LocationListener
637            implements android.location.LocationListener {
638        Location mLastLocation;
639        boolean mValid = false;
640        String mProvider;
641
642        public LocationListener(String provider) {
643            mProvider = provider;
644            mLastLocation = new Location(mProvider);
645        }
646
647        public void onLocationChanged(Location newLocation) {
648            if (newLocation.getLatitude() == 0.0
649                    && newLocation.getLongitude() == 0.0) {
650                // Hack to filter out 0.0,0.0 locations
651                return;
652            }
653            // If GPS is available before start camera, we won't get status
654            // update so update GPS indicator when we receive data.
655            if (mRecordLocation
656                    && LocationManager.GPS_PROVIDER.equals(mProvider)) {
657                showGpsOnScreenIndicator(true);
658            }
659            if (!mValid) {
660                Log.d(TAG, "Got first location.");
661            }
662            mLastLocation.set(newLocation);
663            mValid = true;
664        }
665
666        public void onProviderEnabled(String provider) {
667        }
668
669        public void onProviderDisabled(String provider) {
670            mValid = false;
671        }
672
673        public void onStatusChanged(
674                String provider, int status, Bundle extras) {
675            switch(status) {
676                case LocationProvider.OUT_OF_SERVICE:
677                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
678                    mValid = false;
679                    if (mRecordLocation &&
680                            LocationManager.GPS_PROVIDER.equals(provider)) {
681                        showGpsOnScreenIndicator(false);
682                    }
683                    break;
684                }
685            }
686        }
687
688        public Location current() {
689            return mValid ? mLastLocation : null;
690        }
691    }
692
693    private final class ShutterCallback
694            implements android.hardware.Camera.ShutterCallback {
695        public void onShutter() {
696            mShutterCallbackTime = System.currentTimeMillis();
697            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
698            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
699            updateFocusUI();
700        }
701    }
702
703    private final class PostViewPictureCallback implements PictureCallback {
704        public void onPictureTaken(
705                byte [] data, android.hardware.Camera camera) {
706            mPostViewPictureCallbackTime = System.currentTimeMillis();
707            Log.v(TAG, "mShutterToPostViewCallbackTime = "
708                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
709                    + "ms");
710        }
711    }
712
713    private final class RawPictureCallback implements PictureCallback {
714        public void onPictureTaken(
715                byte [] rawData, android.hardware.Camera camera) {
716            mRawPictureCallbackTime = System.currentTimeMillis();
717            Log.v(TAG, "mShutterToRawCallbackTime = "
718                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
719        }
720    }
721
722    private final class JpegPictureCallback implements PictureCallback {
723        Location mLocation;
724
725        public JpegPictureCallback(Location loc) {
726            mLocation = loc;
727        }
728
729        public void onPictureTaken(
730                final byte [] jpegData, final android.hardware.Camera camera) {
731            if (mPausing) {
732                return;
733            }
734
735            mJpegPictureCallbackTime = System.currentTimeMillis();
736            // If postview callback has arrived, the captured image is displayed
737            // in postview callback. If not, the captured image is displayed in
738            // raw picture callback.
739            if (mPostViewPictureCallbackTime != 0) {
740                mShutterToPictureDisplayedTime =
741                        mPostViewPictureCallbackTime - mShutterCallbackTime;
742                mPictureDisplayedToJpegCallbackTime =
743                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
744            } else {
745                mShutterToPictureDisplayedTime =
746                        mRawPictureCallbackTime - mShutterCallbackTime;
747                mPictureDisplayedToJpegCallbackTime =
748                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
749            }
750            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
751                    + mPictureDisplayedToJpegCallbackTime + "ms");
752
753            if (!mIsImageCaptureIntent) {
754                enableCameraControls(true);
755
756                // We want to show the taken picture for a while, so we wait
757                // for at least 1.2 second before restarting the preview.
758                long delay = 1200 - mPictureDisplayedToJpegCallbackTime;
759                if (delay < 0) {
760                    startPreview();
761                } else {
762                    mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, delay);
763                }
764            }
765            storeImage(jpegData, camera, mLocation);
766
767            // Check this in advance of each shot so we don't add to shutter
768            // latency. It's true that someone else could write to the SD card in
769            // the mean time and fill it, but that could have happened between the
770            // shutter press and saving the JPEG too.
771            checkStorage();
772
773            if (!mHandler.hasMessages(RESTART_PREVIEW)) {
774                long now = System.currentTimeMillis();
775                mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
776                Log.v(TAG, "mJpegCallbackFinishTime = "
777                        + mJpegCallbackFinishTime + "ms");
778                mJpegPictureCallbackTime = 0;
779            }
780        }
781    }
782
783    private final class AutoFocusCallback
784            implements android.hardware.Camera.AutoFocusCallback {
785        public void onAutoFocus(
786                boolean focused, android.hardware.Camera camera) {
787            mFocusCallbackTime = System.currentTimeMillis();
788            mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
789            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
790            if (mCameraState == FOCUSING_SNAP_ON_FINISH) {
791                // Take the picture no matter focus succeeds or fails. No need
792                // to play the AF sound if we're about to play the shutter
793                // sound.
794                if (focused) {
795                    mCameraState = FOCUS_SUCCESS;
796                } else {
797                    mCameraState = FOCUS_FAIL;
798                }
799                updateFocusUI();
800                capture();
801            } else if (mCameraState == FOCUSING) {
802                // This happens when (1) user is half-pressing the focus key or
803                // (2) touch focus is triggered. Play the focus tone. Do not
804                // take the picture now.
805                if (focused) {
806                    mCameraState = FOCUS_SUCCESS;
807                    if (mFocusToneGenerator != null) {
808                        mFocusToneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP2);
809                    }
810                } else {
811                    mCameraState = FOCUS_FAIL;
812                }
813                updateFocusUI();
814                enableCameraControls(true);
815                // If this is triggered by touch focus, cancel focus after a
816                // while.
817                if (mFocusArea != null) {
818                    mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
819                }
820            } else if (mCameraState == IDLE) {
821                // User has released the focus key before focus completes.
822                // Do nothing.
823            }
824
825        }
826    }
827
828    private final class ZoomListener
829            implements android.hardware.Camera.OnZoomChangeListener {
830        @Override
831        public void onZoomChange(
832                int value, boolean stopped, android.hardware.Camera camera) {
833            Log.v(TAG, "Zoom changed: value=" + value + ". stopped="+ stopped);
834            mZoomValue = value;
835
836            // Update the UI when we get zoom value.
837            if (mZoomPicker != null) mZoomPicker.setZoomIndex(value);
838
839            // Keep mParameters up to date. We do not getParameter again in
840            // takePicture. If we do not do this, wrong zoom value will be set.
841            mParameters.setZoom(value);
842
843            if (stopped && mZoomState != ZOOM_STOPPED) {
844                if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
845                    mCameraDevice.startSmoothZoom(mTargetZoomValue);
846                    mZoomState = ZOOM_START;
847                } else {
848                    mZoomState = ZOOM_STOPPED;
849                }
850            }
851        }
852    }
853
854    public void storeImage(final byte[] data,
855            android.hardware.Camera camera, Location loc) {
856        if (!mIsImageCaptureIntent) {
857            long dateTaken = System.currentTimeMillis();
858            String title = createName(dateTaken);
859            int orientation = Exif.getOrientation(data);
860            Uri uri = Storage.addImage(mContentResolver, title, dateTaken,
861                    loc, orientation, data);
862            if (uri != null) {
863                // Create a thumbnail whose size is smaller than half of the surface view.
864                int ratio = (int) Math.ceil((double) mParameters.getPictureSize().width
865                        / (mPreviewFrame.getWidth() / 2));
866                int inSampleSize = Util.nextPowerOf2(ratio);
867                mThumbnail = Thumbnail.createThumbnail(data, orientation, inSampleSize, uri);
868                if (mThumbnail != null) {
869                    mThumbnailButton.setBitmap(mThumbnail.getBitmap());
870                }
871                sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
872            }
873        } else {
874            mJpegImageData = data;
875            if (!mQuickCapture) {
876                showPostCaptureAlert();
877            } else {
878                doAttach();
879            }
880        }
881    }
882
883    private void capture() {
884        // If we are already in the middle of taking a snapshot then ignore.
885        if (mPausing || mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) {
886            return;
887        }
888        mCaptureStartTime = System.currentTimeMillis();
889        mPostViewPictureCallbackTime = 0;
890        enableCameraControls(false);
891        mJpegImageData = null;
892
893        // See android.hardware.Camera.Parameters.setRotation for
894        // documentation.
895        int rotation = 0;
896        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
897            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
898            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
899                rotation = (info.orientation - mOrientation + 360) % 360;
900            } else {  // back-facing camera
901                rotation = (info.orientation + mOrientation) % 360;
902            }
903        }
904        mParameters.setRotation(rotation);
905
906        // Clear previous GPS location from the parameters.
907        mParameters.removeGpsData();
908
909        // We always encode GpsTimeStamp
910        mParameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
911
912        // Set GPS location.
913        Location loc = mRecordLocation ? getCurrentLocation() : null;
914        if (loc != null) {
915            double lat = loc.getLatitude();
916            double lon = loc.getLongitude();
917            boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
918
919            if (hasLatLon) {
920                Log.d(TAG, "Set gps location");
921                mParameters.setGpsLatitude(lat);
922                mParameters.setGpsLongitude(lon);
923                mParameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
924                if (loc.hasAltitude()) {
925                    mParameters.setGpsAltitude(loc.getAltitude());
926                } else {
927                    // for NETWORK_PROVIDER location provider, we may have
928                    // no altitude information, but the driver needs it, so
929                    // we fake one.
930                    mParameters.setGpsAltitude(0);
931                }
932                if (loc.getTime() != 0) {
933                    // Location.getTime() is UTC in milliseconds.
934                    // gps-timestamp is UTC in seconds.
935                    long utcTimeSeconds = loc.getTime() / 1000;
936                    mParameters.setGpsTimestamp(utcTimeSeconds);
937                }
938            } else {
939                loc = null;
940            }
941        }
942
943        mCameraDevice.setParameters(mParameters);
944
945        mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
946                mPostViewPictureCallback, new JpegPictureCallback(loc));
947        mCameraState = SNAPSHOT_IN_PROGRESS;
948        mHandler.removeMessages(RESET_TOUCH_FOCUS);
949    }
950
951    private boolean saveDataToFile(String filePath, byte[] data) {
952        FileOutputStream f = null;
953        try {
954            f = new FileOutputStream(filePath);
955            f.write(data);
956        } catch (IOException e) {
957            return false;
958        } finally {
959            Util.closeSilently(f);
960        }
961        return true;
962    }
963
964    private String createName(long dateTaken) {
965        Date date = new Date(dateTaken);
966        SimpleDateFormat dateFormat = new SimpleDateFormat(
967                getString(R.string.image_file_name_format));
968
969        return dateFormat.format(date);
970    }
971
972    @Override
973    public void onCreate(Bundle icicle) {
974        super.onCreate(icicle);
975
976        mIsImageCaptureIntent = isImageCaptureIntent();
977        if (mIsImageCaptureIntent) {
978            setContentView(R.layout.camera_attach);
979        } else {
980            setContentView(R.layout.camera);
981        }
982        mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
983        mThumbnailButton = (RotateImageView) findViewById(R.id.review_thumbnail);
984        mShareButton = (RotateImageView) findViewById(R.id.btn_share);
985        mCameraSwitchIcon = (RotateImageView) findViewById(R.id.camera_switch_icon);
986        mVideoSwitchIcon = (RotateImageView) findViewById(R.id.video_switch_icon);
987
988        mPreferences = new ComboPreferences(this);
989        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
990
991        mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
992
993        // Testing purpose. Launch a specific camera through the intent extras.
994        int intentCameraId = Util.getCameraFacingIntentExtras(this);
995        if (intentCameraId != -1) {
996            mCameraId = intentCameraId;
997        }
998
999        mPreferences.setLocalId(this, mCameraId);
1000        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1001
1002        mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
1003        mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
1004
1005        // we need to reset exposure for the preview
1006        resetExposureCompensation();
1007
1008        /*
1009         * To reduce startup time, we start the preview in another thread.
1010         * We make sure the preview is started at the end of onCreate.
1011         */
1012        Thread startPreviewThread = new Thread(new Runnable() {
1013            public void run() {
1014                try {
1015                    mCameraDevice = Util.openCamera(Camera.this, mCameraId);
1016                    mInitialParams = mCameraDevice.getParameters();
1017                    startPreview();
1018                } catch (CameraHardwareException e) {
1019                    mOpenCameraFail = true;
1020                } catch (CameraDisabledException e) {
1021                    mCameraDisabled = true;
1022                }
1023            }
1024        });
1025        startPreviewThread.start();
1026
1027        // don't set mSurfaceHolder here. We have it set ONLY within
1028        // surfaceChanged / surfaceDestroyed, other parts of the code
1029        // assume that when it is set, the surface is also set.
1030        SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
1031        SurfaceHolder holder = preview.getHolder();
1032        holder.addCallback(this);
1033        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
1034
1035        if (mIsImageCaptureIntent) {
1036            setupCaptureParams();
1037
1038            findViewById(R.id.review_control).setVisibility(View.VISIBLE);
1039            findViewById(R.id.btn_cancel).setOnClickListener(this);
1040            findViewById(R.id.btn_retake).setOnClickListener(this);
1041            findViewById(R.id.btn_done).setOnClickListener(this);
1042        } else {
1043            mSwitcher = (SwitcherSet) findViewById(R.id.camera_switch);
1044            mSwitcher.setVisibility(View.VISIBLE);
1045            mSwitcher.setOnSwitchListener(this);
1046            mSwitcher.setSwitch(SWITCH_CAMERA);
1047        }
1048
1049        // Make sure preview is started.
1050        try {
1051            startPreviewThread.join();
1052            if (mOpenCameraFail) {
1053                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1054                return;
1055            } else if (mCameraDisabled) {
1056                Util.showErrorAndFinish(this, R.string.camera_disabled);
1057                return;
1058            }
1059        } catch (InterruptedException ex) {
1060            // ignore
1061        }
1062
1063        mBackCameraId = CameraHolder.instance().getBackCameraId();
1064        mFrontCameraId = CameraHolder.instance().getFrontCameraId();
1065
1066        // Do this after starting preview because it depends on camera
1067        // parameters.
1068        initializeIndicatorWheel();
1069        initializeCameraPicker();
1070        initializeZoomPicker();
1071    }
1072
1073    private void changeHeadUpDisplayState() {
1074        if (mHeadUpDisplay == null) return;
1075        // If the camera resumes behind the lock screen, the orientation
1076        // will be portrait. That causes OOM when we try to allocation GPU
1077        // memory for the GLSurfaceView again when the orientation changes. So,
1078        // we delayed initialization of HeadUpDisplay until the orientation
1079        // becomes landscape.
1080        Configuration config = getResources().getConfiguration();
1081        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE
1082                && !mPausing && mFirstTimeInitialized) {
1083            if (mGLRootView == null) attachHeadUpDisplay();
1084        } else if (mGLRootView != null) {
1085            detachHeadUpDisplay();
1086        }
1087    }
1088
1089    private void overrideCameraSettings(final String flashMode,
1090            final String whiteBalance, final String focusMode) {
1091        if (mHeadUpDisplay != null) {
1092            mHeadUpDisplay.overrideSettings(
1093                    CameraSettings.KEY_FLASH_MODE, flashMode,
1094                    CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1095                    CameraSettings.KEY_FOCUS_MODE, focusMode);
1096        }
1097        if (mIndicatorWheel != null) {
1098            mIndicatorWheel.overrideSettings(
1099                    CameraSettings.KEY_FLASH_MODE, flashMode,
1100                    CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1101                    CameraSettings.KEY_FOCUS_MODE, focusMode);
1102        }
1103    }
1104
1105    private void updateSceneModeUI() {
1106        // If scene mode is set, we cannot set flash mode, white balance, and
1107        // focus mode, instead, we read it from driver
1108        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1109            overrideCameraSettings(mParameters.getFlashMode(),
1110                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
1111        } else {
1112            overrideCameraSettings(null, null, null);
1113        }
1114    }
1115
1116    private void loadCameraPreferences() {
1117        CameraSettings settings = new CameraSettings(this, mInitialParams,
1118                mCameraId, CameraHolder.instance().getCameraInfo());
1119        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1120    }
1121
1122    private void initializeIndicatorWheel() {
1123        mIndicatorWheel = (IndicatorWheel) findViewById(R.id.indicator_wheel);
1124        if (mIndicatorWheel == null) return;
1125        loadCameraPreferences();
1126
1127        final String[] SETTING_KEYS = {
1128                CameraSettings.KEY_FLASH_MODE,
1129                CameraSettings.KEY_WHITE_BALANCE,
1130                CameraSettings.KEY_COLOR_EFFECT,
1131                CameraSettings.KEY_SCENE_MODE};
1132        final String[] OTHER_SETTING_KEYS = {
1133                CameraSettings.KEY_RECORD_LOCATION,
1134                CameraSettings.KEY_FOCUS_MODE,
1135                CameraSettings.KEY_EXPOSURE,
1136                CameraSettings.KEY_PICTURE_SIZE};
1137        mIndicatorWheel.initialize(this, mPreferenceGroup, SETTING_KEYS,
1138                OTHER_SETTING_KEYS);
1139        mIndicatorWheel.setListener(new MyIndicatorWheelListener());
1140        mPopupGestureDetector = new GestureDetector(this,
1141                new PopupGestureListener());
1142        updateSceneModeUI();
1143    }
1144
1145    private void initializeHeadUpDisplay() {
1146        if (mHeadUpDisplay == null) return;
1147        loadCameraPreferences();
1148
1149        float[] zoomRatios = null;
1150        if(mParameters.isZoomSupported()) {
1151            zoomRatios = Util.convertZoomRatios(mParameters.getZoomRatios());
1152        }
1153        mHeadUpDisplay.initialize(this, mPreferenceGroup,
1154                zoomRatios, mOrientationCompensation);
1155        if (mParameters.isZoomSupported()) {
1156            mHeadUpDisplay.setZoomListener(new ZoomControllerListener() {
1157                public void onZoomChanged(
1158                        int index, float ratio, boolean isMoving) {
1159                    onZoomValueChanged(index);
1160                }
1161            });
1162        }
1163        updateSceneModeUI();
1164    }
1165
1166    private void attachHeadUpDisplay() {
1167        mHeadUpDisplay.setOrientation(mOrientationCompensation);
1168        if (mParameters.isZoomSupported()) {
1169            mHeadUpDisplay.setZoomIndex(mZoomValue);
1170        }
1171        ViewGroup frame = (ViewGroup) findViewById(R.id.frame);
1172        mGLRootView = new GLRootView(this);
1173        mGLRootView.setContentPane(mHeadUpDisplay);
1174        frame.addView(mGLRootView);
1175    }
1176
1177    private void detachHeadUpDisplay() {
1178        mHeadUpDisplay.collapse();
1179        ((ViewGroup) mGLRootView.getParent()).removeView(mGLRootView);
1180        mGLRootView = null;
1181    }
1182
1183    private boolean collapseCameraControls() {
1184        if (mHeadUpDisplay != null && mHeadUpDisplay.collapse()) {
1185            return true;
1186        }
1187        if (mIndicatorWheel != null && mIndicatorWheel.dismissSettingPopup()) {
1188            return true;
1189        }
1190        return false;
1191    }
1192
1193    private void enableCameraControls(boolean enable) {
1194        if (mHeadUpDisplay != null) mHeadUpDisplay.setEnabled(enable);
1195        if (mIndicatorWheel != null) mIndicatorWheel.setEnabled(enable);
1196        if (mCameraPicker != null) mCameraPicker.setEnabled(enable);
1197        if (mZoomPicker != null) mZoomPicker.setEnabled(enable);
1198        if (mSwitcher != null) mSwitcher.setEnabled(enable);
1199    }
1200
1201    public static int roundOrientation(int orientation) {
1202        return ((orientation + 45) / 90 * 90) % 360;
1203    }
1204
1205    private class MyOrientationEventListener
1206            extends OrientationEventListener {
1207        public MyOrientationEventListener(Context context) {
1208            super(context);
1209        }
1210
1211        @Override
1212        public void onOrientationChanged(int orientation) {
1213            // We keep the last known orientation. So if the user first orient
1214            // the camera then point the camera to floor or sky, we still have
1215            // the correct orientation.
1216            if (orientation == ORIENTATION_UNKNOWN) return;
1217            mOrientation = roundOrientation(orientation);
1218            // When the screen is unlocked, display rotation may change. Always
1219            // calculate the up-to-date orientationCompensation.
1220            int orientationCompensation = mOrientation
1221                    + Util.getDisplayRotation(Camera.this);
1222            if (mOrientationCompensation != orientationCompensation) {
1223                mOrientationCompensation = orientationCompensation;
1224                if (!mIsImageCaptureIntent) {
1225                    setOrientationIndicator(mOrientationCompensation);
1226                }
1227            }
1228        }
1229    }
1230
1231    private void setOrientationIndicator(int degree) {
1232        if (mHeadUpDisplay != null) mHeadUpDisplay.setOrientation(mOrientationCompensation);
1233        if (mThumbnailButton != null) mThumbnailButton.setDegree(degree);
1234        if (mShareButton != null) mShareButton.setDegree(degree);
1235        if (mCameraSwitchIcon != null) mCameraSwitchIcon.setDegree(degree);
1236        if (mVideoSwitchIcon != null) mVideoSwitchIcon.setDegree(degree);
1237        if (mSharePopup != null) mSharePopup.setOrientation(degree);
1238    }
1239
1240    @Override
1241    public void onStop() {
1242        super.onStop();
1243        if (mMediaProviderClient != null) {
1244            mMediaProviderClient.release();
1245            mMediaProviderClient = null;
1246        }
1247    }
1248
1249    private void checkStorage() {
1250        mPicturesRemaining = Storage.getAvailableSpace();
1251        if (mPicturesRemaining > 0) {
1252            mPicturesRemaining /= 1500000;
1253        }
1254        updateStorageHint();
1255    }
1256
1257    public void onClick(View v) {
1258        switch (v.getId()) {
1259            case R.id.btn_retake:
1260                hidePostCaptureAlert();
1261                startPreview();
1262                break;
1263            case R.id.review_thumbnail:
1264                if (isCameraIdle() && mThumbnail != null) {
1265                    Util.viewUri(mThumbnail.getUri(), this);
1266                }
1267                break;
1268            case R.id.btn_done:
1269                doAttach();
1270                break;
1271            case R.id.btn_cancel:
1272                doCancel();
1273                break;
1274            case R.id.btn_gallery:
1275                gotoGallery();
1276                break;
1277            case R.id.btn_share:
1278                if (isCameraIdle() && mThumbnail != null) {
1279                    createSharePopup();
1280                }
1281                break;
1282        }
1283    }
1284
1285    private void doAttach() {
1286        if (mPausing) {
1287            return;
1288        }
1289
1290        byte[] data = mJpegImageData;
1291
1292        if (mCropValue == null) {
1293            // First handle the no crop case -- just return the value.  If the
1294            // caller specifies a "save uri" then write the data to it's
1295            // stream. Otherwise, pass back a scaled down version of the bitmap
1296            // directly in the extras.
1297            if (mSaveUri != null) {
1298                OutputStream outputStream = null;
1299                try {
1300                    outputStream = mContentResolver.openOutputStream(mSaveUri);
1301                    outputStream.write(data);
1302                    outputStream.close();
1303
1304                    setResultEx(RESULT_OK);
1305                    finish();
1306                } catch (IOException ex) {
1307                    // ignore exception
1308                } finally {
1309                    Util.closeSilently(outputStream);
1310                }
1311            } else {
1312                int orientation = Exif.getOrientation(data);
1313                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1314                bitmap = Util.rotate(bitmap, orientation);
1315                setResultEx(RESULT_OK,
1316                        new Intent("inline-data").putExtra("data", bitmap));
1317                finish();
1318            }
1319        } else {
1320            // Save the image to a temp file and invoke the cropper
1321            Uri tempUri = null;
1322            FileOutputStream tempStream = null;
1323            try {
1324                File path = getFileStreamPath(sTempCropFilename);
1325                path.delete();
1326                tempStream = openFileOutput(sTempCropFilename, 0);
1327                tempStream.write(data);
1328                tempStream.close();
1329                tempUri = Uri.fromFile(path);
1330            } catch (FileNotFoundException ex) {
1331                setResultEx(Activity.RESULT_CANCELED);
1332                finish();
1333                return;
1334            } catch (IOException ex) {
1335                setResultEx(Activity.RESULT_CANCELED);
1336                finish();
1337                return;
1338            } finally {
1339                Util.closeSilently(tempStream);
1340            }
1341
1342            Bundle newExtras = new Bundle();
1343            if (mCropValue.equals("circle")) {
1344                newExtras.putString("circleCrop", "true");
1345            }
1346            if (mSaveUri != null) {
1347                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1348            } else {
1349                newExtras.putBoolean("return-data", true);
1350            }
1351
1352            Intent cropIntent = new Intent("com.android.camera.action.CROP");
1353
1354            cropIntent.setData(tempUri);
1355            cropIntent.putExtras(newExtras);
1356
1357            startActivityForResult(cropIntent, CROP_MSG);
1358        }
1359    }
1360
1361    private void doCancel() {
1362        setResultEx(RESULT_CANCELED, new Intent());
1363        finish();
1364    }
1365
1366    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
1367        if (mPausing) {
1368            return;
1369        }
1370        switch (button.getId()) {
1371            case R.id.shutter_button:
1372                doFocus(pressed);
1373                break;
1374        }
1375    }
1376
1377    public void onShutterButtonClick(ShutterButton button) {
1378        if (mPausing) {
1379            return;
1380        }
1381        switch (button.getId()) {
1382            case R.id.shutter_button:
1383                doSnap();
1384                break;
1385        }
1386    }
1387
1388    private OnScreenHint mStorageHint;
1389
1390    private void updateStorageHint() {
1391        String noStorageText = null;
1392
1393        if (mPicturesRemaining == Storage.UNAVAILABLE) {
1394            noStorageText = getString(R.string.no_storage);
1395        } else if (mPicturesRemaining == Storage.PREPARING) {
1396            noStorageText = getString(R.string.preparing_sd);
1397        } else if (mPicturesRemaining == Storage.UNKNOWN_SIZE) {
1398            noStorageText = getString(R.string.access_sd_fail);
1399        } else if (mPicturesRemaining < 1L) {
1400            noStorageText = getString(R.string.not_enough_space);
1401        }
1402
1403        if (noStorageText != null) {
1404            if (mStorageHint == null) {
1405                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1406            } else {
1407                mStorageHint.setText(noStorageText);
1408            }
1409            mStorageHint.show();
1410        } else if (mStorageHint != null) {
1411            mStorageHint.cancel();
1412            mStorageHint = null;
1413        }
1414    }
1415
1416    private void installIntentFilter() {
1417        // install an intent filter to receive SD card related events.
1418        IntentFilter intentFilter =
1419                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1420        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1421        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1422        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1423        intentFilter.addDataScheme("file");
1424        registerReceiver(mReceiver, intentFilter);
1425        mDidRegister = true;
1426    }
1427
1428    private void initializeFocusTone() {
1429        // Initialize focus tone generator.
1430        try {
1431            mFocusToneGenerator = new ToneGenerator(
1432                    AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1433        } catch (Throwable ex) {
1434            Log.w(TAG, "Exception caught while creating tone generator: ", ex);
1435            mFocusToneGenerator = null;
1436        }
1437    }
1438
1439    private void initializeScreenBrightness() {
1440        Window win = getWindow();
1441        // Overright the brightness settings if it is automatic
1442        int mode = Settings.System.getInt(
1443                getContentResolver(),
1444                Settings.System.SCREEN_BRIGHTNESS_MODE,
1445                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
1446        if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
1447            WindowManager.LayoutParams winParams = win.getAttributes();
1448            winParams.screenBrightness = DEFAULT_CAMERA_BRIGHTNESS;
1449            win.setAttributes(winParams);
1450        }
1451    }
1452
1453    @Override
1454    protected void onResume() {
1455        super.onResume();
1456        mPausing = false;
1457        if (mOpenCameraFail || mCameraDisabled) return;
1458
1459        mJpegPictureCallbackTime = 0;
1460        mZoomValue = 0;
1461
1462        // Start the preview if it is not started.
1463        if (mCameraState == PREVIEW_STOPPED) {
1464            try {
1465                mCameraDevice = Util.openCamera(this, mCameraId);
1466                mInitialParams = mCameraDevice.getParameters();
1467                resetExposureCompensation();
1468                startPreview();
1469            } catch(CameraHardwareException e) {
1470                Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1471                return;
1472            } catch(CameraDisabledException e) {
1473                Util.showErrorAndFinish(this, R.string.camera_disabled);
1474                return;
1475            }
1476        }
1477
1478        if (mSurfaceHolder != null) {
1479            // If first time initialization is not finished, put it in the
1480            // message queue.
1481            if (!mFirstTimeInitialized) {
1482                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1483            } else {
1484                initializeSecondTime();
1485            }
1486        }
1487        keepScreenOnAwhile();
1488
1489        if (mCameraState == IDLE) {
1490            mOnResumeTime = SystemClock.uptimeMillis();
1491            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
1492        }
1493    }
1494
1495    @Override
1496    public void onConfigurationChanged(Configuration config) {
1497        super.onConfigurationChanged(config);
1498        changeHeadUpDisplayState();
1499    }
1500
1501    @Override
1502    protected void onPause() {
1503        mPausing = true;
1504        stopPreview();
1505        // Close the camera now because other activities may need to use it.
1506        closeCamera();
1507        resetScreenOn();
1508        collapseCameraControls();
1509        changeHeadUpDisplayState();
1510
1511        if (mFirstTimeInitialized) {
1512            mOrientationListener.disable();
1513            if (!mIsImageCaptureIntent) {
1514                if (mThumbnail != null) {
1515                    mThumbnail.saveTo(new File(getFilesDir(), LAST_THUMB_FILENAME));
1516                }
1517            }
1518            hidePostCaptureAlert();
1519        }
1520
1521        dismissSharePopup();
1522
1523        if (mDidRegister) {
1524            unregisterReceiver(mReceiver);
1525            mDidRegister = false;
1526        }
1527        stopReceivingLocationUpdates();
1528
1529        if (mFocusToneGenerator != null) {
1530            mFocusToneGenerator.release();
1531            mFocusToneGenerator = null;
1532        }
1533
1534        if (mStorageHint != null) {
1535            mStorageHint.cancel();
1536            mStorageHint = null;
1537        }
1538
1539        // If we are in an image capture intent and has taken
1540        // a picture, we just clear it in onPause.
1541        mJpegImageData = null;
1542
1543        // Remove the messages in the event queue.
1544        mHandler.removeMessages(RESTART_PREVIEW);
1545        mHandler.removeMessages(FIRST_TIME_INIT);
1546        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1547        mHandler.removeMessages(RESET_TOUCH_FOCUS);
1548
1549        super.onPause();
1550    }
1551
1552    @Override
1553    protected void onActivityResult(
1554            int requestCode, int resultCode, Intent data) {
1555        switch (requestCode) {
1556            case CROP_MSG: {
1557                Intent intent = new Intent();
1558                if (data != null) {
1559                    Bundle extras = data.getExtras();
1560                    if (extras != null) {
1561                        intent.putExtras(extras);
1562                    }
1563                }
1564                setResultEx(resultCode, intent);
1565                finish();
1566
1567                File path = getFileStreamPath(sTempCropFilename);
1568                path.delete();
1569
1570                break;
1571            }
1572        }
1573    }
1574
1575    private boolean canTakePicture() {
1576        return isCameraIdle() && (mPicturesRemaining > 0);
1577    }
1578
1579    private void autoFocus() {
1580        Log.v(TAG, "Start autofocus.");
1581        mFocusStartTime = System.currentTimeMillis();
1582        mCameraDevice.autoFocus(mAutoFocusCallback);
1583        mCameraState = FOCUSING;
1584        enableCameraControls(false);
1585        updateFocusUI();
1586        mHandler.removeMessages(RESET_TOUCH_FOCUS);
1587    }
1588
1589    private void cancelAutoFocus() {
1590        Log.v(TAG, "Cancel autofocus.");
1591        mCameraDevice.cancelAutoFocus();
1592        mCameraState = IDLE;
1593        enableCameraControls(true);
1594        resetTouchFocus();
1595        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1596        updateFocusUI();
1597        mHandler.removeMessages(RESET_TOUCH_FOCUS);
1598    }
1599
1600    private void updateFocusUI() {
1601        if (mCameraState == FOCUSING || mCameraState == FOCUSING_SNAP_ON_FINISH) {
1602            mFocusRectangle.showStart();
1603        } else if (mCameraState == FOCUS_SUCCESS) {
1604            mFocusRectangle.showSuccess();
1605        } else if (mCameraState == FOCUS_FAIL) {
1606            mFocusRectangle.showFail();
1607        } else if (mCameraState == IDLE && mFocusArea != null) {
1608            // Users touch on the preview and the rectangle indicates the metering area.
1609            // Either focus area is not supported or autoFocus call is not required.
1610            mFocusRectangle.showStart();
1611        } else {
1612            mFocusRectangle.clear();
1613        }
1614    }
1615
1616    // Preview area is touched. Handle touch focus.
1617    @Override
1618    public boolean onTouch(View v, MotionEvent e) {
1619        if (mPausing || !mFirstTimeInitialized || mCameraState == SNAPSHOT_IN_PROGRESS
1620                || mCameraState == FOCUSING_SNAP_ON_FINISH) {
1621            return false;
1622        }
1623
1624        // Do not trigger touch focus if popup window is opened.
1625        if (collapseCameraControls()) return false;
1626
1627        // Check if metering area or focus area is supported.
1628        boolean focusAreaSupported = (mParameters.getMaxNumFocusAreas() > 0
1629                && (mFocusMode.equals(Parameters.FOCUS_MODE_AUTO) ||
1630                    mFocusMode.equals(Parameters.FOCUS_MODE_MACRO) ||
1631                    mFocusMode.equals(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)));
1632        boolean meteringAreaSupported = (mParameters.getMaxNumMeteringAreas() > 0);
1633        if (!focusAreaSupported && !meteringAreaSupported) return false;
1634
1635        boolean callAutoFocusRequired = false;
1636        if (focusAreaSupported &&
1637                (mFocusMode.equals(Parameters.FOCUS_MODE_AUTO) ||
1638                 mFocusMode.equals(Parameters.FOCUS_MODE_MACRO))) {
1639            callAutoFocusRequired = true;
1640        }
1641
1642        // Let users be able to cancel previous touch focus.
1643        if (callAutoFocusRequired && mFocusArea != null && e.getAction() == MotionEvent.ACTION_DOWN
1644                && (mCameraState == FOCUSING || mCameraState == FOCUS_SUCCESS ||
1645                    mCameraState == FOCUS_FAIL)) {
1646            cancelAutoFocus();
1647        }
1648
1649        // Initialize variables.
1650        int x = Math.round(e.getX());
1651        int y = Math.round(e.getY());
1652        int focusWidth = mFocusRectangle.getWidth();
1653        int focusHeight = mFocusRectangle.getHeight();
1654        int previewWidth = mPreviewFrame.getWidth();
1655        int previewHeight = mPreviewFrame.getHeight();
1656        if (mFocusArea == null) {
1657            mFocusArea = new ArrayList<Area>();
1658            mFocusArea.add(new Area(new Rect(), 1));
1659        }
1660
1661        // Convert the coordinates to driver format. The actual focus area is two times bigger than
1662        // UI because a huge rectangle looks strange.
1663        int areaWidth = focusWidth * 2;
1664        int areaHeight = focusHeight * 2;
1665        int areaLeft = Util.clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
1666        int areaTop = Util.clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);
1667        Log.d(TAG, "x=" + x + ". y=" + y);
1668        Log.d(TAG, "Focus area left=" + areaLeft + ". top=" + areaTop);
1669        Log.d(TAG, "Preview width=" + previewWidth + ". height=" + previewHeight);
1670        Log.d(TAG, "focusWidth=" + focusWidth + ". focusHeight=" + focusHeight);
1671        Rect rect = mFocusArea.get(0).rect;
1672        convertToFocusArea(areaLeft, areaTop, areaWidth, areaHeight, previewWidth, previewHeight,
1673                mFocusArea.get(0).rect);
1674
1675        // Use margin to set the focus rectangle to the touched area.
1676        RelativeLayout.LayoutParams p =
1677                (RelativeLayout.LayoutParams) mFocusRectangle.getLayoutParams();
1678        int left = Util.clamp(x - focusWidth / 2, 0, previewWidth - focusWidth);
1679        int top = Util.clamp(y - focusHeight / 2, 0, previewHeight - focusHeight);
1680        p.setMargins(left, top, 0, 0);
1681        // Disable "center" rule because we no longer want to put it in the center.
1682        int[] rules = p.getRules();
1683        rules[RelativeLayout.CENTER_IN_PARENT] = 0;
1684        mFocusRectangle.requestLayout();
1685
1686        // Set the focus area and metering area.
1687        setCameraParameters(UPDATE_PARAM_PREFERENCE);
1688        if (callAutoFocusRequired && e.getAction() == MotionEvent.ACTION_UP) {
1689            autoFocus();
1690        } else {  // Just show the rectangle in all other cases.
1691            updateFocusUI();
1692            // Reset the metering area in 3 seconds.
1693            mHandler.removeMessages(RESET_TOUCH_FOCUS);
1694            mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
1695        }
1696
1697        return true;
1698    }
1699
1700    // Convert the touch point to the focus area in driver format.
1701    public static void convertToFocusArea(int left, int top, int focusWidth, int focusHeight,
1702            int previewWidth, int previewHeight, Rect rect) {
1703        rect.left = Math.round((float) left / previewWidth * 2000 - 1000);
1704        rect.top = Math.round((float) top / previewHeight * 2000 - 1000);
1705        rect.right = Math.round((float) (left + focusWidth) / previewWidth * 2000 - 1000);
1706        rect.bottom = Math.round((float) (top + focusHeight) / previewHeight * 2000 - 1000);
1707    }
1708
1709    void resetTouchFocus() {
1710        // Put focus rectangle to the center.
1711        RelativeLayout.LayoutParams p =
1712                (RelativeLayout.LayoutParams) mFocusRectangle.getLayoutParams();
1713        int[] rules = p.getRules();
1714        rules[RelativeLayout.CENTER_IN_PARENT] = RelativeLayout.TRUE;
1715        p.setMargins(0, 0, 0, 0);
1716
1717        mFocusArea = null;
1718    }
1719
1720    @Override
1721    public void onBackPressed() {
1722        if (!isCameraIdle()) {
1723            // ignore backs while we're taking a picture
1724            return;
1725        } else if (!collapseCameraControls()) {
1726            super.onBackPressed();
1727        }
1728    }
1729
1730    @Override
1731    public boolean onKeyDown(int keyCode, KeyEvent event) {
1732        switch (keyCode) {
1733            case KeyEvent.KEYCODE_FOCUS:
1734                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1735                    doFocus(true);
1736                }
1737                return true;
1738            case KeyEvent.KEYCODE_CAMERA:
1739                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1740                    doSnap();
1741                }
1742                return true;
1743            case KeyEvent.KEYCODE_DPAD_CENTER:
1744                // If we get a dpad center event without any focused view, move
1745                // the focus to the shutter button and press it.
1746                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1747                    // Start auto-focus immediately to reduce shutter lag. After
1748                    // the shutter button gets the focus, doFocus() will be
1749                    // called again but it is fine.
1750                    if (collapseCameraControls()) return true;
1751                    doFocus(true);
1752                    if (mShutterButton.isInTouchMode()) {
1753                        mShutterButton.requestFocusFromTouch();
1754                    } else {
1755                        mShutterButton.requestFocus();
1756                    }
1757                    mShutterButton.setPressed(true);
1758                }
1759                return true;
1760        }
1761
1762        return super.onKeyDown(keyCode, event);
1763    }
1764
1765    @Override
1766    public boolean onKeyUp(int keyCode, KeyEvent event) {
1767        switch (keyCode) {
1768            case KeyEvent.KEYCODE_FOCUS:
1769                if (mFirstTimeInitialized) {
1770                    doFocus(false);
1771                }
1772                return true;
1773        }
1774        return super.onKeyUp(keyCode, event);
1775    }
1776
1777    private void doSnap() {
1778        if (collapseCameraControls()) return;
1779
1780        Log.v(TAG, "doSnap: mCameraState=" + mCameraState);
1781        // If the user has half-pressed the shutter and focus is completed, we
1782        // can take the photo right away. If the focus mode is infinity, we can
1783        // also take the photo.
1784        if (mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)
1785                || mFocusMode.equals(Parameters.FOCUS_MODE_FIXED)
1786                || mFocusMode.equals(Parameters.FOCUS_MODE_EDOF)
1787                || (mCameraState == FOCUS_SUCCESS
1788                || mCameraState == FOCUS_FAIL)) {
1789            capture();
1790        } else if (mCameraState == FOCUSING) {
1791            // Half pressing the shutter (i.e. the focus button event) will
1792            // already have requested AF for us, so just request capture on
1793            // focus here.
1794            mCameraState = FOCUSING_SNAP_ON_FINISH;
1795        } else if (mCameraState == IDLE) {
1796            // Focus key down event is dropped for some reasons. Just ignore.
1797        }
1798    }
1799
1800    private void doFocus(boolean pressed) {
1801        // Do the focus if the mode is not infinity.
1802        if (collapseCameraControls()) return;
1803        if (!(mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)
1804                  || mFocusMode.equals(Parameters.FOCUS_MODE_FIXED)
1805                  || mFocusMode.equals(Parameters.FOCUS_MODE_EDOF))) {
1806            if (pressed) {  // Focus key down.
1807                // Do not do focus if there is not enoguh storage. Do not focus
1808                // if touch focus has been triggered, that is, camera state is
1809                // FOCUS_SUCCESS or FOCUS_FAIL.
1810                if (canTakePicture() && mCameraState != FOCUS_SUCCESS
1811                        && mCameraState != FOCUS_FAIL) {
1812                    autoFocus();
1813                }
1814            } else {  // Focus key up.
1815                // User releases half-pressed focus key.
1816                if (mCameraState == FOCUSING || mCameraState == FOCUS_SUCCESS
1817                        || mCameraState == FOCUS_FAIL) {
1818                    cancelAutoFocus();
1819                }
1820            }
1821        }
1822    }
1823
1824    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1825        // Make sure we have a surface in the holder before proceeding.
1826        if (holder.getSurface() == null) {
1827            Log.d(TAG, "holder.getSurface() == null");
1828            return;
1829        }
1830
1831        Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1832
1833        // We need to save the holder for later use, even when the mCameraDevice
1834        // is null. This could happen if onResume() is invoked after this
1835        // function.
1836        mSurfaceHolder = holder;
1837
1838        // The mCameraDevice will be null if it fails to connect to the camera
1839        // hardware. In this case we will show a dialog and then finish the
1840        // activity, so it's OK to ignore it.
1841        if (mCameraDevice == null) return;
1842
1843        // Sometimes surfaceChanged is called after onPause or before onResume.
1844        // Ignore it.
1845        if (mPausing || isFinishing()) return;
1846
1847        // Set preview display if the surface is being created. Preview was
1848        // already started. Also restart the preview if display rotation has
1849        // changed. Sometimes this happens when the device is held in portrait
1850        // and camera app is opened. Rotation animation takes some time and
1851        // display rotation in onCreate may not be what we want.
1852        if (mCameraState != PREVIEW_STOPPED
1853                && (Util.getDisplayRotation(this) == mDisplayRotation)
1854                && holder.isCreating()) {
1855            // Set preview display if the surface is being created and preview
1856            // was already started. That means preview display was set to null
1857            // and we need to set it now.
1858            setPreviewDisplay(holder);
1859        } else {
1860            // 1. Restart the preview if the size of surface was changed. The
1861            // framework may not support changing preview display on the fly.
1862            // 2. Start the preview now if surface was destroyed and preview
1863            // stopped.
1864            startPreview();
1865        }
1866
1867        // If first time initialization is not finished, send a message to do
1868        // it later. We want to finish surfaceChanged as soon as possible to let
1869        // user see preview first.
1870        if (!mFirstTimeInitialized) {
1871            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1872        } else {
1873            initializeSecondTime();
1874        }
1875    }
1876
1877    public void surfaceCreated(SurfaceHolder holder) {
1878    }
1879
1880    public void surfaceDestroyed(SurfaceHolder holder) {
1881        stopPreview();
1882        mSurfaceHolder = null;
1883    }
1884
1885    private void closeCamera() {
1886        if (mCameraDevice != null) {
1887            CameraHolder.instance().release();
1888            mCameraDevice.setZoomChangeListener(null);
1889            mCameraDevice = null;
1890            mCameraState = PREVIEW_STOPPED;
1891        }
1892    }
1893
1894    private void setPreviewDisplay(SurfaceHolder holder) {
1895        try {
1896            mCameraDevice.setPreviewDisplay(holder);
1897        } catch (Throwable ex) {
1898            closeCamera();
1899            throw new RuntimeException("setPreviewDisplay failed", ex);
1900        }
1901    }
1902
1903    private void startPreview() {
1904        if (mPausing || isFinishing()) return;
1905
1906        resetTouchFocus();
1907
1908        mCameraDevice.setErrorCallback(mErrorCallback);
1909
1910        // If we're previewing already, stop the preview first (this will blank
1911        // the screen).
1912        if (mCameraState != PREVIEW_STOPPED) stopPreview();
1913
1914        setPreviewDisplay(mSurfaceHolder);
1915        mDisplayRotation = Util.getDisplayRotation(this);
1916        Util.setCameraDisplayOrientation(mDisplayRotation, mCameraId, mCameraDevice);
1917        setCameraParameters(UPDATE_PARAM_ALL);
1918
1919
1920        try {
1921            Log.v(TAG, "startPreview");
1922            mCameraDevice.startPreview();
1923        } catch (Throwable ex) {
1924            closeCamera();
1925            throw new RuntimeException("startPreview failed", ex);
1926        }
1927        mZoomState = ZOOM_STOPPED;
1928        mCameraState = IDLE;
1929    }
1930
1931    private void stopPreview() {
1932        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1933            Log.v(TAG, "stopPreview");
1934            mCameraDevice.stopPreview();
1935        }
1936        mCameraState = PREVIEW_STOPPED;
1937        // If auto focus was in progress, it would have been canceled.
1938        updateFocusUI();
1939    }
1940
1941    private static boolean isSupported(String value, List<String> supported) {
1942        return supported == null ? false : supported.indexOf(value) >= 0;
1943    }
1944
1945    private void updateCameraParametersInitialize() {
1946        // Reset preview frame rate to the maximum because it may be lowered by
1947        // video camera application.
1948        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1949        if (frameRates != null) {
1950            Integer max = Collections.max(frameRates);
1951            mParameters.setPreviewFrameRate(max);
1952        }
1953
1954    }
1955
1956    private void updateCameraParametersZoom() {
1957        // Set zoom.
1958        if (mParameters.isZoomSupported()) {
1959            mParameters.setZoom(mZoomValue);
1960        }
1961    }
1962
1963    private void updateCameraParametersPreference() {
1964        if (mParameters.getMaxNumFocusAreas() > 0) {
1965            mParameters.setFocusAreas(mFocusArea);
1966            Log.d(TAG, "Parameter focus areas=" + mParameters.get("focus-areas"));
1967        }
1968
1969        if (mParameters.getMaxNumMeteringAreas() > 0) {
1970            // Use the same area for focus and metering.
1971            mParameters.setMeteringAreas(mFocusArea);
1972        }
1973
1974        // Set picture size.
1975        String pictureSize = mPreferences.getString(
1976                CameraSettings.KEY_PICTURE_SIZE, null);
1977        if (pictureSize == null) {
1978            CameraSettings.initialCameraPictureSize(this, mParameters);
1979        } else {
1980            List<Size> supported = mParameters.getSupportedPictureSizes();
1981            CameraSettings.setCameraPictureSize(
1982                    pictureSize, supported, mParameters);
1983        }
1984
1985        // Set the preview frame aspect ratio according to the picture size.
1986        Size size = mParameters.getPictureSize();
1987        PreviewFrameLayout frameLayout =
1988                (PreviewFrameLayout) findViewById(R.id.frame_layout);
1989        frameLayout.setAspectRatio((double) size.width / size.height);
1990
1991        // Set a preview size that is closest to the viewfinder height and has
1992        // the right aspect ratio.
1993        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1994        Size optimalSize = Util.getOptimalPreviewSize(this,
1995                sizes, (double) size.width / size.height);
1996        Size original = mParameters.getPreviewSize();
1997        if (!original.equals(optimalSize)) {
1998            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1999
2000            // Zoom related settings will be changed for different preview
2001            // sizes, so set and read the parameters to get lastest values
2002            mCameraDevice.setParameters(mParameters);
2003            mParameters = mCameraDevice.getParameters();
2004        }
2005        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
2006
2007        // Since change scene mode may change supported values,
2008        // Set scene mode first,
2009        mSceneMode = mPreferences.getString(
2010                CameraSettings.KEY_SCENE_MODE,
2011                getString(R.string.pref_camera_scenemode_default));
2012        if (isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
2013            if (!mParameters.getSceneMode().equals(mSceneMode)) {
2014                mParameters.setSceneMode(mSceneMode);
2015                mCameraDevice.setParameters(mParameters);
2016
2017                // Setting scene mode will change the settings of flash mode,
2018                // white balance, and focus mode. Here we read back the
2019                // parameters, so we can know those settings.
2020                mParameters = mCameraDevice.getParameters();
2021            }
2022        } else {
2023            mSceneMode = mParameters.getSceneMode();
2024            if (mSceneMode == null) {
2025                mSceneMode = Parameters.SCENE_MODE_AUTO;
2026            }
2027        }
2028
2029        // Set JPEG quality.
2030        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2031                CameraProfile.QUALITY_HIGH);
2032        mParameters.setJpegQuality(jpegQuality);
2033
2034        // For the following settings, we need to check if the settings are
2035        // still supported by latest driver, if not, ignore the settings.
2036
2037        // Set color effect parameter.
2038        String colorEffect = mPreferences.getString(
2039                CameraSettings.KEY_COLOR_EFFECT,
2040                getString(R.string.pref_camera_coloreffect_default));
2041        if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
2042            mParameters.setColorEffect(colorEffect);
2043        }
2044
2045        // Set exposure compensation
2046        String exposure = mPreferences.getString(
2047                CameraSettings.KEY_EXPOSURE,
2048                getString(R.string.pref_exposure_default));
2049        try {
2050            int value = Integer.parseInt(exposure);
2051            int max = mParameters.getMaxExposureCompensation();
2052            int min = mParameters.getMinExposureCompensation();
2053            if (value >= min && value <= max) {
2054                mParameters.setExposureCompensation(value);
2055            } else {
2056                Log.w(TAG, "invalid exposure range: " + exposure);
2057            }
2058        } catch (NumberFormatException e) {
2059            Log.w(TAG, "invalid exposure: " + exposure);
2060        }
2061
2062        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
2063            // Set flash mode.
2064            String flashMode = mPreferences.getString(
2065                    CameraSettings.KEY_FLASH_MODE,
2066                    getString(R.string.pref_camera_flashmode_default));
2067            List<String> supportedFlash = mParameters.getSupportedFlashModes();
2068            if (isSupported(flashMode, supportedFlash)) {
2069                mParameters.setFlashMode(flashMode);
2070            } else {
2071                flashMode = mParameters.getFlashMode();
2072                if (flashMode == null) {
2073                    flashMode = getString(
2074                            R.string.pref_camera_flashmode_no_flash);
2075                }
2076            }
2077
2078            // Set white balance parameter.
2079            String whiteBalance = mPreferences.getString(
2080                    CameraSettings.KEY_WHITE_BALANCE,
2081                    getString(R.string.pref_camera_whitebalance_default));
2082            if (isSupported(whiteBalance,
2083                    mParameters.getSupportedWhiteBalance())) {
2084                mParameters.setWhiteBalance(whiteBalance);
2085            } else {
2086                whiteBalance = mParameters.getWhiteBalance();
2087                if (whiteBalance == null) {
2088                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
2089                }
2090            }
2091
2092            // Set focus mode.
2093            mFocusMode = mPreferences.getString(
2094                    CameraSettings.KEY_FOCUS_MODE,
2095                    getString(R.string.pref_camera_focusmode_default));
2096            if (isSupported(mFocusMode, mParameters.getSupportedFocusModes())) {
2097                mParameters.setFocusMode(mFocusMode);
2098            } else {
2099                mFocusMode = mParameters.getFocusMode();
2100                if (mFocusMode == null) {
2101                    mFocusMode = Parameters.FOCUS_MODE_AUTO;
2102                }
2103            }
2104        } else {
2105            mFocusMode = mParameters.getFocusMode();
2106        }
2107    }
2108
2109    // We separate the parameters into several subsets, so we can update only
2110    // the subsets actually need updating. The PREFERENCE set needs extra
2111    // locking because the preference can be changed from GLThread as well.
2112    private void setCameraParameters(int updateSet) {
2113        mParameters = mCameraDevice.getParameters();
2114
2115        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2116            updateCameraParametersInitialize();
2117        }
2118
2119        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2120            updateCameraParametersZoom();
2121        }
2122
2123        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2124            updateCameraParametersPreference();
2125        }
2126
2127        mCameraDevice.setParameters(mParameters);
2128    }
2129
2130    // If the Camera is idle, update the parameters immediately, otherwise
2131    // accumulate them in mUpdateSet and update later.
2132    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2133        mUpdateSet |= additionalUpdateSet;
2134        if (mCameraDevice == null) {
2135            // We will update all the parameters when we open the device, so
2136            // we don't need to do anything now.
2137            mUpdateSet = 0;
2138            return;
2139        } else if (isCameraIdle()) {
2140            setCameraParameters(mUpdateSet);
2141            updateSceneModeUI();
2142            mUpdateSet = 0;
2143        } else {
2144            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2145                mHandler.sendEmptyMessageDelayed(
2146                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2147            }
2148        }
2149    }
2150
2151    private void gotoGallery() {
2152        MenuHelper.gotoCameraImageGallery(this);
2153    }
2154
2155    private void startReceivingLocationUpdates() {
2156        if (mLocationManager != null) {
2157            try {
2158                mLocationManager.requestLocationUpdates(
2159                        LocationManager.NETWORK_PROVIDER,
2160                        1000,
2161                        0F,
2162                        mLocationListeners[1]);
2163            } catch (SecurityException ex) {
2164                Log.i(TAG, "fail to request location update, ignore", ex);
2165            } catch (IllegalArgumentException ex) {
2166                Log.d(TAG, "provider does not exist " + ex.getMessage());
2167            }
2168            try {
2169                mLocationManager.requestLocationUpdates(
2170                        LocationManager.GPS_PROVIDER,
2171                        1000,
2172                        0F,
2173                        mLocationListeners[0]);
2174                showGpsOnScreenIndicator(false);
2175            } catch (SecurityException ex) {
2176                Log.i(TAG, "fail to request location update, ignore", ex);
2177            } catch (IllegalArgumentException ex) {
2178                Log.d(TAG, "provider does not exist " + ex.getMessage());
2179            }
2180            Log.d(TAG, "startReceivingLocationUpdates");
2181        }
2182    }
2183
2184    private void stopReceivingLocationUpdates() {
2185        if (mLocationManager != null) {
2186            for (int i = 0; i < mLocationListeners.length; i++) {
2187                try {
2188                    mLocationManager.removeUpdates(mLocationListeners[i]);
2189                } catch (Exception ex) {
2190                    Log.i(TAG, "fail to remove location listners, ignore", ex);
2191                }
2192            }
2193            Log.d(TAG, "stopReceivingLocationUpdates");
2194        }
2195        hideGpsOnScreenIndicator();
2196    }
2197
2198    private Location getCurrentLocation() {
2199        // go in best to worst order
2200        for (int i = 0; i < mLocationListeners.length; i++) {
2201            Location l = mLocationListeners[i].current();
2202            if (l != null) return l;
2203        }
2204        Log.d(TAG, "No location received yet.");
2205        return null;
2206    }
2207
2208    private boolean isCameraIdle() {
2209        return mCameraState == IDLE || mCameraState == FOCUS_SUCCESS || mCameraState == FOCUS_FAIL;
2210    }
2211
2212    private boolean isImageCaptureIntent() {
2213        String action = getIntent().getAction();
2214        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
2215    }
2216
2217    private void setupCaptureParams() {
2218        Bundle myExtras = getIntent().getExtras();
2219        if (myExtras != null) {
2220            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2221            mCropValue = myExtras.getString("crop");
2222        }
2223    }
2224
2225    private void showPostCaptureAlert() {
2226        if (mIsImageCaptureIntent) {
2227            if (mIndicatorWheel == null) {
2228                mShutterButton.setVisibility(View.INVISIBLE);
2229            } else {
2230                mShutterButton.setEnabled(false);
2231            }
2232            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2233            for (int id : pickIds) {
2234                View button = findViewById(id);
2235                ((View) button.getParent()).setVisibility(View.VISIBLE);
2236            }
2237
2238            // Remove the text of the cancel button
2239            View view = findViewById(R.id.btn_cancel);
2240            if (view instanceof Button) ((Button) view).setText("");
2241        }
2242    }
2243
2244    private void hidePostCaptureAlert() {
2245        if (mIsImageCaptureIntent) {
2246            if (mIndicatorWheel == null) {
2247                mShutterButton.setVisibility(View.VISIBLE);
2248            } else {
2249                mShutterButton.setEnabled(true);
2250            }
2251            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2252            for (int id : pickIds) {
2253                View button = findViewById(id);
2254                ((View) button.getParent()).setVisibility(View.GONE);
2255            }
2256            enableCameraControls(true);
2257
2258            // Restore the text of the cancel button
2259            View view = findViewById(R.id.btn_cancel);
2260            if (view instanceof Button) {
2261                ((Button) view).setText(R.string.review_cancel);
2262            }
2263        }
2264    }
2265
2266    @Override
2267    public boolean onPrepareOptionsMenu(Menu menu) {
2268        super.onPrepareOptionsMenu(menu);
2269        // Only show the menu when camera is idle.
2270        for (int i = 0; i < menu.size(); i++) {
2271            menu.getItem(i).setVisible(isCameraIdle());
2272        }
2273
2274        return true;
2275    }
2276
2277    @Override
2278    public boolean onCreateOptionsMenu(Menu menu) {
2279        super.onCreateOptionsMenu(menu);
2280
2281        if (mIsImageCaptureIntent) {
2282            // No options menu for attach mode.
2283            return false;
2284        } else {
2285            addBaseMenuItems(menu);
2286        }
2287        return true;
2288    }
2289
2290    private void addBaseMenuItems(Menu menu) {
2291        MenuHelper.addSwitchModeMenuItem(menu, true, new Runnable() {
2292            public void run() {
2293                switchToVideoMode();
2294            }
2295        });
2296        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
2297                MenuHelper.POSITION_GOTO_GALLERY,
2298                R.string.camera_gallery_photos_text)
2299                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2300            public boolean onMenuItemClick(MenuItem item) {
2301                gotoGallery();
2302                return true;
2303            }
2304        });
2305        gallery.setIcon(android.R.drawable.ic_menu_gallery);
2306        mGalleryItems.add(gallery);
2307
2308        if (mNumberOfCameras > 1) {
2309            menu.add(Menu.NONE, Menu.NONE,
2310                    MenuHelper.POSITION_SWITCH_CAMERA_ID,
2311                    R.string.switch_camera_id)
2312                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2313                public boolean onMenuItemClick(MenuItem item) {
2314                    CameraSettings.writePreferredCameraId(mPreferences,
2315                            ((mCameraId == mFrontCameraId)
2316                            ? mBackCameraId : mFrontCameraId));
2317                    onSharedPreferenceChanged();
2318                    return true;
2319                }
2320            }).setIcon(android.R.drawable.ic_menu_camera);
2321        }
2322    }
2323
2324    private boolean switchToVideoMode() {
2325        if (isFinishing() || !isCameraIdle()) return false;
2326        MenuHelper.gotoVideoMode(Camera.this);
2327        mHandler.removeMessages(FIRST_TIME_INIT);
2328        finish();
2329        return true;
2330    }
2331
2332    public boolean onSwitchChanged(Switcher source, boolean onOff) {
2333        if (onOff == SWITCH_VIDEO) {
2334            return switchToVideoMode();
2335        } else {
2336            return true;
2337        }
2338    }
2339
2340    private void onSharedPreferenceChanged() {
2341        // ignore the events after "onPause()"
2342        if (mPausing) return;
2343
2344        boolean recordLocation;
2345
2346        recordLocation = RecordLocationPreference.get(
2347                mPreferences, getContentResolver());
2348
2349        if (mRecordLocation != recordLocation) {
2350            mRecordLocation = recordLocation;
2351            if (mRecordLocation) {
2352                startReceivingLocationUpdates();
2353            } else {
2354                stopReceivingLocationUpdates();
2355            }
2356        }
2357        int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
2358        if (mCameraId != cameraId) {
2359            // Restart the activity to have a crossfade animation.
2360            // TODO: Use SurfaceTexture to implement a better and faster
2361            // animation.
2362            if (mIsImageCaptureIntent) {
2363                // If the intent is camera capture, stay in camera capture mode.
2364                MenuHelper.gotoCameraMode(this, getIntent());
2365            } else {
2366                MenuHelper.gotoCameraMode(this);
2367            }
2368
2369            finish();
2370        } else {
2371            setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2372        }
2373    }
2374
2375    @Override
2376    public void onUserInteraction() {
2377        super.onUserInteraction();
2378        keepScreenOnAwhile();
2379    }
2380
2381    private void resetScreenOn() {
2382        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2383        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2384    }
2385
2386    private void keepScreenOnAwhile() {
2387        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2388        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2389        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2390    }
2391
2392    private class MyHeadUpDisplayListener implements HeadUpDisplay.Listener {
2393
2394        public void onSharedPreferenceChanged() {
2395            Camera.this.onSharedPreferenceChanged();
2396        }
2397
2398        public void onRestorePreferencesClicked() {
2399            Camera.this.onRestorePreferencesClicked();
2400        }
2401
2402        public void onPopupWindowVisibilityChanged(int visibility) {
2403        }
2404    }
2405
2406    protected void onRestorePreferencesClicked() {
2407        if (mPausing) return;
2408        Runnable runnable = new Runnable() {
2409            public void run() {
2410                restorePreferences();
2411            }
2412        };
2413        MenuHelper.confirmAction(this,
2414                getString(R.string.confirm_restore_title),
2415                getString(R.string.confirm_restore_message),
2416                runnable);
2417    }
2418
2419    private void restorePreferences() {
2420        // Reset the zoom. Zoom value is not stored in preference.
2421        if (mParameters.isZoomSupported()) {
2422            mZoomValue = 0;
2423            setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2424            if (mZoomPicker != null) mZoomPicker.setZoomIndex(0);
2425        }
2426
2427        if (mHeadUpDisplay != null) {
2428            mHeadUpDisplay.restorePreferences(mParameters);
2429        }
2430
2431        if (mIndicatorWheel != null) {
2432            mIndicatorWheel.dismissSettingPopup();
2433            CameraSettings.restorePreferences(Camera.this, mPreferences,
2434                    mParameters);
2435            initializeIndicatorWheel();
2436            onSharedPreferenceChanged();
2437        }
2438    }
2439
2440    protected void onOverriddenPreferencesClicked() {
2441        if (mPausing) return;
2442        if (mNotSelectableToast == null) {
2443            String str = getResources().getString(R.string.not_selectable_in_scene_mode);
2444            mNotSelectableToast = Toast.makeText(Camera.this, str, Toast.LENGTH_SHORT);
2445        }
2446        mNotSelectableToast.show();
2447    }
2448
2449    private void createSharePopup() {
2450        if (mSharePopup != null) mSharePopup.dismiss();
2451        mSharePopup = new SharePopup(this, mThumbnail.getUri(),
2452                mThumbnail.getBitmap(), mOrientationCompensation, mThumbnailButton);
2453        mSharePopup.showAtLocation(mThumbnailButton, Gravity.NO_GRAVITY, 0, 0);
2454    }
2455
2456    private void dismissSharePopup() {
2457        if (mSharePopup != null) {
2458            mSharePopup.dismiss();
2459            mSharePopup = null;
2460        }
2461    }
2462
2463    private void onShareButtonClicked() {
2464        if (mPausing) return;
2465
2466        // Share the last captured picture.
2467        if (mThumbnail != null) {
2468            Intent intent = new Intent(Intent.ACTION_SEND);
2469            intent.setType("image/jpeg");
2470            intent.putExtra(Intent.EXTRA_STREAM, mThumbnail.getUri());
2471            startActivity(Intent.createChooser(intent, getString(R.string.share_picture_via)));
2472        } else {  // No last picture
2473            if (mNoShareToast == null) {
2474                mNoShareToast = Toast.makeText(this,
2475                        getResources().getString(R.string.no_picture_to_share), Toast.LENGTH_SHORT);
2476            }
2477            mNoShareToast.show();
2478        }
2479    }
2480
2481    private class MyIndicatorWheelListener implements IndicatorWheel.Listener {
2482        public void onSharedPreferenceChanged() {
2483            Camera.this.onSharedPreferenceChanged();
2484        }
2485
2486        public void onRestorePreferencesClicked() {
2487            Camera.this.onRestorePreferencesClicked();
2488        }
2489
2490        public void onOverriddenPreferencesClicked() {
2491            Camera.this.onOverriddenPreferencesClicked();
2492        }
2493    }
2494
2495    private class MyCameraPickerListener implements CameraPicker.Listener {
2496        public void onSharedPreferenceChanged() {
2497            Camera.this.onSharedPreferenceChanged();
2498        }
2499    }
2500}
2501