Camera.java revision 37acf79b1cc060f355c9cd1803c2bdd5e39deb27
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 android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.BroadcastReceiver;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28import android.content.res.Resources;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.graphics.Matrix;
32import android.hardware.Camera.Parameters;
33import android.hardware.Camera.PictureCallback;
34import android.hardware.Camera.Size;
35import android.location.Location;
36import android.location.LocationManager;
37import android.location.LocationProvider;
38import android.media.AudioManager;
39import android.media.ToneGenerator;
40import android.net.Uri;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Debug;
44import android.os.Environment;
45import android.os.Handler;
46import android.os.Message;
47import android.os.SystemClock;
48import android.preference.PreferenceManager;
49import android.preference.PreferenceScreen;
50import android.provider.MediaStore;
51import android.text.format.DateFormat;
52import android.util.AttributeSet;
53import android.util.Log;
54import android.view.GestureDetector;
55import android.view.KeyEvent;
56import android.view.LayoutInflater;
57import android.view.Menu;
58import android.view.MenuItem;
59import android.view.MotionEvent;
60import android.view.OrientationEventListener;
61import android.view.SurfaceHolder;
62import android.view.View;
63import android.view.ViewGroup;
64import android.view.Window;
65import android.view.WindowManager;
66import android.view.MenuItem.OnMenuItemClickListener;
67import android.widget.ImageView;
68import android.widget.ZoomButtonsController;
69
70import com.android.camera.gallery.IImage;
71import com.android.camera.gallery.IImageList;
72
73import java.io.File;
74import java.io.FileNotFoundException;
75import java.io.FileOutputStream;
76import java.io.IOException;
77import java.io.OutputStream;
78import java.util.ArrayList;
79import java.util.Collections;
80import java.util.List;
81
82/**
83 * Activity of the Camera which used to see preview and take pictures.
84 */
85public class Camera extends Activity implements View.OnClickListener,
86        ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback,
87        Switcher.OnSwitchListener, OnScreenSettings.OnVisibilityChangedListener,
88        OnSharedPreferenceChangeListener {
89
90    private static final String TAG = "camera";
91
92    private static final int CROP_MSG = 1;
93    private static final int FIRST_TIME_INIT = 2;
94    private static final int RESTART_PREVIEW = 3;
95    private static final int CLEAR_SCREEN_DELAY = 4;
96
97    private static final String GPS_MODE_ON = "on";
98    private static final String GPS_MODE_OFF = "off";
99
100    private static final int SCREEN_DELAY = 2 * 60 * 1000;
101    private static final int FOCUS_BEEP_VOLUME = 100;
102
103    private double mZoomValue;  // The current zoom value.
104    private double mZoomStep;
105    private double mZoomMax;
106    public static final double ZOOM_STEP_MIN = 0.25;
107    public static final String ZOOM_STOP = "stop";
108    public static final String ZOOM_IMMEDIATE = "zoom-immediate";
109    public static final String ZOOM_CONTINUOUS = "zoom-continuous";
110    public static final double ZOOM_MIN = 1.0;
111    public static final String ZOOM_SPEED = "99";
112
113    private Parameters mParameters;
114
115    // The parameter strings to communicate with camera driver.
116    public static final String PARM_ZOOM_STATE = "zoom-state";
117    public static final String PARM_ZOOM_STEP = "zoom-step";
118    public static final String PARM_ZOOM_TO_LEVEL = "zoom-to-level";
119    public static final String PARM_ZOOM_SPEED = "zoom-speed";
120    public static final String PARM_ZOOM_MAX = "max-picture-continuous-zoom";
121
122    private OrientationEventListener mOrientationListener;
123    private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
124    private SharedPreferences mPreferences;
125
126    private static final int IDLE = 1;
127    private static final int SNAPSHOT_IN_PROGRESS = 2;
128
129    private static final boolean SWITCH_CAMERA = true;
130    private static final boolean SWITCH_VIDEO = false;
131
132    private int mStatus = IDLE;
133    private static final String sTempCropFilename = "crop-temp";
134
135    private android.hardware.Camera mCameraDevice;
136    private VideoPreview mSurfaceView;
137    private SurfaceHolder mSurfaceHolder = null;
138    private ShutterButton mShutterButton;
139    private FocusRectangle mFocusRectangle;
140    private IconIndicator mGpsIndicator;
141    private IconIndicator mFlashIndicator;
142    private ToneGenerator mFocusToneGenerator;
143    private ZoomButtonsController mZoomButtons;
144    private GestureDetector mGestureDetector;
145    private Switcher mSwitcher;
146    private boolean mStartPreviewFail = false;
147
148    // mPostCaptureAlert, mLastPictureButton, mThumbController
149    // are non-null only if isImageCaptureIntent() is true.
150    private ImageView mLastPictureButton;
151    private ThumbnailController mThumbController;
152
153    private int mViewFinderWidth, mViewFinderHeight;
154
155    private ImageCapture mImageCapture = null;
156
157    private boolean mPreviewing;
158    private boolean mPausing;
159    private boolean mFirstTimeInitialized;
160    private boolean mIsImageCaptureIntent;
161    private boolean mRecordLocation;
162
163    private static final int FOCUS_NOT_STARTED = 0;
164    private static final int FOCUSING = 1;
165    private static final int FOCUSING_SNAP_ON_FINISH = 2;
166    private static final int FOCUS_SUCCESS = 3;
167    private static final int FOCUS_FAIL = 4;
168    private int mFocusState = FOCUS_NOT_STARTED;
169
170    private ContentResolver mContentResolver;
171    private boolean mDidRegister = false;
172
173    private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>();
174
175    private LocationManager mLocationManager = null;
176
177    // Use OneShotPreviewCallback to measure the time between
178    // JpegPictureCallback and preview.
179    private final OneShotPreviewCallback mOneShotPreviewCallback =
180            new OneShotPreviewCallback();
181    private final ShutterCallback mShutterCallback = new ShutterCallback();
182    private final RawPictureCallback mRawPictureCallback =
183            new RawPictureCallback();
184    private final AutoFocusCallback mAutoFocusCallback =
185            new AutoFocusCallback();
186    private final ZoomCallback mZoomCallback = new ZoomCallback();
187    // Use the ErrorCallback to capture the crash count
188    // on the mediaserver
189    private final ErrorCallback mErrorCallback = new ErrorCallback();
190
191    private long mFocusStartTime;
192    private long mFocusCallbackTime;
193    private long mCaptureStartTime;
194    private long mShutterCallbackTime;
195    private long mRawPictureCallbackTime;
196    private long mJpegPictureCallbackTime;
197    private int mPicturesRemaining;
198
199    // These latency time are for the CameraLatency test.
200    public long mAutoFocusTime;
201    public long mShutterLag;
202    public long mShutterAndRawPictureCallbackTime;
203    public long mJpegPictureCallbackTimeLag;
204    public long mRawPictureAndJpegPictureCallbackTime;
205
206    // Add the media server tag
207    public static boolean mMediaServerDied = false;
208    // Focus mode. Options are pref_camera_focusmode_entryvalues.
209    private String mFocusMode;
210
211    private final Handler mHandler = new MainHandler();
212    private OnScreenSettings mSettings;
213
214    /**
215     * This Handler is used to post message back onto the main thread of the
216     * application
217     */
218    private class MainHandler extends Handler {
219        @Override
220        public void handleMessage(Message msg) {
221            switch (msg.what) {
222                case RESTART_PREVIEW: {
223                    restartPreview();
224                    break;
225                }
226
227                case CLEAR_SCREEN_DELAY: {
228                    getWindow().clearFlags(
229                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
230                    break;
231                }
232
233                case FIRST_TIME_INIT: {
234                    initializeFirstTime();
235                    break;
236                }
237            }
238        }
239    }
240
241    // Snapshots can only be taken after this is called. It should be called
242    // once only. We could have done these things in onCreate() but we want to
243    // make preview screen appear as soon as possible.
244    private void initializeFirstTime() {
245        if (mFirstTimeInitialized) return;
246
247        // Create orientation listenter. This should be done first because it
248        // takes some time to get first orientation.
249        mOrientationListener =
250                new OrientationEventListener(Camera.this) {
251            @Override
252            public void onOrientationChanged(int orientation) {
253                // We keep the last known orientation. So if the user
254                // first orient the camera then point the camera to
255                // floor/sky, we still have the correct orientation.
256                if (orientation != ORIENTATION_UNKNOWN) {
257                    mLastOrientation = orientation;
258                }
259            }
260        };
261        mOrientationListener.enable();
262
263        // Initialize location sevice.
264        mLocationManager = (LocationManager)
265                getSystemService(Context.LOCATION_SERVICE);
266        readPreference();
267        if (mRecordLocation) startReceivingLocationUpdates();
268
269        checkStorage();
270
271        // Initialize last picture button.
272        mContentResolver = getContentResolver();
273        if (!mIsImageCaptureIntent)  {
274            findViewById(R.id.camera_switch).setOnClickListener(this);
275            mLastPictureButton =
276                    (ImageView) findViewById(R.id.review_thumbnail);
277            mLastPictureButton.setOnClickListener(this);
278            mThumbController = new ThumbnailController(
279                    getResources(), mLastPictureButton, mContentResolver);
280            mThumbController.loadData(ImageManager.getLastImageThumbPath());
281            // Update last image thumbnail.
282            updateThumbnailButton();
283        }
284
285        // Initialize shutter button.
286        mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
287        mShutterButton.setOnShutterButtonListener(this);
288        mShutterButton.setVisibility(View.VISIBLE);
289
290        mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle);
291        updateFocusIndicator();
292
293        // Initialize GPS indicator.
294        mGpsIndicator = (IconIndicator) findViewById(R.id.gps_icon);
295
296        ImageManager.ensureOSXCompatibleFolder();
297
298        installIntentFilter();
299
300        initializeFocusTone();
301
302        initializeZoom();
303
304        mFirstTimeInitialized = true;
305    }
306
307    private void updateThumbnailButton() {
308        // Update last image if URI is invalid and the storage is ready.
309        if (!mThumbController.isUriValid() && mPicturesRemaining >= 0) {
310            updateLastImage();
311        }
312        mThumbController.updateDisplayIfNeeded();
313    }
314
315    // If the activity is paused and resumed, this method will be called in
316    // onResume.
317    private void initializeSecondTime() {
318        // Start orientation listener as soon as possible because it takes
319        // some time to get first orientation.
320        mOrientationListener.enable();
321
322        // Start location update if needed.
323        readPreference();
324        if (mRecordLocation) startReceivingLocationUpdates();
325
326        installIntentFilter();
327
328        initializeFocusTone();
329
330        checkStorage();
331
332        if (mZoomButtons != null) {
333            mZoomValue = Double.parseDouble(
334                    mParameters.get(PARM_ZOOM_TO_LEVEL));
335            mCameraDevice.setZoomCallback(mZoomCallback);
336        }
337
338        if (!mIsImageCaptureIntent) {
339            updateThumbnailButton();
340        }
341    }
342
343    private void initializeZoom() {
344        // Check if the phone has zoom capability.
345        String zoomState = mParameters.get(PARM_ZOOM_STATE);
346        if (zoomState == null) return;
347
348        mZoomValue = Double.parseDouble(mParameters.get(PARM_ZOOM_TO_LEVEL));
349        mZoomMax = Double.parseDouble(mParameters.get(PARM_ZOOM_MAX));
350        mZoomStep = Double.parseDouble(mParameters.get(PARM_ZOOM_STEP));
351        mParameters.set(PARM_ZOOM_SPEED, ZOOM_SPEED);
352        mCameraDevice.setParameters(mParameters);
353
354        mGestureDetector = new GestureDetector(this, new ZoomGestureListener());
355        mCameraDevice.setZoomCallback(mZoomCallback);
356        mZoomButtons = new ZoomButtonsController(mSurfaceView);
357        mZoomButtons.setAutoDismissed(true);
358        mZoomButtons.setZoomSpeed(100);
359        mZoomButtons.setOnZoomListener(
360                new ZoomButtonsController.OnZoomListener() {
361            public void onVisibilityChanged(boolean visible) {
362                if (visible) {
363                    updateZoomButtonsEnabled();
364                }
365            }
366
367            public void onZoom(boolean zoomIn) {
368                if (isZooming()) return;
369
370                if (zoomIn) {
371                    if (mZoomValue < mZoomMax) {
372                        zoomToLevel(ZOOM_CONTINUOUS, mZoomValue + mZoomStep);
373                    }
374                } else {
375                    if (mZoomValue > ZOOM_MIN) {
376                        zoomToLevel(ZOOM_CONTINUOUS, mZoomValue - mZoomStep);
377                    }
378                }
379            }
380        });
381    }
382
383    public void onVisibilityChanged(boolean visible) {
384        // When the on-screen setting is not displayed, we show the gripper.
385        // When the on-screen setting is displayed, we hide the gripper.
386        findViewById(R.id.btn_gripper).setVisibility(
387                visible ? View.INVISIBLE : View.VISIBLE);
388    }
389
390    private boolean isZooming() {
391        mParameters = mCameraDevice.getParameters();
392        return "continuous".equals(mParameters.get(PARM_ZOOM_STATE));
393    }
394
395    private void zoomToLevel(String type, double zoomValue) {
396        if (zoomValue > mZoomMax) zoomValue = mZoomMax;
397        if (zoomValue < ZOOM_MIN) zoomValue = ZOOM_MIN;
398
399        // If the application sets a unchanged zoom value, the driver will stuck
400        // at the zoom state. This is a work-around to ensure the state is at
401        // "stop".
402        mParameters.set(PARM_ZOOM_STATE, ZOOM_STOP);
403        mCameraDevice.setParameters(mParameters);
404
405        mParameters.set(PARM_ZOOM_TO_LEVEL, Double.toString(zoomValue));
406        mParameters.set(PARM_ZOOM_STATE, type);
407        mCameraDevice.setParameters(mParameters);
408
409        if (ZOOM_IMMEDIATE.equals(type)) mZoomValue = zoomValue;
410    }
411
412    private void updateZoomButtonsEnabled() {
413        mZoomButtons.setZoomInEnabled(mZoomValue < mZoomMax);
414        mZoomButtons.setZoomOutEnabled(mZoomValue > ZOOM_MIN);
415    }
416
417    private class ZoomGestureListener extends
418            GestureDetector.SimpleOnGestureListener {
419        @Override
420        public boolean onDown(MotionEvent e) {
421            // Show zoom buttons only when preview is started and snapshot
422            // is not in progress. mZoomButtons may be null if it is not
423            // initialized.
424            if (!mPausing && isCameraIdle() && mPreviewing
425                    && mZoomButtons != null) {
426                mZoomButtons.setVisible(true);
427            }
428            return true;
429        }
430
431        @Override
432        public boolean onDoubleTap(MotionEvent e) {
433            // Perform zoom only when preview is started and snapshot is not in
434            // progress.
435            if (mPausing || !isCameraIdle() || !mPreviewing
436                    || mZoomButtons == null || isZooming()) {
437                return false;
438            }
439
440            if (mZoomValue < mZoomMax) {
441                // Zoom in to the maximum.
442                while (mZoomValue < mZoomMax) {
443                    zoomToLevel(ZOOM_IMMEDIATE, mZoomValue + ZOOM_STEP_MIN);
444                    // Wait for a while so we are not changing zoom too fast.
445                    try {
446                        Thread.currentThread().sleep(5);
447                    } catch (InterruptedException ex) {
448                    }
449                }
450            } else {
451                // Zoom out to the minimum.
452                while (mZoomValue > ZOOM_MIN) {
453                    zoomToLevel(ZOOM_IMMEDIATE, mZoomValue - ZOOM_STEP_MIN);
454                    // Wait for a while so we are not changing zoom too fast.
455                    try {
456                        Thread.currentThread().sleep(5);
457                    } catch (InterruptedException ex) {
458                    }
459                }
460            }
461            updateZoomButtonsEnabled();
462            return true;
463        }
464    }
465
466    @Override
467    public boolean dispatchTouchEvent(MotionEvent m) {
468        if (!super.dispatchTouchEvent(m) && mGestureDetector != null) {
469            return mGestureDetector.onTouchEvent(m);
470        }
471        return true;
472    }
473
474    LocationListener [] mLocationListeners = new LocationListener[] {
475            new LocationListener(LocationManager.GPS_PROVIDER),
476            new LocationListener(LocationManager.NETWORK_PROVIDER)
477    };
478
479    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
480        @Override
481        public void onReceive(Context context, Intent intent) {
482            String action = intent.getAction();
483            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
484                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
485                    || action.equals(Intent.ACTION_MEDIA_CHECKING)
486                    || action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
487                checkStorage();
488            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
489                checkStorage();
490                if (!mIsImageCaptureIntent)  {
491                    updateThumbnailButton();
492                }
493            }
494        }
495    };
496
497    private class LocationListener
498            implements android.location.LocationListener {
499        Location mLastLocation;
500        boolean mValid = false;
501        String mProvider;
502
503        public LocationListener(String provider) {
504            mProvider = provider;
505            mLastLocation = new Location(mProvider);
506        }
507
508        public void onLocationChanged(Location newLocation) {
509            if (newLocation.getLatitude() == 0.0
510                    && newLocation.getLongitude() == 0.0) {
511                // Hack to filter out 0.0,0.0 locations
512                return;
513            }
514            // If GPS is available before start camera, we won't get status
515            // update so update GPS indicator when we receive data.
516            if (mRecordLocation
517                    && LocationManager.GPS_PROVIDER.equals(mProvider)) {
518                mGpsIndicator.setMode(GPS_MODE_ON);
519            }
520            mLastLocation.set(newLocation);
521            mValid = true;
522        }
523
524        public void onProviderEnabled(String provider) {
525        }
526
527        public void onProviderDisabled(String provider) {
528            mValid = false;
529        }
530
531        public void onStatusChanged(
532                String provider, int status, Bundle extras) {
533            switch(status) {
534                case LocationProvider.OUT_OF_SERVICE:
535                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
536                    mValid = false;
537                    if (mRecordLocation &&
538                            LocationManager.GPS_PROVIDER.equals(provider)) {
539                        mGpsIndicator.setMode(GPS_MODE_OFF);
540                    }
541                    break;
542                }
543            }
544        }
545
546        public Location current() {
547            return mValid ? mLastLocation : null;
548        }
549    }
550
551    private final class OneShotPreviewCallback
552            implements android.hardware.Camera.PreviewCallback {
553        public void onPreviewFrame(byte[] data,
554                                   android.hardware.Camera camera) {
555            long now = System.currentTimeMillis();
556            if (mJpegPictureCallbackTime != 0) {
557                mJpegPictureCallbackTimeLag = now - mJpegPictureCallbackTime;
558                Log.v(TAG, "mJpegPictureCallbackTimeLag = "
559                        + mJpegPictureCallbackTimeLag + "ms");
560                mJpegPictureCallbackTime = 0;
561            } else {
562                Log.v(TAG, "Got first frame");
563            }
564        }
565    }
566
567    private final class ShutterCallback
568            implements android.hardware.Camera.ShutterCallback {
569        public void onShutter() {
570            mShutterCallbackTime = System.currentTimeMillis();
571            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
572            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
573            clearFocusState();
574        }
575    }
576
577    private final class RawPictureCallback implements PictureCallback {
578        public void onPictureTaken(
579                byte [] rawData, android.hardware.Camera camera) {
580            mRawPictureCallbackTime = System.currentTimeMillis();
581            mShutterAndRawPictureCallbackTime =
582                mRawPictureCallbackTime - mShutterCallbackTime;
583            Log.v(TAG, "mShutterAndRawPictureCallbackTime = "
584                    + mShutterAndRawPictureCallbackTime + "ms");
585        }
586    }
587
588    private final class JpegPictureCallback implements PictureCallback {
589        Location mLocation;
590
591        public JpegPictureCallback(Location loc) {
592            mLocation = loc;
593        }
594
595        public void onPictureTaken(
596                final byte [] jpegData, final android.hardware.Camera camera) {
597            if (mPausing) {
598                return;
599            }
600
601            mJpegPictureCallbackTime = System.currentTimeMillis();
602            mRawPictureAndJpegPictureCallbackTime =
603                mJpegPictureCallbackTime - mRawPictureCallbackTime;
604            Log.v(TAG, "mRawPictureAndJpegPictureCallbackTime = "
605                    + mRawPictureAndJpegPictureCallbackTime + "ms");
606            mImageCapture.storeImage(jpegData, camera, mLocation);
607
608            if (!mIsImageCaptureIntent) {
609                long delay = 1200 - (
610                        System.currentTimeMillis() - mRawPictureCallbackTime);
611                mHandler.sendEmptyMessageDelayed(
612                        RESTART_PREVIEW, Math.max(delay, 0));
613            }
614        }
615    }
616
617    private final class AutoFocusCallback
618            implements android.hardware.Camera.AutoFocusCallback {
619        public void onAutoFocus(
620                boolean focused, android.hardware.Camera camera) {
621            mFocusCallbackTime = System.currentTimeMillis();
622            mAutoFocusTime = mFocusCallbackTime - mFocusStartTime;
623            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
624            if (mFocusState == FOCUSING_SNAP_ON_FINISH) {
625                // Take the picture no matter focus succeeds or fails. No need
626                // to play the AF sound if we're about to play the shutter
627                // sound.
628                if (focused) {
629                    mFocusState = FOCUS_SUCCESS;
630                } else {
631                    mFocusState = FOCUS_FAIL;
632                }
633                mImageCapture.onSnap();
634            } else if (mFocusState == FOCUSING) {
635                // User is half-pressing the focus key. Play the focus tone.
636                // Do not take the picture now.
637                ToneGenerator tg = mFocusToneGenerator;
638                if (tg != null) {
639                    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
640                }
641                if (focused) {
642                    mFocusState = FOCUS_SUCCESS;
643                } else {
644                    mFocusState = FOCUS_FAIL;
645                }
646            } else if (mFocusState == FOCUS_NOT_STARTED) {
647                // User has released the focus key before focus completes.
648                // Do nothing.
649            }
650            updateFocusIndicator();
651        }
652    }
653
654    private final class ErrorCallback
655        implements android.hardware.Camera.ErrorCallback {
656        public void onError(int error, android.hardware.Camera camera) {
657            if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
658                 mMediaServerDied = true;
659                 Log.v(TAG, "media server died");
660            }
661        }
662    }
663
664    private final class ZoomCallback
665        implements android.hardware.Camera.ZoomCallback {
666        public void onZoomUpdate(int zoomLevel,
667                                 android.hardware.Camera camera) {
668            mZoomValue = (double) zoomLevel / 1000;
669            Log.v(TAG, "ZoomCallback: zoom level=" + zoomLevel);
670            updateZoomButtonsEnabled();
671        }
672    }
673
674    private class ImageCapture {
675
676        private boolean mCancel = false;
677
678        private Uri mLastContentUri;
679
680        Bitmap mCaptureOnlyBitmap;
681
682        private void storeImage(byte[] data, Location loc) {
683            try {
684                long dateTaken = System.currentTimeMillis();
685                String name = createName(dateTaken) + ".jpg";
686                mLastContentUri = ImageManager.addImage(
687                        mContentResolver,
688                        name,
689                        dateTaken,
690                        loc, // location for the database goes here
691                        0, // the dsp will use the right orientation so
692                           // don't "double set it"
693                        ImageManager.CAMERA_IMAGE_BUCKET_NAME,
694                        name);
695                if (mLastContentUri == null) {
696                    // this means we got an error
697                    mCancel = true;
698                }
699                if (!mCancel) {
700                    ImageManager.storeImage(mLastContentUri, mContentResolver,
701                            null, data);
702                    ImageManager.setImageSize(mContentResolver, mLastContentUri,
703                            new File(ImageManager.CAMERA_IMAGE_BUCKET_NAME,
704                            name).length());
705                }
706            } catch (Exception ex) {
707                Log.e(TAG, "Exception while compressing image.", ex);
708            }
709        }
710
711        public void storeImage(final byte[] data,
712                android.hardware.Camera camera, Location loc) {
713            if (!mIsImageCaptureIntent) {
714                storeImage(data, loc);
715                sendBroadcast(new Intent(
716                        "com.android.camera.NEW_PICTURE", mLastContentUri));
717                setLastPictureThumb(data, mImageCapture.getLastCaptureUri());
718                mThumbController.updateDisplayIfNeeded();
719            } else {
720                BitmapFactory.Options options = new BitmapFactory.Options();
721                options.inSampleSize = 4;
722                mCaptureOnlyBitmap = BitmapFactory.decodeByteArray(
723                        data, 0, data.length, options);
724                showPostCaptureAlert();
725            }
726        }
727
728        /**
729         * Initiate the capture of an image.
730         */
731        public void initiate() {
732            if (mCameraDevice == null) {
733                return;
734            }
735
736            mCancel = false;
737
738            capture();
739        }
740
741        public Uri getLastCaptureUri() {
742            return mLastContentUri;
743        }
744
745        public Bitmap getLastBitmap() {
746            return mCaptureOnlyBitmap;
747        }
748
749        private void capture() {
750            mCaptureOnlyBitmap = null;
751
752            // Set rotation.
753            int orientation = mLastOrientation;
754            if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
755                orientation += 90;
756            }
757            orientation = ImageManager.roundOrientation(orientation);
758            Log.v(TAG, "mLastOrientation = " + mLastOrientation
759                    + ", orientation = " + orientation);
760            mParameters.setRotation(orientation);
761
762            // Clear previous GPS location from the parameters.
763            mParameters.removeGpsData();
764
765            // Set GPS location.
766            Location loc = mRecordLocation ? getCurrentLocation() : null;
767            if (loc != null) {
768                double lat = loc.getLatitude();
769                double lon = loc.getLongitude();
770                boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
771
772                if (hasLatLon) {
773                    mParameters.setGpsLatitude(lat);
774                    mParameters.setGpsLongitude(lon);
775                    if (loc.hasAltitude()) {
776                        mParameters.setGpsAltitude(loc.getAltitude());
777                    } else {
778                        // for NETWORK_PROVIDER location provider, we may have
779                        // no altitude information, but the driver needs it, so
780                        // we fake one.
781                        mParameters.setGpsAltitude(0);
782                    }
783                    if (loc.getTime() != 0) {
784                        // Location.getTime() is UTC in milliseconds.
785                        // gps-timestamp is UTC in seconds.
786                        long utcTimeSeconds = loc.getTime() / 1000;
787                        mParameters.setGpsTimestamp(utcTimeSeconds);
788                    }
789                } else {
790                    loc = null;
791                }
792            }
793
794            mCameraDevice.setParameters(mParameters);
795
796            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
797                    new JpegPictureCallback(loc));
798            mPreviewing = false;
799        }
800
801        public void onSnap() {
802            // If we are already in the middle of taking a snapshot then ignore.
803            if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS) {
804                return;
805            }
806            mCaptureStartTime = System.currentTimeMillis();
807
808            // Don't check the filesystem here, we can't afford the latency.
809            // Instead, check the cached value which was calculated when the
810            // preview was restarted.
811            if (mPicturesRemaining < 1) {
812                updateStorageHint(mPicturesRemaining);
813                return;
814            }
815
816            mStatus = SNAPSHOT_IN_PROGRESS;
817
818            mImageCapture.initiate();
819        }
820
821        private void clearLastBitmap() {
822            if (mCaptureOnlyBitmap != null) {
823                mCaptureOnlyBitmap.recycle();
824                mCaptureOnlyBitmap = null;
825            }
826        }
827    }
828
829    private void setLastPictureThumb(byte[] data, Uri uri) {
830        BitmapFactory.Options options = new BitmapFactory.Options();
831        options.inSampleSize = 16;
832        Bitmap lastPictureThumb =
833                BitmapFactory.decodeByteArray(data, 0, data.length, options);
834        mThumbController.setData(uri, lastPictureThumb);
835    }
836
837    private static String createName(long dateTaken) {
838        return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
839    }
840
841    @Override
842    public void onCreate(Bundle icicle) {
843        super.onCreate(icicle);
844
845        Window win = getWindow();
846        win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
847        setContentView(R.layout.camera);
848        mSurfaceView = (VideoPreview) findViewById(R.id.camera_preview);
849        mViewFinderWidth = mSurfaceView.getLayoutParams().width;
850        mViewFinderHeight = mSurfaceView.getLayoutParams().height;
851        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
852        mPreferences.registerOnSharedPreferenceChangeListener(this);
853
854        /*
855         * To reduce startup time, we start the preview in another thread.
856         * We make sure the preview is started at the end of onCreate.
857         */
858        Thread startPreviewThread = new Thread(new Runnable() {
859            public void run() {
860                try {
861                    mStartPreviewFail = false;
862                    startPreview();
863                } catch (CameraHardwareException e) {
864                    // In eng build, we throw the exception so that test tool
865                    // can detect it and report it
866                    if ("eng".equals(Build.TYPE)) {
867                        throw new RuntimeException(e);
868                    }
869                    mStartPreviewFail = true;
870                }
871            }
872        });
873        startPreviewThread.start();
874
875        // don't set mSurfaceHolder here. We have it set ONLY within
876        // surfaceChanged / surfaceDestroyed, other parts of the code
877        // assume that when it is set, the surface is also set.
878        SurfaceHolder holder = mSurfaceView.getHolder();
879        holder.addCallback(this);
880        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
881
882        mIsImageCaptureIntent = isImageCaptureIntent();
883        LayoutInflater inflater = getLayoutInflater();
884
885        ViewGroup rootView = (ViewGroup) findViewById(R.id.camera);
886        if (mIsImageCaptureIntent) {
887            View controlBar = inflater.inflate(
888                    R.layout.attach_camera_control, rootView);
889            controlBar.findViewById(R.id.btn_cancel).setOnClickListener(this);
890            controlBar.findViewById(R.id.btn_retake).setOnClickListener(this);
891            controlBar.findViewById(R.id.btn_done).setOnClickListener(this);
892        } else {
893            inflater.inflate(R.layout.camera_control, rootView);
894            mSwitcher = ((Switcher) findViewById(R.id.camera_switch));
895            mSwitcher.setOnSwitchListener(this);
896            mSwitcher.addTouchView(findViewById(R.id.camera_switch_set));
897        }
898        findViewById(R.id.btn_gripper)
899                .setOnTouchListener(new GripperTouchListener());
900
901        mFlashIndicator = (IconIndicator) findViewById(R.id.flash_icon);
902
903        // Make sure preview is started.
904        try {
905            startPreviewThread.join();
906            if (mStartPreviewFail) {
907                showCameraErrorAndFinish();
908                return;
909            }
910        } catch (InterruptedException ex) {
911            // ignore
912        }
913
914        // Resize mVideoPreview to the right aspect ratio.
915        resizeForPreviewAspectRatio(mSurfaceView);
916    }
917
918    private class GripperTouchListener implements View.OnTouchListener {
919        public boolean onTouch(View view, MotionEvent event) {
920            switch (event.getAction()) {
921                case MotionEvent.ACTION_DOWN:
922                    return true;
923                case MotionEvent.ACTION_UP:
924                    showOnScreenSettings();
925                    return true;
926            }
927            return false;
928        }
929    }
930
931    @Override
932    public void onStart() {
933        super.onStart();
934        if (!mIsImageCaptureIntent) {
935            mSwitcher.setSwitch(SWITCH_CAMERA);
936        }
937    }
938
939    private void checkStorage() {
940        if (ImageManager.isMediaScannerScanning(getContentResolver())) {
941            mPicturesRemaining = MenuHelper.NO_STORAGE_ERROR;
942        } else {
943            calculatePicturesRemaining();
944        }
945        updateStorageHint(mPicturesRemaining);
946    }
947
948    private void showOnScreenSettings() {
949        if (mSettings == null) {
950            mSettings = new OnScreenSettings(
951                    findViewById(R.id.camera_preview));
952            CameraSettings helper = new CameraSettings(this, mParameters);
953            mSettings.setPreferenceScreen(helper
954                    .getPreferenceScreen(R.xml.camera_preferences));
955            mSettings.setOnVisibilityChangedListener(this);
956        }
957        mSettings.expandPanel();
958    }
959
960    public void onClick(View v) {
961        switch (v.getId()) {
962            case R.id.btn_retake:
963                hidePostCaptureAlert();
964                restartPreview();
965                break;
966            case R.id.review_thumbnail:
967                if (isCameraIdle()) {
968                    viewLastImage();
969                }
970                break;
971            case R.id.btn_done:
972                doAttach();
973                break;
974            case R.id.btn_cancel:
975                doCancel();
976        }
977    }
978
979    private void doAttach() {
980        if (mPausing) {
981            return;
982        }
983        Bitmap bitmap = mImageCapture.getLastBitmap();
984
985        String cropValue = null;
986        Uri saveUri = null;
987
988        Bundle myExtras = getIntent().getExtras();
989        if (myExtras != null) {
990            saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
991            cropValue = myExtras.getString("crop");
992        }
993
994
995        if (cropValue == null) {
996            // First handle the no crop case -- just return the value.  If the
997            // caller specifies a "save uri" then write the data to it's
998            // stream. Otherwise, pass back a scaled down version of the bitmap
999            // directly in the extras.
1000            if (saveUri != null) {
1001                OutputStream outputStream = null;
1002                try {
1003                    outputStream = mContentResolver.openOutputStream(saveUri);
1004                    bitmap.compress(Bitmap.CompressFormat.JPEG, 75,
1005                            outputStream);
1006                    outputStream.close();
1007
1008                    setResult(RESULT_OK);
1009                    finish();
1010                } catch (IOException ex) {
1011                    // ignore exception
1012                } finally {
1013                    if (outputStream != null) {
1014                        try {
1015                            outputStream.close();
1016                        } catch (IOException ex) {
1017                            // ignore exception
1018                        }
1019                    }
1020                }
1021            } else {
1022                float scale = .5F;
1023                Matrix m = new Matrix();
1024                m.setScale(scale, scale);
1025
1026                bitmap = Bitmap.createBitmap(bitmap, 0, 0,
1027                        bitmap.getWidth(),
1028                        bitmap.getHeight(),
1029                        m, true);
1030
1031                setResult(RESULT_OK,
1032                        new Intent("inline-data").putExtra("data", bitmap));
1033                finish();
1034            }
1035        } else {
1036            // Save the image to a temp file and invoke the cropper
1037            Uri tempUri = null;
1038            FileOutputStream tempStream = null;
1039            try {
1040                File path = getFileStreamPath(sTempCropFilename);
1041                path.delete();
1042                tempStream = openFileOutput(sTempCropFilename, 0);
1043                bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream);
1044                tempStream.close();
1045                tempUri = Uri.fromFile(path);
1046            } catch (FileNotFoundException ex) {
1047                setResult(Activity.RESULT_CANCELED);
1048                finish();
1049                return;
1050            } catch (IOException ex) {
1051                setResult(Activity.RESULT_CANCELED);
1052                finish();
1053                return;
1054            } finally {
1055                if (tempStream != null) {
1056                    try {
1057                        tempStream.close();
1058                    } catch (IOException ex) {
1059                        // ignore exception
1060                    }
1061                }
1062            }
1063
1064            Bundle newExtras = new Bundle();
1065            if (cropValue.equals("circle")) {
1066                newExtras.putString("circleCrop", "true");
1067            }
1068            if (saveUri != null) {
1069                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, saveUri);
1070            } else {
1071                newExtras.putBoolean("return-data", true);
1072            }
1073
1074            Intent cropIntent = new Intent();
1075            cropIntent.setClass(this, CropImage.class);
1076            cropIntent.setData(tempUri);
1077            cropIntent.putExtras(newExtras);
1078
1079            startActivityForResult(cropIntent, CROP_MSG);
1080        }
1081    }
1082
1083    private void doCancel() {
1084        setResult(RESULT_CANCELED, new Intent());
1085        finish();
1086    }
1087
1088    public void onShutterButtonFocus(ShutterButton button, boolean pressed) {
1089        if (mPausing) {
1090            return;
1091        }
1092        switch (button.getId()) {
1093            case R.id.shutter_button:
1094                doFocus(pressed);
1095                break;
1096        }
1097    }
1098
1099    public void onShutterButtonClick(ShutterButton button) {
1100        if (mPausing) {
1101            return;
1102        }
1103        switch (button.getId()) {
1104            case R.id.shutter_button:
1105                doSnap();
1106                break;
1107        }
1108    }
1109
1110    private OnScreenHint mStorageHint;
1111
1112    private void updateStorageHint(int remaining) {
1113        String noStorageText = null;
1114
1115        if (remaining == MenuHelper.NO_STORAGE_ERROR) {
1116            String state = Environment.getExternalStorageState();
1117            if (state == Environment.MEDIA_CHECKING ||
1118                    ImageManager.isMediaScannerScanning(getContentResolver())) {
1119                noStorageText = getString(R.string.preparing_sd);
1120            } else {
1121                noStorageText = getString(R.string.no_storage);
1122            }
1123        } else if (remaining < 1) {
1124            noStorageText = getString(R.string.not_enough_space);
1125        }
1126
1127        if (noStorageText != null) {
1128            if (mStorageHint == null) {
1129                mStorageHint = OnScreenHint.makeText(this, noStorageText);
1130            } else {
1131                mStorageHint.setText(noStorageText);
1132            }
1133            mStorageHint.show();
1134        } else if (mStorageHint != null) {
1135            mStorageHint.cancel();
1136            mStorageHint = null;
1137        }
1138    }
1139
1140    private void installIntentFilter() {
1141        // install an intent filter to receive SD card related events.
1142        IntentFilter intentFilter =
1143                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1144        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1145        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
1146        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1147        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1148        intentFilter.addDataScheme("file");
1149        registerReceiver(mReceiver, intentFilter);
1150        mDidRegister = true;
1151    }
1152
1153    private void initializeFocusTone() {
1154        // Initialize focus tone generator.
1155        try {
1156            mFocusToneGenerator = new ToneGenerator(
1157                    AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME);
1158        } catch (Throwable ex) {
1159            Log.w(TAG, "Exception caught while creating tone generator: ", ex);
1160            mFocusToneGenerator = null;
1161        }
1162    }
1163
1164    private void readPreference() {
1165        mRecordLocation = mPreferences.getBoolean(
1166                "pref_camera_recordlocation_key", false);
1167        mFocusMode = mPreferences.getString(
1168                CameraSettings.KEY_FOCUS_MODE,
1169                getString(R.string.pref_camera_focusmode_default));
1170    }
1171
1172    @Override
1173    public void onResume() {
1174        super.onResume();
1175
1176        mPausing = false;
1177        mJpegPictureCallbackTime = 0;
1178        mImageCapture = new ImageCapture();
1179
1180        // Start the preview if it is not started.
1181        if (!mPreviewing && !mStartPreviewFail) {
1182            try {
1183                startPreview();
1184            } catch (CameraHardwareException e) {
1185                showCameraErrorAndFinish();
1186                return;
1187            }
1188        }
1189
1190        if (mSurfaceHolder != null) {
1191            // If first time initialization is not finished, put it in the
1192            // message queue.
1193            if (!mFirstTimeInitialized) {
1194                mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1195            } else {
1196                initializeSecondTime();
1197            }
1198        }
1199        keepScreenOnAwhile();
1200    }
1201
1202    private static ImageManager.DataLocation dataLocation() {
1203        return ImageManager.DataLocation.EXTERNAL;
1204    }
1205
1206    @Override
1207    protected void onPause() {
1208        mPausing = true;
1209        stopPreview();
1210        // Close the camera now because other activities may need to use it.
1211        closeCamera();
1212        resetScreenOn();
1213
1214        if (mSettings != null && mSettings.isVisible()) {
1215            mSettings.setVisible(false);
1216        }
1217
1218        if (mFirstTimeInitialized) {
1219            mOrientationListener.disable();
1220            mGpsIndicator.setMode(GPS_MODE_OFF);
1221            if (!mIsImageCaptureIntent) {
1222                mThumbController.storeData(
1223                        ImageManager.getLastImageThumbPath());
1224            }
1225            hidePostCaptureAlert();
1226        }
1227
1228        if (mDidRegister) {
1229            unregisterReceiver(mReceiver);
1230            mDidRegister = false;
1231        }
1232        stopReceivingLocationUpdates();
1233
1234        if (mFocusToneGenerator != null) {
1235            mFocusToneGenerator.release();
1236            mFocusToneGenerator = null;
1237        }
1238
1239        if (mStorageHint != null) {
1240            mStorageHint.cancel();
1241            mStorageHint = null;
1242        }
1243
1244        // If we are in an image capture intent and has taken
1245        // a picture, we just clear it in onPause.
1246        mImageCapture.clearLastBitmap();
1247        mImageCapture = null;
1248
1249        // This is necessary to make the ZoomButtonsController unregister
1250        // its configuration change receiver.
1251        if (mZoomButtons != null) {
1252            mZoomButtons.setVisible(false);
1253        }
1254
1255        // Remove the messages in the event queue.
1256        mHandler.removeMessages(RESTART_PREVIEW);
1257        mHandler.removeMessages(FIRST_TIME_INIT);
1258
1259        super.onPause();
1260    }
1261
1262    @Override
1263    protected void onActivityResult(
1264            int requestCode, int resultCode, Intent data) {
1265        switch (requestCode) {
1266            case CROP_MSG: {
1267                Intent intent = new Intent();
1268                if (data != null) {
1269                    Bundle extras = data.getExtras();
1270                    if (extras != null) {
1271                        intent.putExtras(extras);
1272                    }
1273                }
1274                setResult(resultCode, intent);
1275                finish();
1276
1277                File path = getFileStreamPath(sTempCropFilename);
1278                path.delete();
1279
1280                break;
1281            }
1282        }
1283    }
1284
1285    private boolean canTakePicture() {
1286        return isCameraIdle() && mPreviewing && (mPicturesRemaining > 0);
1287    }
1288
1289    private void autoFocus() {
1290        // Initiate autofocus only when preview is started and snapshot is not
1291        // in progress.
1292        if (canTakePicture()) {
1293            Log.v(TAG, "Start autofocus.");
1294            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1295            mFocusStartTime = System.currentTimeMillis();
1296            mFocusState = FOCUSING;
1297            updateFocusIndicator();
1298            mCameraDevice.autoFocus(mAutoFocusCallback);
1299        }
1300    }
1301
1302    private void cancelAutoFocus() {
1303        // User releases half-pressed focus key.
1304        if (mFocusState == FOCUSING || mFocusState == FOCUS_SUCCESS
1305                || mFocusState == FOCUS_FAIL) {
1306            Log.v(TAG, "Cancel autofocus.");
1307            mCameraDevice.cancelAutoFocus();
1308        }
1309        if (mFocusState != FOCUSING_SNAP_ON_FINISH) {
1310            clearFocusState();
1311        }
1312    }
1313
1314    private void clearFocusState() {
1315        mFocusState = FOCUS_NOT_STARTED;
1316        updateFocusIndicator();
1317    }
1318
1319    private void updateFocusIndicator() {
1320        if (mFocusRectangle == null) return;
1321
1322        if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) {
1323            mFocusRectangle.showStart();
1324        } else if (mFocusState == FOCUS_SUCCESS) {
1325            mFocusRectangle.showSuccess();
1326        } else if (mFocusState == FOCUS_FAIL) {
1327            mFocusRectangle.showFail();
1328        } else {
1329            mFocusRectangle.clear();
1330        }
1331    }
1332
1333    @Override
1334    public boolean onKeyDown(int keyCode, KeyEvent event) {
1335        switch (keyCode) {
1336            case KeyEvent.KEYCODE_BACK:
1337                if (!isCameraIdle()) {
1338                    // ignore backs while we're taking a picture
1339                    return true;
1340                }
1341                break;
1342            case KeyEvent.KEYCODE_FOCUS:
1343                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1344                    doFocus(true);
1345                }
1346                return true;
1347            case KeyEvent.KEYCODE_CAMERA:
1348                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1349                    doSnap();
1350                }
1351                return true;
1352            case KeyEvent.KEYCODE_DPAD_CENTER:
1353                // If we get a dpad center event without any focused view, move
1354                // the focus to the shutter button and press it.
1355                if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1356                    // Start auto-focus immediately to reduce shutter lag. After
1357                    // the shutter button gets the focus, doFocus() will be
1358                    // called again but it is fine.
1359                    doFocus(true);
1360                    if (mShutterButton.isInTouchMode()) {
1361                        mShutterButton.requestFocusFromTouch();
1362                    } else {
1363                        mShutterButton.requestFocus();
1364                    }
1365                    mShutterButton.setPressed(true);
1366                }
1367                return true;
1368        }
1369
1370        return super.onKeyDown(keyCode, event);
1371    }
1372
1373    @Override
1374    public boolean onKeyUp(int keyCode, KeyEvent event) {
1375        switch (keyCode) {
1376            case KeyEvent.KEYCODE_FOCUS:
1377                if (mFirstTimeInitialized) {
1378                    doFocus(false);
1379                }
1380                return true;
1381        }
1382        return super.onKeyUp(keyCode, event);
1383    }
1384
1385    private void doSnap() {
1386        // If the user has half-pressed the shutter and focus is completed, we
1387        // can take the photo right away. If the focus mode is infinity, we can
1388        // also take the photo.
1389        if (mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)
1390                || (mFocusState == FOCUS_SUCCESS
1391                || mFocusState == FOCUS_FAIL)) {
1392            if (mZoomButtons != null) mZoomButtons.setVisible(false);
1393            mImageCapture.onSnap();
1394        } else if (mFocusState == FOCUSING) {
1395            // Half pressing the shutter (i.e. the focus button event) will
1396            // already have requested AF for us, so just request capture on
1397            // focus here.
1398            mFocusState = FOCUSING_SNAP_ON_FINISH;
1399        } else if (mFocusState == FOCUS_NOT_STARTED) {
1400            // Focus key down event is dropped for some reasons. Just ignore.
1401        }
1402    }
1403
1404    private void doFocus(boolean pressed) {
1405        // Do the focus if the mode is not infinity.
1406        if (!mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY)) {
1407            if (pressed) {  // Focus key down.
1408                autoFocus();
1409            } else {  // Focus key up.
1410                cancelAutoFocus();
1411            }
1412        }
1413    }
1414
1415    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1416        // Make sure we have a surface in the holder before proceeding.
1417        if (holder.getSurface() == null) {
1418            Log.d(TAG, "holder.getSurface() == null");
1419            return;
1420        }
1421
1422        // The mCameraDevice will be null if it fails to connect to the camera
1423        // hardware. In this case we will show a dialog and then finish the
1424        // activity, so it's OK to ignore it.
1425        if (mCameraDevice == null) return;
1426
1427        mSurfaceHolder = holder;
1428        mViewFinderWidth = w;
1429        mViewFinderHeight = h;
1430
1431        // Sometimes surfaceChanged is called after onPause. Ignore it.
1432        if (mPausing || isFinishing()) return;
1433
1434        // Set preview display if the surface is being created. Preview was
1435        // already started.
1436        if (holder.isCreating()) {
1437            setPreviewDisplay(holder);
1438        }
1439
1440        // If first time initialization is not finished, send a message to do
1441        // it later. We want to finish surfaceChanged as soon as possible to let
1442        // user see preview first.
1443        if (!mFirstTimeInitialized) {
1444            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1445        } else {
1446            initializeSecondTime();
1447        }
1448    }
1449
1450    public void surfaceCreated(SurfaceHolder holder) {
1451    }
1452
1453    public void surfaceDestroyed(SurfaceHolder holder) {
1454        stopPreview();
1455        mSurfaceHolder = null;
1456    }
1457
1458    private void closeCamera() {
1459        if (mCameraDevice != null) {
1460            CameraHolder.instance().release();
1461            mCameraDevice = null;
1462            mPreviewing = false;
1463        }
1464    }
1465
1466    private void ensureCameraDevice() throws CameraHardwareException {
1467        if (mCameraDevice == null) {
1468            mCameraDevice = CameraHolder.instance().open();
1469        }
1470    }
1471
1472    private void updateLastImage() {
1473        IImageList list = ImageManager.makeImageList(
1474            mContentResolver,
1475            dataLocation(),
1476            ImageManager.INCLUDE_IMAGES,
1477            ImageManager.SORT_ASCENDING,
1478            ImageManager.CAMERA_IMAGE_BUCKET_ID);
1479        int count = list.getCount();
1480        if (count > 0) {
1481            IImage image = list.getImageAt(count - 1);
1482            Uri uri = image.fullSizeImageUri();
1483            mThumbController.setData(uri, image.miniThumbBitmap());
1484        } else {
1485            mThumbController.setData(null, null);
1486        }
1487        list.close();
1488    }
1489
1490    private void showCameraErrorAndFinish() {
1491        Resources ress = getResources();
1492        Util.showFatalErrorAndFinish(Camera.this,
1493                ress.getString(R.string.camera_error_title),
1494                ress.getString(R.string.cannot_connect_camera));
1495    }
1496
1497    private void restartPreview() {
1498        // make sure the surfaceview fills the whole screen when previewing
1499        mSurfaceView.setAspectRatio(VideoPreview.DONT_CARE);
1500        try {
1501            startPreview();
1502        } catch (CameraHardwareException e) {
1503            showCameraErrorAndFinish();
1504            return;
1505        }
1506
1507        // Calculate this in advance of each shot so we don't add to shutter
1508        // latency. It's true that someone else could write to the SD card in
1509        // the mean time and fill it, but that could have happened between the
1510        // shutter press and saving the JPEG too.
1511        calculatePicturesRemaining();
1512    }
1513
1514    private void setPreviewDisplay(SurfaceHolder holder) {
1515        try {
1516            mCameraDevice.setPreviewDisplay(holder);
1517        } catch (Throwable ex) {
1518            closeCamera();
1519            throw new RuntimeException("setPreviewDisplay failed", ex);
1520        }
1521    }
1522
1523    private void startPreview() throws CameraHardwareException {
1524        if (mPausing || isFinishing()) return;
1525
1526        ensureCameraDevice();
1527
1528        // If we're previewing already, stop the preview first (this will blank
1529        // the screen).
1530        if (mPreviewing) stopPreview();
1531
1532        setPreviewDisplay(mSurfaceHolder);
1533
1534        setCameraParameters();
1535
1536        final long wallTimeStart = SystemClock.elapsedRealtime();
1537        final long threadTimeStart = Debug.threadCpuTimeNanos();
1538
1539        // Set one shot preview callback for latency measurement.
1540        mCameraDevice.setOneShotPreviewCallback(mOneShotPreviewCallback);
1541        mCameraDevice.setErrorCallback(mErrorCallback);
1542
1543        try {
1544            Log.v(TAG, "startPreview");
1545            mCameraDevice.startPreview();
1546        } catch (Throwable ex) {
1547            closeCamera();
1548            throw new RuntimeException("startPreview failed", ex);
1549        }
1550        mPreviewing = true;
1551        mStatus = IDLE;
1552
1553        long threadTimeEnd = Debug.threadCpuTimeNanos();
1554        long wallTimeEnd = SystemClock.elapsedRealtime();
1555        if ((wallTimeEnd - wallTimeStart) > 3000) {
1556            Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart)
1557                    + " ms. Thread time was"
1558                    + (threadTimeEnd - threadTimeStart) / 1000000 + " ms.");
1559        }
1560    }
1561
1562    private void stopPreview() {
1563        if (mCameraDevice != null && mPreviewing) {
1564            Log.v(TAG, "stopPreview");
1565            mCameraDevice.stopPreview();
1566        }
1567        mPreviewing = false;
1568        // If auto focus was in progress, it would have been canceled.
1569        clearFocusState();
1570    }
1571
1572    private void resizeForPreviewAspectRatio(View v) {
1573        ViewGroup.LayoutParams params;
1574        params = v.getLayoutParams();
1575        Size size = mParameters.getPreviewSize();
1576        params.width = (int) (params.height * size.width / size.height);
1577        Log.v(TAG, "resize to " + params.width + "x" + params.height);
1578        v.setLayoutParams(params);
1579    }
1580
1581    private Size getOptimalPreviewSize(List<Size> sizes) {
1582        Size optimalSize = null;
1583        if (sizes != null) {
1584            optimalSize = sizes.get(0);
1585            for (int i = 1; i < sizes.size(); i++) {
1586                if (Math.abs(sizes.get(i).height - mViewFinderHeight) <
1587                        Math.abs(optimalSize.height - mViewFinderHeight)) {
1588                    optimalSize = sizes.get(i);
1589                }
1590            }
1591            Log.v(TAG, "Optimal preview size is " + optimalSize.width + "x"
1592                    + optimalSize.height);
1593        }
1594        return optimalSize;
1595    }
1596
1597    private static boolean isSupported(String value, List<String> supported) {
1598        return supported == null ? false : supported.indexOf(value) >= 0;
1599    }
1600
1601    private void setCameraParameters() {
1602        mParameters = mCameraDevice.getParameters();
1603
1604        // Reset preview frame rate to the maximum because it may be lowered by
1605        // video camera application.
1606        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1607        if (frameRates != null) {
1608            Integer max = Collections.max(frameRates);
1609            mParameters.setPreviewFrameRate(max);
1610        }
1611
1612        // Set a preview size that is closest to the viewfinder height.
1613        List<Size> sizes = mParameters.getSupportedPreviewSizes();
1614        Size optimalSize = getOptimalPreviewSize(sizes);
1615        if (optimalSize != null) {
1616            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1617        }
1618
1619        // Set picture size.
1620        String pictureSize = mPreferences.getString(
1621                CameraSettings.KEY_PICTURE_SIZE,
1622                getString(R.string.pref_camera_picturesize_default));
1623        setCameraPictureSizeIfSupported(pictureSize);
1624
1625        // Set JPEG quality.
1626        String jpegQuality = mPreferences.getString(
1627                CameraSettings.KEY_JPEG_QUALITY,
1628                getString(R.string.pref_camera_jpegquality_default));
1629        mParameters.setJpegQuality(Integer.parseInt(jpegQuality));
1630
1631        // For the following settings, we need to check if the settings are
1632        // still supported by latest driver, if not, ignore the settings.
1633
1634        // Set flash mode.
1635        String flashMode = mPreferences.getString(
1636                CameraSettings.KEY_FLASH_MODE,
1637                getString(R.string.pref_camera_flashmode_default));
1638        List<String> supportedFlash = mParameters.getSupportedFlashModes();
1639        if (isSupported(flashMode, supportedFlash)) {
1640            mParameters.setFlashMode(flashMode);
1641        } else {
1642            // If the current flashMode is not support, show the
1643            // FLASH_MODE_OFF icon.
1644            flashMode = Parameters.FLASH_MODE_OFF;
1645        }
1646
1647        // We post the runner because this function can be called from
1648        // non-UI thread (i.e., startPreviewThread).
1649        final String finalFlashMode = flashMode;
1650        mHandler.post(new Runnable() {
1651            public void run() {
1652                mFlashIndicator.setMode(finalFlashMode);
1653            }
1654        });
1655
1656        // Set white balance parameter.
1657        String whiteBalance = mPreferences.getString(
1658                CameraSettings.KEY_WHITE_BALANCE,
1659                getString(R.string.pref_camera_whitebalance_default));
1660        if (isSupported(whiteBalance, mParameters.getSupportedWhiteBalance())) {
1661            mParameters.setWhiteBalance(whiteBalance);
1662        }
1663
1664        // Set color effect parameter.
1665        String colorEffect = mPreferences.getString(
1666                CameraSettings.KEY_COLOR_EFFECT,
1667                getString(R.string.pref_camera_coloreffect_default));
1668        if (isSupported(colorEffect, mParameters.getSupportedColorEffects())) {
1669            mParameters.setColorEffect(colorEffect);
1670        }
1671
1672        // Set scene mode.
1673        String sceneMode = mPreferences.getString(
1674                CameraSettings.KEY_SCENE_MODE,
1675                getString(R.string.pref_camera_scenemode_default));
1676        if (isSupported(sceneMode, mParameters.getSupportedSceneModes())) {
1677            mParameters.setSceneMode(sceneMode);
1678        }
1679
1680        // Set focus mode.
1681        mFocusMode = mPreferences.getString(
1682                CameraSettings.KEY_FOCUS_MODE,
1683                getString(R.string.pref_camera_focusmode_default));
1684        if (isSupported(mFocusMode, mParameters.getSupportedFocusModes())) {
1685            mParameters.setFocusMode(mFocusMode);
1686        }
1687
1688        mCameraDevice.setParameters(mParameters);
1689    }
1690
1691    private void gotoGallery() {
1692        MenuHelper.gotoCameraImageGallery(this);
1693    }
1694
1695    private void viewLastImage() {
1696        if (mThumbController.isUriValid()) {
1697            Uri targetUri = mThumbController.getUri();
1698            targetUri = targetUri.buildUpon().appendQueryParameter(
1699                    "bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
1700            Intent intent = new Intent(this, ReviewImage.class);
1701            intent.setData(targetUri);
1702            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
1703            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
1704            intent.putExtra("com.android.camera.ReviewMode", true);
1705            try {
1706                startActivity(intent);
1707            } catch (ActivityNotFoundException ex) {
1708                Log.e(TAG, "review image fail", ex);
1709            }
1710        } else {
1711            Log.e(TAG, "Can't view last image.");
1712        }
1713    }
1714
1715    private void startReceivingLocationUpdates() {
1716        if (mLocationManager != null) {
1717            try {
1718                mLocationManager.requestLocationUpdates(
1719                        LocationManager.NETWORK_PROVIDER,
1720                        1000,
1721                        0F,
1722                        mLocationListeners[1]);
1723            } catch (java.lang.SecurityException ex) {
1724                Log.i(TAG, "fail to request location update, ignore", ex);
1725            } catch (IllegalArgumentException ex) {
1726                Log.d(TAG, "provider does not exist " + ex.getMessage());
1727            }
1728            try {
1729                mLocationManager.requestLocationUpdates(
1730                        LocationManager.GPS_PROVIDER,
1731                        1000,
1732                        0F,
1733                        mLocationListeners[0]);
1734            } catch (java.lang.SecurityException ex) {
1735                Log.i(TAG, "fail to request location update, ignore", ex);
1736            } catch (IllegalArgumentException ex) {
1737                Log.d(TAG, "provider does not exist " + ex.getMessage());
1738            }
1739        }
1740    }
1741
1742    private void stopReceivingLocationUpdates() {
1743        if (mLocationManager != null) {
1744            for (int i = 0; i < mLocationListeners.length; i++) {
1745                try {
1746                    mLocationManager.removeUpdates(mLocationListeners[i]);
1747                } catch (Exception ex) {
1748                    Log.i(TAG, "fail to remove location listners, ignore", ex);
1749                }
1750            }
1751        }
1752    }
1753
1754    private Location getCurrentLocation() {
1755        // go in best to worst order
1756        for (int i = 0; i < mLocationListeners.length; i++) {
1757            Location l = mLocationListeners[i].current();
1758            if (l != null) return l;
1759        }
1760        return null;
1761    }
1762
1763    private boolean isCameraIdle() {
1764        return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED;
1765    }
1766
1767    private boolean isImageCaptureIntent() {
1768        String action = getIntent().getAction();
1769        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
1770    }
1771
1772    private void showPostCaptureAlert() {
1773        if (mIsImageCaptureIntent) {
1774            findViewById(R.id.shutter_button).setVisibility(View.INVISIBLE);
1775            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1776            for (int id : pickIds) {
1777                View button = findViewById(id);
1778                ((View) button.getParent()).setVisibility(View.VISIBLE);
1779            }
1780        }
1781    }
1782
1783    private void hidePostCaptureAlert() {
1784        if (mIsImageCaptureIntent) {
1785            findViewById(R.id.shutter_button).setVisibility(View.VISIBLE);
1786            int[] pickIds = {R.id.btn_retake, R.id.btn_done};
1787            for (int id : pickIds) {
1788                View button = findViewById(id);
1789                ((View) button.getParent()).setVisibility(View.GONE);
1790            }
1791        }
1792    }
1793
1794    private int calculatePicturesRemaining() {
1795        mPicturesRemaining = MenuHelper.calculatePicturesRemaining();
1796        return mPicturesRemaining;
1797    }
1798
1799    @Override
1800    public boolean onPrepareOptionsMenu(Menu menu) {
1801        super.onPrepareOptionsMenu(menu);
1802        // Only show the menu when camera is idle.
1803        for (int i = 0; i < menu.size(); i++) {
1804            menu.getItem(i).setVisible(isCameraIdle());
1805        }
1806
1807        return true;
1808    }
1809
1810    @Override
1811    public boolean onCreateOptionsMenu(Menu menu) {
1812        super.onCreateOptionsMenu(menu);
1813
1814        if (mIsImageCaptureIntent) {
1815            // No options menu for attach mode.
1816            return false;
1817        } else {
1818            addBaseMenuItems(menu);
1819        }
1820        return true;
1821    }
1822
1823    private void addBaseMenuItems(Menu menu) {
1824        MenuItem gallery = menu.add(Menu.NONE, Menu.NONE,
1825                MenuHelper.POSITION_GOTO_GALLERY,
1826                R.string.camera_gallery_photos_text)
1827                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1828            public boolean onMenuItemClick(MenuItem item) {
1829                gotoGallery();
1830                return true;
1831            }
1832        });
1833        gallery.setIcon(android.R.drawable.ic_menu_gallery);
1834        mGalleryItems.add(gallery);
1835
1836        MenuItem item = menu.add(Menu.NONE, Menu.NONE,
1837                MenuHelper.POSITION_CAMERA_SETTING, R.string.settings)
1838                .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1839            public boolean onMenuItemClick(MenuItem item) {
1840                showOnScreenSettings();
1841                return true;
1842            }
1843        });
1844        item.setIcon(android.R.drawable.ic_menu_preferences);
1845    }
1846
1847    public boolean onSwitchChanged(Switcher source, boolean onOff) {
1848        if (onOff == SWITCH_VIDEO) {
1849            if (!isCameraIdle()) return false;
1850            MenuHelper.gotoVideoMode(this);
1851            finish();
1852        }
1853        return true;
1854    }
1855
1856    private void setCameraPictureSizeIfSupported(String sizeString) {
1857        List<Size> pictureSizes = mParameters.getSupportedPictureSizes();
1858        if (pictureSizes != null) {
1859            int index = sizeString.indexOf('x');
1860            int width = Integer.parseInt(sizeString.substring(0, index));
1861            int height = Integer.parseInt(sizeString.substring(index + 1));
1862            for (Size size: pictureSizes) {
1863                if (size.width == width && size.height == height) {
1864                    mParameters.setPictureSize(width, height);
1865                    break;
1866                }
1867            }
1868        }
1869    }
1870
1871    public void onSharedPreferenceChanged(
1872            SharedPreferences preferences, String key) {
1873        // ignore the events after "onPause()"
1874        if (mPausing) return;
1875
1876        if (CameraSettings.KEY_RECORD_LOCATION.equals(key)) {
1877            mRecordLocation = preferences.getBoolean(key, false);
1878            if (mRecordLocation) {
1879                startReceivingLocationUpdates();
1880            } else {
1881                stopReceivingLocationUpdates();
1882            }
1883        } else {
1884            // All preferences except RECORD_LOCATION are camera parameters.
1885            // Call setCameraParameters to take effect now.
1886            setCameraParameters();
1887        }
1888    }
1889
1890    @Override
1891    public void onUserInteraction() {
1892        super.onUserInteraction();
1893        keepScreenOnAwhile();
1894    }
1895
1896    private void resetScreenOn() {
1897        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1898        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1899    }
1900
1901    private void keepScreenOnAwhile() {
1902        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
1903        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1904        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
1905    }
1906}
1907
1908class FocusRectangle extends View {
1909
1910    @SuppressWarnings("unused")
1911    private static final String TAG = "FocusRectangle";
1912
1913    public FocusRectangle(Context context, AttributeSet attrs) {
1914        super(context, attrs);
1915    }
1916
1917    private void setDrawable(int resid) {
1918        setBackgroundDrawable(getResources().getDrawable(resid));
1919    }
1920
1921    public void showStart() {
1922        setDrawable(R.drawable.focus_focusing);
1923    }
1924
1925    public void showSuccess() {
1926        setDrawable(R.drawable.focus_focused);
1927    }
1928
1929    public void showFail() {
1930        setDrawable(R.drawable.focus_focus_failed);
1931    }
1932
1933    public void clear() {
1934        setBackgroundDrawable(null);
1935    }
1936}
1937