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