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