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