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