summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Rohde <codelogic@google.com>2014-12-05 12:17:15 -0800
committerPaul Rohde <codelogic@google.com>2014-12-17 10:46:54 -0800
commit987ee64612e2510004fdf08536746c87234d01c1 (patch)
tree453d22c3c2548eecaa9e23c2a6e0bcbe1482b949
parentb79279f49e365cc6da75c1bc44b4c786036d0aa7 (diff)
downloadandroid_packages_apps_Camera2-987ee64612e2510004fdf08536746c87234d01c1.tar.gz
android_packages_apps_Camera2-987ee64612e2510004fdf08536746c87234d01c1.tar.bz2
android_packages_apps_Camera2-987ee64612e2510004fdf08536746c87234d01c1.zip
Drop new focus indicator into Camera2.
* Create a new custom focus view that interacts with physical lens diopter changes. * Replace all occurances of the old focus indicator with the new one. Change-Id: Ia02646ce4d1eb059ecb8a1dfccc15dfc9c167e1b
-rw-r--r--res/layout/camera.xml4
-rw-r--r--res/raw/material_camera_focus.oggbin0 -> 19122 bytes
-rw-r--r--res/values/colors.xml1
-rw-r--r--res/values/dimens.xml3
-rw-r--r--src/com/android/camera/CaptureModule.java133
-rw-r--r--src/com/android/camera/CaptureModuleUI.java34
-rw-r--r--src/com/android/camera/FocusOverlayManager.java134
-rw-r--r--src/com/android/camera/PhotoModule.java5
-rw-r--r--src/com/android/camera/PhotoUI.java11
-rw-r--r--src/com/android/camera/SoundPlayer.java7
-rw-r--r--src/com/android/camera/VideoModule.java6
-rw-r--r--src/com/android/camera/VideoUI.java33
-rw-r--r--src/com/android/camera/app/CameraAppUI.java9
-rw-r--r--src/com/android/camera/one/AbstractOneCamera.java6
-rw-r--r--src/com/android/camera/one/OneCamera.java33
-rw-r--r--src/com/android/camera/one/v2/ImageCaptureManager.java76
-rw-r--r--src/com/android/camera/one/v2/OneCameraImpl.java5
-rw-r--r--src/com/android/camera/one/v2/OneCameraZslImpl.java21
-rw-r--r--src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java38
-rw-r--r--src/com/android/camera/one/v2/common/GenericOneCameraImpl.java33
-rw-r--r--src/com/android/camera/ui/FocusOverlay.java278
-rw-r--r--src/com/android/camera/ui/focus/AutoFocusRing.java102
-rw-r--r--src/com/android/camera/ui/focus/CameraCoordinateTransformer.java109
-rw-r--r--src/com/android/camera/ui/focus/FocusController.java101
-rw-r--r--src/com/android/camera/ui/focus/FocusRing.java55
-rw-r--r--src/com/android/camera/ui/focus/FocusRingRenderer.java239
-rw-r--r--src/com/android/camera/ui/focus/FocusRingView.java183
-rw-r--r--src/com/android/camera/ui/focus/FocusSound.java47
-rw-r--r--src/com/android/camera/ui/focus/ManualFocusRing.java93
-rw-r--r--src/com/android/camera/ui/motion/AnimationClock.java39
-rw-r--r--src/com/android/camera/ui/motion/DampedSpring.java145
-rw-r--r--src/com/android/camera/ui/motion/DynamicAnimation.java41
-rw-r--r--src/com/android/camera/ui/motion/DynamicAnimator.java116
-rw-r--r--src/com/android/camera/ui/motion/InterpolateUtils.java66
-rw-r--r--src/com/android/camera/ui/motion/Invalidator.java28
-rw-r--r--src/com/android/camera/ui/motion/UnitBezier.java157
-rw-r--r--src/com/android/camera/ui/motion/UnitCurve.java41
-rw-r--r--src/com/android/camera/ui/motion/UnitCurves.java44
-rw-r--r--src/com/android/camera/util/CameraUtil.java20
-rw-r--r--src/com/android/camera/widget/FilmstripView.java3
40 files changed, 1905 insertions, 594 deletions
diff --git a/res/layout/camera.xml b/res/layout/camera.xml
index 121dc0fbc..860376ea0 100644
--- a/res/layout/camera.xml
+++ b/res/layout/camera.xml
@@ -26,8 +26,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <com.android.camera.ui.FocusOverlay
- android:id="@+id/focus_overlay"
+ <com.android.camera.ui.focus.FocusRingView
+ android:id="@+id/focus_ring"
android:layout_width="match_parent"
android:layout_height="match_parent" />
diff --git a/res/raw/material_camera_focus.ogg b/res/raw/material_camera_focus.ogg
new file mode 100644
index 000000000..555d7f444
--- /dev/null
+++ b/res/raw/material_camera_focus.ogg
Binary files differ
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 2eee2ac82..3fc3131c9 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -66,6 +66,7 @@
<color name="bright_foreground_disabled_holo_dark">#ff4c4c4c</color>
<color name="bright_foreground_holo_dark">#fff3f3f3</color>
<color name="face_detect_start">#ffffff00</color>
+ <color name="focus_color">#ffffffff</color>
<color name="focus_debug">#90ffffff</color>
<color name="focus_debug_text">#b0ffffff</color>
<color name="focus_debug_success">#9000ff00</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c09463390..18257a24c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -93,6 +93,9 @@
<dimen name="switcher_size">72dp</dimen>
<dimen name="face_circle_stroke">1dip</dimen>
<dimen name="focus_debug_stroke">1dip</dimen>
+ <dimen name="focus_circle_stroke">1.25dp</dimen>
+ <dimen name="focus_circle_min_size">24dp</dimen>
+ <dimen name="focus_circle_max_size">96dp</dimen>
<dimen name="shutter_offset">-22dp</dimen>
<dimen name="size_thumbnail">200dip</dimen>
<dimen name="size_preview">400dip</dimen>
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 6fd69b2ef..7c00015bc 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -64,7 +64,6 @@ import com.android.camera.one.OneCamera.OpenCallback;
import com.android.camera.one.OneCamera.PhotoCaptureParameters;
import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash;
import com.android.camera.one.OneCameraManager;
-import com.android.camera.one.Settings3A;
import com.android.camera.one.v2.OneCameraManagerImpl;
import com.android.camera.remote.RemoteCameraModule;
import com.android.camera.session.CaptureSession;
@@ -73,6 +72,8 @@ import com.android.camera.settings.SettingsManager;
import com.android.camera.ui.CountDownView;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.TouchCoordinate;
+import com.android.camera.ui.focus.FocusController;
+import com.android.camera.ui.focus.FocusSound;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GcamHelper;
import com.android.camera.util.Size;
@@ -122,21 +123,6 @@ public class CaptureModule extends CameraModule
}
};
- /**
- * Hide AF target UI element.
- */
- Runnable mHideAutoFocusTargetRunnable = new Runnable() {
- @Override
- public void run() {
- // For debug UI off, showAutoFocusSuccess() just hides the AF UI.
- if (mFocusedAtEnd) {
- mUI.showAutoFocusSuccess();
- } else {
- mUI.showAutoFocusFailure();
- }
- }
- };
-
private static final Tag TAG = new Tag("CaptureModule");
private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
/** Enable additional debug output. */
@@ -175,7 +161,10 @@ public class CaptureModule extends CameraModule
/** Whether HDR is currently enabled. */
private boolean mHdrEnabled = false;
- /** State by the module state machine. */
+ private FocusController mFocusController;
+
+
+ /** State by the module state machine. */
private static enum ModuleState {
IDLE,
WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
@@ -223,7 +212,7 @@ public class CaptureModule extends CameraModule
/** Used to fetch and embed the location into captured images. */
private final LocationManager mLocationManager;
/** Plays sounds for countdown timer. */
- private SoundPlayer mCountdownSoundPlayer;
+ private SoundPlayer mSoundPlayer;
/** Whether the module is paused right now. */
private boolean mPaused;
@@ -306,6 +295,10 @@ public class CaptureModule extends CameraModule
mLayoutListener);
mAppController.setPreviewStatusListener(mUI);
+ mSoundPlayer = new SoundPlayer(mContext);
+ FocusSound focusSound = new FocusSound(mSoundPlayer, R.raw.material_camera_focus);
+ mFocusController = new FocusController(mUI.getFocusRing(), focusSound, mMainHandler);
+
// Set the preview texture from UI for the SurfaceTextureConsumer.
mPreviewConsumer.setSurfaceTexture(
mAppController.getCameraAppUI().getSurfaceTexture(),
@@ -315,7 +308,7 @@ public class CaptureModule extends CameraModule
mSensorManager = (SensorManager) (mContext.getSystemService(Context.SENSOR_SERVICE));
mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
- mCountdownSoundPlayer = new SoundPlayer(mContext);
+
String action = activity.getIntent().getAction();
mIsImageCaptureIntent = (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
@@ -417,9 +410,9 @@ public class CaptureModule extends CameraModule
@Override
public void onRemainingSecondsChanged(int remainingSeconds) {
if (remainingSeconds == 1) {
- mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
+ mSoundPlayer.play(R.raw.timer_final_second, 0.6f);
} else if (remainingSeconds == 2 || remainingSeconds == 3) {
- mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
+ mSoundPlayer.play(R.raw.timer_increment, 0.6f);
}
}
@@ -590,8 +583,8 @@ public class CaptureModule extends CameraModule
mAppController.getCameraAppUI().getSurfaceHeight());
}
- mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
- mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
+ mSoundPlayer.loadSound(R.raw.timer_final_second);
+ mSoundPlayer.loadSound(R.raw.timer_increment);
}
@Override
@@ -603,8 +596,8 @@ public class CaptureModule extends CameraModule
closeCamera();
resetTextureBufferSize();
mFrameDistributor.close();
- mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
- mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
+ mSoundPlayer.unloadSound(R.raw.timer_final_second);
+ mSoundPlayer.unloadSound(R.raw.timer_increment);
// Remove delayed resume trigger, if it hasn't been executed yet.
mMainHandler.removeCallbacksAndMessages(null);
@@ -619,7 +612,7 @@ public class CaptureModule extends CameraModule
@Override
public void destroy() {
- mCountdownSoundPlayer.release();
+ mSoundPlayer.release();
mCameraHandler.getLooper().quitSafely();
}
@@ -730,47 +723,36 @@ public class CaptureModule extends CameraModule
* Focus sequence starts for zone around tap location for single tap.
*/
@Override
- public void onSingleTapUp(View view, int x, int y) {
- Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
+ public void onSingleTapUp(View view, int viewX, int viewY) {
+ Log.v(TAG, "onSingleTapUp x=" + viewX + " y=" + viewY);
// TODO: This should query actual capability.
if (mCameraFacing == Facing.FRONT) {
return;
}
- triggerFocusAtScreenCoord(x, y);
+ startActiveFocusAt(viewX, viewY);
}
// TODO: Consider refactoring FocusOverlayManager.
// Currently AF state transitions are controlled in OneCameraImpl.
// PhotoModule uses FocusOverlayManager which uses API1/portability
// logic and coordinates.
- private void triggerFocusAtScreenCoord(int x, int y) {
+ private void startActiveFocusAt(int viewX, int viewY) {
if (mCamera == null) {
// If we receive this after the camera is closed, do nothing.
return;
}
- mTapToFocusWaitForActiveScan = true;
- // Show UI immediately even though scan has not started yet.
- float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
- mUI.setAutoFocusTarget(x, y, false,
- (int) (Settings3A.getAutoFocusRegionWidth() * mZoomValue * minEdge),
- (int) (Settings3A.getMeteringRegionWidth() * mZoomValue * minEdge));
- mUI.showAutoFocusInProgress();
-
- // Cancel any scheduled auto focus target UI actions.
- mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
- // Timeout in case camera fails to stop (unlikely).
- mMainHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mMainHandler.post(mHideAutoFocusTargetRunnable);
- }
- }, FOCUS_UI_TIMEOUT_MILLIS);
+ // TODO: make mFocusController final and remove null check.
+ if (mFocusController == null) {
+ Log.v(TAG, "CaptureModule mFocusController is null!");
+ return;
+ }
+ mFocusController.showActiveFocusAt(viewX, viewY);
// Normalize coordinates to [0,1] per CameraOne API.
float points[] = new float[2];
- points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
- points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
+ points[0] = (viewX - mPreviewArea.left) / mPreviewArea.width();
+ points[1] = (viewY - mPreviewArea.top) / mPreviewArea.height();
// Rotate coordinates to portrait orientation per CameraOne API.
Matrix rotationMatrix = new Matrix();
@@ -780,8 +762,11 @@ public class CaptureModule extends CameraModule
// Log touch (screen coordinates).
if (mZoomValue == 1f) {
- TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
- y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
+ TouchCoordinate touchCoordinate = new TouchCoordinate(
+ viewX - mPreviewArea.left,
+ viewY - mPreviewArea.top,
+ mPreviewArea.width(),
+ mPreviewArea.height());
// TODO: Add to logging: duration, rotation.
UsageStatistics.instance().tapToFocus(touchCoordinate, null);
}
@@ -790,13 +775,17 @@ public class CaptureModule extends CameraModule
/**
* Show AF target in center of preview.
*/
- private void setAutoFocusTargetPassive() {
- float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
- mUI.setAutoFocusTarget((int) mPreviewArea.centerX(), (int) mPreviewArea.centerY(),
- true,
- (int) (Settings3A.getAutoFocusRegionWidth() * mZoomValue * minEdge),
- (int) (Settings3A.getMeteringRegionWidth() * mZoomValue * minEdge));
- mUI.showAutoFocusInProgress();
+ private void startPassiveFocus() {
+ // TODO: make mFocusController final and remove null check.
+ if (mFocusController == null) {
+ return;
+ }
+
+ // TODO: Some passive focus scans may trigger on a location
+ // instead of the center of the screen.
+ mFocusController.showPassiveFocusAt(
+ (int) (mPreviewArea.width() / 2.0f),
+ (int) (mPreviewArea.height() / 2.0f));
}
/**
@@ -808,33 +797,17 @@ public class CaptureModule extends CameraModule
switch (state) {
case PASSIVE_SCAN:
- mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- setAutoFocusTargetPassive();
- }
- });
+ startPassiveFocus();
break;
case ACTIVE_SCAN:
- mTapToFocusWaitForActiveScan = false;
break;
case PASSIVE_FOCUSED:
case PASSIVE_UNFOCUSED:
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- mUI.setPassiveFocusSuccess(state == AutoFocusState.PASSIVE_FOCUSED);
- }
- });
+ mFocusController.clearFocusIndicator();
break;
case ACTIVE_FOCUSED:
case ACTIVE_UNFOCUSED:
- if (!mTapToFocusWaitForActiveScan) {
- mFocusedAtEnd = state != AutoFocusState.ACTIVE_UNFOCUSED;
- mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
- mMainHandler.post(mHideAutoFocusTargetRunnable);
- }
+ mFocusController.clearFocusIndicator();
break;
}
@@ -1274,6 +1247,12 @@ public class CaptureModule extends CameraModule
mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
Log.d(TAG, "starting preview ...");
+
+ // TODO: make mFocusController final and remove null check.
+ if (mFocusController != null) {
+ camera.setFocusDistanceListener(mFocusController);
+ }
+
// TODO: Consider rolling these two calls into one.
camera.startPreview(new Surface(mFrameDistributor.getInputSurfaceTexture()),
new CaptureReadyCallback() {
diff --git a/src/com/android/camera/CaptureModuleUI.java b/src/com/android/camera/CaptureModuleUI.java
index 67fb12f4c..6d7166066 100644
--- a/src/com/android/camera/CaptureModuleUI.java
+++ b/src/com/android/camera/CaptureModuleUI.java
@@ -32,6 +32,7 @@ import com.android.camera.ui.PreviewOverlay;
import com.android.camera.ui.PreviewOverlay.OnZoomChangedListener;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.ProgressOverlay;
+import com.android.camera.ui.focus.FocusRing;
import com.android.camera2.R;
/**
@@ -58,7 +59,7 @@ public class CaptureModuleUI implements
return true;
}
};
- private final FocusOverlayManager.FocusUI mFocusUI;
+ private final FocusRing mFocusRing;
private final CountDownView mCountdownView;
private int mPreviewAreaWidth;
@@ -139,7 +140,7 @@ public class CaptureModuleUI implements
mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
mProgressOverlay = (ProgressOverlay) mRootView.findViewById(R.id.progress_overlay);
- mFocusUI = (FocusOverlayManager.FocusUI) mRootView.findViewById(R.id.focus_overlay);
+ mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring);
mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
}
@@ -185,35 +186,12 @@ public class CaptureModuleUI implements
return mPreviewView.getTransform(m);
}
- public void showAutoFocusInProgress() {
- mFocusUI.onFocusStarted();
- }
-
- public void showAutoFocusSuccess() {
- mFocusUI.onFocusSucceeded();
- }
-
- public void showAutoFocusFailure() {
- mFocusUI.onFocusFailed();
- }
-
- public void setPassiveFocusSuccess(boolean success) {
- mFocusUI.setPassiveFocusSuccess(success);
+ public FocusRing getFocusRing() {
+ return mFocusRing;
}
public void showDebugMessage(String message) {
- mFocusUI.showDebugMessage(message);
- }
-
- public void setAutoFocusTarget(int x, int y, boolean isPassiveScan, int afSize, int aeSize) {
- mFocusUI.setFocusPosition(x, y, isPassiveScan, afSize, aeSize);
- }
-
- public void clearAutoFocusIndicator() {
- mFocusUI.clearFocus();
- }
-
- public void clearAutoFocusIndicator(boolean waitUntilProgressIsHidden) {
+ /* NoOp */
}
/**
diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java
index 514d76a85..92372e4d3 100644
--- a/src/com/android/camera/FocusOverlayManager.java
+++ b/src/com/android/camera/FocusOverlayManager.java
@@ -16,12 +16,9 @@
package com.android.camera;
-import android.annotation.TargetApi;
-import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Camera.Area;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -35,6 +32,8 @@ import com.android.camera.settings.SettingsManager;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.TouchCoordinate;
import com.android.camera.util.ApiHelper;
+import com.android.camera.ui.focus.CameraCoordinateTransformer;
+import com.android.camera.ui.focus.FocusRing;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.UsageStatistics;
import com.android.ex.camera2.portability.CameraCapabilities;
@@ -89,7 +88,7 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
private boolean mMeteringAreaSupported;
private boolean mLockAeAwbNeeded;
private boolean mAeAwbLock;
- private final Matrix mMatrix;
+ private CameraCoordinateTransformer mCoordinateTransformer;
private boolean mMirror; // true if the camera is front-facing.
private int mDisplayOrientation;
@@ -104,7 +103,7 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
private final Handler mHandler;
Listener mListener;
private boolean mPreviousMoving;
- private final FocusUI mUI;
+ private final FocusRing mFocusRing;
private final Rect mPreviewRect = new Rect(0, 0, 0, 0);
private boolean mFocusLocked;
@@ -112,20 +111,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
private TouchCoordinate mTouchCoordinate;
private long mTouchTime;
- public interface FocusUI {
- public boolean hasFaces();
- public void clearFocus();
- public void setFocusPosition(int x, int y, boolean isPassiveScan, int aFsize, int aEsize);
- public void setFocusPosition(int x, int y, boolean isPassiveScan);
- public void onFocusStarted();
- public void onFocusSucceeded();
- public void onFocusFailed();
- public void setPassiveFocusSuccess(boolean success);
- public void showDebugMessage(String message);
- public void pauseFaceDetection();
- public void resumeFaceDetection();
- }
-
public interface Listener {
public void autoFocus();
public void cancelAutoFocus();
@@ -170,16 +155,15 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
public FocusOverlayManager(AppController appController,
List<CameraCapabilities.FocusMode> defaultFocusModes, CameraCapabilities capabilities,
- Listener listener, boolean mirror, Looper looper, FocusUI ui) {
+ Listener listener, boolean mirror, Looper looper, FocusRing focusRing) {
mAppController = appController;
mSettingsManager = appController.getSettingsManager();
mHandler = new MainHandler(this, looper);
- mMatrix = new Matrix();
mDefaultFocusModes = new ArrayList<CameraCapabilities.FocusMode>(defaultFocusModes);
updateCapabilities(capabilities);
mListener = listener;
setMirror(mirror);
- mUI = ui;
+ mFocusRing = focusRing;
mFocusLocked = false;
}
@@ -202,7 +186,8 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
public void setPreviewRect(Rect previewRect) {
if (!mPreviewRect.equals(previewRect)) {
mPreviewRect.set(previewRect);
- setMatrix();
+ resetCoordinateTransformer();
+ mInitialized = true;
}
}
@@ -211,34 +196,27 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
setPreviewRect(CameraUtil.rectFToRect(previewArea));
}
- /** Returns a copy of mPreviewRect so that outside class cannot modify preview
- * rect except deliberately doing so through the setter. */
- public Rect getPreviewRect() {
- return new Rect(mPreviewRect);
- }
-
public void setMirror(boolean mirror) {
mMirror = mirror;
- setMatrix();
+ resetCoordinateTransformer();
}
public void setDisplayOrientation(int displayOrientation) {
mDisplayOrientation = displayOrientation;
- setMatrix();
+ resetCoordinateTransformer();
}
- private void setMatrix() {
- if (mPreviewRect.width() != 0 && mPreviewRect.height() != 0) {
- Matrix matrix = new Matrix();
- CameraUtil.prepareMatrix(matrix, mMirror, mDisplayOrientation, getPreviewRect());
- // In face detection, the matrix converts the driver coordinates to UI
- // coordinates. In tap focus, the inverted matrix converts the UI
- // coordinates to driver coordinates.
- matrix.invert(mMatrix);
- mInitialized = true;
+ private void resetCoordinateTransformer() {
+ if (mPreviewRect.width() > 0 && mPreviewRect.height() > 0) {
+ mCoordinateTransformer = new CameraCoordinateTransformer(mMirror, mDisplayOrientation,
+ CameraUtil.rectToRectF(mPreviewRect));
+ } else {
+ Log.w(TAG, "The coordinate transformer could not be built because the preview rect"
+ + "did not have a width and height");
}
}
+
private void lockAeAwbIfNeeded() {
if (mLockAeAwbNeeded && !mAeAwbLock) {
mAeAwbLock = true;
@@ -300,7 +278,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
} else {
mState = STATE_FAIL;
}
- updateFocusUI();
capture();
} else if (mState == STATE_FOCUSING) {
// This happens when (1) user is half-pressing the focus key or
@@ -311,7 +288,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
} else {
mState = STATE_FAIL;
}
- updateFocusUI();
// If this is triggered by touch focus, cancel focus after a
// while.
if (mFocusArea != null) {
@@ -333,13 +309,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
return;
}
-
- // Ignore if the camera has detected some faces.
- if (mUI.hasFaces()) {
- mUI.clearFocus();
- return;
- }
-
// Ignore if we have requested autofocus. This method only handles
// continuous autofocus.
if (mState != STATE_IDLE) {
@@ -349,26 +318,23 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
// animate on false->true trasition only b/8219520
if (moving && !mPreviousMoving) {
// Auto focus at the center of the preview.
- mUI.setFocusPosition(mPreviewRect.centerX(), mPreviewRect.centerY(), true,
- getAFRegionEdge(), getAERegionEdge());
- mUI.onFocusStarted();
- } else if (!moving) {
- mUI.onFocusSucceeded();
+ mFocusRing.startPassiveFocus();
+ } else if (!moving && mFocusRing.isPassiveFocusRunning()) {
+ mFocusRing.stopFocusAnimations();
}
mPreviousMoving = moving;
}
/** Returns width of auto focus region in pixels. */
- private int getAFRegionEdge() {
+ private int getAFRegionSizePx() {
return (int) (Math.min(mPreviewRect.width(), mPreviewRect.height()) * AF_REGION_BOX);
}
/** Returns width of metering region in pixels. */
- private int getAERegionEdge() {
+ private int getAERegionSizePx() {
return (int) (Math.min(mPreviewRect.width(), mPreviewRect.height()) * AE_REGION_BOX);
}
- @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void initializeFocusAreas(int x, int y) {
if (mFocusArea == null) {
mFocusArea = new ArrayList<Area>();
@@ -376,10 +342,9 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
}
// Convert the coordinates to driver format.
- calculateTapArea(x, y, getAFRegionEdge(), mFocusArea.get(0).rect);
+ mFocusArea.get(0).rect = computeCameraRectFromPreviewCoordinates(x, y, getAFRegionSizePx());
}
- @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void initializeMeteringAreas(int x, int y) {
if (mMeteringArea == null) {
mMeteringArea = new ArrayList<Area>();
@@ -387,7 +352,7 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
}
// Convert the coordinates to driver format.
- calculateTapArea(x, y, getAERegionEdge(), mMeteringArea.get(0).rect);
+ mMeteringArea.get(0).rect = computeCameraRectFromPreviewCoordinates(x, y, getAERegionSizePx());
}
public void onSingleTapUp(int x, int y) {
@@ -413,8 +378,9 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
initializeMeteringAreas(x, y);
}
- // Use margin to set the focus indicator to the touched area.
- mUI.setFocusPosition(x, y, false, getAFRegionEdge(), getAERegionEdge());
+ mFocusRing.startActiveFocus();
+ mFocusRing.setFocusLocation(x, y);
+
// Log manual tap to focus.
mTouchCoordinate = new TouchCoordinate(x, y, mPreviewRect.width(), mPreviewRect.height());
mTouchTime = System.currentTimeMillis();
@@ -427,7 +393,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
if (mFocusAreaSupported) {
autoFocus();
} else { // Just show the indicator in all other cases.
- updateFocusUI();
// Reset the metering area in 4 seconds.
mHandler.removeMessages(RESET_TOUCH_FOCUS);
mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY_MILLIS);
@@ -445,7 +410,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
public void onPreviewStopped() {
// If auto focus was in progress, it would have been stopped.
mState = STATE_IDLE;
- updateFocusUI();
}
public void onCameraReleased() {
@@ -468,10 +432,6 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
private void autoFocus(int focusingState) {
mListener.autoFocus();
mState = focusingState;
- // Pause the face view because the driver will keep sending face
- // callbacks after the focus completes.
- mUI.pauseFaceDetection();
- updateFocusUI();
mHandler.removeMessages(RESET_TOUCH_FOCUS);
}
@@ -498,10 +458,8 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
// driver is not reset.
resetTouchFocus();
mListener.cancelAutoFocus();
- mUI.resumeFaceDetection();
mState = STATE_IDLE;
mFocusLocked = false;
- updateFocusUI();
mHandler.removeMessages(RESET_TOUCH_FOCUS);
}
@@ -567,42 +525,11 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
return mMeteringArea;
}
- public void updateFocusUI() {
- if (!mInitialized) {
- // Show only focus indicator or face indicator.
- return;
- }
- if (mState == STATE_IDLE) {
- if (mFocusArea == null) {
- mUI.clearFocus();
- } else {
- // Users touch on the preview and the indicator represents the
- // metering area. Either focus area is not supported or
- // autoFocus call is not required.
- mUI.onFocusStarted();
- }
- } else if (mState == STATE_FOCUSING) {
- mUI.onFocusStarted();
- } else {
- if (mFocusMode == CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
- // TODO: check HAL behavior and decide if this can be removed.
- mUI.onFocusSucceeded();
- } else if (mState == STATE_SUCCESS) {
- mUI.onFocusSucceeded();
- } else if (mState == STATE_FAIL) {
- mUI.onFocusFailed();
- }
- }
- }
-
public void resetTouchFocus() {
if (!mInitialized) {
return;
}
- // Put focus indicator to the center. clear reset position
- mUI.clearFocus();
- // Initialize mFocusArea.
mFocusArea = null;
mMeteringArea = null;
// This will cause current module to call getFocusAreas() and
@@ -616,15 +543,14 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
}
}
- private void calculateTapArea(int x, int y, int size, Rect rect) {
+ private Rect computeCameraRectFromPreviewCoordinates(int x, int y, int size) {
int left = CameraUtil.clamp(x - size / 2, mPreviewRect.left,
mPreviewRect.right - size);
int top = CameraUtil.clamp(y - size / 2, mPreviewRect.top,
mPreviewRect.bottom - size);
RectF rectF = new RectF(left, top, left + size, top + size);
- mMatrix.mapRect(rectF);
- CameraUtil.rectFToRect(rectF, rect);
+ return CameraUtil.rectFToRect(mCoordinateTransformer.toCameraSpace(rectF));
}
/* package */ int getFocusState() {
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 659068dc3..735b4c9c9 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -41,7 +41,6 @@ import android.os.MessageQueue;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.view.KeyEvent;
-import android.view.OrientationEventListener;
import android.view.View;
import com.android.camera.PhotoModule.NamedImages.NamedEntity;
@@ -52,7 +51,6 @@ import com.android.camera.app.MediaSaver;
import com.android.camera.app.MemoryManager;
import com.android.camera.app.MemoryManager.MemoryListener;
import com.android.camera.app.MotionManager;
-import com.android.camera.app.OrientationManager;
import com.android.camera.debug.Log;
import com.android.camera.exif.ExifInterface;
import com.android.camera.exif.ExifTag;
@@ -1051,7 +1049,6 @@ public class PhotoModule
Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+ mPictureDisplayedToJpegCallbackTime + "ms");
- mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
if (!mIsImageCaptureIntent) {
setupPreview();
}
@@ -1658,7 +1655,7 @@ public class PhotoModule
mFocusManager =
new FocusOverlayManager(mAppController, defaultFocusModes,
mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
- mUI.getFocusUI());
+ mUI.getFocusRing());
mMotionManager = getServices().getMotionManager();
if (mMotionManager != null) {
mMotionManager.addListener(mFocusManager);
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index dac4cb3e3..6e59bf9d9 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -24,7 +24,6 @@ import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.Camera.Face;
import android.os.AsyncTask;
-import android.os.Build;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
@@ -32,13 +31,13 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import com.android.camera.FocusOverlayManager.FocusUI;
import com.android.camera.debug.DebugPropertyHelper;
import com.android.camera.debug.Log;
import com.android.camera.ui.CountDownView;
import com.android.camera.ui.FaceView;
import com.android.camera.ui.PreviewOverlay;
import com.android.camera.ui.PreviewStatusListener;
+import com.android.camera.ui.focus.FocusRing;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GservicesHelper;
@@ -58,7 +57,7 @@ public class PhotoUI implements PreviewStatusListener,
private static final float UNSET = 0f;
private final PreviewOverlay mPreviewOverlay;
- private final FocusUI mFocusUI;
+ private final FocusRing mFocusRing;
private final CameraActivity mActivity;
private final PhotoController mController;
@@ -232,7 +231,7 @@ public class PhotoUI implements PreviewStatusListener,
mActivity.getLayoutInflater().inflate(R.layout.photo_module,
moduleRoot, true);
initIndicators();
- mFocusUI = (FocusUI) mRootView.findViewById(R.id.focus_overlay);
+ mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring);
mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
// Show faces if we are in debug mode.
@@ -282,8 +281,8 @@ public class PhotoUI implements PreviewStatusListener,
}
- public FocusUI getFocusUI() {
- return mFocusUI;
+ public FocusRing getFocusRing() {
+ return mFocusRing;
}
public void updatePreviewAspectRatio(float aspectRatio) {
diff --git a/src/com/android/camera/SoundPlayer.java b/src/com/android/camera/SoundPlayer.java
index afb928b5c..ff3f37f7a 100644
--- a/src/com/android/camera/SoundPlayer.java
+++ b/src/com/android/camera/SoundPlayer.java
@@ -32,6 +32,7 @@ public class SoundPlayer {
private final SoundPool mSoundPool;
/** Keeps a mapping from sound resource ID to sound ID */
private final SparseIntArray mResourceToSoundId = new SparseIntArray();
+ private boolean mIsReleased = false;
/**
* Construct a new sound player.
@@ -78,13 +79,17 @@ public class SoundPlayer {
* released and the object cannot be re-used.
*/
public void release() {
+ mIsReleased = true;
mSoundPool.release();
}
+ public boolean isReleased() {
+ return mIsReleased;
+ }
+
private static int getAudioTypeForSoundPool() {
// STREAM_SYSTEM_ENFORCED is hidden API.
return ApiHelper.getIntFieldIfExists(AudioManager.class,
"STREAM_SYSTEM_ENFORCED", null, AudioManager.STREAM_RING);
}
-
}
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 12143c220..03024f94d 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -485,7 +485,7 @@ public class VideoModule extends CameraModule
}
mFocusManager = new FocusOverlayManager(mAppController,
defaultFocusModes, mCameraCapabilities, this, mMirror,
- mActivity.getMainLooper(), mUI.getFocusUI());
+ mActivity.getMainLooper(), mUI.getFocusRing());
}
mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
}
@@ -1324,7 +1324,7 @@ public class VideoModule extends CameraModule
Log.i(TAG, "startVideoRecording: " + Thread.currentThread());
mUI.cancelAnimations();
mUI.setSwipingEnabled(false);
- mUI.showFocusUI(false);
+ mUI.hidePassiveFocusIndicator();
mUI.showVideoRecordingHints(false);
mAppController.getCameraAppUI().hideCaptureIndicator();
@@ -1446,7 +1446,7 @@ public class VideoModule extends CameraModule
Log.v(TAG, "stopVideoRecording");
mUI.setSwipingEnabled(true);
- mUI.showFocusUI(true);
+ mUI.showPassiveFocusIndicator();
mUI.showVideoRecordingHints(true);
boolean fail = false;
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index 2cc02a7eb..76daf3aec 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -29,17 +29,15 @@ import android.widget.TextView;
import com.android.camera.app.OrientationManager;
import com.android.camera.debug.Log;
-import com.android.camera.ui.FocusOverlay;
import com.android.camera.ui.PreviewOverlay;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.RotateLayout;
+import com.android.camera.ui.focus.FocusRing;
import com.android.camera.widget.VideoRecordingHints;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraCapabilities;
import com.android.ex.camera2.portability.CameraSettings;
-import java.util.List;
-
public class VideoUI implements PreviewStatusListener {
private static final Log.Tag TAG = new Log.Tag("VideoUI");
@@ -48,7 +46,7 @@ public class VideoUI implements PreviewStatusListener {
// module fields
private final CameraActivity mActivity;
private final View mRootView;
- private final FocusOverlay mFocusUI;
+ private final FocusRing mFocusRing;
// An review image having same size as preview. It is displayed when
// recording is stopped in capture intent.
private ImageView mReviewImage;
@@ -108,7 +106,7 @@ public class VideoUI implements PreviewStatusListener {
initializeMiscControls();
mAnimationManager = new AnimationManager();
- mFocusUI = (FocusOverlay) mRootView.findViewById(R.id.focus_overlay);
+ mFocusRing = (FocusRing) mRootView.findViewById(R.id.focus_ring);
mVideoHints = (VideoRecordingHints) mRootView.findViewById(R.id.video_shooting_hints);
}
@@ -126,8 +124,8 @@ public class VideoUI implements PreviewStatusListener {
setAspectRatio(aspectRatio);
}
- public FocusOverlayManager.FocusUI getFocusUI() {
- return mFocusUI;
+ public FocusRing getFocusRing() {
+ return mFocusRing;
}
/**
@@ -237,16 +235,25 @@ public class VideoUI implements PreviewStatusListener {
}
/**
- * Shows or hides focus UI.
- *
- * @param show shows focus UI when true, hides it otherwise
+ * Hide the focus indicator.
+ */
+ public void hidePassiveFocusIndicator() {
+ if (mFocusRing != null) {
+ Log.v(TAG, "mFocusRing.stopFocusAnimations()");
+ mFocusRing.stopFocusAnimations();
+ }
+ }
+
+ /**
+ * Show the passive focus indicator.
*/
- public void showFocusUI(boolean show) {
- if (mFocusUI != null) {
- mFocusUI.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+ public void showPassiveFocusIndicator() {
+ if (mFocusRing != null) {
+ mFocusRing.startPassiveFocus();
}
}
+
/**
* Shows or hides video recording hints.
*
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index 5104604b9..e79f754c1 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -58,6 +58,7 @@ import com.android.camera.ui.PreviewOverlay;
import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.StickyBottomCaptureLayout;
import com.android.camera.ui.TouchCoordinate;
+import com.android.camera.ui.focus.FocusRing;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.Gusterpolator;
@@ -513,7 +514,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
private BottomBar mBottomBar;
private ModeOptionsOverlay mModeOptionsOverlay;
private IndicatorIconController mIndicatorIconController;
- private View mFocusOverlay;
+ private FocusRing mFocusRing;
private FrameLayout mTutorialsPlaceHolderWrapper;
private StickyBottomCaptureLayout mStickyBottomCaptureLayout;
private TextureViewHelper mTextureViewHelper;
@@ -1263,7 +1264,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
mController.getSettingsManager().addListener(mIndicatorIconController);
mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
- mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
+ mFocusRing = (FocusRing) mCameraRootView.findViewById(R.id.focus_ring);
mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView
.findViewById(R.id.tutorials_placeholder_wrapper);
mStickyBottomCaptureLayout = (StickyBottomCaptureLayout) mAppRootView
@@ -1307,7 +1308,9 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
setShutterButtonEnabled(true);
mPreviewStatusListener = null;
mPreviewOverlay.reset();
- mFocusOverlay.setVisibility(View.INVISIBLE);
+
+ Log.v(TAG, "mFocusRing.stopFocusAnimations()");
+ mFocusRing.stopFocusAnimations();
}
/**
diff --git a/src/com/android/camera/one/AbstractOneCamera.java b/src/com/android/camera/one/AbstractOneCamera.java
index 15276ecca..b9514b96f 100644
--- a/src/com/android/camera/one/AbstractOneCamera.java
+++ b/src/com/android/camera/one/AbstractOneCamera.java
@@ -32,6 +32,7 @@ import java.util.TimeZone;
public abstract class AbstractOneCamera implements OneCamera {
protected FocusStateListener mFocusStateListener;
protected ReadyStateChangedListener mReadyStateChangedListener;
+ protected FocusDistanceListener mFocusDistanceListener;
/**
* Number of characters from the end of the device serial number used to
@@ -45,6 +46,11 @@ public abstract class AbstractOneCamera implements OneCamera {
}
@Override
+ public void setFocusDistanceListener(FocusDistanceListener listener) {
+ mFocusDistanceListener = listener;
+ }
+
+ @Override
public void setReadyStateChangedListener(ReadyStateChangedListener listener) {
mReadyStateChangedListener = listener;
}
diff --git a/src/com/android/camera/one/OneCamera.java b/src/com/android/camera/one/OneCamera.java
index a45f83433..d4700717a 100644
--- a/src/com/android/camera/one/OneCamera.java
+++ b/src/com/android/camera/one/OneCamera.java
@@ -208,6 +208,33 @@ public interface OneCamera {
}
/**
+ * Classes implementing this interface will be called when the focus
+ * distance of the physical lens changes.
+ */
+ public static interface FocusDistanceListener {
+ /**
+ * Called when physical lens distance on the camera changes.
+ *
+ * @param diopter the lens diopter from the last known position.
+ * @param isActive whether the lens is moving.
+ */
+ public void onFocusDistance(float diopter, boolean isActive);
+ }
+
+ /**
+ * Single instance of the current camera AF state.
+ */
+ public static class FocusState {
+ public final float diopter;
+ public final boolean isActive;
+
+ public FocusState(float diopter, boolean isActive) {
+ this.diopter = diopter;
+ this.isActive = isActive;
+ }
+ }
+
+ /**
* Parameters to be given to capture requests.
*/
public static abstract class CaptureParameters {
@@ -333,6 +360,12 @@ public interface OneCamera {
public void setFocusStateListener(FocusStateListener listener);
/**
+ * Sets or replaces a listener that is called whenever the focus state of
+ * the camera changes.
+ */
+ public void setFocusDistanceListener(FocusDistanceListener listener);
+
+ /**
* Sets or replaces a listener that is called whenever the state of the
* camera changes to be either ready or not ready to take another picture.
*/
diff --git a/src/com/android/camera/one/v2/ImageCaptureManager.java b/src/com/android/camera/one/v2/ImageCaptureManager.java
index 4240a893d..09b3e180a 100644
--- a/src/com/android/camera/one/v2/ImageCaptureManager.java
+++ b/src/com/android/camera/one/v2/ImageCaptureManager.java
@@ -432,42 +432,7 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
@Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
final CaptureResult partialResult) {
- long frameNumber = partialResult.getFrameNumber();
-
- // Update mMetadata for whichever keys are present, if this frame is
- // supplying newer values.
- for (final Key<?> key : partialResult.getKeys()) {
- Pair<Long, Object> oldEntry = mMetadata.get(key);
- final Object oldValue = (oldEntry != null) ? oldEntry.second : null;
-
- boolean newerValueAlreadyExists = oldEntry != null
- && frameNumber < oldEntry.first;
- if (newerValueAlreadyExists) {
- continue;
- }
-
- final Object newValue = partialResult.get(key);
- mMetadata.put(key, new Pair<Long, Object>(frameNumber, newValue));
-
- // If the value has changed, call the appropriate listeners, if
- // any exist.
- if (oldValue == newValue || !mMetadataChangeListeners.containsKey(key)) {
- continue;
- }
-
- for (final MetadataChangeListener listener :
- mMetadataChangeListeners.get(key)) {
- Log.v(TAG, "Dispatching to metadata change listener for key: "
- + key.toString());
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- listener.onImageMetadataChange(key, oldValue, newValue,
- partialResult);
- }
- });
- }
- }
+ updateMetadataChangeListeners(partialResult);
}
@Override
@@ -475,6 +440,8 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
final TotalCaptureResult result) {
final long timestamp = result.get(TotalCaptureResult.SENSOR_TIMESTAMP);
+ updateMetadataChangeListeners(result);
+
// Detect camera thread stall.
long now = SystemClock.uptimeMillis();
if (now - mDebugLastOnCaptureCompletedMillis < DEBUG_INTERFRAME_STALL_WARNING) {
@@ -497,6 +464,43 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
tryExecutePendingCaptureRequest(timestamp);
}
+ private void updateMetadataChangeListeners(final CaptureResult result) {
+ long frameNumber = result.getFrameNumber();
+
+ // Update mMetadata for whichever keys are present, if this frame is
+ // supplying newer values.
+ for (final Key<?> key : result.getKeys()) {
+ Pair<Long, Object> oldEntry = mMetadata.get(key);
+ final Object oldValue = (oldEntry != null) ? oldEntry.second : null;
+
+ boolean newerValueAlreadyExists = oldEntry != null
+ && frameNumber < oldEntry.first;
+ if (newerValueAlreadyExists) {
+ continue;
+ }
+
+ final Object newValue = result.get(key);
+ mMetadata.put(key, new Pair<Long, Object>(frameNumber, newValue));
+
+ // If the value has changed, call the appropriate listeners, if
+ // any exist.
+ if (oldValue == newValue || !mMetadataChangeListeners.containsKey(key)) {
+ continue;
+ }
+
+ for (final MetadataChangeListener listener :
+ mMetadataChangeListeners.get(key)) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onImageMetadataChange(key, oldValue, newValue,
+ result);
+ }
+ });
+ }
+ }
+ }
+
private boolean doMetaDataSwap(final TotalCaptureResult newMetadata, final long timestamp) {
mEvictionHandler.get().onFrameCaptureResultAvailable(timestamp, newMetadata);
diff --git a/src/com/android/camera/one/v2/OneCameraImpl.java b/src/com/android/camera/one/v2/OneCameraImpl.java
index d70c3183b..2837f626c 100644
--- a/src/com/android/camera/one/v2/OneCameraImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraImpl.java
@@ -223,6 +223,11 @@ public class OneCameraImpl extends AbstractOneCamera {
AutoFocusHelper.logExtraFocusInfo(result);
}
+ Float diopter = result.get(CaptureResult.LENS_FOCUS_DISTANCE);
+ if(diopter != null && mFocusDistanceListener != null) {
+ mFocusDistanceListener.onFocusDistance(diopter, true);
+ }
+
if (request.getTag() == RequestTag.CAPTURE) {
// Add the capture result to the latest in-flight
// capture. If all the data for that capture is
diff --git a/src/com/android/camera/one/v2/OneCameraZslImpl.java b/src/com/android/camera/one/v2/OneCameraZslImpl.java
index 97faf0f83..d53e89452 100644
--- a/src/com/android/camera/one/v2/OneCameraZslImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraZslImpl.java
@@ -355,6 +355,27 @@ public class OneCameraZslImpl extends AbstractOneCamera {
mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
}
+ @Override
+ public void setFocusDistanceListener(FocusDistanceListener focusDistanceListener) {
+ if(mFocusDistanceListener == null) {
+ mCaptureManager.addMetadataChangeListener(CaptureResult.LENS_FOCUS_DISTANCE,
+ new ImageCaptureManager.MetadataChangeListener() {
+ @Override
+ public void onImageMetadataChange(Key<?> key, Object oldValue,
+ Object newValue,
+ CaptureResult result) {
+ Integer state = result.get(CaptureResult.LENS_STATE);
+ if (newValue != null && state != null) {
+ mFocusDistanceListener.onFocusDistance((float) newValue, state == 1);
+ } else if (newValue != null) {
+ mFocusDistanceListener.onFocusDistance((float) newValue, true);
+ }
+ }
+ });
+ }
+ mFocusDistanceListener = focusDistanceListener;
+ }
+
/**
* @return The largest supported picture size.
*/
diff --git a/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java b/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java
index 813ca9c66..e48d35715 100644
--- a/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java
+++ b/src/com/android/camera/one/v2/SimpleJpegOneCameraFactory.java
@@ -16,18 +16,6 @@
package com.android.camera.one.v2;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
@@ -57,6 +45,7 @@ import com.android.camera.async.SafeCloseable;
import com.android.camera.async.Updatable;
import com.android.camera.one.CameraDirectionProvider;
import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCamera.FocusState;
import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
import com.android.camera.one.v2.camera2proxy.CameraDeviceRequestBuilderFactory;
@@ -98,6 +87,18 @@ import com.android.camera.session.CaptureSession;
import com.android.camera.util.ScopedFactory;
import com.android.camera.util.Size;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
/**
*/
public class SimpleJpegOneCameraFactory {
@@ -111,6 +112,7 @@ public class SimpleJpegOneCameraFactory {
public final FutureResult<GenericOneCameraImpl.PictureTaker> pictureTaker;
public final FutureResult<GenericOneCameraImpl.ManualAutoFocus> manualAutoFocus;
public final ConcurrentState<Integer> afState;
+ public final ConcurrentState<FocusState> focusState;
public final ConcurrentState<Boolean> readyState;
public final ConcurrentState<Float> zoomState;
public final ConcurrentState<Boolean> previewStartSuccess;
@@ -123,6 +125,7 @@ public class SimpleJpegOneCameraFactory {
FutureResult<GenericOneCameraImpl.PictureTaker> pictureTaker,
FutureResult<GenericOneCameraImpl.ManualAutoFocus> manualAutoFocus,
ConcurrentState<Integer> afState,
+ ConcurrentState<FocusState> focusState,
ConcurrentState<Boolean> readyState,
ConcurrentState<Float> zoomState,
ConcurrentState<Boolean> previewStartSuccess, Size pictureSize) {
@@ -132,6 +135,7 @@ public class SimpleJpegOneCameraFactory {
this.pictureTaker = pictureTaker;
this.manualAutoFocus = manualAutoFocus;
this.afState = afState;
+ this.focusState = focusState;
this.readyState = readyState;
this.zoomState = zoomState;
this.previewStartSuccess = previewStartSuccess;
@@ -175,12 +179,13 @@ public class SimpleJpegOneCameraFactory {
FutureResult<GenericOneCameraImpl.PictureTaker> pictureTakerFutureResult = new FutureResult<>();
FutureResult<GenericOneCameraImpl.ManualAutoFocus> manualAutoFocusFutureResult = new FutureResult<>();
ConcurrentState<Integer> afState = new ConcurrentState<>();
+ ConcurrentState<FocusState> focusState = new ConcurrentState<>();
ConcurrentState<Boolean> readyState = new ConcurrentState<>();
ConcurrentState<Float> zoomState = new ConcurrentState<>();
ConcurrentState<Boolean> previewStartSuccess = new ConcurrentState<>();
return new CameraScope(device, characteristics, mainHandler, pictureTakerFutureResult,
- manualAutoFocusFutureResult, afState,
+ manualAutoFocusFutureResult, afState, focusState,
readyState, zoomState, previewStartSuccess, pictureSize);
}
@@ -216,6 +221,10 @@ public class SimpleJpegOneCameraFactory {
return new ListenableConcurrentState<>(scope.afState, provideMainHandlerExecutor(scope));
}
+ private static Listenable<FocusState> provideFocusStateListenable(CameraScope scope) {
+ return new ListenableConcurrentState<>(scope.focusState, provideMainHandlerExecutor(scope));
+ }
+
private static Listenable<Boolean> provideReadyStateListenable(CameraScope scope) {
return new ListenableConcurrentState<>(scope.readyState, provideMainHandlerExecutor(scope));
}
@@ -519,6 +528,7 @@ public class SimpleJpegOneCameraFactory {
GenericOneCameraImpl.PictureTaker pictureTaker = providePictureTaker(scope);
GenericOneCameraImpl.ManualAutoFocus manualAutoFocus = provideManualAutoFocus(scope);
Listenable<Integer> afStateListenable = provideAFStateListenable(scope);
+ Listenable<FocusState> focusStateListenable = provideFocusStateListenable(scope);
Listenable<Boolean> readyStateListenable = provideReadyStateListenable(scope);
float maxZoom = provideMaxZoom(scope.characteristics);
Updatable<Float> zoom = provideZoom(scope);
@@ -530,7 +540,7 @@ public class SimpleJpegOneCameraFactory {
Listenable<Boolean> previewStartSuccessListenable = providePreviewStartSuccessListenable(scope);
return new GenericOneCameraImpl(closeListeners, pictureTaker, manualAutoFocus,
- afStateListenable, readyStateListenable, maxZoom, zoom,
+ afStateListenable, focusStateListenable, readyStateListenable, maxZoom, zoom,
supportedPreviewSizes, fullSizeAspectRatio, direction, previewSizeSelector,
previewStartSuccessListenable, surfaceRunnableScopedFactory);
}
diff --git a/src/com/android/camera/one/v2/common/GenericOneCameraImpl.java b/src/com/android/camera/one/v2/common/GenericOneCameraImpl.java
index a19fd2844..1357b5cae 100644
--- a/src/com/android/camera/one/v2/common/GenericOneCameraImpl.java
+++ b/src/com/android/camera/one/v2/common/GenericOneCameraImpl.java
@@ -16,9 +16,6 @@
package com.android.camera.one.v2.common;
-import java.util.HashSet;
-import java.util.Set;
-
import android.content.Context;
import android.view.Surface;
@@ -32,6 +29,9 @@ import com.android.camera.util.Callback;
import com.android.camera.util.ScopedFactory;
import com.android.camera.util.Size;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* A generic, composable {@link OneCamera}.
* <p>
@@ -61,6 +61,7 @@ public class GenericOneCameraImpl implements OneCamera {
private final PictureTaker mPictureTaker;
private final ManualAutoFocus mManualAutoFocus;
private final Listenable<Integer> mAFStateListenable;
+ private final Listenable<FocusState> mFocusStateListenable;
private final Listenable<Boolean> mReadyStateListenable;
private final float mMaxZoom;
private final Updatable<Float> mZoom;
@@ -71,10 +72,17 @@ public class GenericOneCameraImpl implements OneCamera {
private final Listenable<Boolean> mPreviewStartSuccessListenable;
private final ScopedFactory<Surface, Runnable> mPreviewScopeEntrance;
- public GenericOneCameraImpl(Set<SafeCloseable> closeListeners, PictureTaker pictureTaker,
- ManualAutoFocus manualAutoFocus, Listenable<Integer> afStateProvider,
- Listenable<Boolean> readyStateListenable, float maxZoom, Updatable<Float> zoom,
- Size[] supportedPreviewSizes, float fullSizeAspectRatio, Facing direction,
+ public GenericOneCameraImpl(Set<SafeCloseable> closeListeners,
+ PictureTaker pictureTaker,
+ ManualAutoFocus manualAutoFocus,
+ Listenable<Integer> afStateProvider,
+ Listenable<FocusState> focusStateProvider,
+ Listenable<Boolean> readyStateListenable,
+ float maxZoom,
+ Updatable<Float> zoom,
+ Size[] supportedPreviewSizes,
+ float fullSizeAspectRatio,
+ Facing direction,
PreviewSizeSelector previewSizeSelector,
Listenable<Boolean> previewStartSuccessListenable,
ScopedFactory<Surface, Runnable> previewScopeEntrance) {
@@ -89,6 +97,7 @@ public class GenericOneCameraImpl implements OneCamera {
mPictureTaker = pictureTaker;
mManualAutoFocus = manualAutoFocus;
mAFStateListenable = afStateProvider;
+ mFocusStateListenable = focusStateProvider;
mReadyStateListenable = readyStateListenable;
mZoom = zoom;
}
@@ -128,6 +137,16 @@ public class GenericOneCameraImpl implements OneCamera {
}
@Override
+ public void setFocusDistanceListener(final FocusDistanceListener listener) {
+ mFocusStateListenable.setCallback(new Callback<FocusState>() {
+ @Override
+ public void onCallback(FocusState focusState) {
+ listener.onFocusDistance(focusState.diopter, focusState.isActive);
+ }
+ });
+ }
+
+ @Override
public void setReadyStateChangedListener(final ReadyStateChangedListener listener) {
mReadyStateListenable.setCallback(new Callback<Boolean>() {
@Override
diff --git a/src/com/android/camera/ui/FocusOverlay.java b/src/com/android/camera/ui/FocusOverlay.java
deleted file mode 100644
index bd913d4f6..000000000
--- a/src/com/android/camera/ui/FocusOverlay.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2013 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.ui;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.camera.FocusOverlayManager;
-import com.android.camera.debug.DebugPropertyHelper;
-import com.android.camera.debug.Log;
-import com.android.camera2.R;
-
-/**
- * Displays a focus indicator.
- */
-public class FocusOverlay extends View implements FocusOverlayManager.FocusUI {
- private static final Log.Tag TAG = new Log.Tag("FocusOverlay");
-
- /** System Properties switch to enable debugging focus UI. */
- private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI();
-
- private final static int FOCUS_DURATION_MS = 500;
- private final static int FOCUS_INDICATOR_ROTATION_DEGREES = 50;
-
- private final Drawable mFocusIndicator;
- private Drawable mFocusOuterRing;
- private final Rect mBounds = new Rect();
- private final ValueAnimator mFocusAnimation = new ValueAnimator();
-
- private Paint mDebugSolidPaint;
- private Paint mDebugCornersPaint;
- private Paint mDebugTextPaint;
- private int mDebugStartColor;
- private int mDebugSuccessColor;
- private int mDebugFailColor;
- private Rect mFocusDebugSolidRect;
- private Rect mFocusDebugCornersRect;
- private boolean mIsPassiveScan;
- private String mDebugMessage;
-
- private int mPositionX;
- private int mPositionY;
- private int mAngle;
- private final int mFocusIndicatorSize;
- private boolean mShowIndicator;
- private final int mFocusOuterRingSize;
-
- public FocusOverlay(Context context, AttributeSet attrs) {
- super(context, attrs);
- mFocusIndicator = getResources().getDrawable(R.drawable.focus_ring_touch_inner);
- mFocusIndicatorSize = getResources().getDimensionPixelSize(R.dimen.focus_inner_ring_size);
- mFocusOuterRing = getResources().getDrawable(R.drawable.focus_ring_touch_outer);
- mFocusOuterRingSize = getResources().getDimensionPixelSize(R.dimen.focus_outer_ring_size);
-
- if (CAPTURE_DEBUG_UI) {
- Resources res = getResources();
- mDebugStartColor = res.getColor(R.color.focus_debug);
- mDebugSuccessColor = res.getColor(R.color.focus_debug_success);
- mDebugFailColor = res.getColor(R.color.focus_debug_fail);
- mDebugTextPaint= new Paint();
- mDebugTextPaint.setColor(res.getColor(R.color.focus_debug_text));
- mDebugTextPaint.setStyle(Paint.Style.FILL);
- mDebugSolidPaint = new Paint();
- mDebugSolidPaint.setColor(res.getColor(R.color.focus_debug));
- mDebugSolidPaint.setAntiAlias(true);
- mDebugSolidPaint.setStyle(Paint.Style.STROKE);
- mDebugSolidPaint.setStrokeWidth(res.getDimension(R.dimen.focus_debug_stroke));
- mDebugCornersPaint = new Paint(mDebugSolidPaint);
- mDebugCornersPaint.setColor(res.getColor(R.color.focus_debug));
- mFocusDebugSolidRect = new Rect();
- mFocusDebugCornersRect = new Rect();
- }
- }
-
- @Override
- public boolean hasFaces() {
- // TODO: Add face detection support.
- return false;
- }
-
- @Override
- public void clearFocus() {
- mShowIndicator = false;
- if (CAPTURE_DEBUG_UI) {
- setVisibility(INVISIBLE);
- }
- }
-
- @Override
- public void setFocusPosition(int x, int y, boolean isPassiveScan) {
- setFocusPosition(x, y, isPassiveScan, 0, 0);
- }
-
- @Override
- public void setFocusPosition(int x, int y, boolean isPassiveScan, int aFsize, int aEsize) {
- mIsPassiveScan = isPassiveScan;
- mPositionX = x;
- mPositionY = y;
- mBounds.set(x - mFocusIndicatorSize / 2, y - mFocusIndicatorSize / 2,
- x + mFocusIndicatorSize / 2, y + mFocusIndicatorSize / 2);
- mFocusIndicator.setBounds(mBounds);
- mFocusOuterRing.setBounds(x - mFocusOuterRingSize / 2, y - mFocusOuterRingSize / 2,
- x + mFocusOuterRingSize / 2, y + mFocusOuterRingSize / 2);
-
- if (CAPTURE_DEBUG_UI) {
- mFocusOuterRing.setBounds(0, 0, 0, 0);
- if (isPassiveScan) {
- // Use AE rect only.
- mFocusDebugSolidRect.setEmpty();
- int avg = (aFsize + aEsize) / 2;
- mFocusDebugCornersRect.set(x - avg / 2, y - avg / 2, x + avg / 2, y + avg / 2);
- } else {
- mFocusDebugSolidRect.set(x - aFsize / 2, y - aFsize / 2, x + aFsize / 2,
- y + aFsize / 2);
- // If AE region is different size than AF region and active scan.
- if (aFsize != aEsize) {
- mFocusDebugCornersRect.set(x - aEsize / 2, y - aEsize / 2, x + aEsize / 2,
- y + aEsize / 2);
- } else {
- mFocusDebugCornersRect.setEmpty();
- }
- }
- mDebugSolidPaint.setColor(mDebugStartColor);
- mDebugCornersPaint.setColor(mDebugStartColor);
- }
-
- if (getVisibility() != VISIBLE) {
- setVisibility(VISIBLE);
- }
- invalidate();
- }
-
- /**
- * This is called in:
- * <ul>
- * <li>API1 non-CAF after autoFocus().</li>
- * <li>API1 CAF mode for onAutoFocusMoving(true).</li>
- * <li>API2 for transition to ACTIVE_SCANNING or PASSIVE_SCANNING.</li>
- * <ul>
- * TODO after PhotoModule/GcamModule deprecation: Do not use this for CAF.
- */
- @Override
- public void onFocusStarted() {
- mShowIndicator = true;
- mFocusAnimation.setIntValues(0, FOCUS_INDICATOR_ROTATION_DEGREES);
- mFocusAnimation.setDuration(FOCUS_DURATION_MS);
- mFocusAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mAngle = (Integer) animation.getAnimatedValue();
- invalidate();
- }
- });
- mFocusAnimation.start();
- if (CAPTURE_DEBUG_UI) {
- mDebugMessage = null;
- }
- }
-
- /**
- * This is called in:
- * <ul>
- * <li>API1 non-CAF for onAutoFocus(true).</li>
- * <li>API2 non-CAF for transition to FOCUSED_LOCKED.</li>
- * <li>API1 CAF mode for onAutoFocusMoving(false).</li>
- * <ul>
- * TODO after PhotoModule/GcamModule deprecation: Do not use this for CAF.
- */
- @Override
- public void onFocusSucceeded() {
- mFocusAnimation.cancel();
- mShowIndicator = false;
- if (CAPTURE_DEBUG_UI && !mIsPassiveScan) {
- mDebugSolidPaint.setColor(mDebugSuccessColor);
- }
- invalidate();
- }
-
- /**
- * This is called in:
- * <ul>
- * <li>API1 non-CAF for onAutoFocus(false).</li>
- * <li>API2 non-CAF for transition to NOT_FOCUSED_LOCKED.</li>
- * <ul>
- */
- @Override
- public void onFocusFailed() {
- mFocusAnimation.cancel();
- mShowIndicator = false;
- if (CAPTURE_DEBUG_UI && !mIsPassiveScan) {
- mDebugSolidPaint.setColor(mDebugFailColor);
- }
- invalidate();
- }
-
- /**
- * This is called in:
- * API2 for CAF state changes to PASSIVE_FOCUSED or PASSIVE_UNFOCUSED.
- */
- @Override
- public void setPassiveFocusSuccess(boolean success) {
- mFocusAnimation.cancel();
- mShowIndicator = false;
- if (CAPTURE_DEBUG_UI) {
- mDebugCornersPaint.setColor(success ? mDebugSuccessColor : mDebugFailColor);
- }
- invalidate();
- }
-
- @Override
- public void showDebugMessage(String message) {
- if (CAPTURE_DEBUG_UI) {
- mDebugMessage = message;
- }
- }
-
- @Override
- public void pauseFaceDetection() {
- // TODO: Add face detection support.
- }
-
- @Override
- public void resumeFaceDetection() {
- // TODO: Add face detection support.
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (mShowIndicator) {
- mFocusOuterRing.draw(canvas);
- canvas.save();
- canvas.rotate(mAngle, mPositionX, mPositionY);
- mFocusIndicator.draw(canvas);
- canvas.restore();
- }
- if (CAPTURE_DEBUG_UI) {
- canvas.drawRect(mFocusDebugSolidRect, mDebugSolidPaint);
- float delta = 0.1f * mFocusDebugCornersRect.width();
- float left = mFocusDebugCornersRect.left;
- float top = mFocusDebugCornersRect.top;
- float right = mFocusDebugCornersRect.right;
- float bot = mFocusDebugCornersRect.bottom;
-
- canvas.drawLines(new float[]{left, top + delta, left, top, left, top, left + delta, top}, mDebugCornersPaint);
- canvas.drawLines(new float[]{right, top + delta, right, top, right, top, right - delta, top}, mDebugCornersPaint);
- canvas.drawLines(new float[]{left, bot - delta, left, bot, left, bot, left + delta, bot}, mDebugCornersPaint);
- canvas.drawLines(new float[]{right, bot - delta, right, bot, right, bot, right - delta, bot}, mDebugCornersPaint);
-
- if (mDebugMessage != null) {
- mDebugTextPaint.setTextSize(40);
- canvas.drawText(mDebugMessage, left - 4, bot + 44, mDebugTextPaint);
- }
- }
- }
-}
diff --git a/src/com/android/camera/ui/focus/AutoFocusRing.java b/src/com/android/camera/ui/focus/AutoFocusRing.java
new file mode 100644
index 000000000..858b11649
--- /dev/null
+++ b/src/com/android/camera/ui/focus/AutoFocusRing.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.android.camera.debug.Log.Tag;
+import com.android.camera.ui.motion.InterpolateUtils;
+import com.android.camera.ui.motion.Invalidator;
+
+/**
+ * Passive focus ring animation renderer.
+ */
+public class AutoFocusRing extends FocusRingRenderer {
+ private static final Tag TAG = new Tag("AutoFocusRing");
+
+ /**
+ * The auto focus ring encapsulates the animation logic for visualizing
+ * a focus event when triggered by the camera subsystem.
+ *
+ * @param invalidator the object to invalidate while running.
+ * @param ringPaint the paint to draw the ring with.
+ * @param enterDurationMillis the fade in time in milliseconds.
+ * @param exitDurationMillis the fade out time in milliseconds.
+ */
+ AutoFocusRing(Invalidator invalidator, Paint ringPaint, float enterDurationMillis,
+ float exitDurationMillis) {
+ super(invalidator, ringPaint, enterDurationMillis, exitDurationMillis);
+ }
+
+ @Override
+ public void draw(long t, long dt, Canvas canvas) {
+ float ringRadius = mRingRadius.update(dt);
+ processStates(t);
+
+ if (!isActive()) {
+ return;
+ }
+
+ mInvalidator.invalidate();
+ int ringAlpha = 255;
+
+ if (mFocusState == FocusState.STATE_ENTER) {
+ float rFade = InterpolateUtils.unitRatio(t, mEnterStartMillis, mEnterDurationMillis);
+ ringAlpha = (int) InterpolateUtils
+ .lerp(0, 255, mEnterOpacityCurve.valueAt(rFade));
+ } else if (mFocusState == FocusState.STATE_FADE_OUT) {
+ float rFade = InterpolateUtils.unitRatio(t, mExitStartMillis, mExitDurationMillis);
+ ringAlpha = (int) InterpolateUtils
+ .lerp(255, 0, mExitOpacityCurve.valueAt(rFade));
+ } else if (mFocusState == FocusState.STATE_HARD_STOP) {
+ float rFade = InterpolateUtils
+ .unitRatio(t, mHardExitStartMillis, mHardExitDurationMillis);
+ ringAlpha = (int) InterpolateUtils
+ .lerp(255, 0, mExitOpacityCurve.valueAt(rFade));
+ } else if (mFocusState == FocusState.STATE_INACTIVE) {
+ ringAlpha = 0;
+ }
+
+ mRingPaint.setAlpha(ringAlpha);
+ canvas.drawCircle(getCenterX(), getCenterY(), ringRadius, mRingPaint);
+ }
+
+ private void processStates(long t) {
+ if (mFocusState == FocusState.STATE_INACTIVE) {
+ return;
+ }
+
+ if (mFocusState == FocusState.STATE_ENTER && t > mEnterStartMillis + mEnterDurationMillis) {
+ mFocusState = FocusState.STATE_ACTIVE;
+ }
+
+ if (mFocusState == FocusState.STATE_ACTIVE && !mRingRadius.isActive()) {
+ mFocusState = FocusState.STATE_FADE_OUT;
+ mExitStartMillis = t;
+ }
+
+ if (mFocusState == FocusState.STATE_FADE_OUT && t > mExitStartMillis + mExitDurationMillis) {
+ mFocusState = FocusState.STATE_INACTIVE;
+ }
+
+ if (mFocusState == FocusState.STATE_HARD_STOP
+ && t > mHardExitStartMillis + mHardExitDurationMillis) {
+ mFocusState = FocusState.STATE_INACTIVE;
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/focus/CameraCoordinateTransformer.java b/src/com/android/camera/ui/focus/CameraCoordinateTransformer.java
new file mode 100644
index 000000000..809503c9a
--- /dev/null
+++ b/src/com/android/camera/ui/focus/CameraCoordinateTransformer.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+/**
+ * Transform coordinates to and from preview coordinate space and camera driver
+ * coordinate space.
+ */
+public class CameraCoordinateTransformer {
+ // http://developer.android.com/guide/topics/media/camera.html#metering-focus-areas
+ private static final RectF CAMERA_DRIVER_RECT = new RectF(-1000, -1000, 1000, 1000);
+
+ private final Matrix mCameraToPreviewTransform;
+ private final Matrix mPreviewToCameraTransform;
+
+ /**
+ * Convert rectangles to / from camera coordinate and preview coordinate space.
+ *
+ * @param mirrorX if the preview is mirrored along the X axis.
+ * @param displayOrientation orientation in degrees.
+ * @param previewRect the preview rectangle size and position.
+ */
+ public CameraCoordinateTransformer(boolean mirrorX, int displayOrientation,
+ RectF previewRect) {
+ if (!hasNonZeroArea(previewRect)) {
+ throw new IllegalArgumentException("previewRect");
+ }
+
+ mCameraToPreviewTransform = cameraToPreviewTransform(mirrorX, displayOrientation,
+ previewRect);
+ mPreviewToCameraTransform = inverse(mCameraToPreviewTransform);
+ }
+
+ /**
+ * Transform a rectangle in camera space into a new rectangle in preview
+ * view space.
+ *
+ * @param source the rectangle in camera space
+ * @return the rectangle in preview view space.
+ */
+ public RectF toPreviewSpace(RectF source) {
+ RectF result = new RectF();
+ mCameraToPreviewTransform.mapRect(result, source);
+ return result;
+ }
+
+ /**
+ * Transform a rectangle in preview view space into a new rectangle in
+ * camera view space.
+ *
+ * @param source the rectangle in preview view space
+ * @return the rectangle in camera view space.
+ */
+ public RectF toCameraSpace(RectF source) {
+ RectF result = new RectF();
+ mPreviewToCameraTransform.mapRect(result, source);
+ return result;
+ }
+
+ private Matrix cameraToPreviewTransform(boolean mirrorX, int displayOrientation,
+ RectF previewRect) {
+ Matrix transform = new Matrix();
+
+ // Need mirror for front camera.
+ transform.setScale(mirrorX ? -1 : 1, 1);
+
+ // Apply a rotate transform.
+ // This is the value for android.hardware.Camera.setDisplayOrientation.
+ transform.postRotate(displayOrientation);
+
+ // Map camera driver coordinates to preview rect coordinates
+ Matrix fill = new Matrix();
+ fill.setRectToRect(CAMERA_DRIVER_RECT,
+ previewRect,
+ Matrix.ScaleToFit.FILL);
+
+ // Concat the previous transform on top of the fill behavior.
+ transform.setConcat(fill, transform);
+
+ return transform;
+ }
+
+ private Matrix inverse(Matrix source) {
+ Matrix newMatrix = new Matrix();
+ source.invert(newMatrix);
+ return newMatrix;
+ }
+
+ private boolean hasNonZeroArea(RectF rect) {
+ return rect.width() != 0 && rect.height() != 0;
+ }
+}
diff --git a/src/com/android/camera/ui/focus/FocusController.java b/src/com/android/camera/ui/focus/FocusController.java
new file mode 100644
index 000000000..06640601b
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusController.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import android.os.Handler;
+
+import com.android.camera.debug.Log.Tag;
+import com.android.camera.one.OneCamera.FocusDistanceListener;
+
+/**
+ * The focus controller interacts with the focus ring UI element.
+ */
+public class FocusController implements FocusDistanceListener {
+ private static final Tag TAG = new Tag("FocusController");
+
+ private final FocusRing mFocusRing;
+ private final Handler mHandler;
+ private final FocusSound mFocusSound;
+
+ public FocusController(FocusRing focusRing, FocusSound focusSound, Handler handler) {
+ mFocusRing = focusRing;
+ mHandler = handler;
+ mFocusSound = focusSound;
+ }
+
+ /**
+ * Show a passive focus animation at the given viewX and viewY position.
+ * This is usually indicates the camera subsystem kicked off an auto-focus
+ * at the given screen position.
+ *
+ * @param viewX the view's x coordinate
+ * @param viewY the view's y coordinate
+ */
+ public void showPassiveFocusAt(final int viewX, final int viewY) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mFocusRing.startPassiveFocus();
+ mFocusRing.setFocusLocation(viewX, viewY);
+ }
+ });
+ }
+
+ /**
+ * Show an active focus animation at the given viewX and viewY position.
+ * This is normally initiated by the user touching the screen at a given
+ * point.
+ *
+ * @param viewX the view's x coordinate
+ * @param viewY the view's y coordinate
+ */
+ public void showActiveFocusAt(final int viewX, final int viewY) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mFocusRing.startActiveFocus();
+ mFocusRing.setFocusLocation(viewX, viewY);
+ mFocusSound.play();
+ }
+ });
+ }
+
+ /**
+ * Stop any currently executing focus animation.
+ */
+ public void clearFocusIndicator() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mFocusRing.stopFocusAnimations();
+ }
+ });
+ }
+
+ @Override
+ public void onFocusDistance(final float diopter, final boolean isActive) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (isActive || mFocusRing.isPassiveFocusRunning() ||
+ mFocusRing.isActiveFocusRunning()) {
+ mFocusRing.setFocusDiopter(diopter);
+ }
+ }
+ });
+ }
+}
diff --git a/src/com/android/camera/ui/focus/FocusRing.java b/src/com/android/camera/ui/focus/FocusRing.java
new file mode 100644
index 000000000..362caa5ce
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusRing.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+/**
+ * Primary interface for interacting with the focus ring UI.
+ */
+public interface FocusRing {
+ /**
+ * Check the state of the passive focus ring animation.
+ *
+ * @return whether the passive focus animation is running.
+ */
+ public boolean isPassiveFocusRunning();
+ /**
+ * Check the state of the active focus ring animation.
+ *
+ * @return whether the active focus animation is running.
+ */
+ public boolean isActiveFocusRunning();
+ /**
+ * Start a passive focus animation.
+ */
+ public void startPassiveFocus();
+ /**
+ * Start an active focus animation.
+ */
+ public void startActiveFocus();
+ /**
+ * Stop any currently running focus animations.
+ */
+ public void stopFocusAnimations();
+ /**
+ * Set the location of the focus ring animation center.
+ */
+ public void setFocusLocation(float viewX, float viewY);
+ /**
+ * Set the lens diopter of the focus ring.
+ */
+ public void setFocusDiopter(float diopter);
+} \ No newline at end of file
diff --git a/src/com/android/camera/ui/focus/FocusRingRenderer.java b/src/com/android/camera/ui/focus/FocusRingRenderer.java
new file mode 100644
index 000000000..c94b42197
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusRingRenderer.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import android.graphics.Paint;
+
+import com.android.camera.debug.Log;
+import com.android.camera.debug.Log.Tag;
+import com.android.camera.ui.motion.DampedSpring;
+import com.android.camera.ui.motion.DynamicAnimation;
+import com.android.camera.ui.motion.Invalidator;
+import com.android.camera.ui.motion.UnitCurve;
+import com.android.camera.ui.motion.UnitCurves;
+
+/**
+ * Base class for defining the focus ring states, enter and exit durations, and
+ * positioning logic.
+ */
+public abstract class FocusRingRenderer implements DynamicAnimation {
+ private static final Tag TAG = new Tag("FocusRingRenderer");
+
+ /**
+ * Primary focus states that a focus ring renderer can go through.
+ */
+ protected static enum FocusState {
+ STATE_INACTIVE,
+ STATE_ENTER,
+ STATE_ACTIVE,
+ STATE_FADE_OUT,
+ STATE_HARD_STOP,
+ }
+
+ protected final Invalidator mInvalidator;
+ protected final Paint mRingPaint;
+ protected final DampedSpring mRingRadius;
+ protected final UnitCurve mEnterOpacityCurve;
+ protected final UnitCurve mExitOpacityCurve;
+ protected final UnitCurve mHardExitOpacityCurve;
+ protected final float mEnterDurationMillis;
+ protected final float mExitDurationMillis;
+ protected final float mHardExitDurationMillis = 64;
+
+ private int mCenterX;
+ private int mCenterY;
+ protected long mEnterStartMillis = 0;
+ protected long mExitStartMillis = 0;
+ protected long mHardExitStartMillis = 0;
+
+ protected FocusState mFocusState = FocusState.STATE_INACTIVE;
+
+ /**
+ * A dynamic, configurable, self contained ring render that will inform
+ * via invalidation if it should continue to be receive updates
+ * and re-draws.
+ *
+ * @param invalidator the object to inform if it requires more draw calls.
+ * @param ringPaint the paint to use to draw the ring.
+ * @param enterDurationMillis the fade in duration in milliseconds
+ * @param exitDurationMillis the fade out duration in milliseconds.
+ */
+ FocusRingRenderer(Invalidator invalidator, Paint ringPaint, float enterDurationMillis,
+ float exitDurationMillis) {
+ mInvalidator = invalidator;
+ mRingPaint = ringPaint;
+ mEnterDurationMillis = enterDurationMillis;
+ mExitDurationMillis = exitDurationMillis;
+
+ mEnterOpacityCurve = UnitCurves.FAST_OUT_SLOW_IN;
+ mExitOpacityCurve = UnitCurves.FAST_OUT_LINEAR_IN;
+ mHardExitOpacityCurve = UnitCurves.FAST_OUT_LINEAR_IN;
+
+ mRingRadius = new DampedSpring();
+ }
+
+ /**
+ * Set the centerX position for this focus ring renderer.
+ *
+ * @param value the x position
+ */
+ public void setCenterX(int value) {
+ mCenterX = value;
+ }
+
+ protected int getCenterX() {
+ return mCenterX;
+ }
+
+ /**
+ * Set the centerY position for this focus ring renderer.
+ *
+ * @param value the y position
+ */
+ public void setCenterY(int value) {
+ mCenterY = value;
+ }
+
+ protected int getCenterY() {
+ return mCenterY;
+ }
+
+ /**
+ * Set the physical radius of this ring.
+ *
+ * @param value the radius of the ring.
+ */
+ public void setRadius(long tMs, float value) {
+ if (mFocusState == FocusState.STATE_FADE_OUT
+ && Math.abs(mRingRadius.getTarget() - value) > 0.1) {
+ Log.v(TAG, "FOCUS STATE ENTER VIA setRadius(" + tMs + ", " + value + ")");
+ mFocusState = FocusState.STATE_ENTER;
+ mEnterStartMillis = computeEnterStartTimeMillis(tMs, mEnterDurationMillis);
+ }
+
+ mRingRadius.setTarget(value);
+ }
+
+ /**
+ * returns true if the renderer is not in an inactive state.
+ */
+ @Override
+ public boolean isActive() {
+ return mFocusState != FocusState.STATE_INACTIVE;
+ }
+
+ /**
+ * returns true if the renderer is in an exit state.
+ */
+ public boolean isExiting() {
+ return mFocusState == FocusState.STATE_FADE_OUT
+ || mFocusState == FocusState.STATE_HARD_STOP;
+ }
+
+ /**
+ * returns true if the renderer is in an enter state.
+ */
+ public boolean isEntering() {
+ return mFocusState == FocusState.STATE_ENTER;
+ }
+
+ /**
+ * Initialize and start the animation with the given start and
+ * target radius.
+ */
+ public void start(long startMs, float initialRadius, float targetRadius) {
+ if (mFocusState != FocusState.STATE_INACTIVE) {
+ Log.w(TAG, "start() called while the ring was still focusing!");
+ }
+ mRingRadius.stop();
+ mRingRadius.setValue(initialRadius);
+ mRingRadius.setTarget(targetRadius);
+ mEnterStartMillis = startMs;
+
+ mFocusState = FocusState.STATE_ENTER;
+ mInvalidator.invalidate();
+ }
+
+ /**
+ * Put the animation in the exit state regardless of the current
+ * dynamic transition. If the animation is currently in an enter state
+ * this will compute an exit start time such that the exit time lines
+ * up with the enter time at the current transition value.
+ *
+ * @param t the current animation time.
+ */
+ public void exit(long t) {
+ if (mRingRadius.isActive()) {
+ mRingRadius.stop();
+ }
+
+ mFocusState = FocusState.STATE_FADE_OUT;
+ mExitStartMillis = computeExitStartTimeMs(t, mExitDurationMillis);
+ }
+
+ /**
+ * Put the animation in the hard stop state regardless of the current
+ * dynamic transition. If the animation is currently in an enter state
+ * this will compute an exit start time such that the exit time lines
+ * up with the enter time at the current transition value.
+ *
+ * @param tMillis the current animation time in milliseconds.
+ */
+ public void stop(long tMillis) {
+ if (mRingRadius.isActive()) {
+ mRingRadius.stop();
+ }
+
+ mFocusState = FocusState.STATE_HARD_STOP;
+ mHardExitStartMillis = computeExitStartTimeMs(tMillis, mHardExitDurationMillis);
+ }
+
+ private long computeExitStartTimeMs(long tMillis, float exitDuration) {
+ if (mEnterStartMillis + mEnterDurationMillis <= tMillis) {
+ return tMillis;
+ }
+
+ // Compute the current progress on the enter animation.
+ float enterT = (tMillis - mEnterStartMillis) / mEnterDurationMillis;
+
+ // Find a time on the exit curve such that it will produce the same value.
+ float exitT = UnitCurves.mapEnterCurveToExitCurveAtT(mEnterOpacityCurve, mExitOpacityCurve,
+ enterT);
+
+ // Compute the a start time before tMs such that the ratio of time completed
+ // equals the computed exit curve animation position.
+ return tMillis - (long) (exitT * exitDuration);
+ }
+
+
+ private long computeEnterStartTimeMillis(long tMillis, float enterDuration) {
+ if (mExitStartMillis + mExitDurationMillis <= tMillis) {
+ return tMillis;
+ }
+
+ // Compute the current progress on the enter animation.
+ float exitT = (tMillis - mExitStartMillis) / mExitDurationMillis;
+
+ // Find a time on the exit curve such that it will produce the same value.
+ float enterT = UnitCurves.mapEnterCurveToExitCurveAtT(mExitOpacityCurve, mEnterOpacityCurve,
+ exitT);
+
+ // Compute the a start time before tMs such that the ratio of time completed
+ // equals the computed exit curve animation position.
+ return tMillis - (long) (enterT * enterDuration);
+ }
+}
diff --git a/src/com/android/camera/ui/focus/FocusRingView.java b/src/com/android/camera/ui/focus/FocusRingView.java
new file mode 100644
index 000000000..3d57fc11f
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusRingView.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.camera.debug.Log.Tag;
+import com.android.camera.ui.motion.AnimationClock.SystemTimeClock;
+import com.android.camera.ui.motion.DynamicAnimator;
+import com.android.camera.ui.motion.InterpolateUtils;
+import com.android.camera.ui.motion.Invalidator;
+import com.android.camera2.R;
+
+/**
+ * Custom view for running the focus ring animations.
+ */
+public class FocusRingView extends View implements Invalidator, FocusRing {
+ private static final Tag TAG = new Tag("FocusRingView");
+
+ // A Diopter of 0.0f ish is infinity.
+ // A Diopter of about 15f or so is focused "as close as possible"
+ // Diopter max is computed from device testing, TODO: Replace with LENS_FOCUS_RANGE
+ // https://developer.android.com/reference/android/hardware/camera2/CaptureResult.html
+ // TODO: Refactor diopter to radius computation outside this class.
+ private static final float DIOPTER_MIN = 0.0f;
+ private static final float DIOPTER_MAX = 15.0f;
+ private static final float DIOPTER_MEDIAN = (DIOPTER_MAX - DIOPTER_MIN) / 2.0f;
+
+ private static final float FADE_IN_DURATION_MILLIS = 1000f;
+ private static final float FADE_OUT_DURATION_MILLIS = 250f;
+
+ private final AutoFocusRing autoFocusRing;
+ private final ManualFocusRing manualFocusRing;
+ private final DynamicAnimator animator;
+
+ private final int mFocusCircleMinSize;
+ private final int mFocusCircleMaxSize;
+
+ private FocusRingRenderer currentFocusAnimation;
+ private boolean isFirstDraw = true;
+ private float mLastDiopter = DIOPTER_MEDIAN;
+
+ public FocusRingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ Resources res = getResources();
+ Paint mPaint = makePaint(res, R.color.focus_color);
+
+ mFocusCircleMinSize = res.getDimensionPixelSize(R.dimen.focus_circle_min_size);
+ mFocusCircleMaxSize = res.getDimensionPixelSize(R.dimen.focus_circle_max_size);
+
+ animator = new DynamicAnimator(this, new SystemTimeClock());
+
+ autoFocusRing = new AutoFocusRing(animator, mPaint,
+ FADE_IN_DURATION_MILLIS,
+ FADE_OUT_DURATION_MILLIS);
+ manualFocusRing = new ManualFocusRing(animator, mPaint,
+ FADE_OUT_DURATION_MILLIS);
+
+ animator.animations.add(autoFocusRing);
+ animator.animations.add(manualFocusRing);
+ }
+
+ @Override
+ public boolean isPassiveFocusRunning() {
+ return autoFocusRing.isActive();
+ }
+
+ @Override
+ public boolean isActiveFocusRunning() {
+ return manualFocusRing.isActive();
+ }
+
+ @Override
+ public void startPassiveFocus() {
+ animator.invalidate();
+ long tMs = animator.getTimeMillis();
+
+ if (manualFocusRing.isActive() && !manualFocusRing.isExiting()) {
+ manualFocusRing.stop(tMs);
+ }
+
+ float lastRadius = radiusForDiopter(mLastDiopter);
+ autoFocusRing.start(tMs, lastRadius, lastRadius);
+ currentFocusAnimation = autoFocusRing;
+ }
+
+ @Override
+ public void startActiveFocus() {
+ animator.invalidate();
+ long tMs = animator.getTimeMillis();
+
+ if (autoFocusRing.isActive() && !autoFocusRing.isExiting()) {
+ autoFocusRing.stop(tMs);
+ }
+
+ manualFocusRing.start(tMs, 0.0f, radiusForDiopter(mLastDiopter));
+ currentFocusAnimation = manualFocusRing;
+ }
+
+ @Override
+ public void stopFocusAnimations() {
+ long tMs = animator.getTimeMillis();
+ if (manualFocusRing.isActive() && !manualFocusRing.isExiting()
+ && !manualFocusRing.isEntering()) {
+ manualFocusRing.exit(tMs);
+ }
+
+ if (autoFocusRing.isActive() && !autoFocusRing.isExiting()) {
+ autoFocusRing.exit(tMs);
+ }
+ }
+
+ @Override
+ public void setFocusLocation(float viewX, float viewY) {
+ autoFocusRing.setCenterX((int) viewX);
+ autoFocusRing.setCenterY((int) viewY);
+ manualFocusRing.setCenterX((int) viewX);
+ manualFocusRing.setCenterY((int) viewY);
+ }
+
+ @Override
+ public void setFocusDiopter(float diopter) {
+ long tMs = animator.getTimeMillis();
+ // Some devices return zero for invalid or "unknown" diopter values.
+ if (currentFocusAnimation != null && diopter > 0.1f) {
+ currentFocusAnimation.setRadius(tMs, radiusForDiopter(diopter));
+ mLastDiopter = diopter;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (isFirstDraw) {
+ isFirstDraw = false;
+ centerAutofocusRing();
+ }
+
+ animator.draw(canvas);
+ }
+
+ private void centerAutofocusRing() {
+ float screenW = this.getWidth();
+ float screenH = this.getHeight();
+
+ autoFocusRing.setCenterX((int) (screenW / 2f));
+ autoFocusRing.setCenterY((int) (screenH / 2f));
+ }
+
+ private float radiusForDiopter(float diopter) {
+ return InterpolateUtils.scale(diopter, DIOPTER_MIN, DIOPTER_MAX, mFocusCircleMinSize,
+ mFocusCircleMaxSize);
+ }
+
+ private Paint makePaint(Resources res, int color) {
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setColor(res.getColor(color));
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(res.getDimension(R.dimen.focus_circle_stroke));
+ return paint;
+ }
+}
diff --git a/src/com/android/camera/ui/focus/FocusSound.java b/src/com/android/camera/ui/focus/FocusSound.java
new file mode 100644
index 000000000..c3ff0107d
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusSound.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import com.android.camera.SoundPlayer;
+
+/**
+ * Wraps the focus sound and the player into a single object that can
+ * be played on demand.
+ *
+ * TODO: This needs some way to better manage the sound lifetimes
+ */
+public class FocusSound {
+ private static final float DEFAULT_VOLUME = 0.6f;
+ private final SoundPlayer mPlayer;
+ private final int mSoundId;
+ public FocusSound(SoundPlayer player, int soundId) {
+ mPlayer = player;
+ mSoundId = soundId;
+
+ mPlayer.loadSound(mSoundId);
+ }
+
+ /**
+ * Play the focus sound with the sound player at the default
+ * volume.
+ */
+ public void play() {
+ if(!mPlayer.isReleased()) {
+ mPlayer.play(mSoundId, DEFAULT_VOLUME);
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/focus/ManualFocusRing.java b/src/com/android/camera/ui/focus/ManualFocusRing.java
new file mode 100644
index 000000000..e3b10d59b
--- /dev/null
+++ b/src/com/android/camera/ui/focus/ManualFocusRing.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.ui.focus;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.android.camera.ui.motion.InterpolateUtils;
+import com.android.camera.ui.motion.Invalidator;
+
+/**
+ * Manual focus ring animation renderer.
+ */
+public class ManualFocusRing extends FocusRingRenderer {
+ /**
+ * The manual focus ring encapsulates the animation logic for visualizing
+ * a focus event when triggered by a physical screen touch.
+ *
+ * @param invalidator the object to invalidate while running.
+ * @param ringPaint the paint to draw the ring with.
+ * @param exitDurationMillis the fade out time in milliseconds.
+ */
+ public ManualFocusRing(Invalidator invalidator, Paint ringPaint,
+ float exitDurationMillis) {
+ super(invalidator, ringPaint, 0.0f, exitDurationMillis);
+ }
+
+ @Override
+ public void draw(long t, long dt, Canvas canvas) {
+ float ringRadius = mRingRadius.update(dt);
+ processStates(t);
+
+ if (!isActive()) {
+ return;
+ }
+
+ mInvalidator.invalidate();
+ int ringAlpha = 255;
+
+ if (mFocusState == FocusState.STATE_FADE_OUT) {
+ float rFade = InterpolateUtils.unitRatio(t, mExitStartMillis, mExitDurationMillis);
+ ringAlpha = (int) InterpolateUtils.lerp(255, 0, mExitOpacityCurve.valueAt(rFade));
+ } else if (mFocusState == FocusState.STATE_HARD_STOP) {
+ float rFade = InterpolateUtils.unitRatio(t, mHardExitStartMillis,
+ mHardExitDurationMillis);
+ ringAlpha = (int) InterpolateUtils.lerp(255, 0, mExitOpacityCurve.valueAt(rFade));
+ } else if (mFocusState == FocusState.STATE_INACTIVE) {
+ ringAlpha = 0;
+ }
+
+ mRingPaint.setAlpha(ringAlpha);
+ canvas.drawCircle(getCenterX(), getCenterY(), ringRadius, mRingPaint);
+ }
+
+ private void processStates(long t) {
+ if (mFocusState == FocusState.STATE_INACTIVE) {
+ return;
+ }
+
+ if (mFocusState == FocusState.STATE_ENTER
+ && (t > mEnterStartMillis + mEnterDurationMillis)) {
+ mFocusState = FocusState.STATE_ACTIVE;
+ }
+
+ if (mFocusState == FocusState.STATE_ACTIVE && !mRingRadius.isActive()) {
+ mFocusState = FocusState.STATE_FADE_OUT;
+ mExitStartMillis = t;
+ }
+
+ if (mFocusState == FocusState.STATE_FADE_OUT && t > mExitStartMillis + mExitDurationMillis) {
+ mFocusState = FocusState.STATE_INACTIVE;
+ }
+
+ if (mFocusState == FocusState.STATE_HARD_STOP
+ && t > mHardExitStartMillis + mHardExitDurationMillis) {
+ mFocusState = FocusState.STATE_INACTIVE;
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/motion/AnimationClock.java b/src/com/android/camera/ui/motion/AnimationClock.java
new file mode 100644
index 000000000..d2504de6b
--- /dev/null
+++ b/src/com/android/camera/ui/motion/AnimationClock.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+import android.os.SystemClock;
+
+/**
+ * Wraps the SystemClock static time methods so they can be exercised in tests.
+ */
+public abstract class AnimationClock {
+
+ public abstract long getTimeMillis();
+
+ /**
+ * Forwards calls to SystemClock.uptimeMillis() since it is the most consistent clock for
+ * animations.
+ */
+ public static class SystemTimeClock extends AnimationClock {
+
+ @Override
+ public long getTimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/motion/DampedSpring.java b/src/com/android/camera/ui/motion/DampedSpring.java
new file mode 100644
index 000000000..84cbfa6f8
--- /dev/null
+++ b/src/com/android/camera/ui/motion/DampedSpring.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+/**
+ * This models a value after the behavior of a spring. The value tracks the current value, a target
+ * value, and the current velocity and applies both a directional force and a damping force to the
+ * value on each update call.
+ */
+public class DampedSpring {
+ public static final float DEFAULT_TIME_TO_90_PERCENT_MILLIS = 200.0f;
+ public static final float DEFAULT_SPRING_STIFFNESS = 3.75f;
+ public static final float EPSILON = 0.01f;
+
+ private final float mSpringStiffness;
+ private final float mTimeTo90PercentMs;
+
+ private float mTarget = 0f;
+ private float mVelocity = 0f;
+ private float mValue = 0f;
+
+ public DampedSpring() {
+ this(DEFAULT_TIME_TO_90_PERCENT_MILLIS, DEFAULT_SPRING_STIFFNESS);
+ }
+
+ public DampedSpring(float timeTo90PercentMs) {
+ this(timeTo90PercentMs, DEFAULT_SPRING_STIFFNESS);
+ }
+
+ public DampedSpring(float timeTo90PercentMs, float springStiffness) {
+ // TODO: Assert timeTo90PercentMs >= 1ms, it might behave badly at low values.
+ // TODO: Assert springStiffness > 2.0f
+
+ mTimeTo90PercentMs = timeTo90PercentMs;
+ mSpringStiffness = springStiffness;
+
+ if (springStiffness > timeTo90PercentMs) {
+ throw new IllegalArgumentException("Creating a spring value with "
+ + "excessive stiffness will oscillate endlessly.");
+ }
+ }
+
+ /**
+ * @return the current value.
+ */
+ public float getValue() {
+ return mValue;
+ }
+
+ /**
+ * @param value the value to set this instance's current state too.
+ */
+ public void setValue(float value) {
+ mValue = value;
+ }
+
+ /**
+ * @return the current target value.
+ */
+ public float getTarget() {
+ return mTarget;
+ }
+
+ /**
+ * Set a target value. The current value will maintain any existing velocity values and will
+ * move towards the new target value. To forcibly stopAt the value use the stopAt() method.
+ *
+ * @param value the new value to move the current value towards.
+ */
+ public void setTarget(float value) {
+ mTarget = value;
+ }
+
+ /**
+ * Update the current value, moving it towards the actual value over the given
+ * time delta (in milliseconds) since the last update. This works off of the
+ * principle of a critically damped spring such that any given current value
+ * will move elastically towards the target value. The current value maintains
+ * and applies velocity, acceleration, and a damping force to give a continuous,
+ * smooth transition towards the target value.
+ *
+ * @param dtMs the time since the last update, or zero.
+ * @return the current value after the update occurs.
+ */
+ public float update(float dtMs) {
+ float dt = dtMs / mTimeTo90PercentMs;
+ float dts = dt * mSpringStiffness;
+
+ // If the dts > 1, and the velocity is zero, the force will exceed the
+ // distance to the target value and it will overshoot the value, causing
+ // weird behavior and unintended oscillation. since a critically damped
+ // spring should never overshoot the value, simply the current value to the
+ // target value.
+ if (dts > 1.0f || dts < 0.0f) {
+ stop();
+ return mValue;
+ }
+
+ float delta = (mTarget - mValue);
+ float force = delta - 2.0f * mVelocity;
+
+ mVelocity += force * dts;
+ mValue += mVelocity * dts;
+
+ // If we get close enough to the actual value, simply set the current value
+ // to the current target value and stop.
+ if (!isActive()) {
+ stop();
+ }
+
+ return mValue;
+ }
+
+ /**
+ * @return true if this instance has velocity or it is not at the target value.
+ */
+ public boolean isActive() {
+ boolean hasVelocity = Math.abs(mVelocity) >= EPSILON;
+ boolean atTarget = Math.abs(mTarget - mValue) < EPSILON;
+ return hasVelocity || !atTarget;
+ }
+
+ /**
+ * Stop the spring motion wherever it is currently at. Sets target to the
+ * current value and sets the velocity to zero.
+ */
+ public void stop() {
+ mTarget = mValue;
+ mVelocity = 0.0f;
+ }
+}
diff --git a/src/com/android/camera/ui/motion/DynamicAnimation.java b/src/com/android/camera/ui/motion/DynamicAnimation.java
new file mode 100644
index 000000000..57d5a1021
--- /dev/null
+++ b/src/com/android/camera/ui/motion/DynamicAnimation.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+import android.graphics.Canvas;
+
+/**
+ * Rendering object that can be driven by an animator instance.
+ */
+public interface DynamicAnimation {
+
+ /**
+ * Check to determine if this animation is currently in a stable state.
+ *
+ * @return true if the animation is stable, false if it should continue to be redrawn.
+ */
+ boolean isActive();
+
+ /**
+ * Update and draw the animation onto the given canvas.
+ *
+ * @param t current animation frame time.
+ * @param dt delta since the last update.
+ * @param canvas the canvas to draw the animation onto.
+ */
+ void draw(long t, long dt, Canvas canvas);
+}
diff --git a/src/com/android/camera/ui/motion/DynamicAnimator.java b/src/com/android/camera/ui/motion/DynamicAnimator.java
new file mode 100644
index 000000000..542ac1e37
--- /dev/null
+++ b/src/com/android/camera/ui/motion/DynamicAnimator.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+import android.graphics.Canvas;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Designed to handle the lifecycle of a view that needs a continuous update /
+ * redraw cycle that does not have a defined start / end time.
+ *
+ * Fixed length animations should NOT use this class.
+ */
+public class DynamicAnimator implements Invalidator {
+
+ public final List<DynamicAnimation> animations = new ArrayList<>();
+
+ private final Invalidator mInvalidator;
+ private final AnimationClock mClock;
+
+ private boolean mUpdateRequested = false;
+ private boolean mIsDrawing = false;
+ private long mLastDrawTimeMillis = 0;
+ private long mDrawTimeMillis = 0;
+
+ public DynamicAnimator(Invalidator invalidator, AnimationClock clock) {
+ mInvalidator = invalidator;
+ mClock = clock;
+ }
+
+ public void draw(Canvas canvas) {
+ mIsDrawing = true;
+ mUpdateRequested = false;
+
+ mDrawTimeMillis = mClock.getTimeMillis();
+
+ if (mLastDrawTimeMillis <= 0) {
+ mLastDrawTimeMillis = mDrawTimeMillis; // On the initial draw, dt is zero.
+ }
+
+ long dt = mDrawTimeMillis - mLastDrawTimeMillis;
+ mLastDrawTimeMillis = mDrawTimeMillis;
+
+ // Run the animation
+ for (DynamicAnimation renderer : animations) {
+ if (renderer.isActive()) {
+ renderer.draw(mDrawTimeMillis, dt, canvas);
+ }
+ }
+
+ // If either the update or the draw methods requested new frames, then
+ // invalidate the view which should give us another frame to work with.
+ // Otherwise, stopAt the last update time.
+ if (mUpdateRequested) {
+ mInvalidator.invalidate();
+ } else {
+ mLastDrawTimeMillis = -1;
+ }
+
+ mIsDrawing = false;
+ }
+
+ /**
+ * If a scheduleNewFrame request comes in outside of the animation loop,
+ * and we didn't schedule a frame after the previous loop (or it's the
+ * first time we've used this instance), invalidate the view and set the
+ * last update time to the current time. Theoretically, a few milliseconds
+ * have elapsed before the view gets updated.
+ */
+ @Override
+ public void invalidate() {
+ if (!mIsDrawing && !mUpdateRequested) {
+ mInvalidator.invalidate();
+ mLastDrawTimeMillis = mClock.getTimeMillis();
+ }
+
+ mUpdateRequested = true;
+ }
+
+ /**
+ * This will return the "best guess" for the most current animation frame
+ * time. If the loop is currently drawing, then it will return the time the
+ * draw began, and if an update is currently requested it will return the
+ * time that the update was requested at, and if neither of these are true
+ * it will return the current system clock time.
+ *
+ * This method will not trigger a new update.
+ */
+ public long getTimeMillis() {
+ if (mIsDrawing) {
+ return mDrawTimeMillis;
+ }
+
+ if (mUpdateRequested) {
+ return mLastDrawTimeMillis;
+ }
+
+ return mClock.getTimeMillis();
+ }
+}
diff --git a/src/com/android/camera/ui/motion/InterpolateUtils.java b/src/com/android/camera/ui/motion/InterpolateUtils.java
new file mode 100644
index 000000000..3c3cd532f
--- /dev/null
+++ b/src/com/android/camera/ui/motion/InterpolateUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+/**
+ * Various static helper functions for interpolating between values.
+ */
+public class InterpolateUtils {
+
+ private InterpolateUtils() {
+ }
+
+ /**
+ * Linear interpolation from v0 to v1 as t goes from 0...1
+ *
+ * @param v0 the value at t=0
+ * @param v1 the value at t=1
+ * @param t value in the range of 0 to 1.
+ * @return the value between v0 and v1 as a ratio between 0 and 1 defined by t.
+ */
+ public static float lerp(float v0, float v1, float t) {
+ return v0 + t * (v1 - v0);
+ }
+
+ /**
+ * Project a value that is within the in(Min/Max) number space into the to(Min/Max) number
+ * space.
+ *
+ * @param v value to scale into the 'to' number space.
+ * @param vMin min value of the values number space.
+ * @param vMax max value of the values number space.
+ * @param pMin min value of the projection number space.
+ * @param pMax max value of the projection number space.
+ * @return the ratio of the value in the source number space as a value in the to(Min/Max)
+ * number space.
+ */
+ public static float scale(float v, float vMin, float vMax, float pMin, float pMax) {
+ return (pMax - pMin) * (v - vMin) / (vMax - vMin) + pMin;
+ }
+
+ /**
+ * Value between 0 and 1 as a ratio between tBegin over tDuration
+ * with no upper bound.
+ */
+ public static float unitRatio(long t, long tBegin, float tDuration) {
+ if (t <= tBegin) {
+ return 0.0f;
+ }
+
+ return (t - tBegin) / tDuration;
+ }
+}
diff --git a/src/com/android/camera/ui/motion/Invalidator.java b/src/com/android/camera/ui/motion/Invalidator.java
new file mode 100644
index 000000000..fdb548748
--- /dev/null
+++ b/src/com/android/camera/ui/motion/Invalidator.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+/**
+ * Basic interface for objects that can be invalidated.
+ */
+public interface Invalidator {
+ /**
+ * Request that the object should be redrawn whenever it gets
+ * the chance.
+ */
+ void invalidate();
+}
diff --git a/src/com/android/camera/ui/motion/UnitBezier.java b/src/com/android/camera/ui/motion/UnitBezier.java
new file mode 100644
index 000000000..242f54556
--- /dev/null
+++ b/src/com/android/camera/ui/motion/UnitBezier.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+/**
+ * This represents is a precomputed cubic bezier curve starting at (0,0) and
+ * going to (1,1) with two configurable control points. Once the instance is
+ * created, the control points cannot be modified.
+ *
+ * Generally, this will be used for computing timing curves for with control
+ * points where an x value will be provide from 0.0 - 1.0, and the y value will
+ * be solved for where y is used as the timing value in some linear
+ * interpolation of a value.
+ */
+public class UnitBezier implements UnitCurve {
+
+ private static final float EPSILON = 1e-6f;
+
+ private final DerivableFloatFn mXFn;
+ private final DerivableFloatFn mYFn;
+
+ /**
+ * Build and pre-compute a unit bezier. This assumes a starting point of
+ * (0, 0) and end point of (1.0, 1.0).
+ *
+ * @param c0x control point x value for p0
+ * @param c0y control point y value for p0
+ * @param c1x control point x value for p1
+ * @param c1y control point y value for p1
+ */
+ public UnitBezier(float c0x, float c0y, float c1x, float c1y) {
+ mXFn = new CubicBezierFn(c0x, c1x);
+ mYFn = new CubicBezierFn(c0y, c1y);
+ }
+
+ /**
+ * Given a unit bezier curve find the height of the curve at t (which is
+ * internally represented as the xAxis).
+ *
+ * @param t the x position between 0 and 1 to solve for y.
+ * @return the closest approximate height of the curve at x.
+ */
+ @Override
+ public float valueAt(float t) {
+ return mYFn.value(solve(t, mXFn));
+ }
+
+ /**
+ * Given a unit bezier curve find a value along the x axis such that
+ * valueAt(result) produces the input value.
+ *
+ * @param value the y position between 0 and 1 to solve for x
+ * @return the closest approximate input that will produce value when provided
+ * to the valueAt function.
+ */
+ @Override
+ public float tAt(float value) {
+ return mXFn.value(solve(value, mYFn));
+ }
+
+ private float solve(float target, DerivableFloatFn fn) {
+ // For a linear fn, t = value. This makes value a good starting guess.
+ float input = target;
+
+ // Newton's method (Faster than bisection)
+ for (int i = 0; i < 8; i++) {
+ float value = fn.value(input) - target;
+ if (Math.abs(value) < EPSILON) {
+ return input;
+ }
+ float derivative = fn.derivative(input);
+ if (Math.abs(derivative) < EPSILON) {
+ break;
+ }
+ input = input - value / derivative;
+ }
+
+ // Fallback on bi-section
+ float min = 0.0f;
+ float max = 1.0f;
+ input = target;
+
+ if (input < min) {
+ return min;
+ }
+ if (input > max) {
+ return max;
+ }
+
+ while (min < max) {
+ float value = fn.value(input);
+ if (Math.abs(value - target) < EPSILON) {
+ return input;
+ }
+
+ if (target > value) {
+ min = input;
+ } else {
+ max = input;
+ }
+
+ input = (max - min) * .5f + min;
+ }
+
+ // Give up, return the closest match we got too.
+ return input;
+ }
+
+ private interface DerivableFloatFn {
+ float value(float x);
+ float derivative(float x);
+ }
+
+ /**
+ * Precomputed constants for a given set of control points along a given
+ * cubic bezier axis.
+ */
+ private static class CubicBezierFn implements DerivableFloatFn {
+ private final float c;
+ private final float a;
+ private final float b;
+
+ /**
+ * Build and pre-compute a single axis for a unit bezier. This assumes p0
+ * is 0 and p1 is 1.
+ *
+ * @param c0 start control point.
+ * @param c1 end control point.
+ */
+ public CubicBezierFn(float c0, float c1) {
+ c = 3.0f * c0;
+ b = 3.0f * (c1 - c0) - c;
+ a = 1.0f - c - b;
+ }
+
+ public float value(float x) {
+ return ((a * x + b) * x + c) * x;
+ }
+ public float derivative(float x) {
+ return (3.0f * a * x + 2.0f * b) * x + c;
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/motion/UnitCurve.java b/src/com/android/camera/ui/motion/UnitCurve.java
new file mode 100644
index 000000000..d89f1fa4d
--- /dev/null
+++ b/src/com/android/camera/ui/motion/UnitCurve.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+/**
+ * Simple functions that produce values along a curve for any given input and can compute input
+ * times for a given output value.
+ */
+public interface UnitCurve {
+
+ /**
+ * Produce a unit value of this curve at time t. The function should always return a valid
+ * return value for any valid t input.
+ *
+ * @param t ratio of time passed from (0..1)
+ * @return the unit value at t.
+ */
+ float valueAt(float t);
+
+ /**
+ * If possible, find a value for t such that valueAt(t) == value or best guess.
+ *
+ * @param value to match to the output of valueAt(t)
+ * @return t where valueAt(t) == value or throw.
+ */
+ float tAt(float value);
+}
diff --git a/src/com/android/camera/ui/motion/UnitCurves.java b/src/com/android/camera/ui/motion/UnitCurves.java
new file mode 100644
index 000000000..a1117fa96
--- /dev/null
+++ b/src/com/android/camera/ui/motion/UnitCurves.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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.ui.motion;
+
+/**
+ * Predefined material curves and animations.
+ */
+public class UnitCurves {
+ public static final UnitCurve FAST_OUT_SLOW_IN = new UnitBezier(0.4f, 0.0f, 0.2f, 1.0f);
+ public static final UnitCurve LINEAR_OUT_SLOW_IN = new UnitBezier(0.0f, 0.0f, 0.2f, 1.0f);
+ public static final UnitCurve FAST_OUT_LINEAR_IN = new UnitBezier(0.4f, 0.0f, 1.0f, 1.0f);
+ public static final UnitCurve LINEAR = new UnitBezier(0.0f, 0.0f, 1.0f, 1.0f);
+
+ /**
+ * Given two curves (from and to) and a time along the from curve, compute
+ * the time at t, and find a t along the 'toCurve' that will produce the
+ * same output. This is useful when interpolating between two different curves
+ * when the animation is not at the beginning or end.
+ *
+ * @param enterCurve the curve to compute the value from
+ * @param exitCurve the curve to find a time t on that matches output of
+ * enterCurve at T.
+ * @param t the time at which to compute the value (0..1)
+ * @return the time along the exitCurve.
+ */
+ public static float mapEnterCurveToExitCurveAtT(UnitCurve enterCurve, UnitCurve exitCurve,
+ float t) {
+ return exitCurve.tAt(1 - enterCurve.valueAt(t));
+ }
+}
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index c265f0033..aa8c5254f 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -41,7 +41,6 @@ import android.os.ParcelFileDescriptor;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.util.TypedValue;
-import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
@@ -736,7 +735,7 @@ public class CameraUtil {
+ "," + rect.right + "," + rect.bottom + ")");
}
- public static void rectFToRect(RectF rectF, Rect rect) {
+ public static void inlineRectToRectF(RectF rectF, Rect rect) {
rect.left = Math.round(rectF.left);
rect.top = Math.round(rectF.top);
rect.right = Math.round(rectF.right);
@@ -745,7 +744,7 @@ public class CameraUtil {
public static Rect rectFToRect(RectF rectF) {
Rect rect = new Rect();
- rectFToRect(rectF, rect);
+ inlineRectToRectF(rectF, rect);
return rect;
}
@@ -765,21 +764,6 @@ public class CameraUtil {
matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
}
- public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
- Rect previewRect) {
- // Need mirror for front camera.
- matrix.setScale(mirror ? -1 : 1, 1);
- // This is the value for android.hardware.Camera.setDisplayOrientation.
- matrix.postRotate(displayOrientation);
-
- // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
- // We need to map camera driver coordinates to preview rect coordinates
- Matrix mapping = new Matrix();
- mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect),
- Matrix.ScaleToFit.FILL);
- matrix.setConcat(mapping, matrix);
- }
-
public static String createJpegName(long dateTaken) {
synchronized (sImageFileNamer) {
return sImageFileNamer.generateName(dateTaken);
diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java
index 95888fe3d..d9b95b01d 100644
--- a/src/com/android/camera/widget/FilmstripView.java
+++ b/src/com/android/camera/widget/FilmstripView.java
@@ -702,6 +702,9 @@ public class FilmstripView extends ViewGroup {
if (recycledViewsForType != null) {
result = recycledViewsForType.poll();
}
+ if (result != null) {
+ result.setVisibility(View.GONE);
+ }
return result;
}