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