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 
17 package com.android.camera;
18 
19 import com.android.camera.ui.CameraPicker;
20 import com.android.camera.ui.FaceView;
21 import com.android.camera.ui.IndicatorControlContainer;
22 import com.android.camera.ui.PopupManager;
23 import com.android.camera.ui.Rotatable;
24 import com.android.camera.ui.RotateImageView;
25 import com.android.camera.ui.RotateLayout;
26 import com.android.camera.ui.RotateTextToast;
27 import com.android.camera.ui.SharePopup;
28 import com.android.camera.ui.ZoomControl;
29 
30 import android.app.Activity;
31 import android.content.BroadcastReceiver;
32 import android.content.ContentProviderClient;
33 import android.content.ContentResolver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences.Editor;
38 import android.content.pm.ActivityInfo;
39 import android.graphics.Bitmap;
40 import android.hardware.Camera.CameraInfo;
41 import android.hardware.Camera.Face;
42 import android.hardware.Camera.FaceDetectionListener;
43 import android.hardware.Camera.Parameters;
44 import android.hardware.Camera.PictureCallback;
45 import android.hardware.Camera.Size;
46 import android.location.Location;
47 import android.media.CameraProfile;
48 import android.media.MediaActionSound;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.Looper;
53 import android.os.Message;
54 import android.os.MessageQueue;
55 import android.os.SystemClock;
56 import android.provider.MediaStore;
57 import android.util.Log;
58 import android.view.GestureDetector;
59 import android.view.Gravity;
60 import android.view.KeyEvent;
61 import android.view.Menu;
62 import android.view.MenuItem;
63 import android.view.MenuItem.OnMenuItemClickListener;
64 import android.view.MotionEvent;
65 import android.view.OrientationEventListener;
66 import android.view.SurfaceHolder;
67 import android.view.SurfaceView;
68 import android.view.View;
69 import android.view.ViewGroup;
70 import android.view.WindowManager;
71 import android.view.animation.AnimationUtils;
72 import android.widget.ImageView;
73 import android.widget.TextView;
74 import android.widget.Toast;
75 
76 import java.io.File;
77 import java.io.FileNotFoundException;
78 import java.io.FileOutputStream;
79 import java.io.IOException;
80 import java.io.OutputStream;
81 import java.util.ArrayList;
82 import java.util.Collections;
83 import java.util.Formatter;
84 import java.util.List;
85 
86 /** The Camera activity which can preview and take pictures. */
87 public class Camera extends ActivityBase implements FocusManager.Listener,
88         View.OnTouchListener, ShutterButton.OnShutterButtonListener,
89         SurfaceHolder.Callback, ModePicker.OnModeChangeListener,
90         FaceDetectionListener, CameraPreference.OnPreferenceChangedListener,
91         LocationManager.Listener {
92 
93     private static final String TAG = "camera";
94 
95     private static final int CROP_MSG = 1;
96     private static final int FIRST_TIME_INIT = 2;
97     private static final int CLEAR_SCREEN_DELAY = 3;
98     private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
99     private static final int CHECK_DISPLAY_ROTATION = 5;
100     private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
101     private static final int UPDATE_THUMBNAIL = 7;
102 
103     // The subset of parameters we need to update in setCameraParameters().
104     private static final int UPDATE_PARAM_INITIALIZE = 1;
105     private static final int UPDATE_PARAM_ZOOM = 2;
106     private static final int UPDATE_PARAM_PREFERENCE = 4;
107     private static final int UPDATE_PARAM_ALL = -1;
108 
109     // When setCameraParametersWhenIdle() is called, we accumulate the subsets
110     // needed to be updated in mUpdateSet.
111     private int mUpdateSet;
112 
113     private static final int SCREEN_DELAY = 2 * 60 * 1000;
114 
115     private static final int ZOOM_STOPPED = 0;
116     private static final int ZOOM_START = 1;
117     private static final int ZOOM_STOPPING = 2;
118 
119     private int mZoomState = ZOOM_STOPPED;
120     private boolean mSmoothZoomSupported = false;
121     private int mZoomValue;  // The current zoom value.
122     private int mZoomMax;
123     private int mTargetZoomValue;
124     private ZoomControl mZoomControl;
125 
126     private Parameters mParameters;
127     private Parameters mInitialParams;
128     private boolean mFocusAreaSupported;
129     private boolean mMeteringAreaSupported;
130     private boolean mAeLockSupported;
131     private boolean mAwbLockSupported;
132 
133     private MyOrientationEventListener mOrientationListener;
134     // The degrees of the device rotated clockwise from its natural orientation.
135     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
136     // The orientation compensation for icons and thumbnails. Ex: if the value
137     // is 90, the UI components should be rotated 90 degrees counter-clockwise.
138     private int mOrientationCompensation = 0;
139     private ComboPreferences mPreferences;
140 
141     private static final String sTempCropFilename = "crop-temp";
142 
143     private ContentProviderClient mMediaProviderClient;
144     private SurfaceHolder mSurfaceHolder = null;
145     private ShutterButton mShutterButton;
146     private GestureDetector mPopupGestureDetector;
147     private boolean mOpenCameraFail = false;
148     private boolean mCameraDisabled = false;
149     private boolean mFaceDetectionStarted = false;
150 
151     private View mPreviewPanel;  // The container of PreviewFrameLayout.
152     private PreviewFrameLayout mPreviewFrameLayout;
153     private View mPreviewFrame;  // Preview frame area.
154     private RotateDialogController mRotateDialog;
155 
156     // A popup window that contains a bigger thumbnail and a list of apps to share.
157     private SharePopup mSharePopup;
158     // The bitmap of the last captured picture thumbnail and the URI of the
159     // original picture.
160     private Thumbnail mThumbnail;
161     // An imageview showing showing the last captured picture thumbnail.
162     private RotateImageView mThumbnailView;
163     private ModePicker mModePicker;
164     private FaceView mFaceView;
165     private RotateLayout mFocusAreaIndicator;
166     private Rotatable mReviewCancelButton;
167     private Rotatable mReviewDoneButton;
168 
169     // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
170     private String mCropValue;
171     private Uri mSaveUri;
172 
173     // Small indicators which show the camera settings in the viewfinder.
174     private TextView mExposureIndicator;
175     private ImageView mGpsIndicator;
176     private ImageView mFlashIndicator;
177     private ImageView mSceneIndicator;
178     private ImageView mWhiteBalanceIndicator;
179     private ImageView mFocusIndicator;
180     // A view group that contains all the small indicators.
181     private Rotatable mOnScreenIndicators;
182 
183     // We use a thread in ImageSaver to do the work of saving images and
184     // generating thumbnails. This reduces the shot-to-shot time.
185     private ImageSaver mImageSaver;
186 
187     private MediaActionSound mCameraSound;
188 
189     private Runnable mDoSnapRunnable = new Runnable() {
190         public void run() {
191             onShutterButtonClick();
192         }
193     };
194 
195     private final StringBuilder mBuilder = new StringBuilder();
196     private final Formatter mFormatter = new Formatter(mBuilder);
197     private final Object[] mFormatterArgs = new Object[1];
198 
199     /**
200      * An unpublished intent flag requesting to return as soon as capturing
201      * is completed.
202      *
203      * TODO: consider publishing by moving into MediaStore.
204      */
205     private static final String EXTRA_QUICK_CAPTURE =
206             "android.intent.extra.quickCapture";
207 
208     // The display rotation in degrees. This is only valid when mCameraState is
209     // not PREVIEW_STOPPED.
210     private int mDisplayRotation;
211     // The value for android.hardware.Camera.setDisplayOrientation.
212     private int mDisplayOrientation;
213     private boolean mPausing;
214     private boolean mFirstTimeInitialized;
215     private boolean mIsImageCaptureIntent;
216 
217     private static final int PREVIEW_STOPPED = 0;
218     private static final int IDLE = 1;  // preview is active
219     // Focus is in progress. The exact focus state is in Focus.java.
220     private static final int FOCUSING = 2;
221     private static final int SNAPSHOT_IN_PROGRESS = 3;
222     private int mCameraState = PREVIEW_STOPPED;
223     private boolean mSnapshotOnIdle = false;
224 
225     private ContentResolver mContentResolver;
226     private boolean mDidRegister = false;
227 
228     private LocationManager mLocationManager;
229 
230     private final ShutterCallback mShutterCallback = new ShutterCallback();
231     private final PostViewPictureCallback mPostViewPictureCallback =
232             new PostViewPictureCallback();
233     private final RawPictureCallback mRawPictureCallback =
234             new RawPictureCallback();
235     private final AutoFocusCallback mAutoFocusCallback =
236             new AutoFocusCallback();
237     private final ZoomListener mZoomListener = new ZoomListener();
238     private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
239 
240     private long mFocusStartTime;
241     private long mCaptureStartTime;
242     private long mShutterCallbackTime;
243     private long mPostViewPictureCallbackTime;
244     private long mRawPictureCallbackTime;
245     private long mJpegPictureCallbackTime;
246     private long mOnResumeTime;
247     private long mPicturesRemaining;
248     private byte[] mJpegImageData;
249 
250     // These latency time are for the CameraLatency test.
251     public long mAutoFocusTime;
252     public long mShutterLag;
253     public long mShutterToPictureDisplayedTime;
254     public long mPictureDisplayedToJpegCallbackTime;
255     public long mJpegCallbackFinishTime;
256 
257     // This handles everything about focus.
258     private FocusManager mFocusManager;
259     private String mSceneMode;
260     private Toast mNotSelectableToast;
261     private Toast mNoShareToast;
262 
263     private final Handler mHandler = new MainHandler();
264     private IndicatorControlContainer mIndicatorControlContainer;
265     private PreferenceGroup mPreferenceGroup;
266 
267     // multiple cameras support
268     private int mNumberOfCameras;
269     private int mCameraId;
270     private int mFrontCameraId;
271     private int mBackCameraId;
272 
273     private boolean mQuickCapture;
274 
275     /**
276      * This Handler is used to post message back onto the main thread of the
277      * application
278      */
279     private class MainHandler extends Handler {
280         @Override
handleMessage(Message msg)281         public void handleMessage(Message msg) {
282             switch (msg.what) {
283                 case CLEAR_SCREEN_DELAY: {
284                     getWindow().clearFlags(
285                             WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
286                     break;
287                 }
288 
289                 case FIRST_TIME_INIT: {
290                     initializeFirstTime();
291                     break;
292                 }
293 
294                 case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
295                     setCameraParametersWhenIdle(0);
296                     break;
297                 }
298 
299                 case CHECK_DISPLAY_ROTATION: {
300                     // Set the display orientation if display rotation has changed.
301                     // Sometimes this happens when the device is held upside
302                     // down and camera app is opened. Rotation animation will
303                     // take some time and the rotation value we have got may be
304                     // wrong. Framework does not have a callback for this now.
305                     if (Util.getDisplayRotation(Camera.this) != mDisplayRotation) {
306                         setDisplayOrientation();
307                     }
308                     if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
309                         mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
310                     }
311                     break;
312                 }
313 
314                 case SHOW_TAP_TO_FOCUS_TOAST: {
315                     showTapToFocusToast();
316                     break;
317                 }
318 
319                 case UPDATE_THUMBNAIL: {
320                     mImageSaver.updateThumbnail();
321                     break;
322                 }
323             }
324         }
325     }
326 
resetExposureCompensation()327     private void resetExposureCompensation() {
328         String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
329                 CameraSettings.EXPOSURE_DEFAULT_VALUE);
330         if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
331             Editor editor = mPreferences.edit();
332             editor.putString(CameraSettings.KEY_EXPOSURE, "0");
333             editor.apply();
334             if (mIndicatorControlContainer != null) {
335                 mIndicatorControlContainer.reloadPreferences();
336             }
337         }
338     }
339 
keepMediaProviderInstance()340     private void keepMediaProviderInstance() {
341         // We want to keep a reference to MediaProvider in camera's lifecycle.
342         // TODO: Utilize mMediaProviderClient instance to replace
343         // ContentResolver calls.
344         if (mMediaProviderClient == null) {
345             mMediaProviderClient = getContentResolver()
346                     .acquireContentProviderClient(MediaStore.AUTHORITY);
347         }
348     }
349 
350     // Snapshots can only be taken after this is called. It should be called
351     // once only. We could have done these things in onCreate() but we want to
352     // make preview screen appear as soon as possible.
initializeFirstTime()353     private void initializeFirstTime() {
354         if (mFirstTimeInitialized) return;
355 
356         // Create orientation listenter. This should be done first because it
357         // takes some time to get first orientation.
358         mOrientationListener = new MyOrientationEventListener(Camera.this);
359         mOrientationListener.enable();
360 
361         // Initialize location sevice.
362         boolean recordLocation = RecordLocationPreference.get(
363                 mPreferences, getContentResolver());
364         initOnScreenIndicator();
365         mLocationManager.recordLocation(recordLocation);
366 
367         keepMediaProviderInstance();
368         checkStorage();
369 
370         // Initialize last picture button.
371         mContentResolver = getContentResolver();
372         if (!mIsImageCaptureIntent) {  // no thumbnail in image capture intent
373             initThumbnailButton();
374         }
375 
376         // Initialize shutter button.
377         mShutterButton = (ShutterButton) findViewById(R.id.shutter_button);
378         mShutterButton.setOnShutterButtonListener(this);
379         mShutterButton.setVisibility(View.VISIBLE);
380 
381         // Initialize focus UI.
382         mPreviewFrame = findViewById(R.id.camera_preview);
383         mPreviewFrame.setOnTouchListener(this);
384         mFocusAreaIndicator = (RotateLayout) findViewById(R.id.focus_indicator_rotate_layout);
385         CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
386         boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
387         mFocusManager.initialize(mFocusAreaIndicator, mPreviewFrame, mFaceView, this,
388                 mirror, mDisplayOrientation);
389         mImageSaver = new ImageSaver();
390         Util.initializeScreenBrightness(getWindow(), getContentResolver());
391         installIntentFilter();
392         initializeZoom();
393         updateOnScreenIndicators();
394         startFaceDetection();
395         // Show the tap to focus toast if this is the first start.
396         if (mFocusAreaSupported &&
397                 mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
398             // Delay the toast for one second to wait for orientation.
399             mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
400         }
401 
402         mFirstTimeInitialized = true;
403         addIdleHandler();
404     }
405 
addIdleHandler()406     private void addIdleHandler() {
407         MessageQueue queue = Looper.myQueue();
408         queue.addIdleHandler(new MessageQueue.IdleHandler() {
409             public boolean queueIdle() {
410                 Storage.ensureOSXCompatible();
411                 return false;
412             }
413         });
414     }
415 
initThumbnailButton()416     private void initThumbnailButton() {
417         // Load the thumbnail from the disk.
418         mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
419         updateThumbnailButton();
420     }
421 
updateThumbnailButton()422     private void updateThumbnailButton() {
423         // Update last image if URI is invalid and the storage is ready.
424         if ((mThumbnail == null || !Util.isUriValid(mThumbnail.getUri(), mContentResolver))
425                 && mPicturesRemaining >= 0) {
426             mThumbnail = Thumbnail.getLastThumbnail(mContentResolver);
427         }
428         if (mThumbnail != null) {
429             mThumbnailView.setBitmap(mThumbnail.getBitmap());
430         } else {
431             mThumbnailView.setBitmap(null);
432         }
433     }
434 
435     // If the activity is paused and resumed, this method will be called in
436     // onResume.
initializeSecondTime()437     private void initializeSecondTime() {
438         // Start orientation listener as soon as possible because it takes
439         // some time to get first orientation.
440         mOrientationListener.enable();
441 
442         // Start location update if needed.
443         boolean recordLocation = RecordLocationPreference.get(
444                 mPreferences, getContentResolver());
445         mLocationManager.recordLocation(recordLocation);
446 
447         installIntentFilter();
448         mImageSaver = new ImageSaver();
449         initializeZoom();
450         keepMediaProviderInstance();
451         checkStorage();
452         hidePostCaptureAlert();
453 
454         if (!mIsImageCaptureIntent) {
455             updateThumbnailButton();
456             mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
457         }
458     }
459 
460     private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
461         // only for immediate zoom
462         @Override
onZoomValueChanged(int index)463         public void onZoomValueChanged(int index) {
464             Camera.this.onZoomValueChanged(index);
465         }
466 
467         // only for smooth zoom
468         @Override
onZoomStateChanged(int state)469         public void onZoomStateChanged(int state) {
470             if (mPausing) return;
471 
472             Log.v(TAG, "zoom picker state=" + state);
473             if (state == ZoomControl.ZOOM_IN) {
474                 Camera.this.onZoomValueChanged(mZoomMax);
475             } else if (state == ZoomControl.ZOOM_OUT) {
476                 Camera.this.onZoomValueChanged(0);
477             } else {
478                 mTargetZoomValue = -1;
479                 if (mZoomState == ZOOM_START) {
480                     mZoomState = ZOOM_STOPPING;
481                     mCameraDevice.stopSmoothZoom();
482                 }
483             }
484         }
485     }
486 
initializeZoom()487     private void initializeZoom() {
488         // Get the parameter to make sure we have the up-to-date zoom value.
489         mParameters = mCameraDevice.getParameters();
490         if (!mParameters.isZoomSupported()) return;
491         mZoomMax = mParameters.getMaxZoom();
492         // Currently we use immediate zoom for fast zooming to get better UX and
493         // there is no plan to take advantage of the smooth zoom.
494         mZoomControl.setZoomMax(mZoomMax);
495         mZoomControl.setZoomIndex(mParameters.getZoom());
496         mZoomControl.setSmoothZoomSupported(mSmoothZoomSupported);
497         mZoomControl.setOnZoomChangeListener(new ZoomChangeListener());
498         mCameraDevice.setZoomChangeListener(mZoomListener);
499     }
500 
onZoomValueChanged(int index)501     private void onZoomValueChanged(int index) {
502         // Not useful to change zoom value when the activity is paused.
503         if (mPausing) return;
504 
505         if (mSmoothZoomSupported) {
506             if (mTargetZoomValue != index && mZoomState != ZOOM_STOPPED) {
507                 mTargetZoomValue = index;
508                 if (mZoomState == ZOOM_START) {
509                     mZoomState = ZOOM_STOPPING;
510                     mCameraDevice.stopSmoothZoom();
511                 }
512             } else if (mZoomState == ZOOM_STOPPED && mZoomValue != index) {
513                 mTargetZoomValue = index;
514                 mCameraDevice.startSmoothZoom(index);
515                 mZoomState = ZOOM_START;
516             }
517         } else {
518             mZoomValue = index;
519             setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
520         }
521     }
522 
523     @Override
startFaceDetection()524     public void startFaceDetection() {
525         if (mFaceDetectionStarted || mCameraState != IDLE) return;
526         if (mParameters.getMaxNumDetectedFaces() > 0) {
527             mFaceDetectionStarted = true;
528             mFaceView = (FaceView) findViewById(R.id.face_view);
529             mFaceView.clear();
530             mFaceView.setVisibility(View.VISIBLE);
531             mFaceView.setDisplayOrientation(mDisplayOrientation);
532             CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
533             mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
534             mFaceView.resume();
535             mCameraDevice.setFaceDetectionListener(this);
536             mCameraDevice.startFaceDetection();
537         }
538     }
539 
540     @Override
stopFaceDetection()541     public void stopFaceDetection() {
542         if (!mFaceDetectionStarted) return;
543         if (mParameters.getMaxNumDetectedFaces() > 0) {
544             mFaceDetectionStarted = false;
545             mCameraDevice.setFaceDetectionListener(null);
546             mCameraDevice.stopFaceDetection();
547             if (mFaceView != null) mFaceView.clear();
548         }
549     }
550 
551     private class PopupGestureListener
552             extends GestureDetector.SimpleOnGestureListener {
553         @Override
onDown(MotionEvent e)554         public boolean onDown(MotionEvent e) {
555             // Check if the popup window is visible.
556             View popup = mIndicatorControlContainer.getActiveSettingPopup();
557             if (popup == null) return false;
558 
559 
560             // Let popup window, indicator control or preview frame handle the
561             // event by themselves. Dismiss the popup window if users touch on
562             // other areas.
563             if (!Util.pointInView(e.getX(), e.getY(), popup)
564                     && !Util.pointInView(e.getX(), e.getY(), mIndicatorControlContainer)
565                     && !Util.pointInView(e.getX(), e.getY(), mPreviewFrame)) {
566                 mIndicatorControlContainer.dismissSettingPopup();
567                 // Let event fall through.
568             }
569             return false;
570         }
571     }
572 
573     @Override
dispatchTouchEvent(MotionEvent m)574     public boolean dispatchTouchEvent(MotionEvent m) {
575         // Check if the popup window should be dismissed first.
576         if (mPopupGestureDetector != null && mPopupGestureDetector.onTouchEvent(m)) {
577             return true;
578         }
579 
580         return super.dispatchTouchEvent(m);
581     }
582 
583     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
584         @Override
585         public void onReceive(Context context, Intent intent) {
586             String action = intent.getAction();
587             Log.d(TAG, "Received intent action=" + action);
588             if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
589                     || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
590                     || action.equals(Intent.ACTION_MEDIA_CHECKING)) {
591                 checkStorage();
592             } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
593                 checkStorage();
594                 if (!mIsImageCaptureIntent) {
595                     updateThumbnailButton();
596                 }
597             }
598         }
599     };
600 
initOnScreenIndicator()601     private void initOnScreenIndicator() {
602         mGpsIndicator = (ImageView) findViewById(R.id.onscreen_gps_indicator);
603         mExposureIndicator = (TextView) findViewById(R.id.onscreen_exposure_indicator);
604         mFlashIndicator = (ImageView) findViewById(R.id.onscreen_flash_indicator);
605         mSceneIndicator = (ImageView) findViewById(R.id.onscreen_scene_indicator);
606         mWhiteBalanceIndicator =
607                 (ImageView) findViewById(R.id.onscreen_white_balance_indicator);
608         mFocusIndicator = (ImageView) findViewById(R.id.onscreen_focus_indicator);
609     }
610 
611     @Override
showGpsOnScreenIndicator(boolean hasSignal)612     public void showGpsOnScreenIndicator(boolean hasSignal) {
613         if (mGpsIndicator == null) {
614             return;
615         }
616         if (hasSignal) {
617             mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_on);
618         } else {
619             mGpsIndicator.setImageResource(R.drawable.ic_viewfinder_gps_no_signal);
620         }
621         mGpsIndicator.setVisibility(View.VISIBLE);
622     }
623 
624     @Override
hideGpsOnScreenIndicator()625     public void hideGpsOnScreenIndicator() {
626         if (mGpsIndicator == null) {
627             return;
628         }
629         mGpsIndicator.setVisibility(View.GONE);
630     }
631 
updateExposureOnScreenIndicator(int value)632     private void updateExposureOnScreenIndicator(int value) {
633         if (mExposureIndicator == null) {
634             return;
635         }
636         if (value == 0) {
637             mExposureIndicator.setText("");
638             mExposureIndicator.setVisibility(View.GONE);
639         } else {
640             float step = mParameters.getExposureCompensationStep();
641             mFormatterArgs[0] = value * step;
642             mBuilder.delete(0, mBuilder.length());
643             mFormatter.format("%+1.1f", mFormatterArgs);
644             String exposure = mFormatter.toString();
645             mExposureIndicator.setText(exposure);
646             mExposureIndicator.setVisibility(View.VISIBLE);
647         }
648     }
649 
updateFlashOnScreenIndicator(String value)650     private void updateFlashOnScreenIndicator(String value) {
651         if (mFlashIndicator == null) {
652             return;
653         }
654         if (Parameters.FLASH_MODE_AUTO.equals(value)) {
655             mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_auto);
656             mFlashIndicator.setVisibility(View.VISIBLE);
657         } else if (Parameters.FLASH_MODE_ON.equals(value)) {
658             mFlashIndicator.setImageResource(R.drawable.ic_indicators_landscape_flash_on);
659             mFlashIndicator.setVisibility(View.VISIBLE);
660         } else if (Parameters.FLASH_MODE_OFF.equals(value)) {
661             mFlashIndicator.setVisibility(View.GONE);
662         }
663     }
664 
updateSceneOnScreenIndicator(boolean isVisible)665     private void updateSceneOnScreenIndicator(boolean isVisible) {
666         if (mSceneIndicator == null) {
667             return;
668         }
669         mSceneIndicator.setVisibility(isVisible ? View.VISIBLE : View.GONE);
670     }
671 
updateWhiteBalanceOnScreenIndicator(String value)672     private void updateWhiteBalanceOnScreenIndicator(String value) {
673         if (mWhiteBalanceIndicator == null) {
674             return;
675         }
676         if (Parameters.WHITE_BALANCE_AUTO.equals(value)) {
677             mWhiteBalanceIndicator.setVisibility(View.GONE);
678         } else {
679             if (Parameters.WHITE_BALANCE_FLUORESCENT.equals(value)) {
680                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_fluorescent);
681             } else if (Parameters.WHITE_BALANCE_INCANDESCENT.equals(value)) {
682                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_incandescent);
683             } else if (Parameters.WHITE_BALANCE_DAYLIGHT.equals(value)) {
684                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_sunlight);
685             } else if (Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT.equals(value)) {
686                 mWhiteBalanceIndicator.setImageResource(R.drawable.ic_indicators_cloudy);
687             }
688             mWhiteBalanceIndicator.setVisibility(View.VISIBLE);
689         }
690     }
691 
updateFocusOnScreenIndicator(String value)692     private void updateFocusOnScreenIndicator(String value) {
693         if (mFocusIndicator == null) {
694             return;
695         }
696         if (Parameters.FOCUS_MODE_INFINITY.equals(value)) {
697             mFocusIndicator.setImageResource(R.drawable.ic_indicators_landscape);
698             mFocusIndicator.setVisibility(View.VISIBLE);
699         } else if (Parameters.FOCUS_MODE_MACRO.equals(value)) {
700             mFocusIndicator.setImageResource(R.drawable.ic_indicators_macro);
701             mFocusIndicator.setVisibility(View.VISIBLE);
702         } else {
703             mFocusIndicator.setVisibility(View.GONE);
704         }
705     }
706 
updateOnScreenIndicators()707     private void updateOnScreenIndicators() {
708         boolean isAutoScene = !(Parameters.SCENE_MODE_AUTO.equals(mParameters.getSceneMode()));
709         updateSceneOnScreenIndicator(isAutoScene);
710         updateExposureOnScreenIndicator(CameraSettings.readExposure(mPreferences));
711         updateFlashOnScreenIndicator(mParameters.getFlashMode());
712         updateWhiteBalanceOnScreenIndicator(mParameters.getWhiteBalance());
713         updateFocusOnScreenIndicator(mParameters.getFocusMode());
714     }
715     private final class ShutterCallback
716             implements android.hardware.Camera.ShutterCallback {
onShutter()717         public void onShutter() {
718             mShutterCallbackTime = System.currentTimeMillis();
719             mShutterLag = mShutterCallbackTime - mCaptureStartTime;
720             Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
721             mFocusManager.onShutter();
722         }
723     }
724 
725     private final class PostViewPictureCallback implements PictureCallback {
onPictureTaken( byte [] data, android.hardware.Camera camera)726         public void onPictureTaken(
727                 byte [] data, android.hardware.Camera camera) {
728             mPostViewPictureCallbackTime = System.currentTimeMillis();
729             Log.v(TAG, "mShutterToPostViewCallbackTime = "
730                     + (mPostViewPictureCallbackTime - mShutterCallbackTime)
731                     + "ms");
732         }
733     }
734 
735     private final class RawPictureCallback implements PictureCallback {
onPictureTaken( byte [] rawData, android.hardware.Camera camera)736         public void onPictureTaken(
737                 byte [] rawData, android.hardware.Camera camera) {
738             mRawPictureCallbackTime = System.currentTimeMillis();
739             Log.v(TAG, "mShutterToRawCallbackTime = "
740                     + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
741         }
742     }
743 
744     private final class JpegPictureCallback implements PictureCallback {
745         Location mLocation;
746 
JpegPictureCallback(Location loc)747         public JpegPictureCallback(Location loc) {
748             mLocation = loc;
749         }
750 
onPictureTaken( final byte [] jpegData, final android.hardware.Camera camera)751         public void onPictureTaken(
752                 final byte [] jpegData, final android.hardware.Camera camera) {
753             if (mPausing) {
754                 return;
755             }
756 
757             mJpegPictureCallbackTime = System.currentTimeMillis();
758             // If postview callback has arrived, the captured image is displayed
759             // in postview callback. If not, the captured image is displayed in
760             // raw picture callback.
761             if (mPostViewPictureCallbackTime != 0) {
762                 mShutterToPictureDisplayedTime =
763                         mPostViewPictureCallbackTime - mShutterCallbackTime;
764                 mPictureDisplayedToJpegCallbackTime =
765                         mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
766             } else {
767                 mShutterToPictureDisplayedTime =
768                         mRawPictureCallbackTime - mShutterCallbackTime;
769                 mPictureDisplayedToJpegCallbackTime =
770                         mJpegPictureCallbackTime - mRawPictureCallbackTime;
771             }
772             Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
773                     + mPictureDisplayedToJpegCallbackTime + "ms");
774 
775             if (!mIsImageCaptureIntent) {
776                 startPreview();
777                 startFaceDetection();
778             }
779 
780             if (!mIsImageCaptureIntent) {
781                 Size s = mParameters.getPictureSize();
782                 mImageSaver.addImage(jpegData, mLocation, s.width, s.height);
783             } else {
784                 mJpegImageData = jpegData;
785                 if (!mQuickCapture) {
786                     showPostCaptureAlert();
787                 } else {
788                     doAttach();
789                 }
790             }
791 
792             // Check this in advance of each shot so we don't add to shutter
793             // latency. It's true that someone else could write to the SD card in
794             // the mean time and fill it, but that could have happened between the
795             // shutter press and saving the JPEG too.
796             checkStorage();
797 
798             long now = System.currentTimeMillis();
799             mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
800             Log.v(TAG, "mJpegCallbackFinishTime = "
801                     + mJpegCallbackFinishTime + "ms");
802             mJpegPictureCallbackTime = 0;
803         }
804     }
805 
806     private final class AutoFocusCallback
807             implements android.hardware.Camera.AutoFocusCallback {
onAutoFocus( boolean focused, android.hardware.Camera camera)808         public void onAutoFocus(
809                 boolean focused, android.hardware.Camera camera) {
810             if (mPausing) return;
811 
812             mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
813             Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
814             setCameraState(IDLE);
815             mFocusManager.onAutoFocus(focused);
816         }
817     }
818 
819     private final class ZoomListener
820             implements android.hardware.Camera.OnZoomChangeListener {
821         @Override
onZoomChange( int value, boolean stopped, android.hardware.Camera camera)822         public void onZoomChange(
823                 int value, boolean stopped, android.hardware.Camera camera) {
824             Log.v(TAG, "Zoom changed: value=" + value + ". stopped=" + stopped);
825             mZoomValue = value;
826 
827             // Update the UI when we get zoom value.
828             mZoomControl.setZoomIndex(value);
829 
830             // Keep mParameters up to date. We do not getParameter again in
831             // takePicture. If we do not do this, wrong zoom value will be set.
832             mParameters.setZoom(value);
833 
834             if (stopped && mZoomState != ZOOM_STOPPED) {
835                 if (mTargetZoomValue != -1 && value != mTargetZoomValue) {
836                     mCameraDevice.startSmoothZoom(mTargetZoomValue);
837                     mZoomState = ZOOM_START;
838                 } else {
839                     mZoomState = ZOOM_STOPPED;
840                 }
841             }
842         }
843     }
844 
845     // Each SaveRequest remembers the data needed to save an image.
846     private static class SaveRequest {
847         byte[] data;
848         Location loc;
849         int width, height;
850         long dateTaken;
851         int previewWidth;
852     }
853 
854     // We use a queue to store the SaveRequests that have not been completed
855     // yet. The main thread puts the request into the queue. The saver thread
856     // gets it from the queue, does the work, and removes it from the queue.
857     //
858     // There are several cases the main thread needs to wait for the saver
859     // thread to finish all the work in the queue:
860     // (1) When the activity's onPause() is called, we need to finish all the
861     // work, so other programs (like Gallery) can see all the images.
862     // (2) When we need to show the SharePop, we need to finish all the work
863     // too, because we want to show the thumbnail of the last image taken.
864     //
865     // If the queue becomes too long, adding a new request will block the main
866     // thread until the queue length drops below the threshold (QUEUE_LIMIT).
867     // If we don't do this, we may face several problems: (1) We may OOM
868     // because we are holding all the jpeg data in memory. (2) We may ANR
869     // when we need to wait for saver thread finishing all the work (in
870     // onPause() or showSharePopup()) because the time to finishing a long queue
871     // of work may be too long.
872     private class ImageSaver extends Thread {
873         private static final int QUEUE_LIMIT = 3;
874 
875         private ArrayList<SaveRequest> mQueue;
876         private Thumbnail mPendingThumbnail;
877         private Object mUpdateThumbnailLock = new Object();
878         private boolean mStop;
879 
880         // Runs in main thread
ImageSaver()881         public ImageSaver() {
882             mQueue = new ArrayList<SaveRequest>();
883             start();
884         }
885 
886         // Runs in main thread
addImage(final byte[] data, Location loc, int width, int height)887         public void addImage(final byte[] data, Location loc, int width,
888                 int height) {
889             SaveRequest r = new SaveRequest();
890             r.data = data;
891             r.loc = (loc == null) ? null : new Location(loc);  // make a copy
892             r.width = width;
893             r.height = height;
894             r.dateTaken = System.currentTimeMillis();
895             if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
896                 r.previewWidth = mPreviewFrameLayout.getHeight();
897             } else {
898                 r.previewWidth = mPreviewFrameLayout.getWidth();
899             }
900             synchronized (this) {
901                 while (mQueue.size() >= QUEUE_LIMIT) {
902                     try {
903                         wait();
904                     } catch (InterruptedException ex) {
905                         // ignore.
906                     }
907                 }
908                 mQueue.add(r);
909                 notifyAll();  // Tell saver thread there is new work to do.
910             }
911         }
912 
913         // Runs in saver thread
914         @Override
run()915         public void run() {
916             while (true) {
917                 SaveRequest r;
918                 synchronized (this) {
919                     if (mQueue.isEmpty()) {
920                         notifyAll();  // notify main thread in waitDone
921 
922                         // Note that we can only stop after we saved all images
923                         // in the queue.
924                         if (mStop) break;
925 
926                         try {
927                             wait();
928                         } catch (InterruptedException ex) {
929                             // ignore.
930                         }
931                         continue;
932                     }
933                     r = mQueue.get(0);
934                 }
935                 storeImage(r.data, r.loc, r.width, r.height, r.dateTaken,
936                         r.previewWidth);
937                 synchronized(this) {
938                     mQueue.remove(0);
939                     notifyAll();  // the main thread may wait in addImage
940                 }
941             }
942         }
943 
944         // Runs in main thread
waitDone()945         public void waitDone() {
946             synchronized (this) {
947                 while (!mQueue.isEmpty()) {
948                     try {
949                         wait();
950                     } catch (InterruptedException ex) {
951                         // ignore.
952                     }
953                 }
954             }
955             updateThumbnail();
956         }
957 
958         // Runs in main thread
finish()959         public void finish() {
960             waitDone();
961             synchronized (this) {
962                 mStop = true;
963                 notifyAll();
964             }
965             try {
966                 join();
967             } catch (InterruptedException ex) {
968                 // ignore.
969             }
970         }
971 
972         // Runs in main thread (because we need to update mThumbnailView in the
973         // main thread)
updateThumbnail()974         public void updateThumbnail() {
975             Thumbnail t;
976             synchronized (mUpdateThumbnailLock) {
977                 mHandler.removeMessages(UPDATE_THUMBNAIL);
978                 t = mPendingThumbnail;
979                 mPendingThumbnail = null;
980             }
981 
982             if (t != null) {
983                 mThumbnail = t;
984                 mThumbnailView.setBitmap(mThumbnail.getBitmap());
985             }
986             // Share popup may still have the reference to the old thumbnail. Clear it.
987             mSharePopup = null;
988         }
989 
990         // Runs in saver thread
storeImage(final byte[] data, Location loc, int width, int height, long dateTaken, int previewWidth)991         private void storeImage(final byte[] data, Location loc, int width,
992                 int height, long dateTaken, int previewWidth) {
993             String title = Util.createJpegName(dateTaken);
994             int orientation = Exif.getOrientation(data);
995             Uri uri = Storage.addImage(mContentResolver, title, dateTaken,
996                     loc, orientation, data, width, height);
997             if (uri != null) {
998                 boolean needThumbnail;
999                 synchronized (this) {
1000                     // If the number of requests in the queue (include the
1001                     // current one) is greater than 1, we don't need to generate
1002                     // thumbnail for this image. Because we'll soon replace it
1003                     // with the thumbnail for some image later in the queue.
1004                     needThumbnail = (mQueue.size() <= 1);
1005                 }
1006                 if (needThumbnail) {
1007                     // Create a thumbnail whose width is equal or bigger than
1008                     // that of the preview.
1009                     int ratio = (int) Math.ceil((double) width / previewWidth);
1010                     int inSampleSize = Integer.highestOneBit(ratio);
1011                     Thumbnail t = Thumbnail.createThumbnail(
1012                                 data, orientation, inSampleSize, uri);
1013                     synchronized (mUpdateThumbnailLock) {
1014                         // We need to update the thumbnail in the main thread,
1015                         // so send a message to run updateThumbnail().
1016                         mPendingThumbnail = t;
1017                         mHandler.sendEmptyMessage(UPDATE_THUMBNAIL);
1018                     }
1019                 }
1020                 Util.broadcastNewPicture(Camera.this, uri);
1021             }
1022         }
1023     }
1024 
setCameraState(int state)1025     private void setCameraState(int state) {
1026         mCameraState = state;
1027         switch (state) {
1028             case SNAPSHOT_IN_PROGRESS:
1029             case FOCUSING:
1030                 enableCameraControls(false);
1031                 break;
1032             case IDLE:
1033             case PREVIEW_STOPPED:
1034                 enableCameraControls(true);
1035                 break;
1036         }
1037     }
1038 
1039     @Override
capture()1040     public boolean capture() {
1041         // If we are already in the middle of taking a snapshot then ignore.
1042         if (mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) {
1043             return false;
1044         }
1045         mCaptureStartTime = System.currentTimeMillis();
1046         mPostViewPictureCallbackTime = 0;
1047         mJpegImageData = null;
1048 
1049         // Set rotation and gps data.
1050         Util.setRotationParameter(mParameters, mCameraId, mOrientation);
1051         Location loc = mLocationManager.getCurrentLocation();
1052         Util.setGpsParameters(mParameters, loc);
1053         mCameraDevice.setParameters(mParameters);
1054 
1055         mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
1056                 mPostViewPictureCallback, new JpegPictureCallback(loc));
1057         mFaceDetectionStarted = false;
1058         setCameraState(SNAPSHOT_IN_PROGRESS);
1059         return true;
1060     }
1061 
1062     @Override
setFocusParameters()1063     public void setFocusParameters() {
1064         setCameraParameters(UPDATE_PARAM_PREFERENCE);
1065     }
1066 
1067     @Override
playSound(int soundId)1068     public void playSound(int soundId) {
1069         mCameraSound.play(soundId);
1070     }
1071 
saveDataToFile(String filePath, byte[] data)1072     private boolean saveDataToFile(String filePath, byte[] data) {
1073         FileOutputStream f = null;
1074         try {
1075             f = new FileOutputStream(filePath);
1076             f.write(data);
1077         } catch (IOException e) {
1078             return false;
1079         } finally {
1080             Util.closeSilently(f);
1081         }
1082         return true;
1083     }
1084 
getPreferredCameraId()1085     private void getPreferredCameraId() {
1086         mPreferences = new ComboPreferences(this);
1087         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
1088         mCameraId = CameraSettings.readPreferredCameraId(mPreferences);
1089 
1090         // Testing purpose. Launch a specific camera through the intent extras.
1091         int intentCameraId = Util.getCameraFacingIntentExtras(this);
1092         if (intentCameraId != -1) {
1093             mCameraId = intentCameraId;
1094         }
1095     }
1096 
1097     Thread mCameraOpenThread = new Thread(new Runnable() {
1098         public void run() {
1099             try {
1100                 mCameraDevice = Util.openCamera(Camera.this, mCameraId);
1101             } catch (CameraHardwareException e) {
1102                 mOpenCameraFail = true;
1103             } catch (CameraDisabledException e) {
1104                 mCameraDisabled = true;
1105             }
1106         }
1107     });
1108 
1109     Thread mCameraPreviewThread = new Thread(new Runnable() {
1110         public void run() {
1111             initializeCapabilities();
1112             startPreview();
1113         }
1114     });
1115 
1116     @Override
onCreate(Bundle icicle)1117     public void onCreate(Bundle icicle) {
1118         super.onCreate(icicle);
1119         getPreferredCameraId();
1120         String[] defaultFocusModes = getResources().getStringArray(
1121                 R.array.pref_camera_focusmode_default_array);
1122         mFocusManager = new FocusManager(mPreferences, defaultFocusModes);
1123 
1124         /*
1125          * To reduce startup time, we start the camera open and preview threads.
1126          * We make sure the preview is started at the end of onCreate.
1127          */
1128         mCameraOpenThread.start();
1129 
1130         mIsImageCaptureIntent = isImageCaptureIntent();
1131         setContentView(R.layout.camera);
1132         if (mIsImageCaptureIntent) {
1133             mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
1134             mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
1135             findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
1136         } else {
1137             mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
1138             mThumbnailView.enableFilter(false);
1139             mThumbnailView.setVisibility(View.VISIBLE);
1140         }
1141 
1142         mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
1143 
1144         mPreferences.setLocalId(this, mCameraId);
1145         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1146 
1147         mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
1148         mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
1149 
1150         // we need to reset exposure for the preview
1151         resetExposureCompensation();
1152 
1153         Util.enterLightsOutMode(getWindow());
1154 
1155         // don't set mSurfaceHolder here. We have it set ONLY within
1156         // surfaceChanged / surfaceDestroyed, other parts of the code
1157         // assume that when it is set, the surface is also set.
1158         SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
1159         SurfaceHolder holder = preview.getHolder();
1160         holder.addCallback(this);
1161         holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
1162 
1163         // Make sure camera device is opened.
1164         try {
1165             mCameraOpenThread.join();
1166             mCameraOpenThread = null;
1167             if (mOpenCameraFail) {
1168                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1169                 return;
1170             } else if (mCameraDisabled) {
1171                 Util.showErrorAndFinish(this, R.string.camera_disabled);
1172                 return;
1173             }
1174         } catch (InterruptedException ex) {
1175             // ignore
1176         }
1177         mCameraPreviewThread.start();
1178 
1179         if (mIsImageCaptureIntent) {
1180             setupCaptureParams();
1181         } else {
1182             mModePicker = (ModePicker) findViewById(R.id.mode_picker);
1183             mModePicker.setVisibility(View.VISIBLE);
1184             mModePicker.setOnModeChangeListener(this);
1185             mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
1186         }
1187 
1188         mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
1189         mOnScreenIndicators = (Rotatable) findViewById(R.id.on_screen_indicators);
1190         mLocationManager = new LocationManager(this, this);
1191 
1192         mBackCameraId = CameraHolder.instance().getBackCameraId();
1193         mFrontCameraId = CameraHolder.instance().getFrontCameraId();
1194 
1195         // Wait until the camera settings are retrieved.
1196         synchronized (mCameraPreviewThread) {
1197             try {
1198                 mCameraPreviewThread.wait();
1199             } catch (InterruptedException ex) {
1200                 // ignore
1201             }
1202         }
1203 
1204         // Do this after starting preview because it depends on camera
1205         // parameters.
1206         initializeIndicatorControl();
1207         mCameraSound = new MediaActionSound();
1208 
1209         // Make sure preview is started.
1210         try {
1211             mCameraPreviewThread.join();
1212         } catch (InterruptedException ex) {
1213             // ignore
1214         }
1215         mCameraPreviewThread = null;
1216     }
1217 
overrideCameraSettings(final String flashMode, final String whiteBalance, final String focusMode)1218     private void overrideCameraSettings(final String flashMode,
1219             final String whiteBalance, final String focusMode) {
1220         if (mIndicatorControlContainer != null) {
1221             mIndicatorControlContainer.overrideSettings(
1222                     CameraSettings.KEY_FLASH_MODE, flashMode,
1223                     CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1224                     CameraSettings.KEY_FOCUS_MODE, focusMode);
1225         }
1226     }
1227 
updateSceneModeUI()1228     private void updateSceneModeUI() {
1229         // If scene mode is set, we cannot set flash mode, white balance, and
1230         // focus mode, instead, we read it from driver
1231         if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1232             overrideCameraSettings(mParameters.getFlashMode(),
1233                     mParameters.getWhiteBalance(), mParameters.getFocusMode());
1234         } else {
1235             overrideCameraSettings(null, null, null);
1236         }
1237     }
1238 
loadCameraPreferences()1239     private void loadCameraPreferences() {
1240         CameraSettings settings = new CameraSettings(this, mInitialParams,
1241                 mCameraId, CameraHolder.instance().getCameraInfo());
1242         mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1243     }
1244 
initializeIndicatorControl()1245     private void initializeIndicatorControl() {
1246         // setting the indicator buttons.
1247         mIndicatorControlContainer =
1248                 (IndicatorControlContainer) findViewById(R.id.indicator_control);
1249         if (mIndicatorControlContainer == null) return;
1250         loadCameraPreferences();
1251         final String[] SETTING_KEYS = {
1252                 CameraSettings.KEY_FLASH_MODE,
1253                 CameraSettings.KEY_WHITE_BALANCE,
1254                 CameraSettings.KEY_EXPOSURE,
1255                 CameraSettings.KEY_SCENE_MODE};
1256         final String[] OTHER_SETTING_KEYS = {
1257                 CameraSettings.KEY_RECORD_LOCATION,
1258                 CameraSettings.KEY_PICTURE_SIZE,
1259                 CameraSettings.KEY_FOCUS_MODE};
1260 
1261         CameraPicker.setImageResourceId(R.drawable.ic_switch_photo_facing_holo_light);
1262         mIndicatorControlContainer.initialize(this, mPreferenceGroup,
1263                 mParameters.isZoomSupported(),
1264                 SETTING_KEYS, OTHER_SETTING_KEYS);
1265         updateSceneModeUI();
1266         mIndicatorControlContainer.setListener(this);
1267     }
1268 
collapseCameraControls()1269     private boolean collapseCameraControls() {
1270         if ((mIndicatorControlContainer != null)
1271                 && mIndicatorControlContainer.dismissSettingPopup()) {
1272             return true;
1273         }
1274         return false;
1275     }
1276 
enableCameraControls(boolean enable)1277     private void enableCameraControls(boolean enable) {
1278         if (mIndicatorControlContainer != null) {
1279             mIndicatorControlContainer.setEnabled(enable);
1280         }
1281         if (mModePicker != null) mModePicker.setEnabled(enable);
1282         if (mZoomControl != null) mZoomControl.setEnabled(enable);
1283         if (mThumbnailView != null) mThumbnailView.setEnabled(enable);
1284     }
1285 
1286     private class MyOrientationEventListener
1287             extends OrientationEventListener {
MyOrientationEventListener(Context context)1288         public MyOrientationEventListener(Context context) {
1289             super(context);
1290         }
1291 
1292         @Override
onOrientationChanged(int orientation)1293         public void onOrientationChanged(int orientation) {
1294             // We keep the last known orientation. So if the user first orient
1295             // the camera then point the camera to floor or sky, we still have
1296             // the correct orientation.
1297             if (orientation == ORIENTATION_UNKNOWN) return;
1298             mOrientation = Util.roundOrientation(orientation, mOrientation);
1299             // When the screen is unlocked, display rotation may change. Always
1300             // calculate the up-to-date orientationCompensation.
1301             int orientationCompensation = mOrientation
1302                     + Util.getDisplayRotation(Camera.this);
1303             if (mOrientationCompensation != orientationCompensation) {
1304                 mOrientationCompensation = orientationCompensation;
1305                 setOrientationIndicator(mOrientationCompensation);
1306             }
1307 
1308             // Show the toast after getting the first orientation changed.
1309             if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
1310                 mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
1311                 showTapToFocusToast();
1312             }
1313         }
1314     }
1315 
setOrientationIndicator(int orientation)1316     private void setOrientationIndicator(int orientation) {
1317         Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup,
1318                 mIndicatorControlContainer, mZoomControl, mFocusAreaIndicator, mFaceView,
1319                 mReviewCancelButton, mReviewDoneButton, mRotateDialog, mOnScreenIndicators};
1320         for (Rotatable indicator : indicators) {
1321             if (indicator != null) indicator.setOrientation(orientation);
1322         }
1323     }
1324 
1325     @Override
onStop()1326     public void onStop() {
1327         super.onStop();
1328         if (mMediaProviderClient != null) {
1329             mMediaProviderClient.release();
1330             mMediaProviderClient = null;
1331         }
1332     }
1333 
checkStorage()1334     private void checkStorage() {
1335         mPicturesRemaining = Storage.getAvailableSpace();
1336         if (mPicturesRemaining > Storage.LOW_STORAGE_THRESHOLD) {
1337             mPicturesRemaining = (mPicturesRemaining - Storage.LOW_STORAGE_THRESHOLD)
1338                     / Storage.PICTURE_SIZE;
1339         } else if (mPicturesRemaining > 0) {
1340             mPicturesRemaining = 0;
1341         }
1342 
1343         updateStorageHint();
1344     }
1345 
1346     @OnClickAttr
onThumbnailClicked(View v)1347     public void onThumbnailClicked(View v) {
1348         if (isCameraIdle() && mThumbnail != null) {
1349             showSharePopup();
1350         }
1351     }
1352 
1353     @OnClickAttr
onReviewRetakeClicked(View v)1354     public void onReviewRetakeClicked(View v) {
1355         hidePostCaptureAlert();
1356         startPreview();
1357         startFaceDetection();
1358     }
1359 
1360     @OnClickAttr
onReviewDoneClicked(View v)1361     public void onReviewDoneClicked(View v) {
1362         doAttach();
1363     }
1364 
1365     @OnClickAttr
onReviewCancelClicked(View v)1366     public void onReviewCancelClicked(View v) {
1367         doCancel();
1368     }
1369 
doAttach()1370     private void doAttach() {
1371         if (mPausing) {
1372             return;
1373         }
1374 
1375         byte[] data = mJpegImageData;
1376 
1377         if (mCropValue == null) {
1378             // First handle the no crop case -- just return the value.  If the
1379             // caller specifies a "save uri" then write the data to it's
1380             // stream. Otherwise, pass back a scaled down version of the bitmap
1381             // directly in the extras.
1382             if (mSaveUri != null) {
1383                 OutputStream outputStream = null;
1384                 try {
1385                     outputStream = mContentResolver.openOutputStream(mSaveUri);
1386                     outputStream.write(data);
1387                     outputStream.close();
1388 
1389                     setResultEx(RESULT_OK);
1390                     finish();
1391                 } catch (IOException ex) {
1392                     // ignore exception
1393                 } finally {
1394                     Util.closeSilently(outputStream);
1395                 }
1396             } else {
1397                 int orientation = Exif.getOrientation(data);
1398                 Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1399                 bitmap = Util.rotate(bitmap, orientation);
1400                 setResultEx(RESULT_OK,
1401                         new Intent("inline-data").putExtra("data", bitmap));
1402                 finish();
1403             }
1404         } else {
1405             // Save the image to a temp file and invoke the cropper
1406             Uri tempUri = null;
1407             FileOutputStream tempStream = null;
1408             try {
1409                 File path = getFileStreamPath(sTempCropFilename);
1410                 path.delete();
1411                 tempStream = openFileOutput(sTempCropFilename, 0);
1412                 tempStream.write(data);
1413                 tempStream.close();
1414                 tempUri = Uri.fromFile(path);
1415             } catch (FileNotFoundException ex) {
1416                 setResultEx(Activity.RESULT_CANCELED);
1417                 finish();
1418                 return;
1419             } catch (IOException ex) {
1420                 setResultEx(Activity.RESULT_CANCELED);
1421                 finish();
1422                 return;
1423             } finally {
1424                 Util.closeSilently(tempStream);
1425             }
1426 
1427             Bundle newExtras = new Bundle();
1428             if (mCropValue.equals("circle")) {
1429                 newExtras.putString("circleCrop", "true");
1430             }
1431             if (mSaveUri != null) {
1432                 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1433             } else {
1434                 newExtras.putBoolean("return-data", true);
1435             }
1436 
1437             Intent cropIntent = new Intent("com.android.camera.action.CROP");
1438 
1439             cropIntent.setData(tempUri);
1440             cropIntent.putExtras(newExtras);
1441 
1442             startActivityForResult(cropIntent, CROP_MSG);
1443         }
1444     }
1445 
doCancel()1446     private void doCancel() {
1447         setResultEx(RESULT_CANCELED, new Intent());
1448         finish();
1449     }
1450 
1451     @Override
onShutterButtonFocus(boolean pressed)1452     public void onShutterButtonFocus(boolean pressed) {
1453         if (mPausing || collapseCameraControls() || mCameraState == SNAPSHOT_IN_PROGRESS) return;
1454 
1455         // Do not do focus if there is not enough storage.
1456         if (pressed && !canTakePicture()) return;
1457 
1458         if (pressed) {
1459             mFocusManager.onShutterDown();
1460         } else {
1461             mFocusManager.onShutterUp();
1462         }
1463     }
1464 
1465     @Override
onShutterButtonClick()1466     public void onShutterButtonClick() {
1467         if (mPausing || collapseCameraControls()) return;
1468 
1469         // Do not take the picture if there is not enough storage.
1470         if (mPicturesRemaining <= 0) {
1471             Log.i(TAG, "Not enough space or storage not ready. remaining=" + mPicturesRemaining);
1472             return;
1473         }
1474 
1475         Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1476 
1477         // If the user wants to do a snapshot while the previous one is still
1478         // in progress, remember the fact and do it after we finish the previous
1479         // one and re-start the preview. Snapshot in progress also includes the
1480         // state that autofocus is focusing and a picture will be taken when
1481         // focus callback arrives.
1482         if (mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS) {
1483             mSnapshotOnIdle = true;
1484             return;
1485         }
1486 
1487         mSnapshotOnIdle = false;
1488         mFocusManager.doSnap();
1489     }
1490 
1491     private OnScreenHint mStorageHint;
1492 
updateStorageHint()1493     private void updateStorageHint() {
1494         String noStorageText = null;
1495 
1496         if (mPicturesRemaining == Storage.UNAVAILABLE) {
1497             noStorageText = getString(R.string.no_storage);
1498         } else if (mPicturesRemaining == Storage.PREPARING) {
1499             noStorageText = getString(R.string.preparing_sd);
1500         } else if (mPicturesRemaining == Storage.UNKNOWN_SIZE) {
1501             noStorageText = getString(R.string.access_sd_fail);
1502         } else if (mPicturesRemaining < 1L) {
1503             noStorageText = getString(R.string.not_enough_space);
1504         }
1505 
1506         if (noStorageText != null) {
1507             if (mStorageHint == null) {
1508                 mStorageHint = OnScreenHint.makeText(this, noStorageText);
1509             } else {
1510                 mStorageHint.setText(noStorageText);
1511             }
1512             mStorageHint.show();
1513         } else if (mStorageHint != null) {
1514             mStorageHint.cancel();
1515             mStorageHint = null;
1516         }
1517     }
1518 
installIntentFilter()1519     private void installIntentFilter() {
1520         // install an intent filter to receive SD card related events.
1521         IntentFilter intentFilter =
1522                 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1523         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1524         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1525         intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1526         intentFilter.addDataScheme("file");
1527         registerReceiver(mReceiver, intentFilter);
1528         mDidRegister = true;
1529     }
1530 
1531     @Override
doOnResume()1532     protected void doOnResume() {
1533         if (mOpenCameraFail || mCameraDisabled) return;
1534 
1535         mPausing = false;
1536         mJpegPictureCallbackTime = 0;
1537         mZoomValue = 0;
1538 
1539         // Start the preview if it is not started.
1540         if (mCameraState == PREVIEW_STOPPED) {
1541             try {
1542                 mCameraDevice = Util.openCamera(this, mCameraId);
1543                 initializeCapabilities();
1544                 resetExposureCompensation();
1545                 startPreview();
1546                 startFaceDetection();
1547             } catch (CameraHardwareException e) {
1548                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1549                 return;
1550             } catch (CameraDisabledException e) {
1551                 Util.showErrorAndFinish(this, R.string.camera_disabled);
1552                 return;
1553             }
1554         }
1555 
1556         if (mSurfaceHolder != null) {
1557             // If first time initialization is not finished, put it in the
1558             // message queue.
1559             if (!mFirstTimeInitialized) {
1560                 mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1561             } else {
1562                 initializeSecondTime();
1563             }
1564         }
1565         keepScreenOnAwhile();
1566 
1567         if (mCameraState == IDLE) {
1568             mOnResumeTime = SystemClock.uptimeMillis();
1569             mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
1570         }
1571         // Dismiss open menu if exists.
1572         PopupManager.getInstance(this).notifyShowPopup(null);
1573     }
1574 
1575     @Override
onPause()1576     protected void onPause() {
1577         mPausing = true;
1578         stopPreview();
1579         // Close the camera now because other activities may need to use it.
1580         closeCamera();
1581         if (mCameraSound != null) mCameraSound.release();
1582         resetScreenOn();
1583 
1584         // Clear UI.
1585         collapseCameraControls();
1586         if (mSharePopup != null) mSharePopup.dismiss();
1587         if (mFaceView != null) mFaceView.clear();
1588 
1589         if (mFirstTimeInitialized) {
1590             mOrientationListener.disable();
1591             if (mImageSaver != null) {
1592                 mImageSaver.finish();
1593                 mImageSaver = null;
1594             }
1595             if (!mIsImageCaptureIntent && mThumbnail != null && !mThumbnail.fromFile()) {
1596                 mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
1597             }
1598         }
1599 
1600         if (mDidRegister) {
1601             unregisterReceiver(mReceiver);
1602             mDidRegister = false;
1603         }
1604         if (mLocationManager != null) mLocationManager.recordLocation(false);
1605         updateExposureOnScreenIndicator(0);
1606 
1607         if (mStorageHint != null) {
1608             mStorageHint.cancel();
1609             mStorageHint = null;
1610         }
1611 
1612         // If we are in an image capture intent and has taken
1613         // a picture, we just clear it in onPause.
1614         mJpegImageData = null;
1615 
1616         // Remove the messages in the event queue.
1617         mHandler.removeMessages(FIRST_TIME_INIT);
1618         mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1619         mFocusManager.removeMessages();
1620 
1621         super.onPause();
1622     }
1623 
1624     @Override
onActivityResult( int requestCode, int resultCode, Intent data)1625     protected void onActivityResult(
1626             int requestCode, int resultCode, Intent data) {
1627         switch (requestCode) {
1628             case CROP_MSG: {
1629                 Intent intent = new Intent();
1630                 if (data != null) {
1631                     Bundle extras = data.getExtras();
1632                     if (extras != null) {
1633                         intent.putExtras(extras);
1634                     }
1635                 }
1636                 setResultEx(resultCode, intent);
1637                 finish();
1638 
1639                 File path = getFileStreamPath(sTempCropFilename);
1640                 path.delete();
1641 
1642                 break;
1643             }
1644         }
1645     }
1646 
canTakePicture()1647     private boolean canTakePicture() {
1648         return isCameraIdle() && (mPicturesRemaining > 0);
1649     }
1650 
1651     @Override
autoFocus()1652     public void autoFocus() {
1653         mFocusStartTime = System.currentTimeMillis();
1654         mCameraDevice.autoFocus(mAutoFocusCallback);
1655         setCameraState(FOCUSING);
1656     }
1657 
1658     @Override
cancelAutoFocus()1659     public void cancelAutoFocus() {
1660         mCameraDevice.cancelAutoFocus();
1661         setCameraState(IDLE);
1662         setCameraParameters(UPDATE_PARAM_PREFERENCE);
1663     }
1664 
1665     // Preview area is touched. Handle touch focus.
1666     @Override
onTouch(View v, MotionEvent e)1667     public boolean onTouch(View v, MotionEvent e) {
1668         if (mPausing || mCameraDevice == null || !mFirstTimeInitialized
1669                 || mCameraState == SNAPSHOT_IN_PROGRESS) {
1670             return false;
1671         }
1672 
1673         // Do not trigger touch focus if popup window is opened.
1674         if (collapseCameraControls()) return false;
1675 
1676         // Check if metering area or focus area is supported.
1677         if (!mFocusAreaSupported && !mMeteringAreaSupported) return false;
1678 
1679         return mFocusManager.onTouch(e);
1680     }
1681 
1682     @Override
onBackPressed()1683     public void onBackPressed() {
1684         if (!isCameraIdle()) {
1685             // ignore backs while we're taking a picture
1686             return;
1687         } else if (!collapseCameraControls()) {
1688             super.onBackPressed();
1689         }
1690     }
1691 
1692     @Override
onKeyDown(int keyCode, KeyEvent event)1693     public boolean onKeyDown(int keyCode, KeyEvent event) {
1694         switch (keyCode) {
1695             case KeyEvent.KEYCODE_FOCUS:
1696                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1697                     onShutterButtonFocus(true);
1698                 }
1699                 return true;
1700             case KeyEvent.KEYCODE_CAMERA:
1701                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1702                     onShutterButtonClick();
1703                 }
1704                 return true;
1705             case KeyEvent.KEYCODE_DPAD_CENTER:
1706                 // If we get a dpad center event without any focused view, move
1707                 // the focus to the shutter button and press it.
1708                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1709                     // Start auto-focus immediately to reduce shutter lag. After
1710                     // the shutter button gets the focus, onShutterButtonFocus()
1711                     // will be called again but it is fine.
1712                     if (collapseCameraControls()) return true;
1713                     onShutterButtonFocus(true);
1714                     if (mShutterButton.isInTouchMode()) {
1715                         mShutterButton.requestFocusFromTouch();
1716                     } else {
1717                         mShutterButton.requestFocus();
1718                     }
1719                     mShutterButton.setPressed(true);
1720                 }
1721                 return true;
1722         }
1723 
1724         return super.onKeyDown(keyCode, event);
1725     }
1726 
1727     @Override
onKeyUp(int keyCode, KeyEvent event)1728     public boolean onKeyUp(int keyCode, KeyEvent event) {
1729         switch (keyCode) {
1730             case KeyEvent.KEYCODE_FOCUS:
1731                 if (mFirstTimeInitialized) {
1732                     onShutterButtonFocus(false);
1733                 }
1734                 return true;
1735         }
1736         return super.onKeyUp(keyCode, event);
1737     }
1738 
surfaceChanged(SurfaceHolder holder, int format, int w, int h)1739     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1740         // Make sure we have a surface in the holder before proceeding.
1741         if (holder.getSurface() == null) {
1742             Log.d(TAG, "holder.getSurface() == null");
1743             return;
1744         }
1745 
1746         Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1747 
1748         // We need to save the holder for later use, even when the mCameraDevice
1749         // is null. This could happen if onResume() is invoked after this
1750         // function.
1751         mSurfaceHolder = holder;
1752 
1753         // The mCameraDevice will be null if it fails to connect to the camera
1754         // hardware. In this case we will show a dialog and then finish the
1755         // activity, so it's OK to ignore it.
1756         if (mCameraDevice == null) return;
1757 
1758         // Sometimes surfaceChanged is called after onPause or before onResume.
1759         // Ignore it.
1760         if (mPausing || isFinishing()) return;
1761 
1762         // Set preview display if the surface is being created. Preview was
1763         // already started. Also restart the preview if display rotation has
1764         // changed. Sometimes this happens when the device is held in portrait
1765         // and camera app is opened. Rotation animation takes some time and
1766         // display rotation in onCreate may not be what we want.
1767         if (mCameraState == PREVIEW_STOPPED) {
1768             startPreview();
1769             startFaceDetection();
1770         } else {
1771             if (Util.getDisplayRotation(this) != mDisplayRotation) {
1772                 setDisplayOrientation();
1773             }
1774             if (holder.isCreating()) {
1775                 // Set preview display if the surface is being created and preview
1776                 // was already started. That means preview display was set to null
1777                 // and we need to set it now.
1778                 setPreviewDisplay(holder);
1779             }
1780         }
1781 
1782         // If first time initialization is not finished, send a message to do
1783         // it later. We want to finish surfaceChanged as soon as possible to let
1784         // user see preview first.
1785         if (!mFirstTimeInitialized) {
1786             mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1787         } else {
1788             initializeSecondTime();
1789         }
1790     }
1791 
surfaceCreated(SurfaceHolder holder)1792     public void surfaceCreated(SurfaceHolder holder) {
1793     }
1794 
surfaceDestroyed(SurfaceHolder holder)1795     public void surfaceDestroyed(SurfaceHolder holder) {
1796         stopPreview();
1797         mSurfaceHolder = null;
1798     }
1799 
closeCamera()1800     private void closeCamera() {
1801         if (mCameraDevice != null) {
1802             CameraHolder.instance().release();
1803             mFaceDetectionStarted = false;
1804             mCameraDevice.setZoomChangeListener(null);
1805             mCameraDevice.setFaceDetectionListener(null);
1806             mCameraDevice.setErrorCallback(null);
1807             mCameraDevice = null;
1808             setCameraState(PREVIEW_STOPPED);
1809             mFocusManager.onCameraReleased();
1810         }
1811     }
1812 
setPreviewDisplay(SurfaceHolder holder)1813     private void setPreviewDisplay(SurfaceHolder holder) {
1814         try {
1815             mCameraDevice.setPreviewDisplay(holder);
1816         } catch (Throwable ex) {
1817             closeCamera();
1818             throw new RuntimeException("setPreviewDisplay failed", ex);
1819         }
1820     }
1821 
setDisplayOrientation()1822     private void setDisplayOrientation() {
1823         mDisplayRotation = Util.getDisplayRotation(this);
1824         mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
1825         mCameraDevice.setDisplayOrientation(mDisplayOrientation);
1826         if (mFaceView != null) {
1827             mFaceView.setDisplayOrientation(mDisplayOrientation);
1828         }
1829     }
1830 
startPreview()1831     private void startPreview() {
1832         if (mPausing || isFinishing()) return;
1833 
1834         mFocusManager.resetTouchFocus();
1835 
1836         mCameraDevice.setErrorCallback(mErrorCallback);
1837 
1838         // If we're previewing already, stop the preview first (this will blank
1839         // the screen).
1840         if (mCameraState != PREVIEW_STOPPED) stopPreview();
1841 
1842         setPreviewDisplay(mSurfaceHolder);
1843         setDisplayOrientation();
1844 
1845         if (!mSnapshotOnIdle) {
1846             // If the focus mode is continuous autofocus, call cancelAutoFocus to
1847             // resume it because it may have been paused by autoFocus call.
1848             if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
1849                 mCameraDevice.cancelAutoFocus();
1850             }
1851             mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1852         }
1853         setCameraParameters(UPDATE_PARAM_ALL);
1854 
1855         // Inform the mainthread to go on the UI initialization.
1856         if (mCameraPreviewThread != null) {
1857             synchronized (mCameraPreviewThread) {
1858                 mCameraPreviewThread.notify();
1859             }
1860         }
1861 
1862         try {
1863             Log.v(TAG, "startPreview");
1864             mCameraDevice.startPreview();
1865         } catch (Throwable ex) {
1866             closeCamera();
1867             throw new RuntimeException("startPreview failed", ex);
1868         }
1869 
1870         mZoomState = ZOOM_STOPPED;
1871         setCameraState(IDLE);
1872         mFocusManager.onPreviewStarted();
1873 
1874         if (mSnapshotOnIdle) {
1875             mHandler.post(mDoSnapRunnable);
1876         }
1877     }
1878 
stopPreview()1879     private void stopPreview() {
1880         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1881             Log.v(TAG, "stopPreview");
1882             mCameraDevice.cancelAutoFocus(); // Reset the focus.
1883             mCameraDevice.stopPreview();
1884             mFaceDetectionStarted = false;
1885         }
1886         setCameraState(PREVIEW_STOPPED);
1887         mFocusManager.onPreviewStopped();
1888     }
1889 
isSupported(String value, List<String> supported)1890     private static boolean isSupported(String value, List<String> supported) {
1891         return supported == null ? false : supported.indexOf(value) >= 0;
1892     }
1893 
updateCameraParametersInitialize()1894     private void updateCameraParametersInitialize() {
1895         // Reset preview frame rate to the maximum because it may be lowered by
1896         // video camera application.
1897         List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1898         if (frameRates != null) {
1899             Integer max = Collections.max(frameRates);
1900             mParameters.setPreviewFrameRate(max);
1901         }
1902 
1903         mParameters.setRecordingHint(false);
1904 
1905         // Disable video stabilization. Convenience methods not available in API
1906         // level <= 14
1907         String vstabSupported = mParameters.get("video-stabilization-supported");
1908         if ("true".equals(vstabSupported)) {
1909             mParameters.set("video-stabilization", "false");
1910         }
1911     }
1912 
updateCameraParametersZoom()1913     private void updateCameraParametersZoom() {
1914         // Set zoom.
1915         if (mParameters.isZoomSupported()) {
1916             mParameters.setZoom(mZoomValue);
1917         }
1918     }
1919 
updateCameraParametersPreference()1920     private void updateCameraParametersPreference() {
1921         if (mAeLockSupported) {
1922             mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
1923         }
1924 
1925         if (mAwbLockSupported) {
1926             mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1927         }
1928 
1929         if (mFocusAreaSupported) {
1930             mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1931         }
1932 
1933         if (mMeteringAreaSupported) {
1934             // Use the same area for focus and metering.
1935             mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1936         }
1937 
1938         // Set picture size.
1939         String pictureSize = mPreferences.getString(
1940                 CameraSettings.KEY_PICTURE_SIZE, null);
1941         if (pictureSize == null) {
1942             CameraSettings.initialCameraPictureSize(this, mParameters);
1943         } else {
1944             List<Size> supported = mParameters.getSupportedPictureSizes();
1945             CameraSettings.setCameraPictureSize(
1946                     pictureSize, supported, mParameters);
1947         }
1948 
1949         // Set the preview frame aspect ratio according to the picture size.
1950         Size size = mParameters.getPictureSize();
1951 
1952         mPreviewPanel = findViewById(R.id.frame_layout);
1953         mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
1954         mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
1955 
1956         // Set a preview size that is closest to the viewfinder height and has
1957         // the right aspect ratio.
1958         List<Size> sizes = mParameters.getSupportedPreviewSizes();
1959         Size optimalSize = Util.getOptimalPreviewSize(this,
1960                 sizes, (double) size.width / size.height);
1961         Size original = mParameters.getPreviewSize();
1962         if (!original.equals(optimalSize)) {
1963             mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1964 
1965             // Zoom related settings will be changed for different preview
1966             // sizes, so set and read the parameters to get lastest values
1967             mCameraDevice.setParameters(mParameters);
1968             mParameters = mCameraDevice.getParameters();
1969         }
1970         Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1971 
1972         // Since change scene mode may change supported values,
1973         // Set scene mode first,
1974         mSceneMode = mPreferences.getString(
1975                 CameraSettings.KEY_SCENE_MODE,
1976                 getString(R.string.pref_camera_scenemode_default));
1977         if (isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1978             if (!mParameters.getSceneMode().equals(mSceneMode)) {
1979                 mParameters.setSceneMode(mSceneMode);
1980                 mCameraDevice.setParameters(mParameters);
1981 
1982                 // Setting scene mode will change the settings of flash mode,
1983                 // white balance, and focus mode. Here we read back the
1984                 // parameters, so we can know those settings.
1985                 mParameters = mCameraDevice.getParameters();
1986             }
1987         } else {
1988             mSceneMode = mParameters.getSceneMode();
1989             if (mSceneMode == null) {
1990                 mSceneMode = Parameters.SCENE_MODE_AUTO;
1991             }
1992         }
1993 
1994         // Set JPEG quality.
1995         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
1996                 CameraProfile.QUALITY_HIGH);
1997         mParameters.setJpegQuality(jpegQuality);
1998 
1999         // For the following settings, we need to check if the settings are
2000         // still supported by latest driver, if not, ignore the settings.
2001 
2002         // Set exposure compensation
2003         int value = CameraSettings.readExposure(mPreferences);
2004         int max = mParameters.getMaxExposureCompensation();
2005         int min = mParameters.getMinExposureCompensation();
2006         if (value >= min && value <= max) {
2007             mParameters.setExposureCompensation(value);
2008         } else {
2009             Log.w(TAG, "invalid exposure range: " + value);
2010         }
2011 
2012         if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
2013             // Set flash mode.
2014             String flashMode = mPreferences.getString(
2015                     CameraSettings.KEY_FLASH_MODE,
2016                     getString(R.string.pref_camera_flashmode_default));
2017             List<String> supportedFlash = mParameters.getSupportedFlashModes();
2018             if (isSupported(flashMode, supportedFlash)) {
2019                 mParameters.setFlashMode(flashMode);
2020             } else {
2021                 flashMode = mParameters.getFlashMode();
2022                 if (flashMode == null) {
2023                     flashMode = getString(
2024                             R.string.pref_camera_flashmode_no_flash);
2025                 }
2026             }
2027 
2028             // Set white balance parameter.
2029             String whiteBalance = mPreferences.getString(
2030                     CameraSettings.KEY_WHITE_BALANCE,
2031                     getString(R.string.pref_camera_whitebalance_default));
2032             if (isSupported(whiteBalance,
2033                     mParameters.getSupportedWhiteBalance())) {
2034                 mParameters.setWhiteBalance(whiteBalance);
2035             } else {
2036                 whiteBalance = mParameters.getWhiteBalance();
2037                 if (whiteBalance == null) {
2038                     whiteBalance = Parameters.WHITE_BALANCE_AUTO;
2039                 }
2040             }
2041 
2042             // Set focus mode.
2043             mFocusManager.overrideFocusMode(null);
2044             mParameters.setFocusMode(mFocusManager.getFocusMode());
2045         } else {
2046             mFocusManager.overrideFocusMode(mParameters.getFocusMode());
2047         }
2048     }
2049 
2050     // We separate the parameters into several subsets, so we can update only
2051     // the subsets actually need updating. The PREFERENCE set needs extra
2052     // locking because the preference can be changed from GLThread as well.
setCameraParameters(int updateSet)2053     private void setCameraParameters(int updateSet) {
2054         mParameters = mCameraDevice.getParameters();
2055 
2056         if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2057             updateCameraParametersInitialize();
2058         }
2059 
2060         if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2061             updateCameraParametersZoom();
2062         }
2063 
2064         if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2065             updateCameraParametersPreference();
2066         }
2067 
2068         mCameraDevice.setParameters(mParameters);
2069     }
2070 
2071     // If the Camera is idle, update the parameters immediately, otherwise
2072     // accumulate them in mUpdateSet and update later.
setCameraParametersWhenIdle(int additionalUpdateSet)2073     private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2074         mUpdateSet |= additionalUpdateSet;
2075         if (mCameraDevice == null) {
2076             // We will update all the parameters when we open the device, so
2077             // we don't need to do anything now.
2078             mUpdateSet = 0;
2079             return;
2080         } else if (isCameraIdle()) {
2081             setCameraParameters(mUpdateSet);
2082             updateSceneModeUI();
2083             mUpdateSet = 0;
2084         } else {
2085             if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2086                 mHandler.sendEmptyMessageDelayed(
2087                         SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2088             }
2089         }
2090     }
2091 
gotoGallery()2092     private void gotoGallery() {
2093         MenuHelper.gotoCameraImageGallery(this);
2094     }
2095 
isCameraIdle()2096     private boolean isCameraIdle() {
2097         return (mCameraState == IDLE) || (mFocusManager.isFocusCompleted());
2098     }
2099 
isImageCaptureIntent()2100     private boolean isImageCaptureIntent() {
2101         String action = getIntent().getAction();
2102         return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
2103     }
2104 
setupCaptureParams()2105     private void setupCaptureParams() {
2106         Bundle myExtras = getIntent().getExtras();
2107         if (myExtras != null) {
2108             mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2109             mCropValue = myExtras.getString("crop");
2110         }
2111     }
2112 
showPostCaptureAlert()2113     private void showPostCaptureAlert() {
2114         if (mIsImageCaptureIntent) {
2115             Util.fadeOut(mIndicatorControlContainer);
2116             Util.fadeOut(mShutterButton);
2117 
2118             int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2119             for (int id : pickIds) {
2120                 Util.fadeIn(findViewById(id));
2121             }
2122         }
2123     }
2124 
hidePostCaptureAlert()2125     private void hidePostCaptureAlert() {
2126         if (mIsImageCaptureIntent) {
2127             int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2128             for (int id : pickIds) {
2129                 Util.fadeOut(findViewById(id));
2130             }
2131 
2132             Util.fadeIn(mShutterButton);
2133             Util.fadeIn(mIndicatorControlContainer);
2134         }
2135     }
2136 
2137     @Override
onPrepareOptionsMenu(Menu menu)2138     public boolean onPrepareOptionsMenu(Menu menu) {
2139         super.onPrepareOptionsMenu(menu);
2140         // Only show the menu when camera is idle.
2141         for (int i = 0; i < menu.size(); i++) {
2142             menu.getItem(i).setVisible(isCameraIdle());
2143         }
2144 
2145         return true;
2146     }
2147 
2148     @Override
onCreateOptionsMenu(Menu menu)2149     public boolean onCreateOptionsMenu(Menu menu) {
2150         super.onCreateOptionsMenu(menu);
2151 
2152         if (mIsImageCaptureIntent) {
2153             // No options menu for attach mode.
2154             return false;
2155         } else {
2156             addBaseMenuItems(menu);
2157         }
2158         return true;
2159     }
2160 
addBaseMenuItems(Menu menu)2161     private void addBaseMenuItems(Menu menu) {
2162         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_VIDEO, new Runnable() {
2163             public void run() {
2164                 switchToOtherMode(ModePicker.MODE_VIDEO);
2165             }
2166         });
2167         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
2168             public void run() {
2169                 switchToOtherMode(ModePicker.MODE_PANORAMA);
2170             }
2171         });
2172 
2173         if (mNumberOfCameras > 1) {
2174             menu.add(R.string.switch_camera_id)
2175                     .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2176                 public boolean onMenuItemClick(MenuItem item) {
2177                     CameraSettings.writePreferredCameraId(mPreferences,
2178                             (mCameraId + 1) % mNumberOfCameras);
2179                     onSharedPreferenceChanged();
2180                     return true;
2181                 }
2182             }).setIcon(android.R.drawable.ic_menu_camera);
2183         }
2184     }
2185 
switchToOtherMode(int mode)2186     private boolean switchToOtherMode(int mode) {
2187         if (isFinishing()) return false;
2188         if (mImageSaver != null) mImageSaver.waitDone();
2189         MenuHelper.gotoMode(mode, Camera.this);
2190         mHandler.removeMessages(FIRST_TIME_INIT);
2191         finish();
2192         return true;
2193     }
2194 
onModeChanged(int mode)2195     public boolean onModeChanged(int mode) {
2196         if (mode != ModePicker.MODE_CAMERA) {
2197             return switchToOtherMode(mode);
2198         } else {
2199             return true;
2200         }
2201     }
2202 
onSharedPreferenceChanged()2203     public void onSharedPreferenceChanged() {
2204         // ignore the events after "onPause()"
2205         if (mPausing) return;
2206 
2207         boolean recordLocation = RecordLocationPreference.get(
2208                 mPreferences, getContentResolver());
2209         mLocationManager.recordLocation(recordLocation);
2210 
2211         int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
2212         if (mCameraId != cameraId) {
2213             // Restart the activity to have a crossfade animation.
2214             // TODO: Use SurfaceTexture to implement a better and faster
2215             // animation.
2216             if (mIsImageCaptureIntent) {
2217                 // If the intent is camera capture, stay in camera capture mode.
2218                 MenuHelper.gotoCameraMode(this, getIntent());
2219             } else {
2220                 MenuHelper.gotoCameraMode(this);
2221             }
2222 
2223             finish();
2224         } else {
2225             setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2226         }
2227 
2228         updateOnScreenIndicators();
2229     }
2230 
2231     @Override
onUserInteraction()2232     public void onUserInteraction() {
2233         super.onUserInteraction();
2234         keepScreenOnAwhile();
2235     }
2236 
resetScreenOn()2237     private void resetScreenOn() {
2238         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2239         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2240     }
2241 
keepScreenOnAwhile()2242     private void keepScreenOnAwhile() {
2243         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2244         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2245         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2246     }
2247 
onRestorePreferencesClicked()2248     public void onRestorePreferencesClicked() {
2249         if (mPausing) return;
2250         Runnable runnable = new Runnable() {
2251             public void run() {
2252                 restorePreferences();
2253             }
2254         };
2255         mRotateDialog.showAlertDialog(
2256                 getString(R.string.confirm_restore_title),
2257                 getString(R.string.confirm_restore_message),
2258                 getString(android.R.string.ok), runnable,
2259                 getString(android.R.string.cancel), null);
2260     }
2261 
restorePreferences()2262     private void restorePreferences() {
2263         // Reset the zoom. Zoom value is not stored in preference.
2264         if (mParameters.isZoomSupported()) {
2265             mZoomValue = 0;
2266             setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2267             mZoomControl.setZoomIndex(0);
2268         }
2269         if (mIndicatorControlContainer != null) {
2270             mIndicatorControlContainer.dismissSettingPopup();
2271             CameraSettings.restorePreferences(Camera.this, mPreferences,
2272                     mParameters);
2273             mIndicatorControlContainer.reloadPreferences();
2274             onSharedPreferenceChanged();
2275         }
2276     }
2277 
onOverriddenPreferencesClicked()2278     public void onOverriddenPreferencesClicked() {
2279         if (mPausing) return;
2280         if (mNotSelectableToast == null) {
2281             String str = getResources().getString(R.string.not_selectable_in_scene_mode);
2282             mNotSelectableToast = Toast.makeText(Camera.this, str, Toast.LENGTH_SHORT);
2283         }
2284         mNotSelectableToast.show();
2285     }
2286 
showSharePopup()2287     private void showSharePopup() {
2288         mImageSaver.waitDone();
2289         Uri uri = mThumbnail.getUri();
2290         if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
2291             // SharePopup window takes the mPreviewPanel as its size reference.
2292             mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
2293                     mOrientationCompensation, mPreviewPanel);
2294         }
2295         mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
2296     }
2297 
2298     @Override
onFaceDetection(Face[] faces, android.hardware.Camera camera)2299     public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
2300         mFaceView.setFaces(faces);
2301     }
2302 
showTapToFocusToast()2303     private void showTapToFocusToast() {
2304         new RotateTextToast(this, R.string.tap_to_focus, mOrientation).show();
2305         // Clear the preference.
2306         Editor editor = mPreferences.edit();
2307         editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
2308         editor.apply();
2309     }
2310 
initializeCapabilities()2311     private void initializeCapabilities() {
2312         mInitialParams = mCameraDevice.getParameters();
2313         mFocusManager.initializeParameters(mInitialParams);
2314         mFocusAreaSupported = (mInitialParams.getMaxNumFocusAreas() > 0
2315                 && isSupported(Parameters.FOCUS_MODE_AUTO,
2316                         mInitialParams.getSupportedFocusModes()));
2317         mMeteringAreaSupported = (mInitialParams.getMaxNumMeteringAreas() > 0);
2318         mAeLockSupported = mInitialParams.isAutoExposureLockSupported();
2319         mAwbLockSupported = mInitialParams.isAutoWhiteBalanceLockSupported();
2320     }
2321 }
2322