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