summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--proguard.flags9
-rw-r--r--res/drawable-hdpi/focus_focus_failed.9.pngbin1361 -> 830 bytes
-rw-r--r--res/drawable-hdpi/focus_focused.9.pngbin1369 -> 832 bytes
-rw-r--r--res/drawable-hdpi/focus_focusing.9.pngbin1355 -> 827 bytes
-rw-r--r--res/drawable-hdpi/ic_viewfinder_share.jpgbin0 -> 906 bytes
-rw-r--r--[-rwxr-xr-x]res/drawable-mdpi/focus_focus_failed.9.pngbin769 -> 447 bytes
-rw-r--r--[-rwxr-xr-x]res/drawable-mdpi/focus_focused.9.pngbin779 -> 449 bytes
-rw-r--r--[-rwxr-xr-x]res/drawable-mdpi/focus_focusing.9.pngbin766 -> 445 bytes
-rw-r--r--res/drawable-mdpi/ic_viewfinder_share.pngbin0 -> 624 bytes
-rw-r--r--res/drawable-xlarge-mdpi/ic_viewfinder_share.pngbin0 -> 1472 bytes
-rw-r--r--res/layout-xlarge/preview_frame.xml5
-rw-r--r--res/layout/preview_frame.xml8
-rw-r--r--res/values/strings.xml12
-rw-r--r--src/com/android/camera/ActivityBase.java23
-rw-r--r--src/com/android/camera/Camera.java381
-rw-r--r--src/com/android/camera/CameraSettings.java28
-rw-r--r--src/com/android/camera/Util.java10
-rw-r--r--src/com/android/camera/VideoCamera.java84
-rw-r--r--src/com/android/camera/ui/AbstractSettingPopup.java2
-rw-r--r--src/com/android/camera/ui/BasicSettingPopup.java1
-rw-r--r--src/com/android/camera/ui/IndicatorWheel.java177
-rw-r--r--src/com/android/camera/ui/OtherSettingsPopup.java33
-rw-r--r--tests/src/com/android/camera/functional/CameraTest.java18
-rw-r--r--tests/src/com/android/camera/functional/ImageCaptureIntentTest.java178
-rw-r--r--tests/src/com/android/camera/functional/VideoCaptureIntentTest.java284
-rw-r--r--tests/src/com/android/camera/stress/CameraStartUp.java16
-rw-r--r--tests/src/com/android/camera/unittest/CameraTest.java33
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
index f0d9ca0..52336cc 100644
--- a/res/drawable-hdpi/focus_focus_failed.9.png
+++ b/res/drawable-hdpi/focus_focus_failed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/focus_focused.9.png b/res/drawable-hdpi/focus_focused.9.png
index e7c5c57..d941c48 100644
--- a/res/drawable-hdpi/focus_focused.9.png
+++ b/res/drawable-hdpi/focus_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi/focus_focusing.9.png b/res/drawable-hdpi/focus_focusing.9.png
index 0bef57a..1838a6c 100644
--- a/res/drawable-hdpi/focus_focusing.9.png
+++ b/res/drawable-hdpi/focus_focusing.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_viewfinder_share.jpg b/res/drawable-hdpi/ic_viewfinder_share.jpg
new file mode 100644
index 0000000..7a8949d
--- /dev/null
+++ b/res/drawable-hdpi/ic_viewfinder_share.jpg
Binary files differ
diff --git a/res/drawable-mdpi/focus_focus_failed.9.png b/res/drawable-mdpi/focus_focus_failed.9.png
index 62d0944..6b425ea 100755..100644
--- a/res/drawable-mdpi/focus_focus_failed.9.png
+++ b/res/drawable-mdpi/focus_focus_failed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/focus_focused.9.png b/res/drawable-mdpi/focus_focused.9.png
index 2cf4423..71adb2a 100755..100644
--- a/res/drawable-mdpi/focus_focused.9.png
+++ b/res/drawable-mdpi/focus_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi/focus_focusing.9.png b/res/drawable-mdpi/focus_focusing.9.png
index 3fea637..83bb2f0 100755..100644
--- a/res/drawable-mdpi/focus_focusing.9.png
+++ b/res/drawable-mdpi/focus_focusing.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_viewfinder_share.png b/res/drawable-mdpi/ic_viewfinder_share.png
new file mode 100644
index 0000000..a73bec5
--- /dev/null
+++ b/res/drawable-mdpi/ic_viewfinder_share.png
Binary files differ
diff --git a/res/drawable-xlarge-mdpi/ic_viewfinder_share.png b/res/drawable-xlarge-mdpi/ic_viewfinder_share.png
new file mode 100644
index 0000000..d8f223a
--- /dev/null
+++ b/res/drawable-xlarge-mdpi/ic_viewfinder_share.png
Binary files differ
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">&gt;</string>
<string name="setting_decrement" translatable="false">&lt;</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);
+ }
}