Camera.java revision 3283e049aa080c375457de46bbf180a88899228a
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.Cancelable;
20import com.android.camera.gallery.IImage;
21import com.android.camera.gallery.IImageList;
22
23import android.app.Activity;
24import android.content.ActivityNotFoundException;
25import android.content.BroadcastReceiver;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.graphics.Bitmap;
32import android.graphics.BitmapFactory;
33import android.graphics.Matrix;
34import android.hardware.Camera.PictureCallback;
35import android.location.Location;
36import android.location.LocationManager;
37import android.location.LocationProvider;
38import android.media.AudioManager;
39import android.media.ToneGenerator;
40import android.net.Uri;
41import android.os.Bundle;
42import android.os.Debug;
43import android.os.Environment;
44import android.os.Handler;
45import android.os.Message;
46import android.os.SystemClock;
47import android.preference.PreferenceManager;
48import android.provider.MediaStore;
49import android.text.format.DateFormat;
50import android.util.AttributeSet;
51import android.util.Log;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.Menu;
55import android.view.MenuItem;
56import android.view.MotionEvent;
57import android.view.OrientationEventListener;
58import android.view.SurfaceHolder;
59import android.view.View;
60import android.view.ViewGroup;
61import android.view.Window;
62import android.view.WindowManager;
63import android.view.MenuItem.OnMenuItemClickListener;
64import android.widget.ImageView;
65import android.widget.Toast;
66import android.widget.ZoomButtonsController;
67
68import java.io.File;
69import java.io.FileNotFoundException;
70import java.io.FileOutputStream;
71import java.io.IOException;
72import java.io.OutputStream;
73import java.util.ArrayList;
74import java.util.StringTokenizer;
75
76/**
77 * Activity of the Camera which used to see preview and take pictures.
78 */
79public class Camera extends Activity implements View.OnClickListener,
80        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
81        Switcher.OnSwitchListener {
82
83    private static final String TAG = "camera";
84
85    private static final int CROP_MSG = 1;
86    private static final int FIRST_TIME_INIT = 2;
87    private static final int RESTART_PREVIEW = 3;
88    private static final int CLEAR_SCREEN_DELAY = 4;
89
90    private static final int SCREEN_DELAY = 2 * 60 * 1000;
91    private static final int FOCUS_BEEP_VOLUME = 100;
92
93    public static final int MENU_SWITCH_TO_VIDEO = 0;
94    public static final int MENU_SWITCH_TO_CAMERA = 1;
95    public static final int MENU_FLASH_SETTING = 2;
96    public static final int MENU_FLASH_AUTO = 3;
97    public static final int MENU_FLASH_ON = 4;
98    public static final int MENU_FLASH_OFF = 5;
99    public static final int MENU_SETTINGS = 6;
100    public static final int MENU_GALLERY_PHOTOS = 7;
101    public static final int MENU_GALLERY_VIDEOS = 8;
102    public static final int MENU_SAVE_SELECT_PHOTOS = 30;
103    public static final int MENU_SAVE_NEW_PHOTO = 31;
104    public static final int MENU_SAVE_GALLERY_PHOTO = 34;
105    public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35;
106    public static final int MENU_SAVE_CAMERA_DONE = 36;
107    public static final int MENU_SAVE_CAMERA_VIDEO_DONE = 37;
108
109    private android.hardware.Camera.Parameters mParameters;
110    private int mZoomIndex = 0;  // The index of the current zoom value.
111    private String[] mZoomValues;  // All possible zoom values.
112
113    // The parameter strings to communicate with camera driver.
114    public static final String PARM_ZOOM = "zoom";
115    public static final String PARM_PICTURE_SIZE = "picture-size";
116    public static final String PARM_JPEG_QUALITY = "jpeg-quality";
117    public static final String PARM_ROTATION = "rotation";
118    public static final String PARM_GPS_LATITUDE = "gps-latitude";
119    public static final String PARM_GPS_LONGITUDE = "gps-longitude";
120    public static final String PARM_GPS_ALTITUDE = "gps-altitude";
121    public static final String PARM_GPS_TIMESTAMP = "gps-timestamp";
122    public static final String SUPPORTED_ZOOM = "zoom-values";
123    public static final String SUPPORTED_PICTURE_SIZE = "picture-size-values";
124
125    private OrientationEventListener mOrientationListener;
126    private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
127    private SharedPreferences mPreferences;
128
129    private static final int IDLE = 1;
130    private static final int SNAPSHOT_IN_PROGRESS = 2;
131
132    private static final boolean SWITCH_CAMERA = true;
133    private static final boolean SWITCH_VIDEO = false;
134
135    private int mStatus = IDLE;
136    private static final String sTempCropFilename = "crop-temp";
137
138    private android.hardware.Camera mCameraDevice;
139    private VideoPreview mSurfaceView;
140    private SurfaceHolder mSurfaceHolder = null;
141    private ShutterButton mShutterButton;
142    private FocusRectangle mFocusRectangle;
143    private ImageView mGpsIndicator;
144    private ToneGenerator mFocusToneGenerator;
145    private ZoomButtonsController mZoomButtons;
146
147    // mPostCaptureAlert, mLastPictureButton, mThumbController
148    // are non-null only if isImageCaptureIntent() is true.
149    private ImageView mLastPictureButton;
150    private ThumbnailController mThumbController;
151
152    private int mViewFinderWidth, mViewFinderHeight;
153
154    private ImageCapture mImageCapture = null;
155
156    private boolean mPreviewing;
157    private boolean mPausing;
158    private boolean mFirstTimeInitialized;
159    private boolean mIsImageCaptureIntent;
160    private boolean mRecordLocation;
161
162    private static final int FOCUS_NOT_STARTED = 0;
163    private static final int FOCUSING = 1;
164    private static final int FOCUSING_SNAP_ON_FINISH = 2;
165    private static final int FOCUS_SUCCESS = 3;
166    private static final int FOCUS_FAIL = 4;
167    private int mFocusState = FOCUS_NOT_STARTED;
168
169    private ContentResolver mContentResolver;
170    private boolean mDidRegister = false;
171
172    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
173
174    private LocationManager mLocationManager = null;
175
176    // Use OneShotPreviewCallback to measure the time between
177    // JpegPictureCallback and preview.
178    private final OneShotPreviewCallback mOneShotPreviewCallback =
179            new OneShotPreviewCallback();
180    private final ShutterCallback mShutterCallback = new ShutterCallback();
181    private final RawPictureCallback mRawPictureCallback =
182            new RawPictureCallback();
183    private final AutoFocusCallback mAutoFocusCallback =
184            new AutoFocusCallback();
185    private long mFocusStartTime;
186    private long mFocusCallbackTime;
187    private long mCaptureStartTime;
188    private long mShutterCallbackTime;
189    private long mRawPictureCallbackTime;
190    private long mJpegPictureCallbackTime;
191    private int mPicturesRemaining;
192
193    //Add the camera latency time
194    public static long mAutoFocusTime;
195    public static long mShutterLag;
196    public static long mShutterAndRawPictureCallbackTime;
197    public static long mJpegPictureCallbackTimeLag;
198    public static long mRawPictureAndJpegPictureCallbackTime;
199
200    // Focus mode. Options are pref_camera_focusmode_entryvalues.
201    private String mFocusMode;
202
203    private final Handler mHandler = new MainHandler();
204
205    private interface Capturer {
206        Uri getLastCaptureUri();
207        void onSnap();
208    }
209
210    /**
211     * This Handler is used to post message back onto the main thread of the
212     * application
213     */
214    private class MainHandler extends Handler {
215        @Override
216        public void handleMessage(Message msg) {
217            switch (msg.what) {
218                case RESTART_PREVIEW: {
219                    restartPreview();
220                    break;
221                }
222
223                case CLEAR_SCREEN_DELAY: {
224                    getWindow().clearFlags(
225                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
226                    break;
227                }
228
229                case FIRST_TIME_INIT: {
230                    initializeFirstTime();
231                    break;
232                }
233            }
234        }
235    }
236
237    // Snapshots can only be taken after this is called. It should be called
238    // once only. We could have done these things in onCreate() but we want to
239    // make preview screen appear as soon as possible.
240    void initializeFirstTime() {
241        if (mFirstTimeInitialized) return;
242
243        // Create orientation listenter. This should be done first because it
244        // takes some time to get first orientation.
245        mOrientationListener =
246                new OrientationEventListener(Camera.this) {
247            @Override
248            public void onOrientationChanged(int orientation) {
249                // We keep the last known orientation. So if the user
250                // first orient the camera then point the camera to
251                // floor/sky, we still have the correct orientation.
252                if (orientation != ORIENTATION_UNKNOWN) {
253                    mLastOrientation = orientation;
254                }
255            }
256        };
257        mOrientationListener.enable();
258
259        // Initialize location sevice.
260        mLocationManager = (LocationManager)
261                getSystemService(Context.LOCATION_SERVICE);
262        readPreference();
263        if (mRecordLocation) startReceivingLocationUpdates();
264
265        // Initialize last picture button.
266        mContentResolver = getContentResolver();
267        if (!mIsImageCaptureIntent)  {
268            findViewById(R.id.camera_switch).setOnClickListener(this);
269            mLastPictureButton =
270                    (ImageView) findViewById(R.id.review_thumbnail);
271            mLastPictureButton.setOnClickListener(this);
272            mThumbController = new ThumbnailController(
273                    mLastPictureButton, mContentResolver);
274            mThumbController.loadData(ImageManager.getLastImageThumbPath());
275            // Update last image thumbnail.
276            updateThumbnailButton();
277        }
278
279        // Initialize shutter button.
280        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
281        mShutterButton.setOnShutterButtonListener(this);
282        mShutterButton.setVisibility(View.VISIBLE);
283
284        mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
285        updateFocusIndicator();
286
287        // Initialize GPS indicator.
288        mGpsIndicator = (ImageView) findViewById(R.id.gps_indicator);
289        mGpsIndicator.setImageResource(R.drawable.ic_camera_sym_gps);
290
291        ImageManager.ensureOSXCompatibleFolder();
292
293        calculatePicturesRemaining();
294
295        installIntentFilter();
296
297        initializeFocusTone();
298
299        // Disable zoom until driver is ready.
300        // TODO: enable this.
301        //initializeZoom();
302
303        mFirstTimeInitialized = true;
304    }
305
306    private void updateThumbnailButton() {
307        if (!mThumbController.isUriValid()) {
308            updateLastImage();
309        }
310        mThumbController.updateDisplayIfNeeded();
311    }
312
313    // If the activity is paused and resumed, this method will be called in
314    // onResume.
315    void initializeSecondTime() {
316        // Start orientation listener as soon as possible because it takes
317        // some time to get first orientation.
318        mOrientationListener.enable();
319
320        // Start location update if needed.
321        readPreference();
322        if (mRecordLocation) startReceivingLocationUpdates();
323
324        installIntentFilter();
325
326        initializeFocusTone();
327
328        if (!mIsImageCaptureIntent) {
329            updateThumbnailButton();
330        }
331    }
332
333    private void initializeZoom() {
334        String zoomValuesStr = mParameters.get(SUPPORTED_ZOOM);
335        if (zoomValuesStr == null) return;
336
337        mZoomValues = getZoomValues(zoomValuesStr);
338        if (mZoomValues == null) return;
339
340        mZoomButtons = new ZoomButtonsController(mSurfaceView);
341        mZoomButtons.setAutoDismissed(true);
342        mZoomButtons.setOnZoomListener(
343                new ZoomButtonsController.OnZoomListener() {
344            public void onVisibilityChanged(boolean visible) {
345                if (visible) {
346                    updateZoomButtonsEnabled();
347                }
348            }
349
350            public void onZoom(boolean zoomIn) {
351                if (zoomIn) {
352                    zoomIn();
353                } else {
354                    zoomOut();
355                }
356                updateZoomButtonsEnabled();
357            }
358        });
359    }
360
361    private void zoomIn() {
362        if (mZoomIndex < mZoomValues.length - 1) {
363            mZoomIndex++;
364            mParameters.set(PARM_ZOOM, mZoomValues[mZoomIndex]);
365            mCameraDevice.setParameters(mParameters);
366        }
367    }
368
369    private void zoomOut() {
370        if (mZoomIndex > 0) {
371            mZoomIndex--;
372            mParameters.set(PARM_ZOOM, mZoomValues[mZoomIndex]);
373            mCameraDevice.setParameters(mParameters);
374        }
375    }
376
377    private void updateZoomButtonsEnabled() {
378        mZoomButtons.setZoomInEnabled(mZoomIndex < mZoomValues.length - 1);
379        mZoomButtons.setZoomOutEnabled(mZoomIndex > 0);
380    }
381
382    private String[] getZoomValues(String zoomValuesStr) {
383        ArrayList<String> list = new ArrayList<String>();
384        String[] zoomValues = null;
385        StringTokenizer tokenizer = new StringTokenizer(zoomValuesStr, ",");
386
387        while (tokenizer.hasMoreElements()) {
388            list.add(tokenizer.nextToken());
389        }
390        if (list.size() > 0) {
391            zoomValues = list.toArray(new String[list.size()]);
392        }
393        return zoomValues;
394    }
395
396
397    LocationListener [] mLocationListeners = new LocationListener[] {
398            new LocationListener(LocationManager.GPS_PROVIDER),
399            new LocationListener(LocationManager.NETWORK_PROVIDER)
400    };
401
402
403    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
404        @Override
405        public void onReceive(Context context, Intent intent) {
406            String action = intent.getAction();
407            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
408                // SD card available
409                updateStorageHint(calculatePicturesRemaining());
410            } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED) ||
411                    action.equals(Intent.ACTION_MEDIA_CHECKING)) {
412                // SD card unavailable
413                mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR;
414                updateStorageHint(mPicturesRemaining);
415            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
416                Toast.makeText(Camera.this,
417                        getResources().getString(R.string.wait), 5000);
418            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
419                updateStorageHint();
420            }
421        }
422    };
423
424    private class LocationListener
425            implements android.location.LocationListener {
426        Location mLastLocation;
427        boolean mValid = false;
428        String mProvider;
429
430        public LocationListener(String provider) {
431            mProvider = provider;
432            mLastLocation = new Location(mProvider);
433        }
434
435        public void onLocationChanged(Location newLocation) {
436            if (newLocation.getLatitude() == 0.0
437                    && newLocation.getLongitude() == 0.0) {
438                // Hack to filter out 0.0,0.0 locations
439                return;
440            }
441            // If GPS is available before start camera, we won't get status
442            // update so update GPS indicator when we receive data.
443            if (mRecordLocation
444                    && LocationManager.GPS_PROVIDER.equals(mProvider)) {
445                mGpsIndicator.setVisibility(View.VISIBLE);
446            }
447            mLastLocation.set(newLocation);
448            mValid = true;
449        }
450
451        public void onProviderEnabled(String provider) {
452        }
453
454        public void onProviderDisabled(String provider) {
455            mValid = false;
456        }
457
458        public void onStatusChanged(
459                String provider, int status, Bundle extras) {
460            switch(status) {
461                case LocationProvider.OUT_OF_SERVICE:
462                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
463                    mValid = false;
464                    if (mRecordLocation &&
465                            LocationManager.GPS_PROVIDER.equals(provider)) {
466                        mGpsIndicator.setVisibility(View.INVISIBLE);
467                    }
468                    break;
469                }
470            }
471        }
472
473        public Location current() {
474            return mValid ? mLastLocation : null;
475        }
476    }
477
478    private final class OneShotPreviewCallback
479            implements android.hardware.Camera.PreviewCallback {
480        public void onPreviewFrame(byte[] data,
481                                   android.hardware.Camera camera) {
482            long now = System.currentTimeMillis();
483            if (mJpegPictureCallbackTime != 0) {
484                mJpegPictureCallbackTimeLag = now - mJpegPictureCallbackTime;
485                Log.v(TAG, "mJpegPictureCallbackTimeLag = "
486                        + mJpegPictureCallbackTimeLag + "ms");
487                mJpegPictureCallbackTime = 0;
488            } else {
489                Log.v(TAG, "Got first frame");
490            }
491        }
492    }
493
494    private final class ShutterCallback
495            implements android.hardware.Camera.ShutterCallback {
496        public void onShutter() {
497            mShutterCallbackTime = System.currentTimeMillis();
498            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
499            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
500            clearFocusState();
501        }
502    }
503
504    private final class RawPictureCallback implements PictureCallback {
505        public void onPictureTaken(
506                byte [] rawData, android.hardware.Camera camera) {
507            mRawPictureCallbackTime = System.currentTimeMillis();
508            mShutterAndRawPictureCallbackTime =
509                mRawPictureCallbackTime - mShutterCallbackTime;
510            Log.v(TAG, "mShutterAndRawPictureCallbackTime = "
511                    + mShutterAndRawPictureCallbackTime + "ms");
512        }
513    }
514
515    private final class JpegPictureCallback implements PictureCallback {
516        Location mLocation;
517
518        public JpegPictureCallback(Location loc) {
519            mLocation = loc;
520        }
521
522        public void onPictureTaken(
523                final byte [] jpegData, final android.hardware.Camera camera) {
524            if (mPausing) {
525                return;
526            }
527
528            mJpegPictureCallbackTime = System.currentTimeMillis();
529            mRawPictureAndJpegPictureCallbackTime =
530                mJpegPictureCallbackTime - mRawPictureCallbackTime;
531            Log.v(TAG, "mRawPictureAndJpegPictureCallbackTime = "
532                    + mRawPictureAndJpegPictureCallbackTime +"ms");
533            mImageCapture.storeImage(jpegData, camera, mLocation);
534
535            if (!mIsImageCaptureIntent) {
536                long delay = 1200 - (
537                        System.currentTimeMillis() - mRawPictureCallbackTime);
538                mHandler.sendEmptyMessageDelayed(
539                        RESTART_PREVIEW, Math.max(delay, 0));
540            }
541        }
542    }
543
544    private final class AutoFocusCallback
545            implements android.hardware.Camera.AutoFocusCallback {
546        public void onAutoFocus(
547                boolean focused, android.hardware.Camera camera) {
548            mFocusCallbackTime = System.currentTimeMillis();
549            mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
550            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
551            if (mFocusState == FOCUSING_SNAP_ON_FINISH) {
552                // Take the picture no matter focus succeeds or fails. No need
553                // to play the AF sound if we're about to play the shutter
554                // sound.
555                if (focused) {
556                    mFocusState = FOCUS_SUCCESS;
557                } else {
558                    mFocusState = FOCUS_FAIL;
559                }
560                mImageCapture.onSnap();
561            } else if (mFocusState == FOCUSING) {
562                // User is half-pressing the focus key. Play the focus tone.
563                // Do not take the picture now.
564                ToneGenerator tg = mFocusToneGenerator;
565                if (tg != null) {
566                    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
567                }
568                if (focused) {
569                    mFocusState = FOCUS_SUCCESS;
570                } else {
571                    mFocusState = FOCUS_FAIL;
572                }
573            } else if (mFocusState == FOCUS_NOT_STARTED) {
574                // User has released the focus key before focus completes.
575                // Do nothing.
576            }
577            updateFocusIndicator();
578        }
579    }
580
581    private class ImageCapture implements Capturer {
582
583        private boolean mCancel = false;
584
585        private Uri mLastContentUri;
586        private Cancelable<Void> mAddImageCancelable;
587
588        Bitmap mCaptureOnlyBitmap;
589
590        private void storeImage(byte[] data, Location loc) {
591            try {
592                long dateTaken = System.currentTimeMillis();
593                String name = createName(dateTaken) + ".jpg";
594                mLastContentUri = ImageManager.addImage(
595                        mContentResolver,
596                        name,
597                        dateTaken,
598                        loc, // location for the database goes here
599                        0, // the dsp will use the right orientation so
600                           // don't "double set it"
601                        ImageManager.CAMERA_IMAGE_BUCKET_NAME,
602                        name);
603                if (mLastContentUri == null) {
604                    // this means we got an error
605                    mCancel = true;
606                }
607                if (!mCancel) {
608                    mAddImageCancelable = ImageManager.storeImage(
609                            mLastContentUri, mContentResolver,
610                            0, null, data);
611                    mAddImageCancelable.get();
612                    mAddImageCancelable = null;
613                    ImageManager.setImageSize(mContentResolver, mLastContentUri,
614                            new File(ImageManager.CAMERA_IMAGE_BUCKET_NAME,
615                            name).length());
616                }
617            } catch (Exception ex) {
618                Log.e(TAG, "Exception while compressing image.", ex);
619            }
620        }
621
622        public void storeImage(
623                final byte[] data, android.hardware.Camera camera, Location loc) {
624            if (!mIsImageCaptureIntent) {
625                storeImage(data, loc);
626                sendBroadcast(new Intent(
627                        "com.android.camera.NEW_PICTURE", mLastContentUri));
628                setLastPictureThumb(data, mImageCapture.getLastCaptureUri());
629                mThumbController.updateDisplayIfNeeded();
630            } else {
631                BitmapFactory.Options options = new BitmapFactory.Options();
632                options.inSampleSize = 4;
633                mCaptureOnlyBitmap = BitmapFactory.decodeByteArray(
634                        data, 0, data.length, options);
635                showPostCaptureAlert();
636            }
637        }
638
639        /**
640         * Initiate the capture of an image.
641         */
642        public void initiate() {
643            if (mCameraDevice == null) {
644                return;
645            }
646
647            mCancel = false;
648
649            capture();
650        }
651
652        public Uri getLastCaptureUri() {
653            return mLastContentUri;
654        }
655
656        public Bitmap getLastBitmap() {
657            return mCaptureOnlyBitmap;
658        }
659
660        private void capture() {
661            mCaptureOnlyBitmap = null;
662
663            int orientation = mLastOrientation;
664            if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
665                orientation += 90;
666            }
667            orientation = ImageManager.roundOrientation(orientation);
668            Log.v(TAG, "mLastOrientation = " + mLastOrientation
669                    + ", orientation = " + orientation);
670
671            mParameters.set(PARM_ROTATION, orientation);
672
673            Location loc = mRecordLocation ? getCurrentLocation() : null;
674
675            mParameters.remove(PARM_GPS_LATITUDE);
676            mParameters.remove(PARM_GPS_LONGITUDE);
677            mParameters.remove(PARM_GPS_ALTITUDE);
678            mParameters.remove(PARM_GPS_TIMESTAMP);
679
680            if (loc != null) {
681                double lat = loc.getLatitude();
682                double lon = loc.getLongitude();
683                boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
684
685                if (hasLatLon) {
686                    String latString = String.valueOf(lat);
687                    String lonString = String.valueOf(lon);
688                    mParameters.set(PARM_GPS_LATITUDE,  latString);
689                    mParameters.set(PARM_GPS_LONGITUDE, lonString);
690                    if (loc.hasAltitude()) {
691                        mParameters.set(PARM_GPS_ALTITUDE,
692                                        String.valueOf(loc.getAltitude()));
693                    } else {
694                        // for NETWORK_PROVIDER location provider, we may have
695                        // no altitude information, but the driver needs it, so
696                        // we fake one.
697                        mParameters.set(PARM_GPS_ALTITUDE,  "0");
698                    }
699                    if (loc.getTime() != 0) {
700                        // Location.getTime() is UTC in milliseconds.
701                        // gps-timestamp is UTC in seconds.
702                        long utcTimeSeconds = loc.getTime() / 1000;
703                        mParameters.set(PARM_GPS_TIMESTAMP,
704                                        String.valueOf(utcTimeSeconds));
705                    }
706                } else {
707                    loc = null;
708                }
709            }
710
711            mCameraDevice.setParameters(mParameters);
712
713            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
714                    new JpegPictureCallback(loc));
715            mPreviewing = false;
716        }
717
718        public void onSnap() {
719            // If we are already in the middle of taking a snapshot then ignore.
720            if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS) {
721                return;
722            }
723            mCaptureStartTime = System.currentTimeMillis();
724
725            // Don't check the filesystem here, we can't afford the latency.
726            // Instead, check the cached value which was calculated when the
727            // preview was restarted.
728            if (mPicturesRemaining < 1) {
729                updateStorageHint(mPicturesRemaining);
730                return;
731            }
732
733            mStatus = SNAPSHOT_IN_PROGRESS;
734
735            mImageCapture.initiate();
736        }
737
738        private void clearLastBitmap() {
739            if (mCaptureOnlyBitmap != null) {
740                mCaptureOnlyBitmap.recycle();
741                mCaptureOnlyBitmap = null;
742            }
743        }
744    }
745
746    private void setLastPictureThumb(byte[] data, Uri uri) {
747        BitmapFactory.Options options = new BitmapFactory.Options();
748        options.inSampleSize = 16;
749        Bitmap lastPictureThumb =
750                BitmapFactory.decodeByteArray(data, 0, data.length, options);
751        mThumbController.setData(uri, lastPictureThumb);
752    }
753
754    private static String createName(long dateTaken) {
755        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
756    }
757
758    public static Matrix getDisplayMatrix(Bitmap b, ImageView v) {
759        Matrix m = new Matrix();
760        float bw = b.getWidth();
761        float bh = b.getHeight();
762        float vw = v.getWidth();
763        float vh = v.getHeight();
764        float scale, x, y;
765        if (bw * vh > vw * bh) {
766            scale = vh / bh;
767            x = (vw - scale * bw) * 0.5F;
768            y = 0;
769        } else {
770            scale = vw / bw;
771            x = 0;
772            y = (vh - scale * bh) * 0.5F;
773        }
774        m.setScale(scale, scale, 0.5F, 0.5F);
775        m.postTranslate(x, y);
776        return m;
777    }
778
779    @Override
780    public void onCreate(Bundle icicle) {
781        super.onCreate(icicle);
782
783        Window win = getWindow();
784        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
785        setContentView(R.layout.camera);
786        mSurfaceView = (VideoPreview) findViewById(R.id.camera_preview);
787        mViewFinderWidth = mSurfaceView.getLayoutParams().width;
788        mViewFinderHeight = mSurfaceView.getLayoutParams().height;
789        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
790
791        /*
792         * To reduce startup time, we start the preview in another thread.
793         * We make sure the preview is started at the end of onCreate.
794         */
795        Thread startPreviewThread = new Thread(new Runnable() {
796            public void run() {
797                startPreview();
798            }
799        });
800        startPreviewThread.start();
801
802        // don't set mSurfaceHolder here. We have it set ONLY within
803        // surfaceChanged / surfaceDestroyed, other parts of the code
804        // assume that when it is set, the surface is also set.
805        SurfaceHolder holder = mSurfaceView.getHolder();
806        holder.addCallback(this);
807        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
808
809        mIsImageCaptureIntent = isImageCaptureIntent();
810        LayoutInflater inflater = getLayoutInflater();
811
812        ViewGroup rootView =(ViewGroup) findViewById(R.id.camera);
813        if (mIsImageCaptureIntent) {
814            View controlBar = inflater.inflate(
815                    R.layout.attach_camera_control, rootView);
816            controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this);
817            controlBar.findViewById(R.id.btn_retake).setOnClickListener(this);
818            controlBar.findViewById(R.id.btn_done).setOnClickListener(this);
819        } else {
820            inflater.inflate(R.layout.camera_control, rootView);
821            Switcher switcher = ((Switcher) findViewById(R.id.camera_switch));
822            switcher.setSwitch(SWITCH_CAMERA);
823            switcher.setOnSwitchListener(this);
824        }
825
826        // Make sure preview is started.
827        try {
828            startPreviewThread.join();
829        } catch (InterruptedException ex) {
830            // ignore
831        }
832    }
833
834    @Override
835    public void onStart() {
836        super.onStart();
837
838        Thread t = new Thread(new Runnable() {
839            public void run() {
840                final boolean storageOK = calculatePicturesRemaining() > 0;
841                if (!storageOK) {
842                    mHandler.post(new Runnable() {
843                        public void run() {
844                            updateStorageHint(mPicturesRemaining);
845                        }
846                    });
847                }
848            }
849        });
850        t.start();
851    }
852
853    public void onClick(View v) {
854        switch (v.getId()) {
855            case R.id.btn_retake:
856                hidePostCaptureAlert();
857                restartPreview();
858                break;
859            case R.id.review_thumbnail:
860                if (isCameraIdle()) {
861                    viewLastImage();
862                }
863                break;
864            case R.id.btn_done:
865                doAttach();
866                break;
867            case R.id.btn_cancel:
868                doCancel();
869        }
870    }
871
872    private void doAttach() {
873        if (mPausing) {
874            return;
875        }
876        Bitmap bitmap = mImageCapture.getLastBitmap();
877
878        String cropValue = null;
879        Uri saveUri = null;
880
881        Bundle myExtras = getIntent().getExtras();
882        if (myExtras != null) {
883            saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
884            cropValue = myExtras.getString("crop");
885        }
886
887
888        if (cropValue == null) {
889            // First handle the no crop case -- just return the value.  If the
890            // caller specifies a "save uri" then write the data to it's
891            // stream. Otherwise, pass back a scaled down version of the bitmap
892            // directly in the extras.
893            if (saveUri != null) {
894                OutputStream outputStream = null;
895                try {
896                    outputStream = mContentResolver.openOutputStream(saveUri);
897                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75,
898                            outputStream);
899                    outputStream.close();
900
901                    setResult(RESULT_OK);
902                    finish();
903                } catch (IOException ex) {
904                    // ignore exception
905                } finally {
906                    if (outputStream != null) {
907                        try {
908                            outputStream.close();
909                        } catch (IOException ex) {
910                            // ignore exception
911                        }
912                    }
913                }
914            } else {
915                float scale = .5F;
916                Matrix m = new Matrix();
917                m.setScale(scale, scale);
918
919                bitmap = Bitmap.createBitmap(bitmap, 0, 0,
920                        bitmap.getWidth(),
921                        bitmap.getHeight(),
922                        m, true);
923
924                setResult(RESULT_OK,
925                        new Intent("inline-data").putExtra("data", bitmap));
926                finish();
927            }
928        } else {
929            // Save the image to a temp file and invoke the cropper
930            Uri tempUri = null;
931            FileOutputStream tempStream = null;
932            try {
933                File path = getFileStreamPath(sTempCropFilename);
934                path.delete();
935                tempStream = openFileOutput(sTempCropFilename, 0);
936                bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream);
937                tempStream.close();
938                tempUri = Uri.fromFile(path);
939            } catch (FileNotFoundException ex) {
940                setResult(Activity.RESULT_CANCELED);
941                finish();
942                return;
943            } catch (IOException ex) {
944                setResult(Activity.RESULT_CANCELED);
945                finish();
946                return;
947            } finally {
948                if (tempStream != null) {
949                    try {
950                        tempStream.close();
951                    } catch (IOException ex) {
952                        // ignore exception
953                    }
954                }
955            }
956
957            Bundle newExtras = new Bundle();
958            if (cropValue.equals("circle")) {
959                newExtras.putString("circleCrop", "true");
960            }
961            if (saveUri != null) {
962                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri);
963            } else {
964                newExtras.putBoolean("return-data", true);
965            }
966
967            Intent cropIntent = new Intent();
968            cropIntent.setClass(Camera.this, CropImage.class);
969            cropIntent.setData(tempUri);
970            cropIntent.putExtras(newExtras);
971
972            startActivityForResult(cropIntent, CROP_MSG);
973        }
974    }
975
976    private void doCancel() {
977        setResult(RESULT_CANCELED, new Intent());
978        finish();
979    }
980
981    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
982        if (mPausing) {
983            return;
984        }
985        switch (button.getId()) {
986            case R.id.shutter_button:
987                doFocus(pressed);
988                break;
989        }
990    }
991
992    public void onShutterButtonClick(ShutterButton button) {
993        if (mPausing) {
994            return;
995        }
996        switch (button.getId()) {
997            case R.id.shutter_button:
998                doSnap();
999                break;
1000        }
1001    }
1002
1003    private void updateStorageHint() {
1004        updateStorageHint(MenuHelper.calculatePicturesRemaining());
1005    }
1006
1007    private OnScreenHint mStorageHint;
1008
1009    private void updateStorageHint(int remaining) {
1010        String noStorageText = null;
1011
1012        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
1013            String state = Environment.getExternalStorageState();
1014            if (state == Environment.MEDIA_CHECKING) {
1015                noStorageText = getString(R.string.preparing_sd);
1016            } else {
1017                noStorageText = getString(R.string.no_storage);
1018            }
1019        } else if (remaining < 1) {
1020            noStorageText = getString(R.string.not_enough_space);
1021        }
1022
1023        if (noStorageText != null) {
1024            if (mStorageHint == null) {
1025                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1026            } else {
1027                mStorageHint.setText(noStorageText);
1028            }
1029            mStorageHint.show();
1030        } else if (mStorageHint != null) {
1031            mStorageHint.cancel();
1032            mStorageHint = null;
1033        }
1034    }
1035
1036    void installIntentFilter() {
1037        // install an intent filter to receive SD card related events.
1038        IntentFilter intentFilter =
1039                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1040        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1041        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
1042        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1043        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1044        intentFilter.addDataScheme("file");
1045        registerReceiver(mReceiver, intentFilter);
1046        mDidRegister = true;
1047    }
1048
1049    void initializeFocusTone() {
1050        // Initialize focus tone generator.
1051        try {
1052            mFocusToneGenerator = new ToneGenerator(
1053                    AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1054        } catch (Throwable ex) {
1055            Log.w(TAG, "Exception caught while creating tone generator: ", ex);
1056            mFocusToneGenerator = null;
1057        }
1058    }
1059
1060    void readPreference() {
1061        mRecordLocation = mPreferences.getBoolean(
1062                "pref_camera_recordlocation_key", false);
1063        mFocusMode = mPreferences.getString(
1064                CameraSettings.KEY_FOCUS_MODE,
1065                getString(R.string.pref_camera_focusmode_default));
1066    }
1067
1068    @Override
1069    public void onResume() {
1070        super.onResume();
1071
1072        mPausing = false;
1073        mJpegPictureCallbackTime = 0;
1074        mImageCapture = new ImageCapture();
1075
1076        // Start the preview if it is not started.
1077        if (!mPreviewing) {
1078            startPreview();
1079        }
1080
1081        if (mSurfaceHolder != null) {
1082            // If first time initialization is not finished, put it in the
1083            // message queue.
1084            if (!mFirstTimeInitialized) {
1085                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1086            } else {
1087                initializeSecondTime();
1088            }
1089        }
1090
1091        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1092    }
1093
1094    private static ImageManager.DataLocation dataLocation() {
1095        return ImageManager.DataLocation.EXTERNAL;
1096    }
1097
1098    @Override
1099    protected void onPause() {
1100        mPausing = true;
1101        stopPreview();
1102        // Close the camera now because other activities may need to use it.
1103        closeCamera();
1104
1105        if (mFirstTimeInitialized) {
1106            mOrientationListener.disable();
1107            mGpsIndicator.setVisibility(View.INVISIBLE);
1108            if (!mIsImageCaptureIntent) {
1109                mThumbController.storeData(
1110                        ImageManager.getLastImageThumbPath());
1111            }
1112            hidePostCaptureAlert();
1113        }
1114
1115        if (mDidRegister) {
1116            unregisterReceiver(mReceiver);
1117            mDidRegister = false;
1118        }
1119        stopReceivingLocationUpdates();
1120
1121        if (mFocusToneGenerator != null) {
1122            mFocusToneGenerator.release();
1123            mFocusToneGenerator = null;
1124        }
1125
1126        if (mStorageHint != null) {
1127            mStorageHint.cancel();
1128            mStorageHint = null;
1129        }
1130
1131        // If we are in an image capture intent and has taken
1132        // a picture, we just clear it in onPause.
1133        mImageCapture.clearLastBitmap();
1134        mImageCapture = null;
1135
1136        // This is necessary to make the ZoomButtonsController unregister
1137        // its configuration change receiver.
1138        if (mZoomButtons != null) {
1139            mZoomButtons.setVisible(false);
1140        }
1141
1142        // Remove the messages in the event queue.
1143        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1144        mHandler.removeMessages(RESTART_PREVIEW);
1145        mHandler.removeMessages(FIRST_TIME_INIT);
1146
1147        super.onPause();
1148    }
1149
1150    @Override
1151    protected void onActivityResult(
1152            int requestCode, int resultCode, Intent data) {
1153        switch (requestCode) {
1154            case CROP_MSG: {
1155                Intent intent = new Intent();
1156                if (data != null) {
1157                    Bundle extras = data.getExtras();
1158                    if (extras != null) {
1159                        intent.putExtras(extras);
1160                    }
1161                }
1162                setResult(resultCode, intent);
1163                finish();
1164
1165                File path = getFileStreamPath(sTempCropFilename);
1166                path.delete();
1167
1168                break;
1169            }
1170        }
1171    }
1172
1173    private void autoFocus() {
1174        // Initiate autofocus only when preview is started and snapshot is not
1175        // in progress.
1176        if (isCameraIdle() && mPreviewing) {
1177            Log.v(TAG, "Start autofocus.");
1178            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1179            mFocusStartTime = System.currentTimeMillis();
1180            mFocusState = FOCUSING;
1181            updateFocusIndicator();
1182            mCameraDevice.autoFocus(mAutoFocusCallback);
1183        }
1184    }
1185
1186    private void clearFocusState() {
1187        mFocusState = FOCUS_NOT_STARTED;
1188        updateFocusIndicator();
1189    }
1190
1191    private void updateFocusIndicator() {
1192        if (mFocusRectangle == null) return;
1193
1194        if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) {
1195            mFocusRectangle.showStart();
1196        } else if (mFocusState == FOCUS_SUCCESS) {
1197            mFocusRectangle.showSuccess();
1198        } else if (mFocusState == FOCUS_FAIL) {
1199            mFocusRectangle.showFail();
1200        } else {
1201            mFocusRectangle.clear();
1202        }
1203    }
1204
1205    @Override
1206    public boolean onKeyDown(int keyCode, KeyEvent event) {
1207        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1208        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1209
1210        switch (keyCode) {
1211            case KeyEvent.KEYCODE_BACK:
1212                if (!isCameraIdle()) {
1213                    // ignore backs while we're taking a picture
1214                    return true;
1215                }
1216                break;
1217            case KeyEvent.KEYCODE_FOCUS:
1218                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1219                    doFocus(true);
1220                }
1221                return true;
1222            case KeyEvent.KEYCODE_CAMERA:
1223                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1224                    doSnap();
1225                }
1226                return true;
1227            case KeyEvent.KEYCODE_DPAD_CENTER:
1228                // If we get a dpad center event without any focused view, move
1229                // the focus to the shutter button and press it.
1230                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1231                    // Start auto-focus immediately to reduce shutter lag. After
1232                    // the shutter button gets the focus, doFocus() will be
1233                    // called again but it is fine.
1234                    doFocus(true);
1235                    if (mShutterButton.isInTouchMode()) {
1236                        mShutterButton.requestFocusFromTouch();
1237                    } else {
1238                        mShutterButton.requestFocus();
1239                    }
1240                    mShutterButton.setPressed(true);
1241                }
1242                return true;
1243        }
1244
1245        return super.onKeyDown(keyCode, event);
1246    }
1247
1248    @Override
1249    public boolean onKeyUp(int keyCode, KeyEvent event) {
1250        switch (keyCode) {
1251            case KeyEvent.KEYCODE_FOCUS:
1252                if (mFirstTimeInitialized) {
1253                    doFocus(false);
1254                }
1255                return true;
1256        }
1257        return super.onKeyUp(keyCode, event);
1258    }
1259
1260    @Override
1261    public boolean onTouchEvent(MotionEvent event) {
1262        switch (event.getAction()) {
1263            case MotionEvent.ACTION_DOWN:
1264                // Show zoom buttons only when preview is started and snapshot
1265                // is not in progress. mZoomButtons may be null if it is not
1266                // initialized.
1267                if (!mPausing && isCameraIdle() && mPreviewing
1268                        && mZoomButtons != null) {
1269                    mZoomButtons.setVisible(true);
1270                }
1271                return true;
1272        }
1273        return super.onTouchEvent(event);
1274    }
1275
1276    private void doSnap() {
1277        // If the user has half-pressed the shutter and focus is completed, we
1278        // can take the photo right away. If the focus mode is infinity, we can
1279        // also take the photo.
1280        if (mFocusMode.equals(getString(
1281                R.string.pref_camera_focusmode_value_infinity))
1282                || (mFocusState == FOCUS_SUCCESS || mFocusState == FOCUS_FAIL)) {
1283            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1284            mImageCapture.onSnap();
1285        } else if (mFocusState == FOCUSING) {
1286            // Half pressing the shutter (i.e. the focus button event) will
1287            // already have requested AF for us, so just request capture on
1288            // focus here.
1289            mFocusState = FOCUSING_SNAP_ON_FINISH;
1290        } else if (mFocusState == FOCUS_NOT_STARTED) {
1291            // Focus key down event is dropped for some reasons. Just ignore.
1292        }
1293    }
1294
1295    private void doFocus(boolean pressed) {
1296        // Do the focus if the mode is auto. No focus needed in infinity mode.
1297        if (mFocusMode.equals(getString(
1298                R.string.pref_camera_focusmode_value_auto))) {
1299            if (pressed) {  // Focus key down.
1300                autoFocus();
1301            } else {  // Focus key up.
1302                if (mFocusState != FOCUSING_SNAP_ON_FINISH) {
1303                    // User releases half-pressed focus key.
1304                    clearFocusState();
1305                }
1306            }
1307        }
1308    }
1309
1310    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1311        // Make sure we have a surface in the holder before proceeding.
1312        if (holder.getSurface() == null) {
1313            Log.d(TAG, "holder.getSurface() == null");
1314            return;
1315        }
1316
1317        mSurfaceHolder = holder;
1318        mViewFinderWidth = w;
1319        mViewFinderHeight = h;
1320
1321        // Sometimes surfaceChanged is called after onPause. Ignore it.
1322        if (mPausing) return;
1323
1324        // Set preview display if the surface is being created. Preview was
1325        // already started.
1326        if (holder.isCreating()) {
1327            setPreviewDisplay(holder);
1328        }
1329
1330        // If first time initialization is not finished, send a message to do
1331        // it later. We want to finish surfaceChanged as soon as possible to let
1332        // user see preview first.
1333        if (!mFirstTimeInitialized) {
1334            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1335        } else {
1336            initializeSecondTime();
1337        }
1338    }
1339
1340    public void surfaceCreated(SurfaceHolder holder) {
1341    }
1342
1343    public void surfaceDestroyed(SurfaceHolder holder) {
1344        stopPreview();
1345        mSurfaceHolder = null;
1346    }
1347
1348    private void closeCamera() {
1349        if (mCameraDevice != null) {
1350            CameraHolder.instance().release();
1351            mCameraDevice = null;
1352            mPreviewing = false;
1353        }
1354    }
1355
1356    private boolean ensureCameraDevice() {
1357        if (mCameraDevice == null) {
1358            mCameraDevice = CameraHolder.instance().open();
1359        }
1360        return mCameraDevice != null;
1361    }
1362
1363    private void updateLastImage() {
1364        IImageList list = ImageManager.allImages(
1365            mContentResolver,
1366            dataLocation(),
1367            ImageManager.INCLUDE_IMAGES,
1368            ImageManager.SORT_ASCENDING,
1369            ImageManager.CAMERA_IMAGE_BUCKET_ID);
1370        int count = list.getCount();
1371        if (count > 0) {
1372            IImage image = list.getImageAt(count - 1);
1373            Uri uri = image.fullSizeImageUri();
1374            mThumbController.setData(uri, image.miniThumbBitmap());
1375        } else {
1376            mThumbController.setData(null, null);
1377        }
1378        list.deactivate();
1379    }
1380
1381    private void restartPreview() {
1382        // make sure the surfaceview fills the whole screen when previewing
1383        mSurfaceView.setAspectRatio(VideoPreview.DONT_CARE);
1384        startPreview();
1385
1386        // Calculate this in advance of each shot so we don't add to shutter
1387        // latency. It's true that someone else could write to the SD card in
1388        // the mean time and fill it, but that could have happened between the
1389        // shutter press and saving the JPEG too.
1390        calculatePicturesRemaining();
1391    }
1392
1393    private void setPreviewDisplay(SurfaceHolder holder) {
1394        try {
1395            mCameraDevice.setPreviewDisplay(holder);
1396        } catch (Throwable ex) {
1397            closeCamera();
1398            throw new RuntimeException("setPreviewDisplay failed", ex);
1399        }
1400    }
1401
1402    private void startPreview() {
1403        if (mPausing) return;
1404
1405        if (!ensureCameraDevice()) return;
1406
1407        if (isFinishing()) return;
1408
1409        // If we're previewing already, stop the preview first (this will blank
1410        // the screen).
1411        if (mPreviewing) stopPreview();
1412
1413        setPreviewDisplay(mSurfaceHolder);
1414
1415        setCameraParameter();
1416
1417        final long wallTimeStart = SystemClock.elapsedRealtime();
1418        final long threadTimeStart = Debug.threadCpuTimeNanos();
1419
1420        // Set one shot preview callback for latency measurement.
1421        mCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
1422
1423        try {
1424            Log.v(TAG, "startPreview");
1425            mCameraDevice.startPreview();
1426        } catch (Throwable ex) {
1427            closeCamera();
1428            throw new RuntimeException("startPreview failed", ex);
1429        }
1430        mPreviewing = true;
1431        mStatus = IDLE;
1432
1433        long threadTimeEnd = Debug.threadCpuTimeNanos();
1434        long wallTimeEnd = SystemClock.elapsedRealtime();
1435        if ((wallTimeEnd - wallTimeStart) > 3000) {
1436            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart)
1437                    + " ms. Thread time was"
1438                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
1439        }
1440    }
1441
1442    private void stopPreview() {
1443        if (mCameraDevice != null && mPreviewing) {
1444            Log.v(TAG, "stopPreview");
1445            mCameraDevice.stopPreview();
1446        }
1447        mPreviewing = false;
1448        // If auto focus was in progress, it would have been canceled.
1449        clearFocusState();
1450    }
1451
1452    private void setCameraParameter() {
1453        // request the preview size, the hardware may not honor it,
1454        // if we depended on it we would have to query the size again
1455        mParameters = mCameraDevice.getParameters();
1456        mParameters.setPreviewSize(mViewFinderWidth, mViewFinderHeight);
1457
1458        // Set picture size parameter.
1459        String pictureSize = mPreferences.getString(
1460                CameraSettings.KEY_PICTURE_SIZE,
1461                getString(R.string.pref_camera_picturesize_default));
1462        mParameters.set(PARM_PICTURE_SIZE, pictureSize);
1463
1464        // Set JPEG quality parameter.
1465        String jpegQuality = mPreferences.getString(
1466                CameraSettings.KEY_JPEG_QUALITY,
1467                getString(R.string.pref_camera_jpegquality_default));
1468        mParameters.set(PARM_JPEG_QUALITY, jpegQuality);
1469
1470        // Set zoom.
1471        if (mZoomValues != null) {
1472            mParameters.set(PARM_ZOOM, mZoomValues[mZoomIndex]);
1473        }
1474
1475        mCameraDevice.setParameters(mParameters);
1476    }
1477
1478    void gotoGallery() {
1479        MenuHelper.gotoCameraImageGallery(this);
1480    }
1481
1482    private void viewLastImage() {
1483        if (mThumbController.isUriValid()) {
1484            Uri targetUri = mThumbController.getUri();
1485            targetUri = targetUri.buildUpon().appendQueryParameter(
1486                    "bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
1487            Intent intent = new Intent(this, ReviewImage.class);
1488            intent.setData(targetUri);
1489            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
1490            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
1491            intent.putExtra("com.android.camera.ReviewMode", true);
1492            try {
1493                startActivity(intent);
1494            } catch (ActivityNotFoundException ex) {
1495                Log.e(TAG, "review image fail", ex);
1496            }
1497        } else {
1498            Log.e(TAG, "Can't view last image.");
1499        }
1500    }
1501
1502    private void startReceivingLocationUpdates() {
1503        if (mLocationManager != null) {
1504            try {
1505                mLocationManager.requestLocationUpdates(
1506                        LocationManager.NETWORK_PROVIDER,
1507                        1000,
1508                        0F,
1509                        mLocationListeners[1]);
1510            } catch (java.lang.SecurityException ex) {
1511                Log.i(TAG, "fail to request location update, ignore", ex);
1512            } catch (IllegalArgumentException ex) {
1513                Log.d(TAG, "provider does not exist " + ex.getMessage());
1514            }
1515            try {
1516                mLocationManager.requestLocationUpdates(
1517                        LocationManager.GPS_PROVIDER,
1518                        1000,
1519                        0F,
1520                        mLocationListeners[0]);
1521            } catch (java.lang.SecurityException ex) {
1522                Log.i(TAG, "fail to request location update, ignore", ex);
1523            } catch (IllegalArgumentException ex) {
1524                Log.d(TAG, "provider does not exist " + ex.getMessage());
1525            }
1526        }
1527    }
1528
1529    private void stopReceivingLocationUpdates() {
1530        if (mLocationManager != null) {
1531            for (int i = 0; i < mLocationListeners.length; i++) {
1532                try {
1533                    mLocationManager.removeUpdates(mLocationListeners[i]);
1534                } catch (Exception ex) {
1535                    Log.i(TAG, "fail to remove location listners, ignore", ex);
1536                }
1537            }
1538        }
1539    }
1540
1541    private Location getCurrentLocation() {
1542        // go in best to worst order
1543        for (int i = 0; i < mLocationListeners.length; i++) {
1544            Location l = mLocationListeners[i].current();
1545            if (l != null) return l;
1546        }
1547        return null;
1548    }
1549
1550    private boolean isCameraIdle() {
1551        return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED;
1552    }
1553
1554    private boolean isImageCaptureIntent() {
1555        String action = getIntent().getAction();
1556        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1557    }
1558
1559    private void showPostCaptureAlert() {
1560        if (mIsImageCaptureIntent) {
1561            findViewById(R.id.shutter_button).setVisibility(View.INVISIBLE);
1562            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1563            for (int id : pickIds) {
1564                View button = findViewById(id);
1565                ((View) button.getParent()).setVisibility(View.VISIBLE);
1566            }
1567        }
1568    }
1569
1570    private void hidePostCaptureAlert() {
1571        if (mIsImageCaptureIntent) {
1572            findViewById(R.id.shutter_button).setVisibility(View.VISIBLE);
1573            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1574            for (int id : pickIds) {
1575                View button = findViewById(id);
1576                ((View) button.getParent()).setVisibility(View.GONE);
1577            }
1578        }
1579    }
1580
1581    private int calculatePicturesRemaining() {
1582        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
1583        return mPicturesRemaining;
1584    }
1585
1586    @Override
1587    public boolean onPrepareOptionsMenu(Menu menu) {
1588        super.onPrepareOptionsMenu(menu);
1589
1590        for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) {
1591            menu.setGroupVisible(i, false);
1592        }
1593
1594        // Only show the menu when camera is idle.
1595        if (isCameraIdle()) {
1596            menu.setGroupVisible(MenuHelper.GENERIC_ITEM, true);
1597            menu.setGroupVisible(MenuHelper.IMAGE_MODE_ITEM, true);
1598        }
1599
1600        return true;
1601    }
1602
1603    @Override
1604    public boolean onCreateOptionsMenu(Menu menu) {
1605        super.onCreateOptionsMenu(menu);
1606
1607        if (mIsImageCaptureIntent) {
1608            // No options menu for attach mode.
1609            return false;
1610        } else {
1611            addBaseMenuItems(menu);
1612        }
1613        return true;
1614    }
1615
1616    private void addBaseMenuItems(Menu menu) {
1617        MenuHelper.addSwitchModeMenuItem(menu, this, true);
1618        {
1619            MenuItem gallery = menu.add(
1620                    MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0,
1621                    R.string.camera_gallery_photos_text)
1622                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1623                public boolean onMenuItemClick(MenuItem item) {
1624                    gotoGallery();
1625                    return true;
1626                }
1627            });
1628            gallery.setIcon(android.R.drawable.ic_menu_gallery);
1629            mGalleryItems.add(gallery);
1630        }
1631        {
1632            MenuItem gallery = menu.add(
1633                    MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0,
1634                    R.string.camera_gallery_photos_text)
1635                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1636                public boolean onMenuItemClick(MenuItem item) {
1637                    gotoGallery();
1638                    return true;
1639                }
1640            });
1641            gallery.setIcon(android.R.drawable.ic_menu_gallery);
1642            mGalleryItems.add(gallery);
1643        }
1644
1645        MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS,
1646                0, R.string.settings)
1647                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1648            public boolean onMenuItemClick(MenuItem item) {
1649                // Keep the camera instance for a while.
1650                // This avoids re-opening the camera and saves time.
1651                CameraHolder.instance().keep();
1652
1653                Intent intent = new Intent();
1654                intent.setClass(Camera.this, CameraSettings.class);
1655                startActivity(intent);
1656                return true;
1657            }
1658        });
1659        item.setIcon(android.R.drawable.ic_menu_preferences);
1660    }
1661
1662    public void onSwitchChanged(Switcher source, boolean onOff) {
1663        if (onOff == SWITCH_VIDEO && isCameraIdle()) {
1664            MenuHelper.gotoVideoMode(this);
1665            finish();
1666        }
1667    }
1668}
1669
1670class FocusRectangle extends View {
1671
1672    @SuppressWarnings("unused")
1673    private static final String TAG = "FocusRectangle";
1674
1675    public FocusRectangle(Context context, AttributeSet attrs) {
1676        super(context, attrs);
1677    }
1678
1679    private void setDrawable(int resid) {
1680        setBackgroundDrawable(getResources().getDrawable(resid));
1681    }
1682
1683    public void showStart() {
1684        setDrawable(R.drawable.focus_focusing);
1685    }
1686
1687    public void showSuccess() {
1688        setDrawable(R.drawable.focus_focused);
1689    }
1690
1691    public void showFail() {
1692        setDrawable(R.drawable.focus_focus_failed);
1693    }
1694
1695    public void clear() {
1696        setBackgroundDrawable(null);
1697    }
1698}
1699