Camera.java revision 5055ba62e22becc9c8d03898b80cda8ed68cfe80
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        // To reduce startup time, we run some service creation code in another thread.
763        // We make sure the services are loaded at the end of onCreate().
764        Thread loadServiceThread = new Thread(new Runnable() {
765            public void run() {
766                mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
767                mOrientationListener = new OrientationListener(Camera.this) {
768                    public void onOrientationChanged(int orientation) {
769                        mLastOrientation = orientation;
770                    }
771                };
772            }
773        });
774        loadServiceThread.start();
775
776        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
777        mContentResolver = getContentResolver();
778
779        //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
780        requestWindowFeature(Window.FEATURE_PROGRESS);
781
782        Window win = getWindow();
783        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
784        setContentView(R.layout.camera);
785
786        mSurfaceView = (VideoPreview) findViewById(R.id.camera_preview);
787
788        // don't set mSurfaceHolder here. We have it set ONLY within
789        // surfaceCreated / surfaceDestroyed, other parts of the code
790        // assume that when it is set, the surface is also set.
791        SurfaceHolder holder = mSurfaceView.getHolder();
792        holder.addCallback(this);
793        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
794
795        mBlackout = (ImageView) findViewById(R.id.blackout);
796        mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000));
797
798        mLastPictureButton = (ImageView) findViewById(R.id.last_picture_button);
799        if (!isImageCaptureIntent())  {
800            ImageManager.IImageList images = ImageManager.instance().allImages(
801                    this,
802                    getContentResolver(),
803                    ImageManager.DataLocation.ALL,
804                    ImageManager.INCLUDE_IMAGES,
805                    ImageManager.SORT_DESCENDING,
806                    ImageManager.CAMERA_IMAGE_BUCKET_ID);
807            ImageManager.IImage lastPicture =
808                    images.isEmpty() ? null : images.getImageAt(0);
809            mLastPictureButton.setOnClickListener(this);
810            if (lastPicture == null) {
811                mLastPictureButton.setVisibility(View.GONE);
812            } else {
813                Bitmap miniThumb = lastPicture.miniThumbBitmap();
814                setLastPictureThumb(miniThumb, lastPicture.fullSizeImageUri());
815            }
816            images.deactivate();
817        }
818
819        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
820        mShutterButton.setOnShutterButtonListener(this);
821
822        try {
823            mClickSound = new MediaPlayer();
824            AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.camera_click);
825
826            mClickSound.setDataSource(afd.getFileDescriptor(),
827                             afd.getStartOffset(),
828                             afd.getLength());
829
830            if (mClickSound != null) {
831                mClickSound.setAudioStreamType(AudioManager.STREAM_ALARM);
832                mClickSound.prepare();
833            }
834        } catch (Exception ex) {
835            Log.w(TAG, "Couldn't create click sound", ex);
836        }
837
838        mFocusIndicator = findViewById(R.id.focus_indicator);
839        mFocusBlinkAnimation = AnimationUtils.loadAnimation(this, R.anim.auto_focus_blink);
840        mFocusBlinkAnimation.setRepeatCount(Animation.INFINITE);
841        mFocusBlinkAnimation.setRepeatMode(Animation.REVERSE);
842
843        mPostCaptureAlert = findViewById(R.id.post_picture_panel);
844
845        // Make sure the services are loaded.
846        try {
847            loadServiceThread.join();
848        } catch (InterruptedException ex) {
849        }
850    }
851
852    @Override
853    public void onStart() {
854        super.onStart();
855
856        Thread t = new Thread(new Runnable() {
857            public void run() {
858                final boolean storageOK = calculatePicturesRemaining() > 0;
859
860                if (!storageOK) {
861                    mHandler.post(new Runnable() {
862                        public void run() {
863                            showStorageToast(mPicturesRemaining);
864                        }
865                    });
866                }
867            }
868        });
869        t.start();
870    }
871
872    public void onClick(View v) {
873        switch (v.getId()) {
874        case R.id.last_picture_button:
875            viewLastImage();
876            break;
877        case R.id.attach:
878            doAttach();
879            break;
880        case R.id.cancel:
881            doCancel();
882        }
883    }
884
885    private void doAttach() {
886        Bitmap bitmap = mImageCapture.getLastBitmap();
887        mCaptureObject.setDone(true);
888
889        String cropValue = null;
890        Uri saveUri = null;
891
892        Bundle myExtras = getIntent().getExtras();
893        if (myExtras != null) {
894            saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
895            cropValue = myExtras.getString("crop");
896        }
897
898
899        if (cropValue == null) {
900            /*
901             * First handle the no crop case -- just return the value.  If the caller
902             * specifies a "save uri" then write the data to it's stream.  Otherwise,
903             * pass back a scaled down version of the bitmap directly in the extras.
904             */
905            if (saveUri != null) {
906                OutputStream outputStream = null;
907                try {
908                    outputStream = mContentResolver.openOutputStream(saveUri);
909                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream);
910                    outputStream.close();
911
912                    setResult(RESULT_OK);
913                    finish();
914                } catch (IOException ex) {
915                    //
916                } finally {
917                    if (outputStream != null) {
918                        try {
919                            outputStream.close();
920                        } catch (IOException ex) {
921
922                        }
923                    }
924                }
925            } else {
926                float scale = .5F;
927                Matrix m = new Matrix();
928                m.setScale(scale, scale);
929
930                bitmap = Bitmap.createBitmap(bitmap, 0, 0,
931                        bitmap.getWidth(),
932                        bitmap.getHeight(),
933                        m, true);
934
935                setResult(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap));
936                finish();
937            }
938        }
939        else {
940            /*
941             * Save the image to a temp file and invoke the cropper
942             */
943            Uri tempUri = null;
944            FileOutputStream tempStream = null;
945            try {
946                File path = getFileStreamPath(sTempCropFilename);
947                path.delete();
948                tempStream = openFileOutput(sTempCropFilename, 0);
949                bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream);
950                tempStream.close();
951                tempUri = Uri.fromFile(path);
952            } catch (FileNotFoundException ex) {
953                setResult(Activity.RESULT_CANCELED);
954                finish();
955                return;
956            } catch (IOException ex) {
957                setResult(Activity.RESULT_CANCELED);
958                finish();
959                return;
960            } finally {
961                if (tempStream != null) {
962                    try {
963                        tempStream.close();
964                    } catch (IOException ex) {
965
966                    }
967                }
968            }
969
970            Bundle newExtras = new Bundle();
971            if (cropValue.equals("circle"))
972                newExtras.putString("circleCrop", "true");
973            if (saveUri != null)
974                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri);
975            else
976                newExtras.putBoolean("return-data", true);
977
978            Intent cropIntent = new Intent();
979            cropIntent.setClass(Camera.this, CropImage.class);
980            cropIntent.setData(tempUri);
981            cropIntent.putExtras(newExtras);
982
983            startActivityForResult(cropIntent, CROP_MSG);
984        }
985    }
986
987    private void doCancel() {
988        setResult(RESULT_CANCELED, new Intent());
989        finish();
990    }
991
992    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
993        switch (button.getId()) {
994            case R.id.shutter_button:
995                doFocus(pressed);
996                break;
997        }
998    }
999
1000    public void onShutterButtonClick(ShutterButton button) {
1001        switch (button.getId()) {
1002            case R.id.shutter_button:
1003                doSnap(false);
1004                break;
1005        }
1006    }
1007
1008    private void showStorageToast() {
1009        MenuHelper.showStorageToast(this);
1010    }
1011
1012    private void showStorageToast(int remainingPictures) {
1013        MenuHelper.showStorageToast(this, remainingPictures);
1014    }
1015
1016    @Override
1017    public void onResume() {
1018        super.onResume();
1019        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1020
1021        mPausing = false;
1022        mOrientationListener.enable();
1023
1024        // install an intent filter to receive SD card related events.
1025        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1026        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1027        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
1028        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1029        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1030        intentFilter.addDataScheme("file");
1031        registerReceiver(mReceiver, intentFilter);
1032        mDidRegister = true;
1033
1034        mImageCapture = new ImageCapture();
1035
1036        restartPreview();
1037
1038        if (mPreferences.getBoolean("pref_camera_recordlocation_key", false))
1039            startReceivingLocationUpdates();
1040
1041        updateFocusIndicator();
1042
1043        try {
1044            mFocusToneGenerator = new ToneGenerator(AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1045        } catch (RuntimeException e) {
1046            Log.w(TAG, "Exception caught while creating local tone generator: " + e);
1047            mFocusToneGenerator = null;
1048        }
1049
1050        mBlackout.setVisibility(View.GONE);
1051
1052        if (mLastPictureUri != null) {
1053            IImageList list = ImageManager.makeImageList(mLastPictureUri, this,
1054                    ImageManager.SORT_ASCENDING);
1055            if (list.getImageForUri(mLastPictureUri) == null) {
1056                mLastPictureUri = null;
1057                mLastPictureButton.setVisibility(View.GONE);
1058            }
1059            list.deactivate();
1060        }
1061    }
1062
1063    private ImageManager.DataLocation dataLocation() {
1064        return ImageManager.DataLocation.EXTERNAL;
1065    }
1066
1067    @Override
1068    public void onStop() {
1069        keep();
1070        stopPreview();
1071        closeCamera();
1072        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1073        super.onStop();
1074    }
1075
1076    @Override
1077    protected void onPause() {
1078        keep();
1079
1080        mPausing = true;
1081        mOrientationListener.disable();
1082
1083        stopPreview();
1084
1085        if (!mImageCapture.mCapturing) {
1086            closeCamera();
1087        }
1088        if (mDidRegister) {
1089            unregisterReceiver(mReceiver);
1090            mDidRegister = false;
1091        }
1092        stopReceivingLocationUpdates();
1093
1094        if (mFocusToneGenerator != null) {
1095            mFocusToneGenerator.release();
1096            mFocusToneGenerator = null;
1097        }
1098
1099        super.onPause();
1100    }
1101
1102    @Override
1103    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1104        switch (requestCode) {
1105            case CROP_MSG: {
1106                Intent intent = new Intent();
1107                if (data != null) {
1108                    Bundle extras = data.getExtras();
1109                    if (extras != null) {
1110                        intent.putExtras(extras);
1111                    }
1112                }
1113                setResult(resultCode, intent);
1114                finish();
1115
1116                File path = getFileStreamPath(sTempCropFilename);
1117                path.delete();
1118
1119                break;
1120            }
1121        }
1122    }
1123
1124    private void autoFocus() {
1125        updateFocusIndicator();
1126        if (!mIsFocusing) {
1127            if (mCameraDevice != null) {
1128                mIsFocusing = true;
1129                mIsFocused = false;
1130                mCameraDevice.autoFocus(mAutoFocusCallback);
1131            }
1132        }
1133    }
1134
1135    private void clearFocus() {
1136        mIsFocusing = false;
1137        mIsFocused = false;
1138        mIsFocusButtonPressed = false;
1139    }
1140
1141    private void updateFocusIndicator() {
1142        mHandler.post(new Runnable() {
1143            public void run() {
1144                if (mIsFocusing || !mIsFocusButtonPressed) {
1145                    mFocusIndicator.setVisibility(View.GONE);
1146                    mFocusIndicator.clearAnimation();
1147                } else {
1148                    if (mIsFocused) {
1149                        mFocusIndicator.setVisibility(View.VISIBLE);
1150                        mFocusIndicator.clearAnimation();
1151                    } else {
1152                        mFocusIndicator.setVisibility(View.VISIBLE);
1153                        mFocusIndicator.startAnimation(mFocusBlinkAnimation);
1154                    }
1155                }
1156            }
1157        });
1158    }
1159
1160
1161    @Override
1162    public boolean onKeyDown(int keyCode, KeyEvent event) {
1163        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1164        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1165
1166        switch (keyCode) {
1167            case KeyEvent.KEYCODE_BACK:
1168                if (mStatus == SNAPSHOT_IN_PROGRESS) {
1169                    // ignore backs while we're taking a picture
1170                    return true;
1171                }
1172                break;
1173            case KeyEvent.KEYCODE_FOCUS:
1174                if (event.getRepeatCount() == 0) {
1175                    doFocus(true);
1176                }
1177                return true;
1178            case KeyEvent.KEYCODE_CAMERA:
1179                if (event.getRepeatCount() == 0) {
1180                    doSnap(false);
1181                }
1182                return true;
1183            case KeyEvent.KEYCODE_DPAD_CENTER:
1184                // If we get a dpad center event without any focused view, move the
1185                // focus to the shutter button and press it.
1186                if (event.getRepeatCount() == 0) {
1187                    if (mShutterButton.isInTouchMode()) {
1188                        mShutterButton.requestFocusFromTouch();
1189                    } else {
1190                        mShutterButton.requestFocus();
1191                    }
1192                    mShutterButton.setPressed(true);
1193                }
1194                return true;
1195        }
1196
1197        return super.onKeyDown(keyCode, event);
1198    }
1199
1200    @Override
1201    public boolean onKeyUp(int keyCode, KeyEvent event) {
1202        switch (keyCode) {
1203            case KeyEvent.KEYCODE_FOCUS:
1204                doFocus(false);
1205                return true;
1206        }
1207        return super.onKeyUp(keyCode, event);
1208    }
1209
1210    private void doSnap(boolean needAutofocus) {
1211        // The camera operates in focus-priority mode, meaning that we take a picture
1212        // when focusing completes, and only if it completes successfully. If the user
1213        // has half-pressed the shutter and already locked focus, we can take the photo
1214        // right away, otherwise we need to start AF.
1215        if (mIsFocused || !mPreviewing) {
1216            // doesn't get set until the idler runs
1217            if (mCaptureObject != null) {
1218                mCaptureObject.onSnap();
1219            }
1220            clearFocus();
1221            updateFocusIndicator();
1222        } else {
1223            // Half pressing the shutter (i.e. the focus button event) will already have
1224            // requested AF for us, so just request capture on focus here. If AF has
1225            // already failed, we don't want to trigger it again.
1226            mCaptureOnFocus = true;
1227            if (needAutofocus && !mIsFocusButtonPressed) {
1228                // But we do need to start AF for DPAD_CENTER
1229                autoFocus();
1230            }
1231        }
1232    }
1233
1234    private void doFocus(boolean pressed) {
1235        if (pressed) {
1236            mIsFocusButtonPressed = true;
1237            mCaptureOnFocus = false;
1238            if (mPreviewing) {
1239                autoFocus();
1240            } else if (mCaptureObject != null) {
1241                // Save and restart preview
1242                mCaptureObject.onSnap();
1243            }
1244        } else {
1245            clearFocus();
1246            updateFocusIndicator();
1247        }
1248    }
1249
1250    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1251        // if we're creating the surface, start the preview as well.
1252        boolean preview = holder.isCreating();
1253        setViewFinder(w, h, preview);
1254        mCaptureObject = mImageCapture;
1255    }
1256
1257    public void surfaceCreated(SurfaceHolder holder) {
1258        mSurfaceHolder = holder;
1259    }
1260
1261    public void surfaceDestroyed(SurfaceHolder holder) {
1262        stopPreview();
1263        mSurfaceHolder = null;
1264    }
1265
1266    private void closeCamera() {
1267        if (mCameraDevice != null) {
1268            mCameraDevice.release();
1269            mCameraDevice = null;
1270            mPreviewing = false;
1271        }
1272    }
1273
1274    private boolean ensureCameraDevice() {
1275        if (mCameraDevice == null) {
1276            mCameraDevice = android.hardware.Camera.open();
1277        }
1278        return mCameraDevice != null;
1279    }
1280
1281    private void restartPreview() {
1282        VideoPreview surfaceView = mSurfaceView;
1283
1284        // make sure the surfaceview fills the whole screen when previewing
1285        surfaceView.setAspectRatio(VideoPreview.DONT_CARE);
1286        setViewFinder(mOriginalViewFinderWidth, mOriginalViewFinderHeight, true);
1287        mStatus = IDLE;
1288
1289        // Calculate this in advance of each shot so we don't add to shutter latency. It's true that
1290        // someone else could write to the SD card in the mean time and fill it, but that could have
1291        // happened between the shutter press and saving the JPEG too.
1292        // TODO: The best longterm solution is to write a reserve file of maximum JPEG size, always
1293        // let the user take a picture, and delete that file if needed to save the new photo.
1294        calculatePicturesRemaining();
1295
1296        if (mShouldShowLastPictureButton) {
1297            mShouldShowLastPictureButton = false;
1298            mLastPictureButton.setVisibility(View.VISIBLE);
1299            Animation a = mShowLastPictureButtonAnimation;
1300            a.setDuration(500);
1301            mLastPictureButton.setAnimation(a);
1302        }
1303
1304        if (mShouldTransitionThumbnails) {
1305            mShouldTransitionThumbnails = false;
1306            mThumbnailTransition.startTransition(500);
1307        }
1308    }
1309
1310    private void setViewFinder(int w, int h, boolean startPreview) {
1311        if (mPausing)
1312            return;
1313
1314        if (mPreviewing &&
1315                w == mViewFinderWidth &&
1316                h == mViewFinderHeight) {
1317            return;
1318        }
1319
1320        if (!ensureCameraDevice())
1321            return;
1322
1323        if (mSurfaceHolder == null)
1324            return;
1325
1326        if (isFinishing())
1327            return;
1328
1329        if (mPausing)
1330            return;
1331
1332        // remember view finder size
1333        mViewFinderWidth = w;
1334        mViewFinderHeight = h;
1335        if (mOriginalViewFinderHeight == 0) {
1336            mOriginalViewFinderWidth = w;
1337            mOriginalViewFinderHeight = h;
1338        }
1339
1340        if (startPreview == false)
1341            return;
1342
1343        /*
1344         * start the preview if we're asked to...
1345         */
1346
1347        // we want to start the preview and we're previewing already,
1348        // stop the preview first (this will blank the screen).
1349        if (mPreviewing)
1350            stopPreview();
1351
1352        // this blanks the screen if the surface changed, no-op otherwise
1353        try {
1354            mCameraDevice.setPreviewDisplay(mSurfaceHolder);
1355        } catch (IOException exception) {
1356            mCameraDevice.release();
1357            mCameraDevice = null;
1358            // TODO: add more exception handling logic here
1359            return;
1360        }
1361
1362        // request the preview size, the hardware may not honor it,
1363        // if we depended on it we would have to query the size again
1364        mParameters = mCameraDevice.getParameters();
1365        mParameters.setPreviewSize(w, h);
1366        try {
1367            mCameraDevice.setParameters(mParameters);
1368        } catch (IllegalArgumentException e) {
1369            // Ignore this error, it happens in the simulator.
1370        }
1371
1372
1373        final long wallTimeStart = SystemClock.elapsedRealtime();
1374        final long threadTimeStart = Debug.threadCpuTimeNanos();
1375
1376        final Object watchDogSync = new Object();
1377        Thread watchDog = new Thread(new Runnable() {
1378            public void run() {
1379                int next_warning = 1;
1380                while (true) {
1381                    try {
1382                        synchronized (watchDogSync) {
1383                            watchDogSync.wait(1000);
1384                        }
1385                    } catch (InterruptedException ex) {
1386                        //
1387                    }
1388                    if (mPreviewing)
1389                        break;
1390
1391                    int delay = (int) (SystemClock.elapsedRealtime() - wallTimeStart) / 1000;
1392                    if (delay >= next_warning) {
1393                        if (delay < 120) {
1394                            Log.e(TAG, "preview hasn't started yet in " + delay + " seconds");
1395                        } else {
1396                            Log.e(TAG, "preview hasn't started yet in " + (delay / 60) + " minutes");
1397                        }
1398                        if (next_warning < 60) {
1399                            next_warning <<= 1;
1400                            if (next_warning == 16) {
1401                                next_warning = 15;
1402                            }
1403                        } else {
1404                            next_warning += 60;
1405                        }
1406                    }
1407                }
1408            }
1409        });
1410
1411        watchDog.start();
1412
1413        if (Config.LOGV)
1414            Log.v(TAG, "calling mCameraDevice.startPreview");
1415        try {
1416            mCameraDevice.startPreview();
1417        } catch (Throwable e) {
1418            // TODO: change Throwable to IOException once android.hardware.Camera.startPreview
1419            // properly declares that it throws IOException.
1420        }
1421        mPreviewing = true;
1422
1423        synchronized (watchDogSync) {
1424            watchDogSync.notify();
1425        }
1426
1427        long threadTimeEnd = Debug.threadCpuTimeNanos();
1428        long wallTimeEnd = SystemClock.elapsedRealtime();
1429        if ((wallTimeEnd - wallTimeStart) > 3000) {
1430            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart) + " ms. Thread time was"
1431                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
1432        }
1433    }
1434
1435    private void stopPreview() {
1436        if (mCameraDevice != null && mPreviewing) {
1437            mCameraDevice.stopPreview();
1438        }
1439        mPreviewing = false;
1440    }
1441
1442    void gotoGallery() {
1443        MenuHelper.gotoCameraImageGallery(this);
1444    }
1445
1446    private void viewLastImage() {
1447        Uri targetUri = mLastPictureUri;
1448        if (targetUri != null) {
1449            targetUri = targetUri.buildUpon().
1450                appendQueryParameter("bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
1451            Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
1452            intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION,
1453                    ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
1454            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
1455            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
1456            intent.putExtra("com.android.camera.ReviewMode", true);
1457
1458            try {
1459                startActivity(intent);
1460            } catch (android.content.ActivityNotFoundException ex) {
1461                // ignore.
1462            }
1463        }
1464    }
1465
1466    void keep() {
1467        cancelSavingNotification();
1468        if (mCaptureObject != null) {
1469            mCaptureObject.dismissFreezeFrame(true);
1470        }
1471    };
1472
1473    void toss() {
1474        cancelSavingNotification();
1475        if (mCaptureObject != null) {
1476            mCaptureObject.cancelSave();
1477        }
1478    };
1479
1480    private ImageManager.IImage getImageForURI(Uri uri) {
1481        ImageManager.IImageList list = ImageManager.instance().allImages(
1482                this,
1483                mContentResolver,
1484                dataLocation(),
1485                ImageManager.INCLUDE_IMAGES,
1486                ImageManager.SORT_ASCENDING);
1487        ImageManager.IImage image = list.getImageForUri(uri);
1488        list.deactivate();
1489        return image;
1490    }
1491
1492
1493    private void startReceivingLocationUpdates() {
1494        if (mLocationManager != null) {
1495            try {
1496                mLocationManager.requestLocationUpdates(
1497                        LocationManager.NETWORK_PROVIDER,
1498                        1000,
1499                        0F,
1500                        mLocationListeners[1]);
1501            } catch (java.lang.SecurityException ex) {
1502                // ok
1503            } catch (IllegalArgumentException ex) {
1504                if (Config.LOGD) {
1505                    Log.d(TAG, "provider does not exist " + ex.getMessage());
1506                }
1507            }
1508            try {
1509                mLocationManager.requestLocationUpdates(
1510                        LocationManager.GPS_PROVIDER,
1511                        1000,
1512                        0F,
1513                        mLocationListeners[0]);
1514            } catch (java.lang.SecurityException ex) {
1515                // ok
1516            } catch (IllegalArgumentException ex) {
1517                if (Config.LOGD) {
1518                    Log.d(TAG, "provider does not exist " + ex.getMessage());
1519                }
1520            }
1521        }
1522    }
1523
1524    private void stopReceivingLocationUpdates() {
1525        if (mLocationManager != null) {
1526            for (int i = 0; i < mLocationListeners.length; i++) {
1527                try {
1528                    mLocationManager.removeUpdates(mLocationListeners[i]);
1529                } catch (Exception ex) {
1530                    // ok
1531                }
1532            }
1533        }
1534    }
1535
1536    private Location getCurrentLocation() {
1537        Location l = null;
1538
1539        // go in best to worst order
1540        for (int i = 0; i < mLocationListeners.length; i++) {
1541            l = mLocationListeners[i].current();
1542            if (l != null)
1543                break;
1544        }
1545
1546        return l;
1547    }
1548
1549    @Override
1550    public void onOptionsMenuClosed(Menu menu) {
1551        super.onOptionsMenuClosed(menu);
1552        if (mImageSavingItem && !mMenuSelectionMade) {
1553            // save the image if we presented the "advanced" menu
1554            // which happens if "menu" is pressed while in
1555            // SNAPSHOT_IN_PROGRESS  or SNAPSHOT_COMPLETED modes
1556            keep();
1557            mHandler.sendEmptyMessage(RESTART_PREVIEW);
1558        }
1559    }
1560
1561    @Override
1562    public boolean onMenuOpened(int featureId, Menu menu) {
1563        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
1564            if (mStatus == SNAPSHOT_IN_PROGRESS) {
1565                cancelAutomaticPreviewRestart();
1566                mMenuSelectionMade = false;
1567            }
1568        }
1569        return super.onMenuOpened(featureId, menu);
1570    }
1571
1572    @Override
1573    public boolean onPrepareOptionsMenu(Menu menu) {
1574        super.onPrepareOptionsMenu(menu);
1575
1576        mMenuSelectionMade = false;
1577
1578        for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) {
1579            if (i != MenuHelper.GENERIC_ITEM) {
1580                menu.setGroupVisible(i, false);
1581            }
1582        }
1583
1584        if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) {
1585            menu.setGroupVisible(MenuHelper.IMAGE_SAVING_ITEM, true);
1586            mImageSavingItem = true;
1587        } else {
1588            menu.setGroupVisible(MenuHelper.IMAGE_MODE_ITEM, true);
1589            mImageSavingItem = false;
1590        }
1591
1592        if (mCaptureObject != null)
1593            mCaptureObject.cancelAutoDismiss();
1594
1595        return true;
1596    }
1597
1598    private void cancelAutomaticPreviewRestart() {
1599        mKeepAndRestartPreview = false;
1600        mHandler.removeMessages(RESTART_PREVIEW);
1601    }
1602
1603    private boolean isImageCaptureIntent() {
1604        String action = getIntent().getAction();
1605        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1606    }
1607
1608    private void showPostCaptureAlert() {
1609        boolean isPick = isImageCaptureIntent();
1610        int pickVisible = isPick ? View.VISIBLE : View.GONE;
1611        mPostCaptureAlert.setVisibility(pickVisible);
1612        if (isPick) {
1613            int[] pickIds = {R.id.attach, R.id.cancel};
1614            for(int id : pickIds) {
1615                View view = mPostCaptureAlert.findViewById(id);
1616                view.setOnClickListener(this);
1617                Animation animation = new AlphaAnimation(0F, 1F);
1618                animation.setDuration(500);
1619                view.setAnimation(animation);
1620            }
1621        }
1622    }
1623
1624    private void hidePostCaptureAlert() {
1625        mPostCaptureAlert.setVisibility(View.GONE);
1626    }
1627
1628    @Override
1629    public boolean onCreateOptionsMenu(Menu menu) {
1630        super.onCreateOptionsMenu(menu);
1631
1632        if (isImageCaptureIntent()) {
1633            // No options menu for attach mode.
1634            return false;
1635        } else {
1636            addBaseMenuItems(menu);
1637            MenuHelper.addImageMenuItems(
1638                    menu,
1639                    MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU,
1640                    true,
1641                    Camera.this,
1642                    mHandler,
1643
1644                    // Handler for deletion
1645                    new Runnable() {
1646                        public void run() {
1647                            if (mCaptureObject != null) {
1648                                mCaptureObject.cancelSave();
1649                                Uri uri = mCaptureObject.getLastCaptureUri();
1650                                if (uri != null) {
1651                                    mContentResolver.delete(uri, null, null);
1652                                }
1653                            }
1654                        }
1655                    },
1656                    new MenuHelper.MenuInvoker() {
1657                        public void run(final MenuHelper.MenuCallback cb) {
1658                            mMenuSelectionMade = true;
1659                            postAfterKeep(new Runnable() {
1660                                public void run() {
1661                                    cb.run(mSelectedImageGetter.getCurrentImageUri(), mSelectedImageGetter.getCurrentImage());
1662                                    if (mCaptureObject != null)
1663                                        mCaptureObject.dismissFreezeFrame(true);
1664                                }
1665                            });
1666                        }
1667                    });
1668
1669            MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1670                public boolean onMenuItemClick(MenuItem item) {
1671                    postAfterKeep(new Runnable() {
1672                        public void run() {
1673                            gotoGallery();
1674                        }
1675                    });
1676                    return true;
1677                }
1678            });
1679            gallery.setIcon(android.R.drawable.ic_menu_gallery);
1680        }
1681        return true;
1682    }
1683
1684    SelectedImageGetter mSelectedImageGetter =
1685        new SelectedImageGetter() {
1686            public ImageManager.IImage getCurrentImage() {
1687                return getImageForURI(getCurrentImageUri());
1688            }
1689            public Uri getCurrentImageUri() {
1690                keep();
1691                return mCaptureObject.getLastCaptureUri();
1692            }
1693        };
1694
1695    private int calculatePicturesRemaining() {
1696        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
1697        return mPicturesRemaining;
1698    }
1699
1700    private void addBaseMenuItems(Menu menu) {
1701        MenuHelper.addSwitchModeMenuItem(menu, this, true);
1702        {
1703            MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
1704                public boolean onMenuItemClick(MenuItem item) {
1705                    gotoGallery();
1706                    return true;
1707                }
1708            });
1709            gallery.setIcon(android.R.drawable.ic_menu_gallery);
1710            mGalleryItems.add(gallery);
1711        }
1712        {
1713            MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() {
1714                public boolean onMenuItemClick(MenuItem item) {
1715                    gotoGallery();
1716                    return true;
1717                }
1718            });
1719            gallery.setIcon(android.R.drawable.ic_menu_gallery);
1720            mGalleryItems.add(gallery);
1721        }
1722
1723        MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() {
1724            public boolean onMenuItemClick(MenuItem item) {
1725                Intent intent = new Intent();
1726                intent.setClass(Camera.this, CameraSettings.class);
1727                startActivity(intent);
1728                return true;
1729            }
1730        });
1731        item.setIcon(android.R.drawable.ic_menu_preferences);
1732    }
1733}
1734
1735