summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/ui')
-rw-r--r--src/com/android/camera/ui/Arrows.java91
-rw-r--r--src/com/android/camera/ui/CameraControls.java39
-rw-r--r--src/com/android/camera/ui/FilmStripView.java4
-rw-r--r--src/com/android/camera/ui/ListMenu.java20
-rw-r--r--src/com/android/camera/ui/MenuHelp.java387
-rw-r--r--src/com/android/camera/ui/ModuleSwitcher.java16
-rwxr-xr-xsrc/com/android/camera/ui/OneUICameraControls.java4
-rw-r--r--src/com/android/camera/ui/Switch.java2
-rwxr-xr-xsrc/com/android/camera/ui/ZoomRenderer.java65
-rw-r--r--src/com/android/camera/ui/focus/AutoFocusRing.java101
-rw-r--r--src/com/android/camera/ui/focus/CameraCoordinateTransformer.java109
-rw-r--r--src/com/android/camera/ui/focus/FocusController.java128
-rw-r--r--src/com/android/camera/ui/focus/FocusRing.java72
-rw-r--r--src/com/android/camera/ui/focus/FocusRingRenderer.java237
-rw-r--r--src/com/android/camera/ui/focus/FocusRingView.java211
-rw-r--r--src/com/android/camera/ui/focus/FocusSound.java47
-rw-r--r--src/com/android/camera/ui/focus/LensRangeCalculator.java71
-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/InterpolatorHelper.java38
-rw-r--r--src/com/android/camera/ui/motion/Invalidator.java28
-rw-r--r--src/com/android/camera/ui/motion/LinearScale.java85
-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
29 files changed, 1964 insertions, 533 deletions
diff --git a/src/com/android/camera/ui/Arrows.java b/src/com/android/camera/ui/Arrows.java
deleted file mode 100644
index 4923eb10e..000000000
--- a/src/com/android/camera/ui/Arrows.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
-Copyright (c) 2016, The Linux Foundation. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
- * Neither the name of The Linux Foundation nor the names of its
- contributors may be used to endorse or promote products derived
- from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
-BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-package com.android.camera.ui;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.view.View;
-
-import java.util.ArrayList;
-
-public class Arrows extends View {
- private static final int ARROW_COLOR = Color.WHITE;
- private static final double ARROW_END_DEGREE = 15d;
- private static final int ARROW_END_LENGTH = 50;
-
- private Paint mPaint;
- private ArrayList<Path> mPaths;
-
- public Arrows(Context context, AttributeSet attrs) {
- super(context, attrs);
- mPaths = new ArrayList<Path>();
- mPaint = new Paint();
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setColor(ARROW_COLOR);
- mPaint.setStrokeWidth(2f);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mPaths != null) {
- for(int i=0; i < mPaths.size(); i++) {
- canvas.drawPath(mPaths.get(i), mPaint);
- }
- }
- }
-
- public void addPath(float[] x, float[] y) {
- Path path = new Path();
- path.reset();
- path.moveTo(x[0], y[0]);
- for(int i=1; i < x.length; i++) {
- if(i == x.length-1) {
- path.lineTo(x[i], y[i]);
-
- double setha = Math.toDegrees(Math.atan2(y[i] - y[i - 1], x[i] - x[i - 1]));
- setha = (setha + ARROW_END_DEGREE + 360) % 360;
- path.lineTo(x[i]-(float)(ARROW_END_LENGTH*Math.cos(Math.toRadians(setha))),
- y[i]-(float)(ARROW_END_LENGTH*Math.sin(Math.toRadians(setha))));
- path.lineTo(x[i], y[i]);
- setha = (setha - ARROW_END_DEGREE*2 + 360) % 360;
- path.lineTo(x[i]-(float)(ARROW_END_LENGTH*Math.cos(Math.toRadians(setha))),
- y[i]-(float)(ARROW_END_LENGTH*Math.sin(Math.toRadians(setha))));
- }
- else
- path.quadTo(x[i],y[i], x[i+1], y[i+1]);
- }
- mPaths.add(path);
- invalidate();
- }
-}
diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java
index b85d7f1ca..26a405727 100644
--- a/src/com/android/camera/ui/CameraControls.java
+++ b/src/com/android/camera/ui/CameraControls.java
@@ -59,7 +59,6 @@ public class CameraControls extends RotatableLayout {
private View mFrontBackSwitcher;
private View mHdrSwitcher;
private View mTsMakeupSwitcher;
- private View mIndicators;
private View mPreview;
private View mSceneModeSwitcher;
private View mFilterModeSwitcher;
@@ -135,7 +134,6 @@ public class CameraControls extends RotatableLayout {
mMenu.setVisibility(View.INVISIBLE);
mMute.setVisibility(View.INVISIBLE);
mExitPanorama.setVisibility(View.INVISIBLE);
- mIndicators.setVisibility(View.INVISIBLE);
mPreview.setVisibility(View.INVISIBLE);
isAnimating = false;
enableTouch(true);
@@ -160,7 +158,6 @@ public class CameraControls extends RotatableLayout {
mMenu.setVisibility(View.INVISIBLE);
mMute.setVisibility(View.INVISIBLE);
mExitPanorama.setVisibility(View.INVISIBLE);
- mIndicators.setVisibility(View.INVISIBLE);
mPreview.setVisibility(View.INVISIBLE);
isAnimating = false;
enableTouch(true);
@@ -276,8 +273,6 @@ public class CameraControls extends RotatableLayout {
mViewList.add(mMute);
if (mExitPanorama.getVisibility() == View.VISIBLE)
mViewList.add(mExitPanorama);
- if (mIndicators.getVisibility() == View.VISIBLE)
- mViewList.add(mIndicators);
}
public void removeFromViewList(View view) {
@@ -303,7 +298,6 @@ public class CameraControls extends RotatableLayout {
mMute = findViewById(R.id.mute_button);
mExitPanorama = findViewById(R.id.exit_panorama);
mExitPanorama.setVisibility(View.GONE);
- mIndicators = findViewById(R.id.on_screen_indicators);
mPreview = findViewById(R.id.preview_thumb);
mSceneModeSwitcher = findViewById(R.id.scene_mode_switcher);
mFilterModeSwitcher = findViewById(R.id.filter_mode_switcher);
@@ -349,6 +343,7 @@ public class CameraControls extends RotatableLayout {
mReviewDoneButton = null;
}
layoutRemaingPhotos();
+ mRemainingPhotos.setVisibility(View.VISIBLE);
}
@Override
@@ -381,19 +376,18 @@ public class CameraControls extends RotatableLayout {
int rotation = getUnifiedRotation();
toIndex(mSwitcher, w, h, rotation, 4, 6, SWITCHER_INDEX);
toIndex(mVideoShutter, w, h, rotation, 3, 6, VIDEO_SHUTTER_INDEX);
- toIndex(mMenu, w, h, rotation, 4, 0, MENU_INDEX);
- toIndex(mMute, w, h, rotation, 3, 0, MUTE_INDEX);
+ toIndex(mSceneModeSwitcher, w, h, rotation, 4, 0, SCENE_MODE_INDEX);
+ toIndex(mMute, w, h, rotation, 4, 0, MUTE_INDEX);
+ toIndex(mFilterModeSwitcher, w, h, rotation, 3, 0, FILTER_MODE_INDEX);
toIndex(mExitPanorama, w, h, rotation, 0, 0, EXIT_PANORAMA_INDEX);
- toIndex(mIndicators, w, h, rotation, 0, 6, INDICATOR_INDEX);
- toIndex(mFrontBackSwitcher, w, h, rotation, 2, 0, FRONT_BACK_INDEX);
+ toIndex(mFrontBackSwitcher, w, h, rotation, 1, 0, FRONT_BACK_INDEX);
toIndex(mPreview, w, h, rotation, 0, 6, PREVIEW_INDEX);
if(TsMakeupManager.HAS_TS_MAKEUP) {
- toIndex(mTsMakeupSwitcher, w, h, rotation, 3, 0, TS_MAKEUP_INDEX);
+ toIndex(mTsMakeupSwitcher, w, h, rotation, 2, 0, TS_MAKEUP_INDEX);
} else {
- toIndex(mHdrSwitcher, w, h, rotation, 3, 0, HDR_INDEX);
+ toIndex(mHdrSwitcher, w, h, rotation, 2, 0, HDR_INDEX);
}
- toIndex(mFilterModeSwitcher, w, h, rotation, 1, 0, FILTER_MODE_INDEX);
- toIndex(mSceneModeSwitcher, w, h, rotation, 0, 0, SCENE_MODE_INDEX);
+ toIndex(mMenu, w, h, rotation, 0, 0, MENU_INDEX);
layoutToast(mRefocusToast, w, h, rotation);
}
@@ -498,7 +492,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.setX(mLocX[idx1][SWITCHER_INDEX] - x);
mShutter.setX(mLocX[idx1][SHUTTER_INDEX] - x);
mVideoShutter.setX(mLocX[idx1][VIDEO_SHUTTER_INDEX] - x);
- mIndicators.setX(mLocX[idx1][INDICATOR_INDEX] - x);
mPreview.setX(mLocX[idx1][PREVIEW_INDEX] - x);
mFrontBackSwitcher.setY(mLocY[idx1][FRONT_BACK_INDEX] + y);
@@ -515,7 +508,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.setY(mLocY[idx1][SWITCHER_INDEX] - y);
mShutter.setY(mLocY[idx1][SHUTTER_INDEX] - y);
mVideoShutter.setY(mLocY[idx1][VIDEO_SHUTTER_INDEX] - y);
- mIndicators.setY(mLocY[idx1][INDICATOR_INDEX] - y);
mPreview.setY(mLocY[idx1][PREVIEW_INDEX] - y);
}
@@ -550,7 +542,6 @@ public class CameraControls extends RotatableLayout {
mMenu.animate().cancel();
mMute.animate().cancel();
mExitPanorama.animate().cancel();
- mIndicators.animate().cancel();
mPreview.animate().cancel();
mFrontBackSwitcher.animate().setListener(outlistener);
((ModuleSwitcher) mSwitcher).removePopup();
@@ -573,7 +564,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
break;
case 90:
@@ -592,7 +582,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
break;
case 180:
@@ -611,7 +600,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
break;
case 270:
@@ -630,7 +618,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
break;
}
@@ -657,7 +644,6 @@ public class CameraControls extends RotatableLayout {
mMenu.animate().cancel();
mMute.animate().cancel();
mExitPanorama.animate().cancel();
- mIndicators.animate().cancel();
mPreview.animate().cancel();
if (mViewList != null)
for (View v : mViewList) {
@@ -669,7 +655,6 @@ public class CameraControls extends RotatableLayout {
shutterAnim.stop();
mMenu.setVisibility(View.VISIBLE);
- mIndicators.setVisibility(View.VISIBLE);
mPreview.setVisibility(View.VISIBLE);
mFrontBackSwitcher.animate().setListener(inlistener);
@@ -692,7 +677,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
break;
case 90:
@@ -713,7 +697,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
break;
case 180:
@@ -734,7 +717,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
break;
case 270:
@@ -755,7 +737,6 @@ public class CameraControls extends RotatableLayout {
mSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mVideoShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
- mIndicators.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mPreview.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
break;
}
@@ -1010,7 +991,7 @@ public class CameraControls extends RotatableLayout {
mPaint.setColor(getResources().getColor(R.color.camera_control_bg_transparent));
}
}
- invalidate();
+ requestLayout();
}
public void showRefocusToast(boolean show) {
@@ -1025,7 +1006,7 @@ public class CameraControls extends RotatableLayout {
View[] views = {
mSceneModeSwitcher, mFilterModeSwitcher, mFrontBackSwitcher,
TsMakeupManager.HAS_TS_MAKEUP ? mTsMakeupSwitcher : mHdrSwitcher,
- mMenu, mShutter, mPreview, mSwitcher, mMute, mReviewRetakeButton,
+ mMenu, mPreview, mSwitcher, mMute, mReviewRetakeButton,
mReviewCancelButton, mReviewDoneButton, mExitPanorama
};
for (View v : views) {
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index e6e00ad56..6a767862f 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -2825,8 +2825,6 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
@Override
public boolean onScroll(float x, float y, float dx, float dy) {
- if (mPreviewGestures != null && mPreviewGestures.waitUntilNextDown())
- return false;
ViewItem currItem = mViewItem[mCurrentItem];
if (currItem == null) {
return false;
@@ -2892,8 +2890,6 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener {
@Override
public boolean onFling(float velocityX, float velocityY) {
- if (mPreviewGestures != null && mPreviewGestures.waitUntilNextDown())
- return false;
final ViewItem currItem = mViewItem[mCurrentItem];
if (currItem == null) {
return false;
diff --git a/src/com/android/camera/ui/ListMenu.java b/src/com/android/camera/ui/ListMenu.java
index d83114cce..88ad8fe2f 100644
--- a/src/com/android/camera/ui/ListMenu.java
+++ b/src/com/android/camera/ui/ListMenu.java
@@ -120,8 +120,7 @@ public class ListMenu extends ListView
+ " position " + position);
}
if (position == mHighlighted)
- view.setBackgroundColor(getContext().getResources()
- .getColor(R.color.setting_color));
+ view.setActivated(true);
return view;
}
@@ -160,7 +159,6 @@ public class ListMenu extends ListView
ArrayAdapter<ListPreference> mListItemAdapter = new MoreSettingAdapter();
setAdapter(mListItemAdapter);
setOnItemClickListener(this);
- setSelector(android.R.color.transparent);
// Initialize mEnabled
mEnabled = new boolean[mListItem.size()];
for (int i = 0; i < mEnabled.length; i++) {
@@ -183,7 +181,6 @@ public class ListMenu extends ListView
ArrayAdapter<ListPreference> mListItemAdapter = new MoreSettingAdapter();
setAdapter(mListItemAdapter);
setOnItemClickListener(this);
- setSelector(android.R.color.transparent);
// Initialize mEnabled
mEnabled = new boolean[mListItem.size()];
for (int i = 0; i < mEnabled.length; i++) {
@@ -229,7 +226,7 @@ public class ListMenu extends ListView
mEnabled[j] = enable;
int offset = getFirstVisiblePosition();
if (offset >= 0) {
- int indexInView = j - offset;
+ int indexInView = j + getHeaderViewsCount() - offset;
if (getChildCount() > indexInView && indexInView >= 0) {
getChildAt(indexInView).setEnabled(enable);
}
@@ -243,8 +240,7 @@ public class ListMenu extends ListView
public void resetHighlight() {
int count = getChildCount();
for (int i = 0; i < count; i++) {
- View v = getChildAt(i);
- v.setBackground(null);
+ getChildAt(i).setActivated(false);
}
mHighlighted = -1;
}
@@ -265,7 +261,7 @@ public class ListMenu extends ListView
resetHighlight();
ListPreference pref = mListItem.get(position);
mHighlighted = position;
- view.setBackgroundColor(getContext().getResources().getColor(R.color.setting_color));
+ view.setActivated(true);
mListener.onPreferenceClicked(pref, (int) view.getY());
}
@@ -274,11 +270,9 @@ public class ListMenu extends ListView
public void reloadPreference() {
int count = getChildCount();
for (int i = 0; i < count; i++) {
- ListPreference pref = mListItem.get(i);
- if (pref != null) {
- ListMenuItem listMenuItem =
- (ListMenuItem) getChildAt(i);
- listMenuItem.reloadPreference();
+ View view = getChildAt(i);
+ if (view instanceof ListMenuItem) {
+ ((ListMenuItem) view).reloadPreference();
}
}
}
diff --git a/src/com/android/camera/ui/MenuHelp.java b/src/com/android/camera/ui/MenuHelp.java
deleted file mode 100644
index 4adff0fd6..000000000
--- a/src/com/android/camera/ui/MenuHelp.java
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
-Copyright (c) 2016, The Linux Foundation. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
- * Neither the name of The Linux Foundation nor the names of its
- contributors may be used to endorse or promote products derived
- from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
-BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-package com.android.camera.ui;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.graphics.drawable.AnimationDrawable;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TableLayout;
-import android.widget.TextView;
-import java.util.ArrayList;
-
-import org.codeaurora.snapcam.R;
-import com.android.camera.ui.ModuleSwitcher;
-import com.android.camera.ui.RotateImageView;
-import com.android.camera.ShutterButton;
-import com.android.camera.Storage;
-import com.android.camera.util.CameraUtil;
-import com.android.camera.TsMakeupManager;
-
-public class MenuHelp extends RotatableLayout {
-
- private static final String TAG = "MenuHelp";
- private View mBackgroundView;
- private Arrows mArrows;
- private static int mTopMargin = 0;
- private static int mBottomMargin = 0;
- private static final int HELP_0_0_INDEX = 0;
- private static final int HELP_1_0_INDEX = 1;
- private static final int HELP_3_0_INDEX = 2;
- private static final int HELP_4_6_INDEX = 3;
- private static final int OK_2_4_INDEX = 4;
- private static final int MAX_INDEX = 5;
- private float[][] mLocX = new float[4][MAX_INDEX];
- private float[][] mLocY = new float[4][MAX_INDEX];
- private RotateLayout mHelp0_0;
- private RotateLayout mHelp1_0;
- private RotateLayout mHelp3_0;
- private RotateLayout mHelp4_6;
- private RotateLayout mOk2_4;
- private Context mContext;
- private int mOrientation;
- private final static int POINT_MARGIN = 50;
- private static final int WIDTH_GRID = 5;
- private static final int HEIGHT_GRID = 7;
- private Typeface mTypeface;
- private boolean forCamera2 = false;
-
- public MenuHelp(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mTypeface = Typeface.create(Typeface.SERIF, Typeface.NORMAL);
- }
-
- public MenuHelp(Context context) {
- this(context, null);
- }
-
- public void setForCamera2(boolean forCamera2) {
- this.forCamera2 = forCamera2;
- }
-
- public void setMargins(int top, int bottom) {
- mTopMargin = top;
- mBottomMargin = bottom;
- }
-
- @Override
- public void onLayout(boolean changed, int l, int t, int r, int b) {
- r = r - l;
- b = b - t;
- l = 0;
- t = 0;
- for (int i = 0; i < getChildCount(); i++) {
- View v = getChildAt(i);
- v.layout(l, t, r, b);
- }
- setLocation(r - l, b - t);
- }
-
- private void setLocation(int w, int h) {
- int rotation = getUnifiedRotation();
- toIndex(mHelp0_0, w, h, rotation, 1, 3, HELP_0_0_INDEX);
- toIndex(mHelp1_0, w, h, rotation, 2, 2, HELP_1_0_INDEX);
- if(TsMakeupManager.HAS_TS_MAKEUP)
- toIndex(mHelp3_0, w, h, rotation, 3, 1, HELP_3_0_INDEX);
- if (!forCamera2) {
- toIndex(mHelp4_6, w, h, rotation, 3, 4, HELP_4_6_INDEX);
- } else {
- mHelp4_6.setVisibility(View.GONE);
- }
- toIndex(mOk2_4, w, h, rotation, 1, 5, OK_2_4_INDEX);
- fillArrows(w, h, rotation);
- }
-
- private void fillArrows(int w, int h, int rotation) {
- View v1 = new View(mContext);
- View v2 = new View(mContext);
- View v3 = new View(mContext);
- {
- toIndex(v1, w, h, rotation, 1, 3, -1);
- toIndex(v2, w, h, rotation, 0, 1, -1);
- toIndex(v3, w, h, rotation, 0, 0, -1);
- float[] x = {v1.getX()-POINT_MARGIN, v2.getX(), v3.getX()};
- float[] y = {v1.getY()-POINT_MARGIN, v2.getY(), v3.getY()+POINT_MARGIN};
- mArrows.addPath(x, y);
- }
-
- {
- toIndex(v1, w, h, rotation, 2, 2, -1);
- toIndex(v2, w, h, rotation, 1, 1, -1);
- toIndex(v3, w, h, rotation, 1, 0, -1);
- float[] x = {v1.getX()-POINT_MARGIN, v2.getX(), v3.getX()};
- float[] y = {v1.getY()-POINT_MARGIN, v2.getY(), v3.getY()+POINT_MARGIN};
- mArrows.addPath(x, y);
- }
-
- if(TsMakeupManager.HAS_TS_MAKEUP) {
- toIndex(v1, w, h, rotation, 3, 1, -1);
- toIndex(v2, w, h, rotation, 3, 0, -1);
- float[] x = {v1.getX(), v2.getX()};
- float[] y = {v1.getY()-POINT_MARGIN*2, v2.getY()+POINT_MARGIN};
- mArrows.addPath(x, y);
- }
-
- if (!forCamera2) {
- toIndex(v1, w, h, rotation, 3, 4, -1);
- toIndex(v2, w, h, rotation, 3, 5, -1);
- toIndex(v3, w, h, rotation, 4, 6, -1);
- float[] x = {v1.getX(), v2.getX(), v3.getX()};
- float[] y = {v1.getY()+POINT_MARGIN, v2.getY(), v3.getY()-POINT_MARGIN};
- mArrows.addPath(x, y);
- }
- }
-
- private void toIndex(View v, int w, int h, int rotation, int index, int index2, int index3) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
- int tw = v.getMeasuredWidth();
- int th = v.getMeasuredHeight();
- int l = 0, r = 0, t = 0, b = 0;
-
- int wnumber = WIDTH_GRID;
- int hnumber = HEIGHT_GRID;
- int windex = 0;
- int hindex = 0;
- switch (rotation) {
- case 0:
- // portrait, to left of anchor at bottom
- wnumber = WIDTH_GRID;
- hnumber = HEIGHT_GRID;
- windex = index;
- hindex = index2;
- break;
- case 90:
- // phone landscape: below anchor on right
- wnumber = HEIGHT_GRID;
- hnumber = WIDTH_GRID;
- windex = index2;
- hindex = hnumber - index - 1;
- break;
- case 180:
- // phone upside down: right of anchor at top
- wnumber = WIDTH_GRID;
- hnumber = HEIGHT_GRID;
- windex = wnumber - index - 1;
- hindex = hnumber - index2 - 1;
- break;
- case 270:
- // reverse landscape: above anchor on left
- wnumber = HEIGHT_GRID;
- hnumber = WIDTH_GRID;
- windex = wnumber - index2 - 1;
- hindex = index;
- break;
- }
- int boxh = h / hnumber;
- int boxw = w / wnumber;
- int cx = (2 * windex + 1) * boxw / 2;
- int cy = (2 * hindex + 1) * boxh / 2;
-
- if (index2 == 0 && mTopMargin != 0) {
- switch (rotation) {
- case 90:
- cx = mTopMargin / 2;
- break;
- case 180:
- cy = h - mTopMargin / 2;
- break;
- case 270:
- cx = w - mTopMargin / 2;
- break;
- default:
- cy = mTopMargin / 2;
- break;
- }
- }
-
- l = cx - tw / 2;
- r = cx + tw / 2;
- t = cy - th / 2;
- b = cy + th / 2;
-
- if (index3 != -1) {
- int idx1 = rotation / 90;
- int idx2 = index3;
- mLocX[idx1][idx2] = l;
- mLocY[idx1][idx2] = t;
- }
- v.layout(l, t, r, b);
- }
-
- public void setOrientation(int orientation, boolean animation) {
- mOrientation = orientation;
- RotateLayout[] layouts = {
- mHelp0_0, mHelp1_0, mHelp3_0, mHelp4_6, mOk2_4
- };
- for (RotateLayout l : layouts) {
- l.setOrientation(orientation, animation);
- }
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- mBackgroundView = findViewById(R.id.background);
- mBackgroundView.setBackgroundColor(Color.argb(200, 0, 0, 0));
- mHelp0_0 = (RotateLayout)findViewById(R.id.help_text_0_0);
- fillHelp0_0();
- mHelp1_0 = (RotateLayout)findViewById(R.id.help_text_1_0);
- fillHelp1_0();
- mHelp3_0 = (RotateLayout) findViewById(R.id.help_text_3_0);
- fillHelp3_0();
- mHelp4_6 = (RotateLayout)findViewById(R.id.help_text_4_6);
- fillHelp4_6();
- mOk2_4 = (RotateLayout)findViewById(R.id.help_ok_2_4);
- fillOk2_4();
- mArrows = (Arrows)findViewById(R.id.arrows);
- }
-
- private void fillOk2_4() {
- LinearLayout linearLayout = new LinearLayout(mContext);
- mOk2_4.addView(linearLayout);
- linearLayout.setGravity(Gravity.CENTER);
- linearLayout.setPadding(40, 20, 40, 20);
- linearLayout.setBackgroundColor(Color.WHITE);
- TextView text1 = new TextView(mContext);
- text1.setText(getResources().getString(R.string.help_menu_ok));
- text1.setTextColor(Color.BLACK);
- text1.setTypeface(mTypeface);
- linearLayout.addView(text1);
- }
-
- private void fillHelp0_0() {
- TableLayout tableLayout = new TableLayout(mContext);
- mHelp0_0.addView(tableLayout);
- LinearLayout linearLayout = new LinearLayout(mContext);
- TextView text1 = new TextView(mContext);
- text1.setTextColor(getResources().getColor(R.color.help_menu_scene_mode_1));
- text1.setText(getResources().getString(R.string.help_menu_scene_mode_1)+" ");
- text1.setTypeface(mTypeface);
- linearLayout.addView(text1);
- TextView text2 = new TextView(mContext);
- text2.setText(getResources().getString(R.string.help_menu_scene_mode_2));
- text2.setTypeface(mTypeface);
- linearLayout.addView(text2);
- text2.setTextColor(getResources().getColor(R.color.help_menu_scene_mode_2));
- tableLayout.addView(linearLayout);
- TextView text3 = new TextView(mContext);
- text3.setText(getResources().getString(R.string.help_menu_scene_mode_3));
- text3.setTextColor(getResources().getColor(R.color.help_menu_scene_mode_3));
- text3.setTypeface(mTypeface);
- tableLayout.addView(text3);
- }
-
- private void fillHelp1_0() {
- TableLayout tableLayout = new TableLayout(mContext);
- mHelp1_0.addView(tableLayout);
- LinearLayout linearLayout = new LinearLayout(mContext);
- TextView text1 = new TextView(mContext);
- text1.setText(getResources().getString(R.string.help_menu_color_filter_1)+" ");
- text1.setTextColor(getResources().getColor(R.color.help_menu_color_filter_1));
- text1.setTypeface(mTypeface);
- linearLayout.addView(text1);
- TextView text2 = new TextView(mContext);
- text2.setText(getResources().getString(R.string.help_menu_color_filter_2)+" ");
- text2.setTextColor(getResources().getColor(R.color.help_menu_color_filter_2));
- text2.setTypeface(mTypeface);
- linearLayout.addView(text2);
- TextView text3 = new TextView(mContext);
- text3.setText(getResources().getString(R.string.help_menu_color_filter_3));
- text3.setTextColor(getResources().getColor(R.color.help_menu_color_filter_3));
- text3.setTypeface(mTypeface);
- linearLayout.addView(text3);
- tableLayout.addView(linearLayout);
- TextView text4 = new TextView(mContext);
- text4.setText(getResources().getString(R.string.help_menu_color_filter_4));
- text4.setTextColor(getResources().getColor(R.color.help_menu_color_filter_4));
- text4.setTypeface(mTypeface);
- tableLayout.addView(text4);
- }
-
- private void fillHelp3_0() {
- TableLayout tableLayout = new TableLayout(mContext);
- mHelp3_0.addView(tableLayout);
- if(TsMakeupManager.HAS_TS_MAKEUP) {
- TextView text1 = new TextView(mContext);
- text1.setText(getResources().getString(R.string.help_menu_beautify_1));
- text1.setTextColor(getResources().getColor(R.color.help_menu_beautify_1));
- text1.setTypeface(mTypeface);
- tableLayout.addView(text1);
- TextView text2 = new TextView(mContext);
- text2.setText(getResources().getString(R.string.help_menu_beautify_2));
- text2.setTextColor(getResources().getColor(R.color.help_menu_beautify_2));
- text2.setTypeface(mTypeface);
- tableLayout.addView(text2);
- TextView text3 = new TextView(mContext);
- text3.setText(getResources().getString(R.string.help_menu_beautify_3));
- text3.setTextColor(getResources().getColor(R.color.help_menu_beautify_3));
- text3.setTypeface(mTypeface);
- tableLayout.addView(text3);
- }
- }
-
- private void fillHelp4_6() {
- TableLayout tableLayout = new TableLayout(mContext);
- mHelp4_6.addView(tableLayout);
- LinearLayout linearLayout = new LinearLayout(mContext);
- TextView text1 = new TextView(mContext);
- text1.setText(getResources().getString(R.string.help_menu_switcher_1)+" ");
- text1.setTextColor(Color.GREEN);
- text1.setTypeface(mTypeface);
- linearLayout.addView(text1);
- TextView text2 = new TextView(mContext);
- text2.setText(getResources().getString(R.string.help_menu_switcher_2));
- text2.setTextColor(Color.WHITE);
- text2.setTypeface(mTypeface);
- linearLayout.addView(text2);
- tableLayout.addView(linearLayout);
- TextView text3 = new TextView(mContext);
- text3.setText(getResources().getString(R.string.help_menu_switcher_3));
- text3.setTextColor(Color.WHITE);
- text3.setTypeface(mTypeface);
- tableLayout.addView(text3);
- }
-}
diff --git a/src/com/android/camera/ui/ModuleSwitcher.java b/src/com/android/camera/ui/ModuleSwitcher.java
index d96775d14..058e64d7b 100644
--- a/src/com/android/camera/ui/ModuleSwitcher.java
+++ b/src/com/android/camera/ui/ModuleSwitcher.java
@@ -51,10 +51,11 @@ public class ModuleSwitcher extends RotateImageView
public static final int PHOTO_MODULE_INDEX = 0;
public static final int VIDEO_MODULE_INDEX = 1;
public static final int WIDE_ANGLE_PANO_MODULE_INDEX = 2;
- public static final int LIGHTCYCLE_MODULE_INDEX = 3;
- public static final int GCAM_MODULE_INDEX = 4;
- public static final int CAPTURE_MODULE_INDEX = 5;
- public static final int PANOCAPTURE_MODULE_INDEX = 6;
+ public static final int QR_MODULE_INDEX = 3;
+ public static final int LIGHTCYCLE_MODULE_INDEX = 4;
+ public static final int GCAM_MODULE_INDEX = 5;
+ public static final int CAPTURE_MODULE_INDEX = 6;
+ public static final int PANOCAPTURE_MODULE_INDEX = 7;
private boolean mTouchEnabled = true;
private boolean mIsVisible = true;
@@ -63,6 +64,7 @@ public class ModuleSwitcher extends RotateImageView
R.drawable.ic_switch_camera,
R.drawable.ic_switch_video,
R.drawable.ic_switch_pan,
+ R.drawable.ic_cam_switcher_qr,
R.drawable.ic_switch_photosphere,
R.drawable.ic_switch_gcam,
};
@@ -102,7 +104,7 @@ public class ModuleSwitcher extends RotateImageView
private void init(Context context) {
mItemSize = context.getResources().getDimensionPixelSize(R.dimen.switcher_size);
- mIndicator = context.getResources().getDrawable(R.drawable.ic_switcher_menu_indicator);
+ mIndicator = context.getResources().getDrawable(R.color.transparent);
initializeDrawables(context);
}
@@ -231,6 +233,10 @@ public class ModuleSwitcher extends RotateImageView
item.setContentDescription(getContext().getResources().getString(
R.string.accessibility_switch_to_panorama));
break;
+ case R.drawable.ic_cam_switcher_qr:
+ item.setContentDescription(getContext().getResources().getString(
+ R.string.accessibility_switch_to_qr));
+ break;
case R.drawable.ic_switch_photosphere:
item.setContentDescription(getContext().getResources().getString(
R.string.accessibility_switch_to_photo_sphere));
diff --git a/src/com/android/camera/ui/OneUICameraControls.java b/src/com/android/camera/ui/OneUICameraControls.java
index 646596912..73cb68e16 100755
--- a/src/com/android/camera/ui/OneUICameraControls.java
+++ b/src/com/android/camera/ui/OneUICameraControls.java
@@ -232,7 +232,7 @@ public class OneUICameraControls extends RotatableLayout {
mViews = new View[]{
mSceneModeSwitcher, mFilterModeSwitcher, mFrontBackSwitcher,
- mTsMakeupSwitcher,mDeepportraitSwitcher, mFlashButton, mShutter,
+ mTsMakeupSwitcher, mDeepportraitSwitcher, mFlashButton,
mPreview, mVideoShutter, mPauseButton, mCancelButton
};
mBottomLargeSize = getResources().getDimensionPixelSize(
@@ -501,7 +501,7 @@ public class OneUICameraControls extends RotatableLayout {
View[] views = {
mSceneModeSwitcher, mFilterModeSwitcher, mFrontBackSwitcher,
mTsMakeupSwitcher, mFlashButton, mDeepportraitSwitcher, mPreview, mMute,
- mShutter, mVideoShutter, mMakeupSeekBarLowText, mMakeupSeekBarHighText,
+ mMakeupSeekBarLowText, mMakeupSeekBarHighText,
mPauseButton, mExitBestPhotpMode
};
diff --git a/src/com/android/camera/ui/Switch.java b/src/com/android/camera/ui/Switch.java
index 3e7b9bfb9..68fb36542 100644
--- a/src/com/android/camera/ui/Switch.java
+++ b/src/com/android/camera/ui/Switch.java
@@ -123,7 +123,7 @@ public class Switch extends CompoundButton {
mSwitchMinWidth = res.getDimensionPixelSize(R.dimen.switch_min_width);
mSwitchTextMaxWidth = res.getDimensionPixelSize(R.dimen.switch_text_max_width);
mSwitchPadding = res.getDimensionPixelSize(R.dimen.switch_padding);
- setSwitchTextAppearance(context, android.R.style.TextAppearance_Holo_Small);
+ setSwitchTextAppearance(context, android.R.style.TextAppearance_Material_Small);
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
diff --git a/src/com/android/camera/ui/ZoomRenderer.java b/src/com/android/camera/ui/ZoomRenderer.java
index 48a565a6f..eb88decc2 100755
--- a/src/com/android/camera/ui/ZoomRenderer.java
+++ b/src/com/android/camera/ui/ZoomRenderer.java
@@ -16,12 +16,15 @@
package com.android.camera.ui;
+import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Handler;
import android.view.ScaleGestureDetector;
import org.codeaurora.snapcam.R;
@@ -35,6 +38,8 @@ public class ZoomRenderer extends OverlayRenderer
private int mMinZoom;
private OnZoomChangedListener mListener;
+ private final Handler mHandler = new Handler();
+
private ScaleGestureDetector mDetector;
private Paint mPaint;
private Paint mTextPaint;
@@ -47,12 +52,17 @@ public class ZoomRenderer extends OverlayRenderer
private int mOuterStroke;
private int mZoomSig;
private int mZoomFraction;
+ private boolean mInZoom;
private Rect mTextBounds;
private int mOrientation;
private boolean mCamera2 = false;
private float mZoomMinValue;
private float mZoomMaxValue;
+ private Point mDispSize;
+ private int mBottomMargin;
+ private int mTopMargin;
+
public interface OnZoomChangedListener {
void onZoomStart();
void onZoomEnd();
@@ -77,6 +87,13 @@ public class ZoomRenderer extends OverlayRenderer
mMinCircle = res.getDimensionPixelSize(R.dimen.zoom_ring_min);
mTextBounds = new Rect();
setVisible(false);
+ mBottomMargin =
+ ctx.getResources().getDimensionPixelSize(R.dimen.preview_bottom_margin);
+ mTopMargin =
+ ctx.getResources().getDimensionPixelSize(R.dimen.preview_top_margin);
+ mDispSize = new Point();
+ Activity activity = (Activity) ctx;
+ activity.getWindowManager().getDefaultDisplay().getRealSize(mDispSize);
}
// set from module
@@ -115,9 +132,13 @@ public class ZoomRenderer extends OverlayRenderer
@Override
public void layout(int l, int t, int r, int b) {
+ l = 0;
+ t = mTopMargin;
+ r = mDispSize.x;
+ b = mDispSize.y - mBottomMargin;
super.layout(l, t, r, b);
mCenterX = (r - l) / 2;
- mCenterY = (b - t) / 2;
+ mCenterY = ((b - t) / 2) + t;
mMaxCircle = Math.min(getWidth(), getHeight());
mMaxCircle = (mMaxCircle - mMinCircle) / 2;
}
@@ -165,6 +186,48 @@ public class ZoomRenderer extends OverlayRenderer
return true;
}
+ public boolean onScaleStepResize(boolean direction) {
+ int zoom;
+ float circle;
+ float circleStep = (mMaxCircle - mMinCircle) / 18;
+ if (direction) {
+ circle = (int) (mCircleSize + circleStep);
+ } else {
+ circle = (int) (mCircleSize - circleStep);
+ }
+ circle = Math.max(mMinCircle, circle);
+ circle = Math.min(mMaxCircle, circle);
+ if (mListener != null && (int) circle != mCircleSize && (mMaxCircle - mMinCircle) > 0) {
+ mCircleSize = (int) circle;
+ zoom = mMinZoom + (int) ((mCircleSize - mMinCircle)
+ * (mMaxZoom - mMinZoom) / (mMaxCircle - mMinCircle));
+ if (mListener != null) {
+ mHandler.removeCallbacks(mOnZoomEnd);
+ if (!mInZoom) {
+ mInZoom = true;
+ setVisible(true);
+ mListener.onZoomStart();
+ update();
+ }
+ mListener.onZoomValueChanged(zoom);
+ mHandler.postDelayed(mOnZoomEnd, 300);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ Runnable mOnZoomEnd = new Runnable() {
+ public void run() {
+ mInZoom = false;
+ setVisible(false);
+ if (mListener != null) {
+ mListener.onZoomEnd();
+ }
+ }
+ };
+
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
setVisible(true);
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..ff09ebc65
--- /dev/null
+++ b/src/com/android/camera/ui/focus/AutoFocusRing.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.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.android.camera.ui.motion.InterpolateUtils;
+import com.android.camera.ui.motion.Invalidator;
+
+/**
+ * Passive focus ring animation renderer.
+ */
+class AutoFocusRing extends FocusRingRenderer {
+ private static final String 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.
+ */
+ public 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..a0c103443
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusController.java
@@ -0,0 +1,128 @@
+/*
+ * 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.RectF;
+import android.util.Log;
+
+import com.android.camera.async.MainThread;
+import com.android.camera.ui.motion.LinearScale;
+
+/**
+ * The focus controller interacts with the focus ring UI element.
+ */
+public class FocusController {
+ private static final String TAG = "FocusController";
+
+ private final FocusRing mFocusRing;
+ private final FocusSound mFocusSound;
+ private final MainThread mMainThread;
+
+ public FocusController(FocusRing focusRing, FocusSound focusSound, MainThread mainThread) {
+ mFocusRing = focusRing;
+ mFocusSound = focusSound;
+ mMainThread = mainThread;
+ }
+
+ /**
+ * Show a passive focus animation at the center of the active area.
+ * This will likely be different than the view bounds due to varying image
+ * ratios and dimensions.
+ */
+ public void showPassiveFocusAtCenter() {
+ mMainThread.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(TAG, "Running showPassiveFocusAtCenter()");
+ mFocusRing.startPassiveFocus();
+ mFocusRing.centerFocusLocation();
+ }
+ });
+ }
+
+ /**
+ * 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) {
+ mMainThread.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(TAG, "Running showPassiveFocusAt(" + viewX + ", " + viewY + ")");
+ 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) {
+ mMainThread.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(TAG, "showActiveFocusAt(" + viewX + ", " + viewY + ")");
+ mFocusRing.startActiveFocus();
+ mFocusRing.setFocusLocation(viewX, viewY);
+
+ // TODO: Enable focus sound when better audio controls exist.
+ // mFocusSound.play();
+ }
+ });
+ }
+
+ /**
+ * Computing the correct location for the focus ring requires knowing
+ * the screen position and size of the preview area so the drawing
+ * operations can be clipped correctly.
+ */
+ public void configurePreviewDimensions(final RectF previewArea) {
+ mMainThread.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(TAG, "configurePreviewDimensions(" + previewArea + ")");
+ mFocusRing.configurePreviewDimensions(previewArea);
+ }
+ });
+ }
+
+ /**
+ * Set the radius of the focus ring as a radius between 0 and 1.
+ * This will map to the min and max values computed for the UI.
+ */
+ public void setFocusRatio(final float ratio) {
+ mMainThread.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mFocusRing.isPassiveFocusRunning() ||
+ mFocusRing.isActiveFocusRunning()) {
+ mFocusRing.setRadiusRatio(ratio);
+ }
+ }
+ });
+ }
+}
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..89de357ad
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusRing.java
@@ -0,0 +1,72 @@
+/*
+ * 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.RectF;
+
+/**
+ * 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 location of the focus ring animation center.
+ */
+ public void centerFocusLocation();
+
+ /**
+ * Set the target radius as a ratio of min to max visible radius
+ * which will internally convert and clamp the value to the
+ * correct pixel radius.
+ */
+ public void setRadiusRatio(float ratio);
+
+ /**
+ * The physical size of preview can vary and does not map directly
+ * to the size of the view. This allows for conversions between view
+ * and preview space for values that are provided in preview space.
+ */
+ void configurePreviewDimensions(RectF previewArea);
+} \ 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..264af2ace
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusRingRenderer.java
@@ -0,0 +1,237 @@
+/*
+ * 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 android.util.Log;
+
+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.
+ */
+abstract class FocusRingRenderer implements DynamicAnimation {
+ private static final String 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..14a7f6cc9
--- /dev/null
+++ b/src/com/android/camera/ui/focus/FocusRingView.java
@@ -0,0 +1,211 @@
+/*
+ * 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.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.camera.ui.motion.AnimationClock.SystemTimeClock;
+import com.android.camera.ui.motion.DynamicAnimator;
+import com.android.camera.ui.motion.Invalidator;
+import com.android.camera.ui.motion.LinearScale;
+
+import org.codeaurora.snapcam.R;
+
+/**
+ * Custom view for running the focus ring animations.
+ */
+public class FocusRingView extends View implements Invalidator, FocusRing {
+ private static final String TAG = "FocusRingView";
+ private static final float FADE_IN_DURATION_MILLIS = 1000f;
+ private static final float FADE_OUT_DURATION_MILLIS = 250f;
+
+ private final AutoFocusRing mAutoFocusRing;
+ private final ManualFocusRing mManualFocusRing;
+ private final DynamicAnimator mAnimator;
+ private final LinearScale mRatioScale;
+ private final float mDefaultRadiusPx;
+
+ private FocusRingRenderer currentFocusAnimation;
+ private boolean isFirstDraw;
+ private float mLastRadiusPx;
+
+ private RectF mPreviewSize;
+
+ public FocusRingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ Resources res = getResources();
+ Paint paint = makePaint(res, R.color.focus_color);
+
+ float focusCircleMinSize = res.getDimensionPixelSize(R.dimen.focus_circle_min_size);
+ float focusCircleMaxSize = res.getDimensionPixelSize(R.dimen.focus_circle_max_size);
+ mDefaultRadiusPx = res.getDimensionPixelSize(R.dimen.focus_circle_initial_size);
+
+ mRatioScale = new LinearScale(0, 1, focusCircleMinSize, focusCircleMaxSize);
+ mAnimator = new DynamicAnimator(this, new SystemTimeClock());
+
+ mAutoFocusRing = new AutoFocusRing(mAnimator, paint,
+ FADE_IN_DURATION_MILLIS,
+ FADE_OUT_DURATION_MILLIS);
+ mManualFocusRing = new ManualFocusRing(mAnimator, paint,
+ FADE_OUT_DURATION_MILLIS);
+
+ mAnimator.animations.add(mAutoFocusRing);
+ mAnimator.animations.add(mManualFocusRing);
+
+ isFirstDraw = true;
+ mLastRadiusPx = mDefaultRadiusPx;
+ }
+
+ @Override
+ public boolean isPassiveFocusRunning() {
+ return mAutoFocusRing.isActive();
+ }
+
+ @Override
+ public boolean isActiveFocusRunning() {
+ return mManualFocusRing.isActive();
+ }
+
+ @Override
+ public void startPassiveFocus() {
+ mAnimator.invalidate();
+ long tMs = mAnimator.getTimeMillis();
+
+ if (mManualFocusRing.isActive() && !mManualFocusRing.isExiting()) {
+ mManualFocusRing.stop(tMs);
+ }
+
+ mAutoFocusRing.start(tMs, mLastRadiusPx, mLastRadiusPx);
+ currentFocusAnimation = mAutoFocusRing;
+ }
+
+ @Override
+ public void startActiveFocus() {
+ mAnimator.invalidate();
+ long tMs = mAnimator.getTimeMillis();
+
+ if (mAutoFocusRing.isActive() && !mAutoFocusRing.isExiting()) {
+ mAutoFocusRing.stop(tMs);
+ }
+
+ mManualFocusRing.start(tMs, 0.0f, mLastRadiusPx);
+ currentFocusAnimation = mManualFocusRing;
+ }
+
+ @Override
+ public void stopFocusAnimations() {
+ long tMs = mAnimator.getTimeMillis();
+ if (mManualFocusRing.isActive() && !mManualFocusRing.isExiting()
+ && !mManualFocusRing.isEntering()) {
+ mManualFocusRing.exit(tMs);
+ }
+
+ if (mAutoFocusRing.isActive() && !mAutoFocusRing.isExiting()) {
+ mAutoFocusRing.exit(tMs);
+ }
+ }
+
+ @Override
+ public void setFocusLocation(float viewX, float viewY) {
+ mAutoFocusRing.setCenterX((int) viewX);
+ mAutoFocusRing.setCenterY((int) viewY);
+ mManualFocusRing.setCenterX((int) viewX);
+ mManualFocusRing.setCenterY((int) viewY);
+ }
+
+ @Override
+ public void centerFocusLocation() {
+ Point center = computeCenter();
+ mAutoFocusRing.setCenterX(center.x);
+ mAutoFocusRing.setCenterY(center.y);
+ mManualFocusRing.setCenterX(center.x);
+ mManualFocusRing.setCenterY(center.y);
+ }
+
+ @Override
+ public void setRadiusRatio(float ratio) {
+ setRadius(mRatioScale.scale(mRatioScale.clamp(ratio)));
+ }
+
+ @Override
+ public void configurePreviewDimensions(RectF previewArea) {
+ mPreviewSize = previewArea;
+ mLastRadiusPx = mDefaultRadiusPx;
+
+ if (!isFirstDraw) {
+ centerAutofocusRing();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (isFirstDraw) {
+ isFirstDraw = false;
+ centerAutofocusRing();
+ }
+
+ if (mPreviewSize != null) {
+ canvas.clipRect(mPreviewSize, Region.Op.REPLACE);
+ }
+
+ mAnimator.draw(canvas);
+ }
+
+ private void setRadius(float radiusPx) {
+ long tMs = mAnimator.getTimeMillis();
+ // Some devices return zero for invalid or "unknown" diopter values.
+ if (currentFocusAnimation != null && radiusPx > 0.1f) {
+ currentFocusAnimation.setRadius(tMs, radiusPx);
+ mLastRadiusPx = radiusPx;
+ }
+ }
+
+ private void centerAutofocusRing() {
+ Point center = computeCenter();
+ mAutoFocusRing.setCenterX(center.x);
+ mAutoFocusRing.setCenterY(center.y);
+ }
+
+ private Point computeCenter() {
+ if (mPreviewSize != null && (mPreviewSize.width() * mPreviewSize.height() > 0.01f)) {
+ Log.i(TAG, "Computing center via preview size.");
+ return new Point((int) mPreviewSize.centerX(), (int) mPreviewSize.centerY());
+ }
+ Log.i(TAG, "Computing center via view bounds.");
+ return new Point(getWidth() / 2, getHeight() / 2);
+ }
+
+ 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/LensRangeCalculator.java b/src/com/android/camera/ui/focus/LensRangeCalculator.java
new file mode 100644
index 000000000..ef9cbaec7
--- /dev/null
+++ b/src/com/android/camera/ui/focus/LensRangeCalculator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 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.annotation.TargetApi;
+import android.hardware.camera2.CameraCharacteristics;
+import android.os.Build.VERSION_CODES;
+
+import com.android.camera.ui.motion.LinearScale;
+
+/**
+ * Compute diopter range scale to convert lens focus distances into
+ * a ratio value.
+ */
+@TargetApi(VERSION_CODES.LOLLIPOP)
+public class LensRangeCalculator {
+
+ /**
+ * A NoOp linear scale for computing diopter values will always return 0
+ */
+ public static LinearScale getNoOp() {
+ return new LinearScale(0, 0, 0, 0);
+ }
+
+ /**
+ * Compute the focus range from the camera characteristics and build
+ * a linear scale model that maps a focus distance to a ratio between
+ * the min and max range.
+ */
+ public static LinearScale getDiopterToRatioCalculator(CameraCharacteristics characteristics) {
+ // From the android documentation:
+ //
+ // 0.0f represents farthest focus, and LENS_INFO_MINIMUM_FOCUS_DISTANCE
+ // represents the nearest focus the device can achieve.
+ //
+ // Example:
+ //
+ // Infinity Hyperfocal Minimum Camera
+ // <----------|-----------------------------| |
+ // [0.0] [0.31] [14.29]
+ Float nearest = characteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+ Float hyperfocal = characteristics.get(CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE);
+
+ if (nearest == null && hyperfocal == null) {
+ return getNoOp();
+ }
+
+ nearest = (nearest == null) ? 0.0f : nearest;
+ hyperfocal = (hyperfocal == null) ? 0.0f : hyperfocal;
+
+ if (nearest > hyperfocal) {
+ return new LinearScale(hyperfocal, nearest, 0, 1);
+ }
+
+ return new LinearScale(nearest, hyperfocal, 0, 1);
+ }
+}
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..0133d8e09
--- /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.
+ */
+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/InterpolatorHelper.java b/src/com/android/camera/ui/motion/InterpolatorHelper.java
new file mode 100644
index 000000000..84114cb03
--- /dev/null
+++ b/src/com/android/camera/ui/motion/InterpolatorHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.camera.util.ApiHelper;
+
+public class InterpolatorHelper {
+ private static Interpolator LINEAR_OUT_SLOW_IN = null;
+
+ public static Interpolator getLinearOutSlowInInterpolator(final Context context) {
+ if (LINEAR_OUT_SLOW_IN != null) {
+ return LINEAR_OUT_SLOW_IN;
+ }
+
+ LINEAR_OUT_SLOW_IN = AnimationUtils.loadInterpolator(
+ context, android.R.interpolator.linear_out_slow_in);
+ return LINEAR_OUT_SLOW_IN;
+ }
+}
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/LinearScale.java b/src/com/android/camera/ui/motion/LinearScale.java
new file mode 100644
index 000000000..5886f6882
--- /dev/null
+++ b/src/com/android/camera/ui/motion/LinearScale.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Represents a discrete linear scale function.
+ */
+public final class LinearScale {
+ private final float mDomainA;
+ private final float mDomainB;
+ private final float mRangeA;
+ private final float mRangeB;
+
+ private final float mScale;
+
+ public LinearScale(float domainA, float domainB, float rangeA, float rangeB) {
+ mDomainA = domainA;
+ mDomainB = domainB;
+ mRangeA = rangeA;
+ mRangeB = rangeB;
+
+ // Precomputed ratio between input domain and output range.
+ float scale = (mRangeB - mRangeA) / (mDomainB - mDomainA);
+ mScale = Float.isNaN(scale) ? 0.0f : scale;
+ }
+
+ /**
+ * Clamp a given domain value to the given domain.
+ */
+ public float clamp(float domainValue) {
+ if (mDomainA > mDomainB) {
+ return Math.max(mDomainB, Math.min(mDomainA, domainValue));
+ }
+
+ return Math.max(mDomainA, Math.min(mDomainB, domainValue));
+ }
+
+ /**
+ * Returns true if the value is within the domain.
+ */
+ public boolean isInDomain(float domainValue) {
+ if (mDomainA > mDomainB) {
+ return domainValue <= mDomainA && domainValue >= mDomainB;
+ }
+ return domainValue >= mDomainA && domainValue <= mDomainB;
+ }
+
+ /**
+ * Linearly scale a given domain value into the output range.
+ */
+ public float scale(float domainValue) {
+ return mRangeA + (domainValue - mDomainA) * mScale;
+ }
+
+ /**
+ * For the current domain and range parameters produce a new scale function
+ * that is the inverse of the current scale function.
+ */
+ public LinearScale inverse() {
+ return new LinearScale(mRangeA, mRangeB, mDomainA, mDomainB);
+ }
+
+ @Override
+ public String toString() {
+ return "LinearScale{" +
+ "mDomainA=" + mDomainA +
+ ", mDomainB=" + mDomainB +
+ ", mRangeA=" + mRangeA +
+ ", mRangeB=" + mRangeB + "}";
+ }
+} \ No newline at end of file
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));
+ }
+}