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