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