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