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