diff options
27 files changed, 1019 insertions, 283 deletions
diff --git a/proguard.flags b/proguard.flags index d1b41b1..5dcd2b9 100644 --- a/proguard.flags +++ b/proguard.flags @@ -2,3 +2,12 @@ -keep class * extends com.android.camera.CameraPreference { <init>(...); } + +-keep class com.android.camera.ActivityBase { + public int getResultCode(); + public android.content.Intent getResultData(); +} + +-keep class com.android.camera.VideoCamera { + public boolean isRecording(); +} diff --git a/res/drawable-hdpi/focus_focus_failed.9.png b/res/drawable-hdpi/focus_focus_failed.9.png Binary files differindex f0d9ca0..52336cc 100644 --- a/res/drawable-hdpi/focus_focus_failed.9.png +++ b/res/drawable-hdpi/focus_focus_failed.9.png diff --git a/res/drawable-hdpi/focus_focused.9.png b/res/drawable-hdpi/focus_focused.9.png Binary files differindex e7c5c57..d941c48 100644 --- a/res/drawable-hdpi/focus_focused.9.png +++ b/res/drawable-hdpi/focus_focused.9.png diff --git a/res/drawable-hdpi/focus_focusing.9.png b/res/drawable-hdpi/focus_focusing.9.png Binary files differindex 0bef57a..1838a6c 100644 --- a/res/drawable-hdpi/focus_focusing.9.png +++ b/res/drawable-hdpi/focus_focusing.9.png diff --git a/res/drawable-hdpi/ic_viewfinder_share.jpg b/res/drawable-hdpi/ic_viewfinder_share.jpg Binary files differnew file mode 100644 index 0000000..7a8949d --- /dev/null +++ b/res/drawable-hdpi/ic_viewfinder_share.jpg diff --git a/res/drawable-mdpi/focus_focus_failed.9.png b/res/drawable-mdpi/focus_focus_failed.9.png Binary files differindex 62d0944..6b425ea 100755..100644 --- a/res/drawable-mdpi/focus_focus_failed.9.png +++ b/res/drawable-mdpi/focus_focus_failed.9.png diff --git a/res/drawable-mdpi/focus_focused.9.png b/res/drawable-mdpi/focus_focused.9.png Binary files differindex 2cf4423..71adb2a 100755..100644 --- a/res/drawable-mdpi/focus_focused.9.png +++ b/res/drawable-mdpi/focus_focused.9.png diff --git a/res/drawable-mdpi/focus_focusing.9.png b/res/drawable-mdpi/focus_focusing.9.png Binary files differindex 3fea637..83bb2f0 100755..100644 --- a/res/drawable-mdpi/focus_focusing.9.png +++ b/res/drawable-mdpi/focus_focusing.9.png diff --git a/res/drawable-mdpi/ic_viewfinder_share.png b/res/drawable-mdpi/ic_viewfinder_share.png Binary files differnew file mode 100644 index 0000000..a73bec5 --- /dev/null +++ b/res/drawable-mdpi/ic_viewfinder_share.png diff --git a/res/drawable-xlarge-mdpi/ic_viewfinder_share.png b/res/drawable-xlarge-mdpi/ic_viewfinder_share.png Binary files differnew file mode 100644 index 0000000..d8f223a --- /dev/null +++ b/res/drawable-xlarge-mdpi/ic_viewfinder_share.png diff --git a/res/layout-xlarge/preview_frame.xml b/res/layout-xlarge/preview_frame.xml index 74445ff..9a1eb99 100644 --- a/res/layout-xlarge/preview_frame.xml +++ b/res/layout-xlarge/preview_frame.xml @@ -32,8 +32,9 @@ android:background="@drawable/border_preview_holo"/> <com.android.camera.ui.FocusRectangle android:id="@+id/focus_rectangle" - android:layout_width="match_parent" - android:layout_height="match_parent"/> + android:layout_width="180dp" + android:layout_height="180dp" + android:layout_centerInParent="true"/> <TextView android:id="@+id/zoom_ratio" style="@style/OnViewfinderLabel" android:layout_alignParentRight="true" diff --git a/res/layout/preview_frame.xml b/res/layout/preview_frame.xml index 5a4a700..693ae65 100644 --- a/res/layout/preview_frame.xml +++ b/res/layout/preview_frame.xml @@ -30,9 +30,9 @@ <SurfaceView android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent"/> - <com.android.camera.ui.FocusRectangle - android:id="@+id/focus_rectangle" - android:layout_width="match_parent" - android:layout_height="match_parent"/> + <com.android.camera.ui.FocusRectangle android:id="@+id/focus_rectangle" + android:layout_width="120dp" + android:layout_height="120dp" + android:layout_centerInParent="true"/> </RelativeLayout> </com.android.camera.PreviewFrameLayout> diff --git a/res/values/strings.xml b/res/values/strings.xml index a073872..cfeb8fb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -372,4 +372,16 @@ <string name="setting_increment" translatable="false">></string> <string name="setting_decrement" translatable="false"><</string> + + <!-- Title of the dialog showing a list of applications that can share the captured picture. [CHAR LIMIT=30] --> + <string name="share_picture_via">Share picture via</string> + + <!-- Title of the dialog showing a list of applications that can share the captured video. [CHAR LIMIT=30] --> + <string name="share_video_via">Share video via</string> + + <!-- Toast saying that there is no picture to share. [CHAR LIMIT=30] --> + <string name="no_picture_to_share">No picture to share</string> + + <!-- Toast saying that there is no video to share. [CHAR LIMIT=30] --> + <string name="no_video_to_share">No video to share</string> </resources> diff --git a/src/com/android/camera/ActivityBase.java b/src/com/android/camera/ActivityBase.java index 5f9c8a5..5839668 100644 --- a/src/com/android/camera/ActivityBase.java +++ b/src/com/android/camera/ActivityBase.java @@ -18,11 +18,15 @@ package com.android.camera; import android.app.Activity; import android.view.KeyEvent; +import android.content.Intent; /** * Superclass of Camera and VideoCamera activities. */ public class ActivityBase extends Activity { + private int mResultCodeForTesting; + private Intent mResultDataForTesting; + @Override public boolean onSearchRequested() { return false; @@ -38,4 +42,23 @@ public class ActivityBase extends Activity { return super.onKeyDown(keyCode, event); } + + protected void setResultEx(int resultCode) { + mResultCodeForTesting = resultCode; + setResult(resultCode); + } + + protected void setResultEx(int resultCode, Intent data) { + mResultCodeForTesting = resultCode; + mResultDataForTesting = data; + setResult(resultCode, data); + } + + public int getResultCode() { + return mResultCodeForTesting; + } + + public Intent getResultData() { + return mResultDataForTesting; + } } diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index 67ac246..dc399a4 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -39,6 +39,8 @@ import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Rect; +import android.hardware.Camera.Area; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; import android.hardware.Camera.PictureCallback; @@ -77,6 +79,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.MenuItem.OnMenuItemClickListener; import android.widget.Button; +import android.widget.RelativeLayout; import android.widget.Toast; import java.io.File; @@ -93,8 +96,8 @@ import java.util.List; /** The Camera activity which can preview and take pictures. */ public class Camera extends ActivityBase implements View.OnClickListener, - ShutterButton.OnShutterButtonListener, SurfaceHolder.Callback, - Switcher.OnSwitchListener { + View.OnTouchListener, ShutterButton.OnShutterButtonListener, + SurfaceHolder.Callback, Switcher.OnSwitchListener { private static final String TAG = "camera"; @@ -107,6 +110,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, private static final int CLEAR_SCREEN_DELAY = 4; private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 5; private static final int CHECK_DISPLAY_ROTATION = 6; + private static final int CANCEL_AUTOFOCUS = 7; // The subset of parameters we need to update in setCameraParameters(). private static final int UPDATE_PARAM_INITIALIZE = 1; @@ -146,13 +150,9 @@ public class Camera extends ActivityBase implements View.OnClickListener, private int mOrientationCompensation = 0; private ComboPreferences mPreferences; - private static final int IDLE = 1; - private static final int SNAPSHOT_IN_PROGRESS = 2; - private static final boolean SWITCH_CAMERA = true; private static final boolean SWITCH_VIDEO = false; - private int mStatus = IDLE; private static final String sTempCropFilename = "crop-temp"; private android.hardware.Camera mCameraDevice; @@ -160,12 +160,16 @@ public class Camera extends ActivityBase implements View.OnClickListener, private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder = null; private ShutterButton mShutterButton; - private FocusRectangle mFocusRectangle; private ToneGenerator mFocusToneGenerator; private GestureDetector mPopupGestureDetector; private SwitcherSet mSwitcher; private boolean mStartPreviewFail = false; + private View mPreviewFrame; // Preview frame area. + private View mPreviewBorder; + private FocusRectangle mFocusRectangle; + private List<Area> mFocusArea; // focus area in driver format + private GLRootView mGLRootView; // The last captured picture. @@ -191,21 +195,22 @@ public class Camera extends ActivityBase implements View.OnClickListener, private final static String EXTRA_QUICK_CAPTURE = "android.intent.extra.quickCapture"; - private boolean mPreviewing; - // The display rotation in degrees. This is only valid when mPreviewing is - // true. + // The display rotation in degrees. This is only valid when mCameraState is + // not PREVIEW_STOPPED. private int mDisplayRotation; private boolean mPausing; private boolean mFirstTimeInitialized; private boolean mIsImageCaptureIntent; private boolean mRecordLocation; - private static final int FOCUS_NOT_STARTED = 0; - private static final int FOCUSING = 1; - private static final int FOCUSING_SNAP_ON_FINISH = 2; - private static final int FOCUS_SUCCESS = 3; - private static final int FOCUS_FAIL = 4; - private int mFocusState = FOCUS_NOT_STARTED; + private static final int PREVIEW_STOPPED = 0; + private static final int IDLE = 1; // preview is active + private static final int FOCUSING = 2; + private static final int FOCUSING_SNAP_ON_FINISH = 3; + private static final int FOCUS_SUCCESS = 4; + private static final int FOCUS_FAIL = 5; + private static final int SNAPSHOT_IN_PROGRESS = 6; + private int mCameraState = PREVIEW_STOPPED; private ContentResolver mContentResolver; private boolean mDidRegister = false; @@ -246,6 +251,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, private String mFocusMode; private String mSceneMode; private Toast mNotSelectableToast; + private Toast mNoShareToast; private final Handler mHandler = new MainHandler(); // xlarge devices use indicator wheel. Other devices use head-up display. @@ -312,6 +318,11 @@ public class Camera extends ActivityBase implements View.OnClickListener, } break; } + + case CANCEL_AUTOFOCUS: { + cancelAutoFocus(); + break; + } } } } @@ -376,8 +387,15 @@ public class Camera extends ActivityBase implements View.OnClickListener, mShutterButton.setOnShutterButtonListener(this); mShutterButton.setVisibility(View.VISIBLE); - mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle); - updateFocusIndicator(); + // Initialize focus UI. + mPreviewFrame = findViewById(R.id.camera_preview); + mPreviewFrame.setOnTouchListener(this); + mPreviewBorder = (View) findViewById(R.id.preview_border); + // Set the length of focus rectangle according to preview frame size. + int len = Math.min(mPreviewFrame.getWidth(), mPreviewFrame.getHeight()) / 4; + ViewGroup.LayoutParams layout = mFocusRectangle.getLayoutParams(); + layout.width = len; + layout.height = len; initializeScreenBrightness(); installIntentFilter(); @@ -527,27 +545,21 @@ public class Camera extends ActivityBase implements View.OnClickListener, return result; } - private int mLocation[] = new int[2]; - private class PopupGestureListener extends - GestureDetector.SimpleOnGestureListener { + private class PopupGestureListener + extends GestureDetector.SimpleOnGestureListener { public boolean onDown(MotionEvent e) { // Check if the popup window is visible. - View v = mIndicatorWheel.getActivePopupWindow(); - if (v == null) return false; - - int x = Math.round(e.getX()); - int y = Math.round(e.getY()); - - // Dismiss the popup window if users touch on the outside. - v.getLocationOnScreen(mLocation); - if (x < mLocation[0] || (x > mLocation[0] + v.getWidth()) - || y < mLocation[1] || (y > mLocation[1] + v.getHeight())) { - // Let indicator wheel handle its own event. - mIndicatorWheel.getLocationOnScreen(mLocation); - if (x < mLocation[0] || (x > mLocation[0] + mIndicatorWheel.getWidth()) - || y < mLocation[1] || (y > mLocation[1] + mIndicatorWheel.getHeight())) { - mIndicatorWheel.dismissSettingPopup(); - } + View popup = mIndicatorWheel.getActivePopupWindow(); + if (popup == null) return false; + + + // Let popup window, indicator wheel or preview frame handle the + // event by themselves. Dismiss the popup window if users touch on + // other areas. + if (!Util.pointInView(e.getX(), e.getY(), popup) + && !Util.pointInView(e.getX(), e.getY(), mIndicatorWheel) + && !Util.pointInView(e.getX(), e.getY(), mPreviewFrame)) { + mIndicatorWheel.dismissSettingPopup(); // Let event fall through. } return false; @@ -688,7 +700,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, mShutterCallbackTime = System.currentTimeMillis(); mShutterLag = mShutterCallbackTime - mCaptureStartTime; Log.v(TAG, "mShutterLag = " + mShutterLag + "ms"); - clearFocusState(); + updateFocusUI(); } } @@ -779,32 +791,41 @@ public class Camera extends ActivityBase implements View.OnClickListener, mFocusCallbackTime = System.currentTimeMillis(); mAutoFocusTime = mFocusCallbackTime - mFocusStartTime; Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms"); - if (mFocusState == FOCUSING_SNAP_ON_FINISH) { + if (mCameraState == FOCUSING_SNAP_ON_FINISH) { // Take the picture no matter focus succeeds or fails. No need // to play the AF sound if we're about to play the shutter // sound. if (focused) { - mFocusState = FOCUS_SUCCESS; + mCameraState = FOCUS_SUCCESS; } else { - mFocusState = FOCUS_FAIL; + mCameraState = FOCUS_FAIL; } + updateFocusUI(); capture(); - } else if (mFocusState == FOCUSING) { - // User is half-pressing the focus key. Play the focus tone. - // Do not take the picture now. + } else if (mCameraState == FOCUSING) { + // This happens when (1) user is half-pressing the focus key or + // (2) touch focus is triggered. Play the focus tone. Do not + // take the picture now. if (focused) { - mFocusState = FOCUS_SUCCESS; + mCameraState = FOCUS_SUCCESS; if (mFocusToneGenerator != null) { mFocusToneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP2); } } else { - mFocusState = FOCUS_FAIL; + mCameraState = FOCUS_FAIL; } - } else if (mFocusState == FOCUS_NOT_STARTED) { + updateFocusUI(); + enableCameraControls(true); + // If this is triggered by touch focus, cancel focus after a + // while. + if (mFocusArea != null) { + mHandler.sendEmptyMessageDelayed(CANCEL_AUTOFOCUS, 3000); + } + } else if (mCameraState == IDLE) { // User has released the focus key before focus completes. // Do nothing. } - updateFocusIndicator(); + } } @@ -862,13 +883,12 @@ public class Camera extends ActivityBase implements View.OnClickListener, private void capture() { // If we are already in the middle of taking a snapshot then ignore. - if (mPausing || mStatus == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) { + if (mPausing || mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) { return; } mCaptureStartTime = System.currentTimeMillis(); mPostViewPictureCallbackTime = 0; enableCameraControls(false); - mStatus = SNAPSHOT_IN_PROGRESS; mJpegImageData = null; // See android.hardware.Camera.Parameters.setRotation for @@ -925,7 +945,8 @@ public class Camera extends ActivityBase implements View.OnClickListener, mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, mPostViewPictureCallback, new JpegPictureCallback(loc)); - mPreviewing = false; + mCameraState = SNAPSHOT_IN_PROGRESS; + mHandler.removeMessages(CANCEL_AUTOFOCUS); } private boolean saveDataToFile(String filePath, byte[] data) { @@ -960,13 +981,14 @@ public class Camera extends ActivityBase implements View.OnClickListener, setContentView(R.layout.camera); } mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview); + mFocusRectangle = (FocusRectangle) findViewById(R.id.focus_rectangle); mPreferences = new ComboPreferences(this); CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); mCameraId = CameraSettings.readPreferredCameraId(mPreferences); - //Testing purpose. Launch a specific camera through the intent extras. + // Testing purpose. Launch a specific camera through the intent extras. int intentCameraId = Util.getCameraFacingIntentExtras(this); if (intentCameraId != -1) { mCameraId = intentCameraId; @@ -1099,11 +1121,19 @@ public class Camera extends ActivityBase implements View.OnClickListener, if (mIndicatorWheel == null) return; loadCameraPreferences(); - String[] keys = new String[]{CameraSettings.KEY_FLASH_MODE, - CameraSettings.KEY_WHITE_BALANCE, - CameraSettings.KEY_COLOR_EFFECT, - CameraSettings.KEY_SCENE_MODE}; - mIndicatorWheel.initialize(this, mPreferenceGroup, keys, true); + final String[] SETTING_KEYS = { + CameraSettings.KEY_FLASH_MODE, + CameraSettings.KEY_WHITE_BALANCE, + CameraSettings.KEY_SCENE_MODE}; + final String[] OTHER_SETTING_KEYS = { + CameraSettings.KEY_RECORD_LOCATION, + CameraSettings.KEY_FOCUS_MODE, + CameraSettings.KEY_EXPOSURE, + CameraSettings.KEY_COLOR_EFFECT, + CameraSettings.KEY_PICTURE_SIZE, + CameraSettings.KEY_JPEG_QUALITY}; + mIndicatorWheel.initialize(this, mPreferenceGroup, SETTING_KEYS, + OTHER_SETTING_KEYS); mIndicatorWheel.setListener(new MyIndicatorWheelListener()); mPopupGestureDetector = new GestureDetector(this, new PopupGestureListener()); @@ -1277,7 +1307,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, outputStream.write(data); outputStream.close(); - setResult(RESULT_OK); + setResultEx(RESULT_OK); finish(); } catch (IOException ex) { // ignore exception @@ -1288,7 +1318,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, int orientation = Exif.getOrientation(data); Bitmap bitmap = Util.makeBitmap(data, 50 * 1024); bitmap = Util.rotate(bitmap, orientation); - setResult(RESULT_OK, + setResultEx(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap)); finish(); } @@ -1304,11 +1334,11 @@ public class Camera extends ActivityBase implements View.OnClickListener, tempStream.close(); tempUri = Uri.fromFile(path); } catch (FileNotFoundException ex) { - setResult(Activity.RESULT_CANCELED); + setResultEx(Activity.RESULT_CANCELED); finish(); return; } catch (IOException ex) { - setResult(Activity.RESULT_CANCELED); + setResultEx(Activity.RESULT_CANCELED); finish(); return; } finally { @@ -1335,7 +1365,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, } private void doCancel() { - setResult(RESULT_CANCELED, new Intent()); + setResultEx(RESULT_CANCELED, new Intent()); finish(); } @@ -1435,7 +1465,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, mZoomValue = 0; // Start the preview if it is not started. - if (!mPreviewing && !mStartPreviewFail) { + if (mCameraState == PREVIEW_STOPPED && !mStartPreviewFail) { resetExposureCompensation(); if (!restartPreview()) return; } @@ -1451,7 +1481,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, } keepScreenOnAwhile(); - if (mPreviewing) { + if (mCameraState == IDLE) { mOnResumeTime = SystemClock.uptimeMillis(); mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100); } @@ -1507,6 +1537,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, mHandler.removeMessages(RESTART_PREVIEW); mHandler.removeMessages(FIRST_TIME_INIT); mHandler.removeMessages(CHECK_DISPLAY_ROTATION); + mHandler.removeMessages(CANCEL_AUTOFOCUS); super.onPause(); } @@ -1523,7 +1554,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, intent.putExtras(extras); } } - setResult(resultCode, intent); + setResultEx(resultCode, intent); finish(); File path = getFileStreamPath(sTempCropFilename); @@ -1535,52 +1566,123 @@ public class Camera extends ActivityBase implements View.OnClickListener, } private boolean canTakePicture() { - return isCameraIdle() && mPreviewing && (mPicturesRemaining > 0); + return isCameraIdle() && (mPicturesRemaining > 0); } private void autoFocus() { - // Initiate autofocus only when preview is started and snapshot is not - // in progress. - if (canTakePicture()) { - enableCameraControls(false); - Log.v(TAG, "Start autofocus."); - mFocusStartTime = System.currentTimeMillis(); - mFocusState = FOCUSING; - updateFocusIndicator(); - mCameraDevice.autoFocus(mAutoFocusCallback); - } + Log.v(TAG, "Start autofocus."); + mFocusStartTime = System.currentTimeMillis(); + mCameraDevice.autoFocus(mAutoFocusCallback); + mCameraState = FOCUSING; + enableCameraControls(false); + updateFocusUI(); + mHandler.removeMessages(CANCEL_AUTOFOCUS); } private void cancelAutoFocus() { - // User releases half-pressed focus key. - if (mStatus != SNAPSHOT_IN_PROGRESS && (mFocusState == FOCUSING - || mFocusState == FOCUS_SUCCESS || mFocusState == FOCUS_FAIL)) { - Log.v(TAG, "Cancel autofocus."); - enableCameraControls(true); - mCameraDevice.cancelAutoFocus(); + Log.v(TAG, "Cancel autofocus."); + mCameraDevice.cancelAutoFocus(); + mCameraState = IDLE; + enableCameraControls(true); + resetTouchFocus(); + setCameraParameters(UPDATE_PARAM_PREFERENCE); + updateFocusUI(); + mHandler.removeMessages(CANCEL_AUTOFOCUS); + } + + private void updateFocusUI() { + if (mCameraState == FOCUSING || mCameraState == FOCUSING_SNAP_ON_FINISH) { + mFocusRectangle.showStart(); + } else if (mCameraState == FOCUS_SUCCESS) { + mFocusRectangle.showSuccess(); + } else if (mCameraState == FOCUS_FAIL) { + mFocusRectangle.showFail(); + } else { + mFocusRectangle.clear(); + } + } + + // Preview area is touched. Handle touch focus. + @Override + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() != MotionEvent.ACTION_DOWN) return false; + + // Do not trigger touch focus when popup window is dismissed. + if (collapseCameraControls()) return false; + + if (mPausing || !mFirstTimeInitialized || !canTakePicture()) { + return false; } - if (mFocusState != FOCUSING_SNAP_ON_FINISH) { - clearFocusState(); + + // Take a picture if metering area or focus area is supported. + if (mParameters.getMaxNumMeteringAreas() == 0 + && (mParameters.getMaxNumFocusAreas() == 0 + || (!mFocusMode.equals(Parameters.FOCUS_MODE_AUTO) && + !mFocusMode.equals(Parameters.FOCUS_MODE_MACRO) && + !mFocusMode.equals(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)))) { + return false; } + + // Calculate the position of the focus rectangle. + int x = Math.round(e.getX()); + int y = Math.round(e.getY()); + int focusWidth = mFocusRectangle.getWidth(); + int focusHeight = mFocusRectangle.getHeight(); + int left = Util.clamp(x - focusWidth / 2, 0, + mPreviewFrame.getWidth() - focusWidth); + int top = Util.clamp(y - focusHeight / 2, 0, + mPreviewFrame.getHeight() - focusHeight); + Log.d(TAG, "x=" + x + ". y=" + y); + Log.d(TAG, "Margin left=" + left + ". top=" + top); + Log.d(TAG, "Preview width=" + mPreviewFrame.getWidth() + + ". height=" + mPreviewFrame.getHeight()); + Log.d(TAG, "focusWidth=" + focusWidth + ". focusHeight=" + focusHeight); + + // Convert the coordinates to driver format. The coordinates range from + // -1000 to 1000. + if (mFocusArea == null) { + mFocusArea = new ArrayList<Area>(); + mFocusArea.add(new Area(new Rect(), 1)); + } + Rect rect = mFocusArea.get(0).rect; + convertToFocusArea(left, top, focusWidth, focusHeight, mPreviewFrame.getWidth(), + mPreviewFrame.getHeight(), mFocusArea.get(0).rect); + + // Use margin to set the focus rectangle to the touched area. + RelativeLayout.LayoutParams p = + (RelativeLayout.LayoutParams) mFocusRectangle.getLayoutParams(); + p.setMargins(left + mPreviewBorder.getPaddingLeft(), + top + mPreviewBorder.getPaddingTop(), 0, 0); + // Disable "center" rule because we no longer want to put it in the center. + int[] rules = p.getRules(); + rules[RelativeLayout.CENTER_IN_PARENT] = 0; + mFocusRectangle.requestLayout(); + + // Set the focus area and do autofocus. + setCameraParameters(UPDATE_PARAM_PREFERENCE); + autoFocus(); + + return true; } - private void clearFocusState() { - mFocusState = FOCUS_NOT_STARTED; - updateFocusIndicator(); + // Convert the touch point to the focus area in driver format. + public static void convertToFocusArea(int left, int top, int focusWidth, int focusHeight, + int previewWidth, int previewHeight, Rect rect) { + rect.left = Math.round((float) left / previewWidth * 2000 - 1000); + rect.top = Math.round((float) top / previewHeight * 2000 - 1000); + rect.right = Math.round((float) (left + focusWidth) / previewWidth * 2000 - 1000); + rect.bottom = Math.round((float) (top + focusHeight) / previewHeight * 2000 - 1000); } - private void updateFocusIndicator() { - if (mFocusRectangle == null) return; + void resetTouchFocus() { + // Put focus rectangle to the center. + RelativeLayout.LayoutParams p = + (RelativeLayout.LayoutParams) mFocusRectangle.getLayoutParams(); + int[] rules = p.getRules(); + rules[RelativeLayout.CENTER_IN_PARENT] = RelativeLayout.TRUE; + p.setMargins(0, 0, 0, 0); - if (mFocusState == FOCUSING || mFocusState == FOCUSING_SNAP_ON_FINISH) { - mFocusRectangle.showStart(); - } else if (mFocusState == FOCUS_SUCCESS) { - mFocusRectangle.showSuccess(); - } else if (mFocusState == FOCUS_FAIL) { - mFocusRectangle.showFail(); - } else { - mFocusRectangle.clear(); - } + mFocusArea = null; } @Override @@ -1643,22 +1745,22 @@ public class Camera extends ActivityBase implements View.OnClickListener, private void doSnap() { if (collapseCameraControls()) return; - Log.v(TAG, "doSnap: mFocusState=" + mFocusState); + Log.v(TAG, "doSnap: mCameraState=" + mCameraState); // If the user has half-pressed the shutter and focus is completed, we // can take the photo right away. If the focus mode is infinity, we can // also take the photo. if (mFocusMode.equals(Parameters.FOCUS_MODE_INFINITY) || mFocusMode.equals(Parameters.FOCUS_MODE_FIXED) || mFocusMode.equals(Parameters.FOCUS_MODE_EDOF) - || (mFocusState == FOCUS_SUCCESS - || mFocusState == FOCUS_FAIL)) { + || (mCameraState == FOCUS_SUCCESS + || mCameraState == FOCUS_FAIL)) { capture(); - } else if (mFocusState == FOCUSING) { + } else if (mCameraState == FOCUSING) { // Half pressing the shutter (i.e. the focus button event) will // already have requested AF for us, so just request capture on // focus here. - mFocusState = FOCUSING_SNAP_ON_FINISH; - } else if (mFocusState == FOCUS_NOT_STARTED) { + mCameraState = FOCUSING_SNAP_ON_FINISH; + } else if (mCameraState == IDLE) { // Focus key down event is dropped for some reasons. Just ignore. } } @@ -1670,9 +1772,19 @@ public class Camera extends ActivityBase implements View.OnClickListener, || mFocusMode.equals(Parameters.FOCUS_MODE_FIXED) || mFocusMode.equals(Parameters.FOCUS_MODE_EDOF))) { if (pressed) { // Focus key down. - autoFocus(); + // Do not do focus if there is not enoguh storage. Do not focus + // if touch focus has been triggered, that is, camera state is + // FOCUS_SUCCESS or FOCUS_FAIL. + if (canTakePicture() && mCameraState != FOCUS_SUCCESS + && mCameraState != FOCUS_FAIL) { + autoFocus(); + } } else { // Focus key up. - cancelAutoFocus(); + // User releases half-pressed focus key. + if (mCameraState == FOCUSING || mCameraState == FOCUS_SUCCESS + || mCameraState == FOCUS_FAIL) { + cancelAutoFocus(); + } } } } @@ -1705,7 +1817,8 @@ public class Camera extends ActivityBase implements View.OnClickListener, // changed. Sometimes this happens when the device is held in portrait // and camera app is opened. Rotation animation takes some time and // display rotation in onCreate may not be what we want. - if (mPreviewing && (Util.getDisplayRotation(this) == mDisplayRotation) + if (mCameraState != PREVIEW_STOPPED + && (Util.getDisplayRotation(this) == mDisplayRotation) && holder.isCreating()) { // Set preview display if the surface is being created and preview // was already started. That means preview display was set to null @@ -1742,7 +1855,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, CameraHolder.instance().release(); mCameraDevice.setZoomChangeListener(null); mCameraDevice = null; - mPreviewing = false; + mCameraState = PREVIEW_STOPPED; } } @@ -1782,12 +1895,14 @@ public class Camera extends ActivityBase implements View.OnClickListener, private void startPreview() throws CameraHardwareException { if (mPausing || isFinishing()) return; + resetTouchFocus(); + ensureCameraDevice(); mCameraDevice.setErrorCallback(mErrorCallback); // If we're previewing already, stop the preview first (this will blank // the screen). - if (mPreviewing) stopPreview(); + if (mCameraState != PREVIEW_STOPPED) stopPreview(); setPreviewDisplay(mSurfaceHolder); mDisplayRotation = Util.getDisplayRotation(this); @@ -1802,19 +1917,18 @@ public class Camera extends ActivityBase implements View.OnClickListener, closeCamera(); throw new RuntimeException("startPreview failed", ex); } - mPreviewing = true; mZoomState = ZOOM_STOPPED; - mStatus = IDLE; + mCameraState = IDLE; } private void stopPreview() { - if (mCameraDevice != null && mPreviewing) { + if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { Log.v(TAG, "stopPreview"); mCameraDevice.stopPreview(); } - mPreviewing = false; + mCameraState = PREVIEW_STOPPED; // If auto focus was in progress, it would have been canceled. - clearFocusState(); + updateFocusUI(); } private static boolean isSupported(String value, List<String> supported) { @@ -1840,6 +1954,16 @@ public class Camera extends ActivityBase implements View.OnClickListener, } private void updateCameraParametersPreference() { + if (mParameters.getMaxNumFocusAreas() > 0) { + mParameters.setFocusAreas(mFocusArea); + Log.d(TAG, "Parameter focus areas=" + mParameters.get("focus-areas")); + } + + if (mParameters.getMaxNumMeteringAreas() > 0) { + // Use the same area for focus and metering. + mParameters.setMeteringAreas(mFocusArea); + } + // Set picture size. String pictureSize = mPreferences.getString( CameraSettings.KEY_PICTURE_SIZE, null); @@ -2049,7 +2173,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, 1000, 0F, mLocationListeners[1]); - } catch (java.lang.SecurityException ex) { + } catch (SecurityException ex) { Log.i(TAG, "fail to request location update, ignore", ex); } catch (IllegalArgumentException ex) { Log.d(TAG, "provider does not exist " + ex.getMessage()); @@ -2061,7 +2185,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, 0F, mLocationListeners[0]); showGpsOnScreenIndicator(false); - } catch (java.lang.SecurityException ex) { + } catch (SecurityException ex) { Log.i(TAG, "fail to request location update, ignore", ex); } catch (IllegalArgumentException ex) { Log.d(TAG, "provider does not exist " + ex.getMessage()); @@ -2095,7 +2219,7 @@ public class Camera extends ActivityBase implements View.OnClickListener, } private boolean isCameraIdle() { - return mStatus == IDLE && mFocusState == FOCUS_NOT_STARTED; + return mCameraState == IDLE || mCameraState == FOCUS_SUCCESS || mCameraState == FOCUS_FAIL; } private boolean isImageCaptureIntent() { @@ -2332,10 +2456,27 @@ public class Camera extends ActivityBase implements View.OnClickListener, String str = getResources().getString(R.string.not_selectable_in_scene_mode); mNotSelectableToast = Toast.makeText(Camera.this, str, Toast.LENGTH_SHORT); } - mNotSelectableToast.cancel(); mNotSelectableToast.show(); } + private void onShareButtonClicked() { + if (mPausing) return; + + // Share the last captured picture. + if (mThumbnailButton.getUri() != null) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("image/jpeg"); + intent.putExtra(Intent.EXTRA_STREAM, mThumbnailButton.getUri()); + startActivity(Intent.createChooser(intent, getString(R.string.share_picture_via))); + } else { // No last picture + if (mNoShareToast == null) { + mNoShareToast = Toast.makeText(this, + getResources().getString(R.string.no_picture_to_share), Toast.LENGTH_SHORT); + } + mNoShareToast.show(); + } + } + private class MyIndicatorWheelListener implements IndicatorWheel.Listener { public void onSharedPreferenceChanged() { Camera.this.onSharedPreferenceChanged(); @@ -2348,6 +2489,10 @@ public class Camera extends ActivityBase implements View.OnClickListener, public void onOverriddenPreferencesClicked() { Camera.this.onOverriddenPreferencesClicked(); } + + public void onShareButtonClicked() { + Camera.this.onShareButtonClicked(); + } } private class MyCameraPickerListener implements CameraPicker.Listener { diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 86dacd0..225cf88 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -56,8 +56,7 @@ public class CameraSettings { public static final int CURRENT_VERSION = 4; public static final int CURRENT_LOCAL_VERSION = 1; - // max video duration in seconds for mms and youtube. - private static final int MMS_VIDEO_DURATION = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW).duration; + // max video duration in seconds for youtube. private static final int YOUTUBE_VIDEO_DURATION = 15 * 60; // 15 mins private static final int DEFAULT_VIDEO_DURATION = 0; // no limit @@ -181,21 +180,6 @@ public class CameraSettings { if (timeLapseInterval != null) resetIfInvalid(timeLapseInterval); } - private static List<String> getSupportedTimeLapseProfiles(int cameraId) { - ArrayList<String> supportedProfiles = new ArrayList<String>(); - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_TIME_LAPSE_480P)) { - supportedProfiles.add(Integer.toString(CamcorderProfile.QUALITY_TIME_LAPSE_480P)); - } - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_TIME_LAPSE_720P)) { - supportedProfiles.add(Integer.toString(CamcorderProfile.QUALITY_TIME_LAPSE_720P)); - } - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_TIME_LAPSE_1080P)) { - supportedProfiles.add(Integer.toString(CamcorderProfile.QUALITY_TIME_LAPSE_1080P)); - } - - return supportedProfiles; - } - private void buildExposureCompensation( PreferenceGroup group, ListPreference exposure) { int max = mParameters.getMaxExposureCompensation(); @@ -358,9 +342,11 @@ public class CameraSettings { || context.getString(R.string.pref_video_quality_high).equals(quality); } - public static int getVideoDurationInMillis(Context context, String quality) { + public static int getVideoDurationInMillis(Context context, String quality, int cameraId) { if (context.getString(R.string.pref_video_quality_mms).equals(quality)) { - return MMS_VIDEO_DURATION * 1000; + int mmsVideoDuration = CamcorderProfile.get(cameraId, + CamcorderProfile.QUALITY_LOW).duration; + return mmsVideoDuration * 1000; } else if (context.getString(R.string.pref_video_quality_youtube).equals(quality)) { return YOUTUBE_VIDEO_DURATION * 1000; } @@ -417,11 +403,13 @@ public class CameraSettings { CharSequence[] entries = videoQuality.getEntries(); CharSequence[] values = videoQuality.getEntryValues(); if (Util.isMmsCapable(mContext)) { + int mmsVideoDuration = CamcorderProfile.get(mCameraId, + CamcorderProfile.QUALITY_LOW).duration; // We need to fill in the device-dependent value (in seconds). for (int i = 0; i < entries.length; ++i) { if (mContext.getString(R.string.pref_video_quality_mms).equals(values[i])) { entries[i] = entries[i].toString().replace( - "30", Integer.toString(MMS_VIDEO_DURATION)); + "30", Integer.toString(mmsVideoDuration)); break; } } diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java index cfef950..de176c4 100644 --- a/src/com/android/camera/Util.java +++ b/src/com/android/camera/Util.java @@ -404,7 +404,7 @@ public class Util { } catch (java.lang.reflect.InvocationTargetException ite) { // Failure, must be another device. // Assume that it is voice capable. - } catch (java.lang.IllegalAccessException iae) { + } catch (IllegalAccessException iae) { // Failure, must be an other device. // Assume that it is voice capable. } catch (NoSuchMethodException nsme) { @@ -443,4 +443,12 @@ public class Util { return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); } + private static int mLocation[] = new int[2]; + + // This method is not thread-safe. + public static boolean pointInView(float x, float y, View v) { + v.getLocationInWindow(mLocation); + return x >= mLocation[0] && x < (mLocation[0] + v.getWidth()) + && y >= mLocation[1] && y < (mLocation[1] + v.getHeight()); + } } diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index f51a370..9615b90 100644 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -150,11 +150,15 @@ public class VideoCamera extends ActivityBase private CameraPicker mCameraPicker; private View mReviewControl; - private boolean mIsVideoCaptureIntent; - private boolean mQuickCapture; - + private Toast mNoShareToast; // The last recorded video. private RotateImageView mThumbnailButton; + private ShutterButton mShutterButton; + private TextView mRecordingTimeView; + private SwitcherSet mSwitcher; + + private boolean mIsVideoCaptureIntent; + private boolean mQuickCapture; private boolean mOpenCameraFail = false; @@ -163,6 +167,7 @@ public class VideoCamera extends ActivityBase private MediaRecorder mMediaRecorder; private boolean mMediaRecorderRecording = false; private long mRecordingStartTime; + private boolean mRecordingTimeCountsDown = false; private long mOnResumeTime; // The video file that the hardware camera is about to record into // (or is recording into.) @@ -203,11 +208,6 @@ public class VideoCamera extends ActivityBase private ContentResolver mContentResolver; - private ShutterButton mShutterButton; - private TextView mRecordingTimeView; - private SwitcherSet mSwitcher; - private boolean mRecordingTimeCountsDown = false; - private final ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); private final Handler mHandler = new MainHandler(); @@ -527,12 +527,14 @@ public class VideoCamera extends ActivityBase if (mIndicatorWheel == null) return; loadCameraPreferences(); - String[] keys = new String[]{CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, - CameraSettings.KEY_WHITE_BALANCE, - CameraSettings.KEY_COLOR_EFFECT, + final String[] SETTING_KEYS = { + CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, CameraSettings.KEY_VIDEO_QUALITY, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL}; - mIndicatorWheel.initialize(this, mPreferenceGroup, keys, false); + final String[] OTHER_SETTING_KEYS = { + CameraSettings.KEY_WHITE_BALANCE, + CameraSettings.KEY_COLOR_EFFECT}; + mIndicatorWheel.initialize(this, mPreferenceGroup, SETTING_KEYS, OTHER_SETTING_KEYS); mIndicatorWheel.setListener(new MyIndicatorWheelListener()); mPopupGestureDetector = new GestureDetector(this, new PopupGestureListener()); @@ -725,7 +727,7 @@ public class VideoCamera extends ActivityBase mMaxVideoDurationInMs = 1000 * seconds; } else { mMaxVideoDurationInMs = - CameraSettings.getVideoDurationInMillis(this, quality); + CameraSettings.getVideoDurationInMillis(this, quality, mCameraId); } // Read time lapse recording interval. @@ -1103,7 +1105,7 @@ public class VideoCamera extends ActivityBase } else { resultCode = RESULT_CANCELED; } - setResult(resultCode, resultIntent); + setResultEx(resultCode, resultIntent); finish(); } @@ -1360,6 +1362,7 @@ public class VideoCamera extends ActivityBase // from MediaRecorder.OnErrorListener public void onError(MediaRecorder mr, int what, int extra) { + Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra); if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { // We may have run out of space on the sdcard. stopVideoRecording(); @@ -1394,6 +1397,11 @@ public class VideoCamera extends ActivityBase sendBroadcast(i); } + // For testing. + public boolean isRecording() { + return mMediaRecorderRecording; + } + private void startVideoRecording() { Log.v(TAG, "startVideoRecording"); @@ -1906,6 +1914,24 @@ public class VideoCamera extends ActivityBase } + private void onShareButtonClicked() { + if (mPausing) return; + + // Share the last captured video. + if (mThumbnailButton.getUri() != null) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("video/*"); + intent.putExtra(Intent.EXTRA_STREAM, mThumbnailButton.getUri()); + startActivity(Intent.createChooser(intent, getString(R.string.share_video_via))); + } else { // No last picture + if (mNoShareToast == null) { + mNoShareToast = Toast.makeText(this, + getResources().getString(R.string.no_picture_to_share), Toast.LENGTH_SHORT); + } + mNoShareToast.show(); + } + } + private class MyIndicatorWheelListener implements IndicatorWheel.Listener { public void onSharedPreferenceChanged() { VideoCamera.this.onSharedPreferenceChanged(); @@ -1917,6 +1943,10 @@ public class VideoCamera extends ActivityBase public void onOverriddenPreferencesClicked() { } + + public void onShareButtonClicked() { + VideoCamera.this.onShareButtonClicked(); + } } private class MyCameraPickerListener implements CameraPicker.Listener { @@ -1935,28 +1965,20 @@ public class VideoCamera extends ActivityBase return super.dispatchTouchEvent(m); } - private int mLocation[] = new int[2]; private class PopupGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { // Check if the popup window is visible. - View v = mIndicatorWheel.getActivePopupWindow(); - if (v == null) return false; - - int x = Math.round(e.getX()); - int y = Math.round(e.getY()); - - // Dismiss the popup window if users touch on the outside. - v.getLocationOnScreen(mLocation); - if (x < mLocation[0] || (x > mLocation[0] + v.getWidth()) - || y < mLocation[1] || (y > mLocation[1] + v.getHeight())) { - // Let indicator wheel handle its own event. - mIndicatorWheel.getLocationOnScreen(mLocation); - if (x < mLocation[0] || (x > mLocation[0] + mIndicatorWheel.getWidth()) - || y < mLocation[1] || (y > mLocation[1] + mIndicatorWheel.getHeight())) { - mIndicatorWheel.dismissSettingPopup(); - } + View popup = mIndicatorWheel.getActivePopupWindow(); + if (popup == null) return false; + + // Let popup window or indicator wheel handle the event by + // themselves. Dismiss the popup window if users touch on other + // areas. + if (!Util.pointInView(e.getX(), e.getY(), popup) + && !Util.pointInView(e.getX(), e.getY(), mIndicatorWheel)) { + mIndicatorWheel.dismissSettingPopup(); // Let event fall through. } return false; diff --git a/src/com/android/camera/ui/AbstractSettingPopup.java b/src/com/android/camera/ui/AbstractSettingPopup.java index 32fbe67..794152e 100644 --- a/src/com/android/camera/ui/AbstractSettingPopup.java +++ b/src/com/android/camera/ui/AbstractSettingPopup.java @@ -44,4 +44,6 @@ abstract public class AbstractSettingPopup extends LinearLayout { mTitle = (TextView) findViewById(R.id.title); mSettingList = (ViewGroup) findViewById(R.id.settingList); } + + abstract public void reloadPreference(); } diff --git a/src/com/android/camera/ui/BasicSettingPopup.java b/src/com/android/camera/ui/BasicSettingPopup.java index 2e33720..1496e9e 100644 --- a/src/com/android/camera/ui/BasicSettingPopup.java +++ b/src/com/android/camera/ui/BasicSettingPopup.java @@ -80,6 +80,7 @@ public class BasicSettingPopup extends AbstractSettingPopup implements } // The value of the preference may have changed. Update the UI. + @Override public void reloadPreference() { int index = mPreference.findIndexOfValue(mPreference.getValue()); if (index != -1) { diff --git a/src/com/android/camera/ui/IndicatorWheel.java b/src/com/android/camera/ui/IndicatorWheel.java index e24fbc5..df0ebbb 100644 --- a/src/com/android/camera/ui/IndicatorWheel.java +++ b/src/com/android/camera/ui/IndicatorWheel.java @@ -38,13 +38,12 @@ import android.view.MotionEvent; import android.view.ViewGroup; import android.view.View; -import java.lang.Math; import java.util.ArrayList; /** * A view that contains shutter button and camera setting indicators. The * indicators are spreaded around the shutter button. The first child is always - * the shutter button. + * the shutter button. The last indicator is always the share button. */ public class IndicatorWheel extends ViewGroup implements BasicSettingPopup.Listener, OtherSettingsPopup.Listener { @@ -84,9 +83,12 @@ public class IndicatorWheel extends ViewGroup implements private Context mContext; private PreferenceGroup mPreferenceGroup; - private ArrayList<String> mPreferenceKeys; - private BasicSettingPopup[] mBasicSettingPopups; - private OtherSettingsPopup mOtherSettingsPopup; + // Preference key of every setting (except other settings) on the wheel . + private ArrayList<String> mPrefKeys; + private String[] mOtherSettingPrefKeys; + // Popup window of every camera setting on the wheel. + private AbstractSettingPopup[] mSettingPopups; + private int mIndicatorCount; private Animation mFadeIn, mFadeOut; // The previous view that has the animation. The animation may have stopped. @@ -96,6 +98,7 @@ public class IndicatorWheel extends ViewGroup implements public void onSharedPreferenceChanged(); public void onRestorePreferencesClicked(); public void onOverriddenPreferencesClicked(); + public void onShareButtonClicked(); } public void setListener(Listener listener) { @@ -141,8 +144,7 @@ public class IndicatorWheel extends ViewGroup implements public boolean onTouchEvent(MotionEvent event) { if (!isEnabled()) return false; - int count = getChildCount(); - if (count <= 1) return false; + if (mIndicatorCount == 0) return false; // Check if any setting is pressed. int action = event.getAction(); @@ -155,17 +157,16 @@ public class IndicatorWheel extends ViewGroup implements double radius = Math.sqrt(dx * dx + dy * dy); // Ignore the event if it's too near to the shutter button or too far // from the shutter button. - if (radius >= mShutterButtonRadius && radius <= mWheelRadius + mStrokeWidth) { double delta = Math.atan2(dy, dx); if (delta < 0) delta += Math.PI * 2; // Check which sector is pressed. if (delta > mSectorInitialRadians[0]) { - for (int i = 1; i < count; i++) { - if (delta < mSectorInitialRadians[i]) { + for (int i = 0; i < mIndicatorCount; i++) { + if (delta < mSectorInitialRadians[i + 1]) { // If the touch is moving around the same indicator with // popup opened, return now to avoid redundent works. - if (action == MotionEvent.ACTION_MOVE && (mSelectedIndex == i - 1)) { + if (action == MotionEvent.ACTION_MOVE && (mSelectedIndex == i)) { return false; } @@ -173,7 +174,7 @@ public class IndicatorWheel extends ViewGroup implements dismissSettingPopup(); // Do nothing if scene mode overrides the setting. - View child = getChildAt(i); + View child = getChildAt(i + 1); // first child is shutter button if (child instanceof IndicatorButton) { if (((IndicatorButton) child).isOverridden()) { // Do not notify in ACTION_MOVE to avoid lots of @@ -185,13 +186,13 @@ public class IndicatorWheel extends ViewGroup implements } } if (action == MotionEvent.ACTION_DOWN - && (selectedIndex == i - 1) && (mJustDeselectedIndex != i - 1)) { + && (selectedIndex == i) && (mJustDeselectedIndex != i)) { // The same indicator is pressed with popup opened. - mJustDeselectedIndex = i - 1; + mJustDeselectedIndex = i; } else { - if ((mJustDeselectedIndex != i - 1) + if ((mJustDeselectedIndex != i) || (selectedIndex == -1 && action == MotionEvent.ACTION_DOWN)) { - showSettingPopup(i - 1); + showSettingPopup(i); mJustDeselectedIndex = -1; } } @@ -219,6 +220,7 @@ public class IndicatorWheel extends ViewGroup implements if (count > 1) { removeViews(1, count - 1); } + mIndicatorCount = 0; } @Override @@ -425,43 +427,49 @@ public class IndicatorWheel extends ViewGroup implements } } - protected boolean addIndicator( - Context context, PreferenceGroup group, String key) { - IconListPreference pref = (IconListPreference) group.findPreference(key); - if (pref == null) return false; - IndicatorButton b = new IndicatorButton(context, pref); - addView(b); - return true; + protected void addIndicator(Context context, IconListPreference pref) { + addView(new IndicatorButton(context, pref)); + mIndicatorCount++; } - private void addOtherSettingIndicator(Context context) { + private void addIndicator(Context context, int resId) { ImageView b = new ImageView(context); - b.setImageResource(R.drawable.ic_viewfinder_settings); + b.setImageResource(resId); b.setClickable(false); addView(b); + mIndicatorCount++; } public void initialize(Context context, PreferenceGroup group, - String[] keys, boolean enableOtherSettings) { + String[] keys, String[] otherSettingKeys) { // Reset the variables and states. dismissSettingPopup(); removeIndicators(); - mOtherSettingsPopup = null; mSelectedIndex = -1; - mPreferenceKeys = new ArrayList<String>(); + mPrefKeys = new ArrayList<String>(); // Initialize all variables and icons. mPreferenceGroup = group; for (int i = 0; i < keys.length; i++) { - if (addIndicator(context, group, keys[i])) { - mPreferenceKeys.add(keys[i]); + IconListPreference pref = (IconListPreference) group.findPreference(keys[i]); + if (pref != null) { + addIndicator(context, pref); + mPrefKeys.add(keys[i]); } } - mBasicSettingPopups = new BasicSettingPopup[mPreferenceKeys.size()]; - if (enableOtherSettings) { - addOtherSettingIndicator(context); + int len = mPrefKeys.size(); + // Add other settings indicator. + mOtherSettingPrefKeys = otherSettingKeys; + if (mOtherSettingPrefKeys != null) { + addIndicator(context, R.drawable.ic_viewfinder_settings); + len++; } + mSettingPopups = new AbstractSettingPopup[len]; + + // Add share button. It is always the last one. + addIndicator(context, R.drawable.ic_viewfinder_share); + requestLayout(); } @@ -486,53 +494,51 @@ public class IndicatorWheel extends ViewGroup implements } } - private void initializeSettingPopup(int index) { - IconListPreference pref = (IconListPreference) - mPreferenceGroup.findPreference(mPreferenceKeys.get(index)); - - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - ViewGroup root = (ViewGroup) getRootView().findViewById(R.id.app_root); - BasicSettingPopup popup = (BasicSettingPopup) inflater.inflate( - R.layout.basic_setting_popup, root, false); - mBasicSettingPopups[index] = popup; - popup.setSettingChangedListener(this); - popup.initialize(pref); - root.addView(popup); + public void onShareButtonClicked() { + if (mListener != null) { + mListener.onShareButtonClicked(); + } } - private void initializeOtherSettingPopup() { + private void initializeSettingPopup(int index) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); ViewGroup root = (ViewGroup) getRootView().findViewById(R.id.app_root); - mOtherSettingsPopup = (OtherSettingsPopup) inflater.inflate( - R.layout.other_setting_popup, root, false); - mOtherSettingsPopup.setOtherSettingChangedListener(this); - mOtherSettingsPopup.initialize(mPreferenceGroup); - root.addView(mOtherSettingsPopup); + if (index < mPrefKeys.size()) { + IconListPreference pref = (IconListPreference) + mPreferenceGroup.findPreference(mPrefKeys.get(index)); + + BasicSettingPopup popup = (BasicSettingPopup) inflater.inflate( + R.layout.basic_setting_popup, root, false); + mSettingPopups[index] = popup; + popup.setSettingChangedListener(this); + popup.initialize(pref); + } else { + // Initialize other settings popup window. + OtherSettingsPopup popup = (OtherSettingsPopup) inflater.inflate( + R.layout.other_setting_popup, root, false); + mSettingPopups[index] = popup; + popup.setSettingChangedListener(this); + popup.initialize(mPreferenceGroup, mOtherSettingPrefKeys); + } + root.addView(mSettingPopups[index]); } private void showSettingPopup(int index) { if (index == mSelectedIndex) return; - if (index < mBasicSettingPopups.length) { - if (mBasicSettingPopups[index] == null) { - initializeSettingPopup(index); - } - } else if (mOtherSettingsPopup == null) { - initializeOtherSettingPopup(); + // The share button is the last indicator. + if (index == mIndicatorCount - 1) { + onShareButtonClicked(); + return; } - View popup; + if (mSettingPopups[index] == null) initializeSettingPopup(index); + if (mPrevAnimatingView != null) mPrevAnimatingView.clearAnimation(); - if (index == mBasicSettingPopups.length) { - popup = mOtherSettingsPopup; - } else { - popup = mBasicSettingPopups[index]; - } - popup.startAnimation(mFadeIn); - popup.setVisibility(View.VISIBLE); - mPrevAnimatingView = popup; + mSettingPopups[index].startAnimation(mFadeIn); + mSettingPopups[index].setVisibility(View.VISIBLE); + mPrevAnimatingView = mSettingPopups[index]; setHighlight(index, true); mSelectedIndex = index; invalidate(); @@ -540,16 +546,10 @@ public class IndicatorWheel extends ViewGroup implements public boolean dismissSettingPopup() { if (mSelectedIndex >= 0) { - View popup; if (mPrevAnimatingView != null) mPrevAnimatingView.clearAnimation(); - if (mSelectedIndex == mBasicSettingPopups.length) { - popup = mOtherSettingsPopup; - } else { - popup = mBasicSettingPopups[mSelectedIndex]; - } - popup.startAnimation(mFadeOut); - popup.setVisibility(View.INVISIBLE); - mPrevAnimatingView = popup; + mSettingPopups[mSelectedIndex].startAnimation(mFadeOut); + mSettingPopups[mSelectedIndex].setVisibility(View.INVISIBLE); + mPrevAnimatingView = mSettingPopups[mSelectedIndex]; setHighlight(mSelectedIndex, false); mSelectedIndex = -1; invalidate(); @@ -560,11 +560,7 @@ public class IndicatorWheel extends ViewGroup implements public View getActivePopupWindow() { if (mSelectedIndex >= 0) { - if (mSelectedIndex == mBasicSettingPopups.length) { - return mOtherSettingsPopup; - } else { - return mBasicSettingPopups[mSelectedIndex]; - } + return mSettingPopups[mSelectedIndex]; } else { return null; } @@ -576,25 +572,26 @@ public class IndicatorWheel extends ViewGroup implements throw new IllegalArgumentException(); } - if (mOtherSettingsPopup == null) { - initializeOtherSettingPopup(); - } - + // Override the setting indicator. for (int i = 0; i < keyvalues.length; i += 2) { String key = keyvalues[i]; String value = keyvalues[i + 1]; overrideSettings(key, value); - mOtherSettingsPopup.overrideSettings(key, value); + } + + // Override other settings. + if (mOtherSettingPrefKeys != null) { + int index = mPrefKeys.size(); + if (mSettingPopups[index] == null) initializeSettingPopup(index); + OtherSettingsPopup popup = (OtherSettingsPopup) mSettingPopups[index]; + popup.overrideSettings(keyvalues); } } public void reloadPreferences() { mPreferenceGroup.reloadValue(); - for (BasicSettingPopup popup: mBasicSettingPopups) { + for (AbstractSettingPopup popup: mSettingPopups) { if (popup != null) popup.reloadPreference(); } - if (mOtherSettingsPopup != null) { - mOtherSettingsPopup.reloadPreference(); - } } } diff --git a/src/com/android/camera/ui/OtherSettingsPopup.java b/src/com/android/camera/ui/OtherSettingsPopup.java index 018eb1c..920d270 100644 --- a/src/com/android/camera/ui/OtherSettingsPopup.java +++ b/src/com/android/camera/ui/OtherSettingsPopup.java @@ -41,12 +41,6 @@ public class OtherSettingsPopup extends AbstractSettingPopup implements InLineSettingPicker.Listener, AdapterView.OnItemClickListener { private static final String TAG = "OtherSettingsPopup"; - private static final String[] OTHER_SETTING_KEYS = { - CameraSettings.KEY_RECORD_LOCATION, - CameraSettings.KEY_FOCUS_MODE, - CameraSettings.KEY_EXPOSURE, - CameraSettings.KEY_PICTURE_SIZE, - CameraSettings.KEY_JPEG_QUALITY}; private static final String ITEM_KEY = "key"; private static final String ITEM_TITLE = "text"; private static final String ITEM_VALUE = "value"; @@ -100,7 +94,7 @@ public class OtherSettingsPopup extends AbstractSettingPopup } } - public void setOtherSettingChangedListener(Listener listener) { + public void setSettingChangedListener(Listener listener) { mListener = listener; } @@ -109,12 +103,12 @@ public class OtherSettingsPopup extends AbstractSettingPopup mContext = context; } - public void initialize(PreferenceGroup group) { + public void initialize(PreferenceGroup group, String[] keys) { mPreferenceGroup = group; // Prepare the setting items. - for (int i = 0; i < OTHER_SETTING_KEYS.length; ++i) { + for (int i = 0; i < keys.length; ++i) { HashMap<String, Object> map = new HashMap<String, Object>(); - ListPreference pref = group.findPreference(OTHER_SETTING_KEYS[i]); + ListPreference pref = group.findPreference(keys[i]); if (pref != null) { map.put(ITEM_KEY, pref); map.put(ITEM_TITLE, pref.getTitle()); @@ -145,14 +139,18 @@ public class OtherSettingsPopup extends AbstractSettingPopup } // Scene mode can override other camera settings (ex: flash mode). - public void overrideSettings(String key, String value) { + public void overrideSettings(final String ... keyvalues) { int count = mSettingList.getChildCount(); - for (int i = 0; i < count; i++) { - ListPreference pref = (ListPreference) mListItem.get(i).get(ITEM_KEY); - if (pref != null && key.equals(pref.getKey())) { - InLineSettingPicker picker = - (InLineSettingPicker) mSettingList.getChildAt(i); - picker.overrideSettings(value); + for (int i = 0; i < keyvalues.length; i += 2) { + String key = keyvalues[i]; + String value = keyvalues[i + 1]; + for (int j = 0; j < count; j++) { + ListPreference pref = (ListPreference) mListItem.get(j).get(ITEM_KEY); + if (pref != null && key.equals(pref.getKey())) { + InLineSettingPicker picker = + (InLineSettingPicker) mSettingList.getChildAt(j); + picker.overrideSettings(value); + } } } } @@ -165,6 +163,7 @@ public class OtherSettingsPopup extends AbstractSettingPopup } } + @Override public void reloadPreference() { int count = mSettingList.getChildCount(); for (int i = 0; i < count; i++) { diff --git a/tests/src/com/android/camera/functional/CameraTest.java b/tests/src/com/android/camera/functional/CameraTest.java index 9d71301..5d2fd2d 100644 --- a/tests/src/com/android/camera/functional/CameraTest.java +++ b/tests/src/com/android/camera/functional/CameraTest.java @@ -1,6 +1,23 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.camera.functional; import com.android.camera.Camera; +import com.android.camera.R; import com.android.camera.VideoCamera; import android.app.Activity; @@ -12,6 +29,7 @@ import android.provider.MediaStore; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; +import android.view.KeyEvent; import java.io.File; import java.lang.ref.WeakReference; diff --git a/tests/src/com/android/camera/functional/ImageCaptureIntentTest.java b/tests/src/com/android/camera/functional/ImageCaptureIntentTest.java new file mode 100644 index 0000000..e9067be --- /dev/null +++ b/tests/src/com/android/camera/functional/ImageCaptureIntentTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.functional; + +import com.android.camera.Camera; +import com.android.camera.R; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.Process; +import android.provider.MediaStore; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.UiThreadTest; +import android.util.Log; +import android.view.KeyEvent; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; + +public class ImageCaptureIntentTest extends ActivityInstrumentationTestCase2 <Camera> { + private static final String TAG = "ImageCaptureIntentTest"; + private Intent mIntent; + + public ImageCaptureIntentTest() { + super(Camera.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + } + + @LargeTest + public void testNoExtraOutput() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + takePicture(); + pressDone(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_OK, getActivity().getResultCode()); + Intent resultData = getActivity().getResultData(); + Bitmap bitmap = (Bitmap) resultData.getParcelableExtra("data"); + assertNotNull(bitmap); + assertTrue(bitmap.getWidth() > 0); + assertTrue(bitmap.getHeight() > 0); + } + + @LargeTest + public void testExtraOutput() throws Exception { + File file = new File(Environment.getExternalStorageDirectory(), + "test.jpg"); + BufferedInputStream stream = null; + byte[] jpegData; + + try { + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); + setActivityIntent(mIntent); + getActivity(); + + takePicture(); + pressDone(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_OK, getActivity().getResultCode()); + + // Verify the jpeg file + int fileLength = (int) file.length(); + assertTrue(fileLength > 0); + jpegData = new byte[fileLength]; + stream = new BufferedInputStream(new FileInputStream(file)); + stream.read(jpegData); + } finally { + if (stream != null) stream.close(); + file.delete(); + } + + Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length); + assertTrue(b.getWidth() > 0); + assertTrue(b.getHeight() > 0); + } + + @LargeTest + public void testRetake() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + takePicture(); + pressRetake(); + takePicture(); + pressDone(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_OK, getActivity().getResultCode()); + Intent resultData = getActivity().getResultData(); + Bitmap bitmap = (Bitmap) resultData.getParcelableExtra("data"); + assertNotNull(bitmap); + assertTrue(bitmap.getWidth() > 0); + assertTrue(bitmap.getHeight() > 0); + } + + @LargeTest + public void testCancel() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + pressCancel(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode()); + } + + @LargeTest + public void testSnapshotCancel() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + takePicture(); + pressCancel(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode()); + } + + private void takePicture() throws Exception { + getInstrumentation().sendKeySync( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS)); + getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + Thread.sleep(4000); + } + + private void pressDone() { + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + getActivity().findViewById(R.id.btn_done).performClick(); + } + }); + } + + private void pressRetake() { + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + getActivity().findViewById(R.id.btn_retake).performClick(); + } + }); + } + + private void pressCancel() { + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + getActivity().findViewById(R.id.btn_cancel).performClick(); + } + }); + } +} diff --git a/tests/src/com/android/camera/functional/VideoCaptureIntentTest.java b/tests/src/com/android/camera/functional/VideoCaptureIntentTest.java new file mode 100644 index 0000000..b2a007e --- /dev/null +++ b/tests/src/com/android/camera/functional/VideoCaptureIntentTest.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.functional; + +import com.android.camera.VideoCamera; +import com.android.camera.R; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Environment; +import android.os.Process; +import android.provider.MediaStore; +import android.provider.MediaStore.Video.VideoColumns; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.UiThreadTest; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.net.URI; + +public class VideoCaptureIntentTest extends ActivityInstrumentationTestCase2 <VideoCamera> { + private static final String TAG = "VideoCaptureIntentTest"; + private Intent mIntent; + private Uri mVideoUri; + private File mFile, mFile2; + + public VideoCaptureIntentTest() { + super(VideoCamera.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + } + + @Override + protected void tearDown() throws Exception { + if (mVideoUri != null) { + ContentResolver resolver = getActivity().getContentResolver(); + Uri query = mVideoUri.buildUpon().build(); + String[] projection = new String[] {VideoColumns.DATA}; + + Cursor cursor = null; + try { + cursor = resolver.query(query, projection, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + new File(cursor.getString(0)).delete(); + } + } finally { + if (cursor != null) cursor.close(); + } + + resolver.delete(mVideoUri, null, null); + } + if (mFile != null) mFile.delete(); + if (mFile2 != null) mFile2.delete(); + super.tearDown(); + } + + @LargeTest + public void testNoExtraOutput() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + recordVideo(); + pressDone(); + + Intent resultData = getActivity().getResultData(); + mVideoUri = resultData.getData(); + assertNotNull(mVideoUri); + verify(getActivity(), mVideoUri); + } + + @LargeTest + public void testExtraOutput() throws Exception { + mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp"); + + Uri uri = Uri.fromFile(mFile); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + setActivityIntent(mIntent); + getActivity(); + + recordVideo(); + pressDone(); + + verify(getActivity(), uri); + } + + @LargeTest + public void testRetake() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + recordVideo(); + pressRetake(); + recordVideo(); + pressDone(); + + Intent resultData = getActivity().getResultData(); + mVideoUri = resultData.getData(); + assertNotNull(mVideoUri); + verify(getActivity(), mVideoUri); + } + + @LargeTest + public void testCancel() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + pressCancel(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode()); + } + + @LargeTest + public void testRecordCancel() throws Exception { + setActivityIntent(mIntent); + getActivity(); + + recordVideo(); + pressCancel(); + + assertTrue(getActivity().isFinishing()); + assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode()); + } + + @LargeTest + public void testExtraSizeLimit() throws Exception { + mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp"); + final long sizeLimit = 10000; // bytes + + Uri uri = Uri.fromFile(mFile); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mIntent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, sizeLimit); + setActivityIntent(mIntent); + getActivity(); + + recordVideo(5000); + pressDone(); + + verify(getActivity(), uri); + long length = mFile.length(); + Log.v(TAG, "Video size is " + length + " bytes."); + assertTrue(length > 0); + assertTrue("Actual size=" + length, length <= sizeLimit); + } + + @LargeTest + public void testExtraDurationLimit() throws Exception { + mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp"); + final int durationLimit = 2; // seconds + + Uri uri = Uri.fromFile(mFile); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit); + setActivityIntent(mIntent); + getActivity(); + + recordVideo(5000); + pressDone(); + + int duration = verify(getActivity(), uri); + // The duraion should be close to to the limit. The last video duration + // also has duration, so the total duration may exceeds the limit a + // little bit. + Log.v(TAG, "Video length is " + duration + " ms."); + assertTrue(duration < (durationLimit + 1) * 1000); + } + + @LargeTest + public void testExtraVideoQuality() throws Exception { + mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp"); + mFile2 = new File(Environment.getExternalStorageDirectory(), "video2.tmp"); + + Uri uri = Uri.fromFile(mFile); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); // low quality + setActivityIntent(mIntent); + getActivity(); + + recordVideo(); + pressDone(); + + verify(getActivity(), uri); + setActivity(null); + + uri = Uri.fromFile(mFile2); + mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // high quality + setActivityIntent(mIntent); + getActivity(); + + recordVideo(); + pressDone(); + + verify(getActivity(), uri); + assertTrue(mFile.length() <= mFile2.length()); + } + + // Verify result code, result data, and the duration. + private int verify(VideoCamera activity, Uri uri) throws Exception { + assertTrue(activity.isFinishing()); + assertEquals(Activity.RESULT_OK, activity.getResultCode()); + + // Verify the video file + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(activity, uri); + String duration = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION); + assertNotNull(duration); + int durationValue = Integer.parseInt(duration); + Log.v(TAG, "Video duration is " + durationValue); + assertTrue(durationValue > 0); + return durationValue; + } + + private void recordVideo(int ms) throws Exception { + getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + Thread.sleep(ms); + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + // If recording is in progress, stop it. Run these atomically in + // UI thread. + if (getActivity().isRecording()) { + getActivity().findViewById(R.id.shutter_button).performClick(); + } + } + }); + } + + private void recordVideo() throws Exception { + recordVideo(2000); + } + + private void pressDone() { + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + getActivity().findViewById(R.id.btn_done).performClick(); + } + }); + } + + private void pressRetake() { + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + getActivity().findViewById(R.id.btn_retake).performClick(); + } + }); + } + + private void pressCancel() { + getInstrumentation().runOnMainSync(new Runnable() { + public void run() { + getActivity().findViewById(R.id.btn_cancel).performClick(); + } + }); + } +} diff --git a/tests/src/com/android/camera/stress/CameraStartUp.java b/tests/src/com/android/camera/stress/CameraStartUp.java index 3e1ae25..781093d 100644 --- a/tests/src/com/android/camera/stress/CameraStartUp.java +++ b/tests/src/com/android/camera/stress/CameraStartUp.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.camera.stress; import com.android.camera.Camera; diff --git a/tests/src/com/android/camera/unittest/CameraTest.java b/tests/src/com/android/camera/unittest/CameraTest.java index 0e851e4..b0aa13b 100644 --- a/tests/src/com/android/camera/unittest/CameraTest.java +++ b/tests/src/com/android/camera/unittest/CameraTest.java @@ -1,7 +1,24 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.camera.unittest; import com.android.camera.Camera; +import android.graphics.Rect; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; @@ -23,4 +40,20 @@ public class CameraTest extends TestCase { assertEquals(0, Camera.roundOrientation(270 + 45)); assertEquals(0, Camera.roundOrientation(359)); } + + public void testConvertToFocusArea() { + Rect rect = new Rect(); + Camera.convertToFocusArea(0, 0, 100, 100, 800, 480, rect); + assertEquals(new Rect(-1000, -1000, -750, -583), rect); + Camera.convertToFocusArea(0, 0, 400, 240, 800, 480, rect); + assertEquals(new Rect(-1000, -1000, 0, 0), rect); + Camera.convertToFocusArea(400, 240, 400, 240, 800, 480, rect); + assertEquals(new Rect(0, 0, 1000, 1000), rect); + Camera.convertToFocusArea(200, 120, 400, 240, 800, 480, rect); + assertEquals(new Rect(-500, -500, 500, 500), rect); + Camera.convertToFocusArea(0, 0, 800, 480, 800, 480, rect); + assertEquals(new Rect(-1000, -1000, 1000, 1000), rect); + Camera.convertToFocusArea(860, 620, 100, 100, 960, 720, rect); + assertEquals(new Rect(792, 722, 1000, 1000), rect); + } } |
