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