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