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