• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
1089         mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
1090         int attemptedCameraId = CameraSettings.readPreferredCameraId(mPreferences);
1091 
1092         // It is possible that the user can connect/disconnect cameras
1093         // between device boots.
1094         // We need to check that the preferred camera ID
1095         // does not refer to a disconnected camera.
1096         if (attemptedCameraId >= mNumberOfCameras) {
1097             Log.v(TAG, "Preferred camera (id= " + attemptedCameraId +
1098                        ") missing. Defaulting to the first one");
1099             mCameraId = 0;
1100         } else {
1101             mCameraId = attemptedCameraId;
1102         }
1103 
1104         // Testing purpose. Launch a specific camera through the intent extras.
1105         int intentCameraId = Util.getCameraFacingIntentExtras(this);
1106         if (intentCameraId != -1) {
1107             mCameraId = intentCameraId;
1108         }
1109     }
1110 
1111     Thread mCameraOpenThread = new Thread(new Runnable() {
1112         public void run() {
1113             try {
1114                 mCameraDevice = Util.openCamera(Camera.this, mCameraId);
1115             } catch (CameraHardwareException e) {
1116                 mOpenCameraFail = true;
1117             } catch (CameraDisabledException e) {
1118                 mCameraDisabled = true;
1119             }
1120         }
1121     });
1122 
1123     Thread mCameraPreviewThread = new Thread(new Runnable() {
1124         public void run() {
1125             initializeCapabilities();
1126             startPreview();
1127         }
1128     });
1129 
1130     @Override
onCreate(Bundle icicle)1131     public void onCreate(Bundle icicle) {
1132         super.onCreate(icicle);
1133         getPreferredCameraId();
1134         String[] defaultFocusModes = getResources().getStringArray(
1135                 R.array.pref_camera_focusmode_default_array);
1136         mFocusManager = new FocusManager(mPreferences, defaultFocusModes);
1137 
1138         /*
1139          * To reduce startup time, we start the camera open and preview threads.
1140          * We make sure the preview is started at the end of onCreate.
1141          */
1142         mCameraOpenThread.start();
1143 
1144         mIsImageCaptureIntent = isImageCaptureIntent();
1145         setContentView(R.layout.camera);
1146         if (mIsImageCaptureIntent) {
1147             mReviewDoneButton = (Rotatable) findViewById(R.id.btn_done);
1148             mReviewCancelButton = (Rotatable) findViewById(R.id.btn_cancel);
1149             findViewById(R.id.btn_cancel).setVisibility(View.VISIBLE);
1150         } else {
1151             mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail);
1152             mThumbnailView.enableFilter(false);
1153             mThumbnailView.setVisibility(View.VISIBLE);
1154         }
1155 
1156         mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog);
1157 
1158         mPreferences.setLocalId(this, mCameraId);
1159         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
1160 
1161         mQuickCapture = getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
1162 
1163         // we need to reset exposure for the preview
1164         resetExposureCompensation();
1165 
1166         Util.enterLightsOutMode(getWindow());
1167 
1168         // don't set mSurfaceHolder here. We have it set ONLY within
1169         // surfaceChanged / surfaceDestroyed, other parts of the code
1170         // assume that when it is set, the surface is also set.
1171         SurfaceView preview = (SurfaceView) findViewById(R.id.camera_preview);
1172         SurfaceHolder holder = preview.getHolder();
1173         holder.addCallback(this);
1174         holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
1175 
1176         // Make sure camera device is opened.
1177         try {
1178             mCameraOpenThread.join();
1179             mCameraOpenThread = null;
1180             if (mOpenCameraFail) {
1181                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1182                 return;
1183             } else if (mCameraDisabled) {
1184                 Util.showErrorAndFinish(this, R.string.camera_disabled);
1185                 return;
1186             }
1187         } catch (InterruptedException ex) {
1188             // ignore
1189         }
1190         mCameraPreviewThread.start();
1191 
1192         if (mIsImageCaptureIntent) {
1193             setupCaptureParams();
1194         } else {
1195             mModePicker = (ModePicker) findViewById(R.id.mode_picker);
1196             mModePicker.setVisibility(View.VISIBLE);
1197             mModePicker.setOnModeChangeListener(this);
1198             mModePicker.setCurrentMode(ModePicker.MODE_CAMERA);
1199         }
1200 
1201         mZoomControl = (ZoomControl) findViewById(R.id.zoom_control);
1202         mOnScreenIndicators = (Rotatable) findViewById(R.id.on_screen_indicators);
1203         mLocationManager = new LocationManager(this, this);
1204 
1205         mBackCameraId = CameraHolder.instance().getBackCameraId();
1206         mFrontCameraId = CameraHolder.instance().getFrontCameraId();
1207 
1208         // Wait until the camera settings are retrieved.
1209         synchronized (mCameraPreviewThread) {
1210             try {
1211                 mCameraPreviewThread.wait();
1212             } catch (InterruptedException ex) {
1213                 // ignore
1214             }
1215         }
1216 
1217         // Do this after starting preview because it depends on camera
1218         // parameters.
1219         initializeIndicatorControl();
1220         mCameraSound = new MediaActionSound();
1221 
1222         // Make sure preview is started.
1223         try {
1224             mCameraPreviewThread.join();
1225         } catch (InterruptedException ex) {
1226             // ignore
1227         }
1228         mCameraPreviewThread = null;
1229     }
1230 
overrideCameraSettings(final String flashMode, final String whiteBalance, final String focusMode)1231     private void overrideCameraSettings(final String flashMode,
1232             final String whiteBalance, final String focusMode) {
1233         if (mIndicatorControlContainer != null) {
1234             mIndicatorControlContainer.overrideSettings(
1235                     CameraSettings.KEY_FLASH_MODE, flashMode,
1236                     CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
1237                     CameraSettings.KEY_FOCUS_MODE, focusMode);
1238         }
1239     }
1240 
updateSceneModeUI()1241     private void updateSceneModeUI() {
1242         // If scene mode is set, we cannot set flash mode, white balance, and
1243         // focus mode, instead, we read it from driver
1244         if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
1245             overrideCameraSettings(mParameters.getFlashMode(),
1246                     mParameters.getWhiteBalance(), mParameters.getFocusMode());
1247         } else {
1248             overrideCameraSettings(null, null, null);
1249         }
1250     }
1251 
loadCameraPreferences()1252     private void loadCameraPreferences() {
1253         CameraSettings settings = new CameraSettings(this, mInitialParams,
1254                 mCameraId, CameraHolder.instance().getCameraInfo());
1255         mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
1256     }
1257 
initializeIndicatorControl()1258     private void initializeIndicatorControl() {
1259         // setting the indicator buttons.
1260         mIndicatorControlContainer =
1261                 (IndicatorControlContainer) findViewById(R.id.indicator_control);
1262         if (mIndicatorControlContainer == null) return;
1263         loadCameraPreferences();
1264         final String[] SETTING_KEYS = {
1265                 CameraSettings.KEY_FLASH_MODE,
1266                 CameraSettings.KEY_WHITE_BALANCE,
1267                 CameraSettings.KEY_EXPOSURE,
1268                 CameraSettings.KEY_SCENE_MODE};
1269         final String[] OTHER_SETTING_KEYS = {
1270                 CameraSettings.KEY_RECORD_LOCATION,
1271                 CameraSettings.KEY_PICTURE_SIZE,
1272                 CameraSettings.KEY_FOCUS_MODE};
1273 
1274         CameraPicker.setImageResourceId(R.drawable.ic_switch_photo_facing_holo_light);
1275         mIndicatorControlContainer.initialize(this, mPreferenceGroup,
1276                 mParameters.isZoomSupported(),
1277                 SETTING_KEYS, OTHER_SETTING_KEYS);
1278         updateSceneModeUI();
1279         mIndicatorControlContainer.setListener(this);
1280     }
1281 
collapseCameraControls()1282     private boolean collapseCameraControls() {
1283         if ((mIndicatorControlContainer != null)
1284                 && mIndicatorControlContainer.dismissSettingPopup()) {
1285             return true;
1286         }
1287         return false;
1288     }
1289 
enableCameraControls(boolean enable)1290     private void enableCameraControls(boolean enable) {
1291         if (mIndicatorControlContainer != null) {
1292             mIndicatorControlContainer.setEnabled(enable);
1293         }
1294         if (mModePicker != null) mModePicker.setEnabled(enable);
1295         if (mZoomControl != null) mZoomControl.setEnabled(enable);
1296         if (mThumbnailView != null) mThumbnailView.setEnabled(enable);
1297     }
1298 
1299     private class MyOrientationEventListener
1300             extends OrientationEventListener {
MyOrientationEventListener(Context context)1301         public MyOrientationEventListener(Context context) {
1302             super(context);
1303         }
1304 
1305         @Override
onOrientationChanged(int orientation)1306         public void onOrientationChanged(int orientation) {
1307             // We keep the last known orientation. So if the user first orient
1308             // the camera then point the camera to floor or sky, we still have
1309             // the correct orientation.
1310             if (orientation == ORIENTATION_UNKNOWN) return;
1311             mOrientation = Util.roundOrientation(orientation, mOrientation);
1312             // When the screen is unlocked, display rotation may change. Always
1313             // calculate the up-to-date orientationCompensation.
1314             int orientationCompensation = mOrientation
1315                     + Util.getDisplayRotation(Camera.this);
1316             if (mOrientationCompensation != orientationCompensation) {
1317                 mOrientationCompensation = orientationCompensation;
1318                 setOrientationIndicator(mOrientationCompensation);
1319             }
1320 
1321             // Show the toast after getting the first orientation changed.
1322             if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
1323                 mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
1324                 showTapToFocusToast();
1325             }
1326         }
1327     }
1328 
setOrientationIndicator(int orientation)1329     private void setOrientationIndicator(int orientation) {
1330         Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup,
1331                 mIndicatorControlContainer, mZoomControl, mFocusAreaIndicator, mFaceView,
1332                 mReviewCancelButton, mReviewDoneButton, mRotateDialog, mOnScreenIndicators};
1333         for (Rotatable indicator : indicators) {
1334             if (indicator != null) indicator.setOrientation(orientation);
1335         }
1336     }
1337 
1338     @Override
onStop()1339     public void onStop() {
1340         super.onStop();
1341         if (mMediaProviderClient != null) {
1342             mMediaProviderClient.release();
1343             mMediaProviderClient = null;
1344         }
1345     }
1346 
checkStorage()1347     private void checkStorage() {
1348         mPicturesRemaining = Storage.getAvailableSpace();
1349         if (mPicturesRemaining > Storage.LOW_STORAGE_THRESHOLD) {
1350             mPicturesRemaining = (mPicturesRemaining - Storage.LOW_STORAGE_THRESHOLD)
1351                     / Storage.PICTURE_SIZE;
1352         } else if (mPicturesRemaining > 0) {
1353             mPicturesRemaining = 0;
1354         }
1355 
1356         updateStorageHint();
1357     }
1358 
1359     @OnClickAttr
onThumbnailClicked(View v)1360     public void onThumbnailClicked(View v) {
1361         if (isCameraIdle() && mThumbnail != null) {
1362             showSharePopup();
1363         }
1364     }
1365 
1366     @OnClickAttr
onReviewRetakeClicked(View v)1367     public void onReviewRetakeClicked(View v) {
1368         hidePostCaptureAlert();
1369         startPreview();
1370         startFaceDetection();
1371     }
1372 
1373     @OnClickAttr
onReviewDoneClicked(View v)1374     public void onReviewDoneClicked(View v) {
1375         doAttach();
1376     }
1377 
1378     @OnClickAttr
onReviewCancelClicked(View v)1379     public void onReviewCancelClicked(View v) {
1380         doCancel();
1381     }
1382 
doAttach()1383     private void doAttach() {
1384         if (mPausing) {
1385             return;
1386         }
1387 
1388         byte[] data = mJpegImageData;
1389 
1390         if (mCropValue == null) {
1391             // First handle the no crop case -- just return the value.  If the
1392             // caller specifies a "save uri" then write the data to it's
1393             // stream. Otherwise, pass back a scaled down version of the bitmap
1394             // directly in the extras.
1395             if (mSaveUri != null) {
1396                 OutputStream outputStream = null;
1397                 try {
1398                     outputStream = mContentResolver.openOutputStream(mSaveUri);
1399                     outputStream.write(data);
1400                     outputStream.close();
1401 
1402                     setResultEx(RESULT_OK);
1403                     finish();
1404                 } catch (IOException ex) {
1405                     // ignore exception
1406                 } finally {
1407                     Util.closeSilently(outputStream);
1408                 }
1409             } else {
1410                 int orientation = Exif.getOrientation(data);
1411                 Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
1412                 bitmap = Util.rotate(bitmap, orientation);
1413                 setResultEx(RESULT_OK,
1414                         new Intent("inline-data").putExtra("data", bitmap));
1415                 finish();
1416             }
1417         } else {
1418             // Save the image to a temp file and invoke the cropper
1419             Uri tempUri = null;
1420             FileOutputStream tempStream = null;
1421             try {
1422                 File path = getFileStreamPath(sTempCropFilename);
1423                 path.delete();
1424                 tempStream = openFileOutput(sTempCropFilename, 0);
1425                 tempStream.write(data);
1426                 tempStream.close();
1427                 tempUri = Uri.fromFile(path);
1428             } catch (FileNotFoundException ex) {
1429                 setResultEx(Activity.RESULT_CANCELED);
1430                 finish();
1431                 return;
1432             } catch (IOException ex) {
1433                 setResultEx(Activity.RESULT_CANCELED);
1434                 finish();
1435                 return;
1436             } finally {
1437                 Util.closeSilently(tempStream);
1438             }
1439 
1440             Bundle newExtras = new Bundle();
1441             if (mCropValue.equals("circle")) {
1442                 newExtras.putString("circleCrop", "true");
1443             }
1444             if (mSaveUri != null) {
1445                 newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
1446             } else {
1447                 newExtras.putBoolean("return-data", true);
1448             }
1449 
1450             Intent cropIntent = new Intent("com.android.camera.action.CROP");
1451 
1452             cropIntent.setData(tempUri);
1453             cropIntent.putExtras(newExtras);
1454 
1455             startActivityForResult(cropIntent, CROP_MSG);
1456         }
1457     }
1458 
doCancel()1459     private void doCancel() {
1460         setResultEx(RESULT_CANCELED, new Intent());
1461         finish();
1462     }
1463 
1464     @Override
onShutterButtonFocus(boolean pressed)1465     public void onShutterButtonFocus(boolean pressed) {
1466         if (mPausing || collapseCameraControls() || mCameraState == SNAPSHOT_IN_PROGRESS) return;
1467 
1468         // Do not do focus if there is not enough storage.
1469         if (pressed && !canTakePicture()) return;
1470 
1471         if (pressed) {
1472             mFocusManager.onShutterDown();
1473         } else {
1474             mFocusManager.onShutterUp();
1475         }
1476     }
1477 
1478     @Override
onShutterButtonClick()1479     public void onShutterButtonClick() {
1480         if (mPausing || collapseCameraControls()) return;
1481 
1482         // Do not take the picture if there is not enough storage.
1483         if (mPicturesRemaining <= 0) {
1484             Log.i(TAG, "Not enough space or storage not ready. remaining=" + mPicturesRemaining);
1485             return;
1486         }
1487 
1488         Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
1489 
1490         // If the user wants to do a snapshot while the previous one is still
1491         // in progress, remember the fact and do it after we finish the previous
1492         // one and re-start the preview. Snapshot in progress also includes the
1493         // state that autofocus is focusing and a picture will be taken when
1494         // focus callback arrives.
1495         if (mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS) {
1496             mSnapshotOnIdle = true;
1497             return;
1498         }
1499 
1500         mSnapshotOnIdle = false;
1501         mFocusManager.doSnap();
1502     }
1503 
1504     private OnScreenHint mStorageHint;
1505 
updateStorageHint()1506     private void updateStorageHint() {
1507         String noStorageText = null;
1508 
1509         if (mPicturesRemaining == Storage.UNAVAILABLE) {
1510             noStorageText = getString(R.string.no_storage);
1511         } else if (mPicturesRemaining == Storage.PREPARING) {
1512             noStorageText = getString(R.string.preparing_sd);
1513         } else if (mPicturesRemaining == Storage.UNKNOWN_SIZE) {
1514             noStorageText = getString(R.string.access_sd_fail);
1515         } else if (mPicturesRemaining < 1L) {
1516             noStorageText = getString(R.string.not_enough_space);
1517         }
1518 
1519         if (noStorageText != null) {
1520             if (mStorageHint == null) {
1521                 mStorageHint = OnScreenHint.makeText(this, noStorageText);
1522             } else {
1523                 mStorageHint.setText(noStorageText);
1524             }
1525             mStorageHint.show();
1526         } else if (mStorageHint != null) {
1527             mStorageHint.cancel();
1528             mStorageHint = null;
1529         }
1530     }
1531 
installIntentFilter()1532     private void installIntentFilter() {
1533         // install an intent filter to receive SD card related events.
1534         IntentFilter intentFilter =
1535                 new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
1536         intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
1537         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
1538         intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
1539         intentFilter.addDataScheme("file");
1540         registerReceiver(mReceiver, intentFilter);
1541         mDidRegister = true;
1542     }
1543 
1544     @Override
doOnResume()1545     protected void doOnResume() {
1546         if (mOpenCameraFail || mCameraDisabled) return;
1547 
1548         mPausing = false;
1549         mJpegPictureCallbackTime = 0;
1550         mZoomValue = 0;
1551 
1552         // Start the preview if it is not started.
1553         if (mCameraState == PREVIEW_STOPPED) {
1554             try {
1555                 mCameraDevice = Util.openCamera(this, mCameraId);
1556                 initializeCapabilities();
1557                 resetExposureCompensation();
1558                 startPreview();
1559                 startFaceDetection();
1560             } catch (CameraHardwareException e) {
1561                 Util.showErrorAndFinish(this, R.string.cannot_connect_camera);
1562                 return;
1563             } catch (CameraDisabledException e) {
1564                 Util.showErrorAndFinish(this, R.string.camera_disabled);
1565                 return;
1566             }
1567         }
1568 
1569         if (mSurfaceHolder != null) {
1570             // If first time initialization is not finished, put it in the
1571             // message queue.
1572             if (!mFirstTimeInitialized) {
1573                 mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1574             } else {
1575                 initializeSecondTime();
1576             }
1577         }
1578         keepScreenOnAwhile();
1579 
1580         if (mCameraState == IDLE) {
1581             mOnResumeTime = SystemClock.uptimeMillis();
1582             mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
1583         }
1584         // Dismiss open menu if exists.
1585         PopupManager.getInstance(this).notifyShowPopup(null);
1586     }
1587 
1588     @Override
onPause()1589     protected void onPause() {
1590         mPausing = true;
1591         stopPreview();
1592         // Close the camera now because other activities may need to use it.
1593         closeCamera();
1594         if (mCameraSound != null) mCameraSound.release();
1595         resetScreenOn();
1596 
1597         // Clear UI.
1598         collapseCameraControls();
1599         if (mSharePopup != null) mSharePopup.dismiss();
1600         if (mFaceView != null) mFaceView.clear();
1601 
1602         if (mFirstTimeInitialized) {
1603             mOrientationListener.disable();
1604             if (mImageSaver != null) {
1605                 mImageSaver.finish();
1606                 mImageSaver = null;
1607             }
1608             if (!mIsImageCaptureIntent && mThumbnail != null && !mThumbnail.fromFile()) {
1609                 mThumbnail.saveTo(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME));
1610             }
1611         }
1612 
1613         if (mDidRegister) {
1614             unregisterReceiver(mReceiver);
1615             mDidRegister = false;
1616         }
1617         if (mLocationManager != null) mLocationManager.recordLocation(false);
1618         updateExposureOnScreenIndicator(0);
1619 
1620         if (mStorageHint != null) {
1621             mStorageHint.cancel();
1622             mStorageHint = null;
1623         }
1624 
1625         // If we are in an image capture intent and has taken
1626         // a picture, we just clear it in onPause.
1627         mJpegImageData = null;
1628 
1629         // Remove the messages in the event queue.
1630         mHandler.removeMessages(FIRST_TIME_INIT);
1631         mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
1632         mFocusManager.removeMessages();
1633 
1634         super.onPause();
1635     }
1636 
1637     @Override
onActivityResult( int requestCode, int resultCode, Intent data)1638     protected void onActivityResult(
1639             int requestCode, int resultCode, Intent data) {
1640         switch (requestCode) {
1641             case CROP_MSG: {
1642                 Intent intent = new Intent();
1643                 if (data != null) {
1644                     Bundle extras = data.getExtras();
1645                     if (extras != null) {
1646                         intent.putExtras(extras);
1647                     }
1648                 }
1649                 setResultEx(resultCode, intent);
1650                 finish();
1651 
1652                 File path = getFileStreamPath(sTempCropFilename);
1653                 path.delete();
1654 
1655                 break;
1656             }
1657         }
1658     }
1659 
canTakePicture()1660     private boolean canTakePicture() {
1661         return isCameraIdle() && (mPicturesRemaining > 0);
1662     }
1663 
1664     @Override
autoFocus()1665     public void autoFocus() {
1666         mFocusStartTime = System.currentTimeMillis();
1667         mCameraDevice.autoFocus(mAutoFocusCallback);
1668         setCameraState(FOCUSING);
1669     }
1670 
1671     @Override
cancelAutoFocus()1672     public void cancelAutoFocus() {
1673         mCameraDevice.cancelAutoFocus();
1674         setCameraState(IDLE);
1675         setCameraParameters(UPDATE_PARAM_PREFERENCE);
1676     }
1677 
1678     // Preview area is touched. Handle touch focus.
1679     @Override
onTouch(View v, MotionEvent e)1680     public boolean onTouch(View v, MotionEvent e) {
1681         if (mPausing || mCameraDevice == null || !mFirstTimeInitialized
1682                 || mCameraState == SNAPSHOT_IN_PROGRESS) {
1683             return false;
1684         }
1685 
1686         // Do not trigger touch focus if popup window is opened.
1687         if (collapseCameraControls()) return false;
1688 
1689         // Check if metering area or focus area is supported.
1690         if (!mFocusAreaSupported && !mMeteringAreaSupported) return false;
1691 
1692         return mFocusManager.onTouch(e);
1693     }
1694 
1695     @Override
onBackPressed()1696     public void onBackPressed() {
1697         if (!isCameraIdle()) {
1698             // ignore backs while we're taking a picture
1699             return;
1700         } else if (!collapseCameraControls()) {
1701             super.onBackPressed();
1702         }
1703     }
1704 
1705     @Override
onKeyDown(int keyCode, KeyEvent event)1706     public boolean onKeyDown(int keyCode, KeyEvent event) {
1707         switch (keyCode) {
1708             case KeyEvent.KEYCODE_FOCUS:
1709                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1710                     onShutterButtonFocus(true);
1711                 }
1712                 return true;
1713             case KeyEvent.KEYCODE_CAMERA:
1714                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1715                     onShutterButtonClick();
1716                 }
1717                 return true;
1718             case KeyEvent.KEYCODE_DPAD_CENTER:
1719                 // If we get a dpad center event without any focused view, move
1720                 // the focus to the shutter button and press it.
1721                 if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
1722                     // Start auto-focus immediately to reduce shutter lag. After
1723                     // the shutter button gets the focus, onShutterButtonFocus()
1724                     // will be called again but it is fine.
1725                     if (collapseCameraControls()) return true;
1726                     onShutterButtonFocus(true);
1727                     if (mShutterButton.isInTouchMode()) {
1728                         mShutterButton.requestFocusFromTouch();
1729                     } else {
1730                         mShutterButton.requestFocus();
1731                     }
1732                     mShutterButton.setPressed(true);
1733                 }
1734                 return true;
1735         }
1736 
1737         return super.onKeyDown(keyCode, event);
1738     }
1739 
1740     @Override
onKeyUp(int keyCode, KeyEvent event)1741     public boolean onKeyUp(int keyCode, KeyEvent event) {
1742         switch (keyCode) {
1743             case KeyEvent.KEYCODE_FOCUS:
1744                 if (mFirstTimeInitialized) {
1745                     onShutterButtonFocus(false);
1746                 }
1747                 return true;
1748         }
1749         return super.onKeyUp(keyCode, event);
1750     }
1751 
surfaceChanged(SurfaceHolder holder, int format, int w, int h)1752     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
1753         // Make sure we have a surface in the holder before proceeding.
1754         if (holder.getSurface() == null) {
1755             Log.d(TAG, "holder.getSurface() == null");
1756             return;
1757         }
1758 
1759         Log.v(TAG, "surfaceChanged. w=" + w + ". h=" + h);
1760 
1761         // We need to save the holder for later use, even when the mCameraDevice
1762         // is null. This could happen if onResume() is invoked after this
1763         // function.
1764         mSurfaceHolder = holder;
1765 
1766         // The mCameraDevice will be null if it fails to connect to the camera
1767         // hardware. In this case we will show a dialog and then finish the
1768         // activity, so it's OK to ignore it.
1769         if (mCameraDevice == null) return;
1770 
1771         // Sometimes surfaceChanged is called after onPause or before onResume.
1772         // Ignore it.
1773         if (mPausing || isFinishing()) return;
1774 
1775         // Set preview display if the surface is being created. Preview was
1776         // already started. Also restart the preview if display rotation has
1777         // changed. Sometimes this happens when the device is held in portrait
1778         // and camera app is opened. Rotation animation takes some time and
1779         // display rotation in onCreate may not be what we want.
1780         if (mCameraState == PREVIEW_STOPPED) {
1781             startPreview();
1782             startFaceDetection();
1783         } else {
1784             if (Util.getDisplayRotation(this) != mDisplayRotation) {
1785                 setDisplayOrientation();
1786             }
1787             if (holder.isCreating()) {
1788                 // Set preview display if the surface is being created and preview
1789                 // was already started. That means preview display was set to null
1790                 // and we need to set it now.
1791                 setPreviewDisplay(holder);
1792             }
1793         }
1794 
1795         // If first time initialization is not finished, send a message to do
1796         // it later. We want to finish surfaceChanged as soon as possible to let
1797         // user see preview first.
1798         if (!mFirstTimeInitialized) {
1799             mHandler.sendEmptyMessage(FIRST_TIME_INIT);
1800         } else {
1801             initializeSecondTime();
1802         }
1803     }
1804 
surfaceCreated(SurfaceHolder holder)1805     public void surfaceCreated(SurfaceHolder holder) {
1806     }
1807 
surfaceDestroyed(SurfaceHolder holder)1808     public void surfaceDestroyed(SurfaceHolder holder) {
1809         stopPreview();
1810         mSurfaceHolder = null;
1811     }
1812 
closeCamera()1813     private void closeCamera() {
1814         if (mCameraDevice != null) {
1815             CameraHolder.instance().release();
1816             mFaceDetectionStarted = false;
1817             mCameraDevice.setZoomChangeListener(null);
1818             mCameraDevice.setFaceDetectionListener(null);
1819             mCameraDevice.setErrorCallback(null);
1820             mCameraDevice = null;
1821             setCameraState(PREVIEW_STOPPED);
1822             mFocusManager.onCameraReleased();
1823         }
1824     }
1825 
setPreviewDisplay(SurfaceHolder holder)1826     private void setPreviewDisplay(SurfaceHolder holder) {
1827         try {
1828             mCameraDevice.setPreviewDisplay(holder);
1829         } catch (Throwable ex) {
1830             closeCamera();
1831             throw new RuntimeException("setPreviewDisplay failed", ex);
1832         }
1833     }
1834 
setDisplayOrientation()1835     private void setDisplayOrientation() {
1836         mDisplayRotation = Util.getDisplayRotation(this);
1837         mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
1838         mCameraDevice.setDisplayOrientation(mDisplayOrientation);
1839         if (mFaceView != null) {
1840             mFaceView.setDisplayOrientation(mDisplayOrientation);
1841         }
1842     }
1843 
startPreview()1844     private void startPreview() {
1845         if (mPausing || isFinishing()) return;
1846 
1847         mFocusManager.resetTouchFocus();
1848 
1849         mCameraDevice.setErrorCallback(mErrorCallback);
1850 
1851         // If we're previewing already, stop the preview first (this will blank
1852         // the screen).
1853         if (mCameraState != PREVIEW_STOPPED) stopPreview();
1854 
1855         setPreviewDisplay(mSurfaceHolder);
1856         setDisplayOrientation();
1857 
1858         if (!mSnapshotOnIdle) {
1859             // If the focus mode is continuous autofocus, call cancelAutoFocus to
1860             // resume it because it may have been paused by autoFocus call.
1861             if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
1862                 mCameraDevice.cancelAutoFocus();
1863             }
1864             mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
1865         }
1866         setCameraParameters(UPDATE_PARAM_ALL);
1867 
1868         // Inform the mainthread to go on the UI initialization.
1869         if (mCameraPreviewThread != null) {
1870             synchronized (mCameraPreviewThread) {
1871                 mCameraPreviewThread.notify();
1872             }
1873         }
1874 
1875         try {
1876             Log.v(TAG, "startPreview");
1877             mCameraDevice.startPreview();
1878         } catch (Throwable ex) {
1879             closeCamera();
1880             throw new RuntimeException("startPreview failed", ex);
1881         }
1882 
1883         mZoomState = ZOOM_STOPPED;
1884         setCameraState(IDLE);
1885         mFocusManager.onPreviewStarted();
1886 
1887         if (mSnapshotOnIdle) {
1888             mHandler.post(mDoSnapRunnable);
1889         }
1890     }
1891 
stopPreview()1892     private void stopPreview() {
1893         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
1894             Log.v(TAG, "stopPreview");
1895             mCameraDevice.cancelAutoFocus(); // Reset the focus.
1896             mCameraDevice.stopPreview();
1897             mFaceDetectionStarted = false;
1898         }
1899         setCameraState(PREVIEW_STOPPED);
1900         mFocusManager.onPreviewStopped();
1901     }
1902 
isSupported(String value, List<String> supported)1903     private static boolean isSupported(String value, List<String> supported) {
1904         return supported == null ? false : supported.indexOf(value) >= 0;
1905     }
1906 
updateCameraParametersInitialize()1907     private void updateCameraParametersInitialize() {
1908         // Reset preview frame rate to the maximum because it may be lowered by
1909         // video camera application.
1910         List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
1911         if (frameRates != null) {
1912             Integer max = Collections.max(frameRates);
1913             mParameters.setPreviewFrameRate(max);
1914         }
1915 
1916         mParameters.setRecordingHint(false);
1917 
1918         // Disable video stabilization. Convenience methods not available in API
1919         // level <= 14
1920         String vstabSupported = mParameters.get("video-stabilization-supported");
1921         if ("true".equals(vstabSupported)) {
1922             mParameters.set("video-stabilization", "false");
1923         }
1924     }
1925 
updateCameraParametersZoom()1926     private void updateCameraParametersZoom() {
1927         // Set zoom.
1928         if (mParameters.isZoomSupported()) {
1929             mParameters.setZoom(mZoomValue);
1930         }
1931     }
1932 
updateCameraParametersPreference()1933     private void updateCameraParametersPreference() {
1934         if (mAeLockSupported) {
1935             mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
1936         }
1937 
1938         if (mAwbLockSupported) {
1939             mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
1940         }
1941 
1942         if (mFocusAreaSupported) {
1943             mParameters.setFocusAreas(mFocusManager.getFocusAreas());
1944         }
1945 
1946         if (mMeteringAreaSupported) {
1947             // Use the same area for focus and metering.
1948             mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
1949         }
1950 
1951         // Set picture size.
1952         String pictureSize = mPreferences.getString(
1953                 CameraSettings.KEY_PICTURE_SIZE, null);
1954         if (pictureSize == null) {
1955             CameraSettings.initialCameraPictureSize(this, mParameters);
1956         } else {
1957             List<Size> supported = mParameters.getSupportedPictureSizes();
1958             CameraSettings.setCameraPictureSize(
1959                     pictureSize, supported, mParameters);
1960         }
1961 
1962         // Set the preview frame aspect ratio according to the picture size.
1963         Size size = mParameters.getPictureSize();
1964 
1965         mPreviewPanel = findViewById(R.id.frame_layout);
1966         mPreviewFrameLayout = (PreviewFrameLayout) findViewById(R.id.frame);
1967         mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
1968 
1969         // Set a preview size that is closest to the viewfinder height and has
1970         // the right aspect ratio.
1971         List<Size> sizes = mParameters.getSupportedPreviewSizes();
1972         Size optimalSize = Util.getOptimalPreviewSize(this,
1973                 sizes, (double) size.width / size.height);
1974         Size original = mParameters.getPreviewSize();
1975         if (!original.equals(optimalSize)) {
1976             mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
1977 
1978             // Zoom related settings will be changed for different preview
1979             // sizes, so set and read the parameters to get lastest values
1980             mCameraDevice.setParameters(mParameters);
1981             mParameters = mCameraDevice.getParameters();
1982         }
1983         Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
1984 
1985         // Since change scene mode may change supported values,
1986         // Set scene mode first,
1987         mSceneMode = mPreferences.getString(
1988                 CameraSettings.KEY_SCENE_MODE,
1989                 getString(R.string.pref_camera_scenemode_default));
1990         if (isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
1991             if (!mParameters.getSceneMode().equals(mSceneMode)) {
1992                 mParameters.setSceneMode(mSceneMode);
1993                 mCameraDevice.setParameters(mParameters);
1994 
1995                 // Setting scene mode will change the settings of flash mode,
1996                 // white balance, and focus mode. Here we read back the
1997                 // parameters, so we can know those settings.
1998                 mParameters = mCameraDevice.getParameters();
1999             }
2000         } else {
2001             mSceneMode = mParameters.getSceneMode();
2002             if (mSceneMode == null) {
2003                 mSceneMode = Parameters.SCENE_MODE_AUTO;
2004             }
2005         }
2006 
2007         // Set JPEG quality.
2008         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
2009                 CameraProfile.QUALITY_HIGH);
2010         mParameters.setJpegQuality(jpegQuality);
2011 
2012         // For the following settings, we need to check if the settings are
2013         // still supported by latest driver, if not, ignore the settings.
2014 
2015         // Set exposure compensation
2016         int value = CameraSettings.readExposure(mPreferences);
2017         int max = mParameters.getMaxExposureCompensation();
2018         int min = mParameters.getMinExposureCompensation();
2019         if (value >= min && value <= max) {
2020             mParameters.setExposureCompensation(value);
2021         } else {
2022             Log.w(TAG, "invalid exposure range: " + value);
2023         }
2024 
2025         if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
2026             // Set flash mode.
2027             String flashMode = mPreferences.getString(
2028                     CameraSettings.KEY_FLASH_MODE,
2029                     getString(R.string.pref_camera_flashmode_default));
2030             List<String> supportedFlash = mParameters.getSupportedFlashModes();
2031             if (isSupported(flashMode, supportedFlash)) {
2032                 mParameters.setFlashMode(flashMode);
2033             } else {
2034                 flashMode = mParameters.getFlashMode();
2035                 if (flashMode == null) {
2036                     flashMode = getString(
2037                             R.string.pref_camera_flashmode_no_flash);
2038                 }
2039             }
2040 
2041             // Set white balance parameter.
2042             String whiteBalance = mPreferences.getString(
2043                     CameraSettings.KEY_WHITE_BALANCE,
2044                     getString(R.string.pref_camera_whitebalance_default));
2045             if (isSupported(whiteBalance,
2046                     mParameters.getSupportedWhiteBalance())) {
2047                 mParameters.setWhiteBalance(whiteBalance);
2048             } else {
2049                 whiteBalance = mParameters.getWhiteBalance();
2050                 if (whiteBalance == null) {
2051                     whiteBalance = Parameters.WHITE_BALANCE_AUTO;
2052                 }
2053             }
2054 
2055             // Set focus mode.
2056             mFocusManager.overrideFocusMode(null);
2057             mParameters.setFocusMode(mFocusManager.getFocusMode());
2058         } else {
2059             mFocusManager.overrideFocusMode(mParameters.getFocusMode());
2060         }
2061     }
2062 
2063     // We separate the parameters into several subsets, so we can update only
2064     // the subsets actually need updating. The PREFERENCE set needs extra
2065     // locking because the preference can be changed from GLThread as well.
setCameraParameters(int updateSet)2066     private void setCameraParameters(int updateSet) {
2067         mParameters = mCameraDevice.getParameters();
2068 
2069         if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
2070             updateCameraParametersInitialize();
2071         }
2072 
2073         if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
2074             updateCameraParametersZoom();
2075         }
2076 
2077         if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
2078             updateCameraParametersPreference();
2079         }
2080 
2081         mCameraDevice.setParameters(mParameters);
2082     }
2083 
2084     // If the Camera is idle, update the parameters immediately, otherwise
2085     // accumulate them in mUpdateSet and update later.
setCameraParametersWhenIdle(int additionalUpdateSet)2086     private void setCameraParametersWhenIdle(int additionalUpdateSet) {
2087         mUpdateSet |= additionalUpdateSet;
2088         if (mCameraDevice == null) {
2089             // We will update all the parameters when we open the device, so
2090             // we don't need to do anything now.
2091             mUpdateSet = 0;
2092             return;
2093         } else if (isCameraIdle()) {
2094             setCameraParameters(mUpdateSet);
2095             updateSceneModeUI();
2096             mUpdateSet = 0;
2097         } else {
2098             if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
2099                 mHandler.sendEmptyMessageDelayed(
2100                         SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
2101             }
2102         }
2103     }
2104 
gotoGallery()2105     private void gotoGallery() {
2106         MenuHelper.gotoCameraImageGallery(this);
2107     }
2108 
isCameraIdle()2109     private boolean isCameraIdle() {
2110         return (mCameraState == IDLE) || (mFocusManager.isFocusCompleted());
2111     }
2112 
isImageCaptureIntent()2113     private boolean isImageCaptureIntent() {
2114         String action = getIntent().getAction();
2115         return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action));
2116     }
2117 
setupCaptureParams()2118     private void setupCaptureParams() {
2119         Bundle myExtras = getIntent().getExtras();
2120         if (myExtras != null) {
2121             mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
2122             mCropValue = myExtras.getString("crop");
2123         }
2124     }
2125 
showPostCaptureAlert()2126     private void showPostCaptureAlert() {
2127         if (mIsImageCaptureIntent) {
2128             Util.fadeOut(mIndicatorControlContainer);
2129             Util.fadeOut(mShutterButton);
2130 
2131             int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2132             for (int id : pickIds) {
2133                 Util.fadeIn(findViewById(id));
2134             }
2135         }
2136     }
2137 
hidePostCaptureAlert()2138     private void hidePostCaptureAlert() {
2139         if (mIsImageCaptureIntent) {
2140             int[] pickIds = {R.id.btn_retake, R.id.btn_done};
2141             for (int id : pickIds) {
2142                 Util.fadeOut(findViewById(id));
2143             }
2144 
2145             Util.fadeIn(mShutterButton);
2146             Util.fadeIn(mIndicatorControlContainer);
2147         }
2148     }
2149 
2150     @Override
onPrepareOptionsMenu(Menu menu)2151     public boolean onPrepareOptionsMenu(Menu menu) {
2152         super.onPrepareOptionsMenu(menu);
2153         // Only show the menu when camera is idle.
2154         for (int i = 0; i < menu.size(); i++) {
2155             menu.getItem(i).setVisible(isCameraIdle());
2156         }
2157 
2158         return true;
2159     }
2160 
2161     @Override
onCreateOptionsMenu(Menu menu)2162     public boolean onCreateOptionsMenu(Menu menu) {
2163         super.onCreateOptionsMenu(menu);
2164 
2165         if (mIsImageCaptureIntent) {
2166             // No options menu for attach mode.
2167             return false;
2168         } else {
2169             addBaseMenuItems(menu);
2170         }
2171         return true;
2172     }
2173 
addBaseMenuItems(Menu menu)2174     private void addBaseMenuItems(Menu menu) {
2175         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_VIDEO, new Runnable() {
2176             public void run() {
2177                 switchToOtherMode(ModePicker.MODE_VIDEO);
2178             }
2179         });
2180         MenuHelper.addSwitchModeMenuItem(menu, ModePicker.MODE_PANORAMA, new Runnable() {
2181             public void run() {
2182                 switchToOtherMode(ModePicker.MODE_PANORAMA);
2183             }
2184         });
2185 
2186         if (mNumberOfCameras > 1) {
2187             menu.add(R.string.switch_camera_id)
2188                     .setOnMenuItemClickListener(new OnMenuItemClickListener() {
2189                 public boolean onMenuItemClick(MenuItem item) {
2190                     CameraSettings.writePreferredCameraId(mPreferences,
2191                             (mCameraId + 1) % mNumberOfCameras);
2192                     onSharedPreferenceChanged();
2193                     return true;
2194                 }
2195             }).setIcon(android.R.drawable.ic_menu_camera);
2196         }
2197     }
2198 
switchToOtherMode(int mode)2199     private boolean switchToOtherMode(int mode) {
2200         if (isFinishing()) return false;
2201         if (mImageSaver != null) mImageSaver.waitDone();
2202         MenuHelper.gotoMode(mode, Camera.this);
2203         mHandler.removeMessages(FIRST_TIME_INIT);
2204         finish();
2205         return true;
2206     }
2207 
onModeChanged(int mode)2208     public boolean onModeChanged(int mode) {
2209         if (mode != ModePicker.MODE_CAMERA) {
2210             return switchToOtherMode(mode);
2211         } else {
2212             return true;
2213         }
2214     }
2215 
onSharedPreferenceChanged()2216     public void onSharedPreferenceChanged() {
2217         // ignore the events after "onPause()"
2218         if (mPausing) return;
2219 
2220         boolean recordLocation = RecordLocationPreference.get(
2221                 mPreferences, getContentResolver());
2222         mLocationManager.recordLocation(recordLocation);
2223 
2224         int cameraId = CameraSettings.readPreferredCameraId(mPreferences);
2225         if (mCameraId != cameraId) {
2226             // Restart the activity to have a crossfade animation.
2227             // TODO: Use SurfaceTexture to implement a better and faster
2228             // animation.
2229             if (mIsImageCaptureIntent) {
2230                 // If the intent is camera capture, stay in camera capture mode.
2231                 MenuHelper.gotoCameraMode(this, getIntent());
2232             } else {
2233                 MenuHelper.gotoCameraMode(this);
2234             }
2235 
2236             finish();
2237         } else {
2238             setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
2239         }
2240 
2241         updateOnScreenIndicators();
2242     }
2243 
2244     @Override
onUserInteraction()2245     public void onUserInteraction() {
2246         super.onUserInteraction();
2247         keepScreenOnAwhile();
2248     }
2249 
resetScreenOn()2250     private void resetScreenOn() {
2251         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2252         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2253     }
2254 
keepScreenOnAwhile()2255     private void keepScreenOnAwhile() {
2256         mHandler.removeMessages(CLEAR_SCREEN_DELAY);
2257         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2258         mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
2259     }
2260 
onRestorePreferencesClicked()2261     public void onRestorePreferencesClicked() {
2262         if (mPausing) return;
2263         Runnable runnable = new Runnable() {
2264             public void run() {
2265                 restorePreferences();
2266             }
2267         };
2268         mRotateDialog.showAlertDialog(
2269                 getString(R.string.confirm_restore_title),
2270                 getString(R.string.confirm_restore_message),
2271                 getString(android.R.string.ok), runnable,
2272                 getString(android.R.string.cancel), null);
2273     }
2274 
restorePreferences()2275     private void restorePreferences() {
2276         // Reset the zoom. Zoom value is not stored in preference.
2277         if (mParameters.isZoomSupported()) {
2278             mZoomValue = 0;
2279             setCameraParametersWhenIdle(UPDATE_PARAM_ZOOM);
2280             mZoomControl.setZoomIndex(0);
2281         }
2282         if (mIndicatorControlContainer != null) {
2283             mIndicatorControlContainer.dismissSettingPopup();
2284             CameraSettings.restorePreferences(Camera.this, mPreferences,
2285                     mParameters);
2286             mIndicatorControlContainer.reloadPreferences();
2287             onSharedPreferenceChanged();
2288         }
2289     }
2290 
onOverriddenPreferencesClicked()2291     public void onOverriddenPreferencesClicked() {
2292         if (mPausing) return;
2293         if (mNotSelectableToast == null) {
2294             String str = getResources().getString(R.string.not_selectable_in_scene_mode);
2295             mNotSelectableToast = Toast.makeText(Camera.this, str, Toast.LENGTH_SHORT);
2296         }
2297         mNotSelectableToast.show();
2298     }
2299 
showSharePopup()2300     private void showSharePopup() {
2301         mImageSaver.waitDone();
2302         Uri uri = mThumbnail.getUri();
2303         if (mSharePopup == null || !uri.equals(mSharePopup.getUri())) {
2304             // SharePopup window takes the mPreviewPanel as its size reference.
2305             mSharePopup = new SharePopup(this, uri, mThumbnail.getBitmap(),
2306                     mOrientationCompensation, mPreviewPanel);
2307         }
2308         mSharePopup.showAtLocation(mThumbnailView, Gravity.NO_GRAVITY, 0, 0);
2309     }
2310 
2311     @Override
onFaceDetection(Face[] faces, android.hardware.Camera camera)2312     public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
2313         mFaceView.setFaces(faces);
2314     }
2315 
showTapToFocusToast()2316     private void showTapToFocusToast() {
2317         new RotateTextToast(this, R.string.tap_to_focus, mOrientation).show();
2318         // Clear the preference.
2319         Editor editor = mPreferences.edit();
2320         editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
2321         editor.apply();
2322     }
2323 
initializeCapabilities()2324     private void initializeCapabilities() {
2325         mInitialParams = mCameraDevice.getParameters();
2326         mFocusManager.initializeParameters(mInitialParams);
2327         mFocusAreaSupported = (mInitialParams.getMaxNumFocusAreas() > 0
2328                 && isSupported(Parameters.FOCUS_MODE_AUTO,
2329                         mInitialParams.getSupportedFocusModes()));
2330         mMeteringAreaSupported = (mInitialParams.getMaxNumMeteringAreas() > 0);
2331         mAeLockSupported = mInitialParams.isAutoExposureLockSupported();
2332         mAwbLockSupported = mInitialParams.isAutoWhiteBalanceLockSupported();
2333     }
2334 }
2335