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