<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:drawable="@drawable/ic_discard_disabled" />
+ android:drawable="@drawable/ic_trash_disabled" />
<item android:state_enabled="true"
- android:drawable="@drawable/ic_discard_normal" />
+ android:drawable="@drawable/ic_trash_normal" />
</selector>
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageButton;
public static final int ANIM_DIRECTION_VERTICAL = 0;
public static final int ANIM_DIRECTION_HORIZONTAL = 1;
- private static final int AINM_DURATION_MS = 250;
+ private static final int ANIM_DURATION_MS = 250;
private static final int UNSET = -1;
private OnStateChangeListener mOnStateChangeListener;
private boolean mClickEnabled = true;
private int mParentSize;
private int mAnimDirection;
+ private Matrix mMatrix = new Matrix();
+ private ValueAnimator mAnimator;
public MultiToggleImageButton(Context context) {
super(context);
* @param callListener should the state change listener be called?
*/
public void setState(final int state, final boolean callListener) {
- // TODO: animate button transitions, b/17414652
- setStateInternal(state, callListener);
+ setStateAnimatedInternal(state, callListener);
}
/**
return;
}
- Bitmap bitmap = combine(mState, state);
- if (bitmap == null) {
- setStateInternal(state, callListener);
- return;
- }
-
- setImageBitmap(bitmap);
- final Matrix matrix = new Matrix();
-
- int offset;
- if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
- offset = (mParentSize+getHeight())/2;
- } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
- offset = (mParentSize+getWidth())/2;
- } else {
- return;
- }
-
- ValueAnimator animator = ValueAnimator.ofFloat(-offset, 0.0f);
- animator.setDuration(AINM_DURATION_MS);
- animator.setInterpolator(Gusterpolator.INSTANCE);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- matrix.reset();
- if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
- matrix.setTranslate(0.0f, (Float) animation.getAnimatedValue());
- } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
- matrix.setTranslate((Float) animation.getAnimatedValue(), 0.0f);
- }
-
- setImageMatrix(matrix);
- invalidate();
- }
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- setClickEnabled(false);
- }
-
+ new AsyncTask<Integer, Void, Bitmap>() {
@Override
- public void onAnimationEnd(Animator animation) {
- setStateInternal(state, callListener);
- setClickEnabled(true);
+ protected Bitmap doInBackground(Integer... params) {
+ return combine(params[0], params[1]);
}
@Override
- public void onAnimationCancel(Animator animation) {
- setStateInternal(state, callListener);
- setClickEnabled(true);
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap == null) {
+ setStateInternal(state, callListener);
+ } else {
+ setImageBitmap(bitmap);
+
+ int offset;
+ if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
+ offset = (mParentSize+getHeight())/2;
+ } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
+ offset = (mParentSize+getWidth())/2;
+ } else {
+ return;
+ }
+
+ mAnimator.setFloatValues(-offset, 0.0f);
+ AnimatorSet s = new AnimatorSet();
+ s.play(mAnimator);
+ s.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ setClickEnabled(false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setStateInternal(state, callListener);
+ setClickEnabled(true);
+ }
+ });
+ s.start();
+ }
}
- });
- animator.start();
+ }.execute(mState, state);
}
/**
}
});
setScaleType(ImageView.ScaleType.MATRIX);
+
+ mAnimator = ValueAnimator.ofFloat(0.0f, 0.0f);
+ mAnimator.setDuration(ANIM_DURATION_MS);
+ mAnimator.setInterpolator(Gusterpolator.INSTANCE);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mMatrix.reset();
+ if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
+ mMatrix.setTranslate(0.0f, (Float) animation.getAnimatedValue());
+ } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
+ mMatrix.setTranslate((Float) animation.getAnimatedValue(), 0.0f);
+ }
+
+ setImageMatrix(mMatrix);
+ invalidate();
+ }
+ });
}
private void parseAttributes(Context context, AttributeSet attrs) {
import com.android.camera.util.UsageStatistics;
import com.android.camera.widget.AspectRatioSelector;
import com.android.camera2.R;
+import com.android.ex.camera2.portability.CameraAgent;
import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback;
import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback;
import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
Log.i(TAG, "startPreview");
- mCameraDevice.startPreview();
-
- mFocusManager.onPreviewStarted();
- onPreviewStarted();
- SessionStatsCollector.instance().previewActive(true);
- if (mSnapshotOnIdle) {
- mHandler.post(mDoSnapRunnable);
+ // If we're using API2 in portability layers, don't use startPreviewWithCallback()
+ // b/17576554
+ CameraAgent.CameraStartPreviewCallback startPreviewCallback =
+ new CameraAgent.CameraStartPreviewCallback() {
+ @Override
+ public void onPreviewStarted() {
+ mFocusManager.onPreviewStarted();
+ PhotoModule.this.onPreviewStarted();
+ SessionStatsCollector.instance().previewActive(true);
+ if (mSnapshotOnIdle) {
+ mHandler.post(mDoSnapRunnable);
+ }
+ }
+ };
+ if (GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity)) {
+ mCameraDevice.startPreview();
+ startPreviewCallback.onPreviewStarted();
+ } else {
+ mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()),
+ startPreviewCallback);
}
} finally {
mStartPreviewLock = false;
}
private void updateCameraParametersPreference() {
+ // some monkey tests can get here when shutting the app down
+ // make sure mCameraDevice is still valid, b/17580046
+ if (mCameraDevice == null) {
+ return;
+ }
+
setAutoExposureLockIfSupported();
setAutoWhiteBalanceLockIfSupported();
setFocusAreasIfSupported();
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
}
try {
mCameraDevice.setPreviewTexture(surfaceTexture);
- mCameraDevice.startPreview();
+ mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()),
+ new CameraAgent.CameraStartPreviewCallback() {
+ @Override
+ public void onPreviewStarted() {
+ VideoModule.this.onPreviewStarted();
+ }
+ });
mPreviewing = true;
- onPreviewStarted();
} catch (Throwable ex) {
closeCamera();
throw new RuntimeException("startPreview failed", ex);
mController.getQuickSwitchToModuleId(currentModuleIndex);
if (currentModuleIndex != moduleToTransitionTo) {
mAppRootView.redirectTouchEventsTo(mModeTransitionView);
-
int shadeColorId = R.color.mode_cover_default_color;
int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
mController.getAndroidContext());
}
}
};
- if (mSwipeState == SWIPE_UP) {
- mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener);
- } else {
- mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener);
- }
}
} else if (swipeState == SWIPE_LEFT) {
// Pass the touch sequence to filmstrip layout.
Log.v(TAG, "onNewPreviewFrame");
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
hideModeCover();
- mModeCoverState = COVER_HIDDEN;
}
/**
}
private void updateModeSpecificUIColors() {
- // set up UI colors to match the current mode
- /*
- int colorId = CameraUtil.getCameraThemeColorId(mController.getCurrentModuleIndex(),
- mController.getAndroidContext());
- int pressedColor = mController.getAndroidContext().getResources().getColor(colorId);
- setBottomBarPressedColor(pressedColor);
- */
setBottomBarColorsForModeIndex(mController.getCurrentModuleIndex());
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
mSurface = surface;
+ if (mPreviewStatusListener != null) {
+ mPreviewStatusListener.onSurfaceTextureUpdated(surface);
+ }
if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
Log.v(TAG, "hiding cover via onSurfaceTextureUpdated");
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
hideModeCover();
- mModeCoverState = COVER_HIDDEN;
- }
- if (mPreviewStatusListener != null) {
- mPreviewStatusListener.onSurfaceTextureUpdated(surface);
}
}
/**
* Meters and triggers auto focus scan with ROI around tap point.
* <p/>
- * Normalized coordinates are referenced to portrait preview window with 0,0
- * top left and 1,1 bottom right. Rotation has no effect.
+ * Normalized coordinates are referenced to portrait preview window with
+ * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
*
* @param nx normalized x coordinate.
- * @param nx normalized y coordinate.
+ * @param ny normalized y coordinate.
*/
public void triggerFocusAndMeterAtPoint(float nx, float ny);
public class Settings3A {
/**
- * Width of touch AF region relative to shortest edge at 1.0 zoom.
- * Was 0.125 * longest edge prior to L release.
+ * Width of touch AF region in [0,1] relative to shorter edge of the current
+ * crop region. Multiply this number by the number of pixels along the
+ * shorter edge of the current crop region's width to get a value in pixels.
+ *
+ * <p>
+ * This value has been tested on Nexus 5 and Shamu, but will need to be
+ * tuned per device depending on how its ISP interprets the metering box and weight.
+ * </p>
+ *
+ * <p>
+ * Values prior to L release:
+ * Normal mode: 0.125 * longest edge
+ * Gcam: Fixed at 300px x 300px.
+ * </p>
*/
private static final float AF_REGION_BOX = 0.2f;
/**
- * Width of touch metering region relative to shortest edge at 1.0 zoom.
- * Larger than {@link #AF_REGION_BOX} because exposure is sensitive and it is
- * easy to over- or underexposure if area is too small.
+ * Width of touch metering region in [0,1] relative to shorter edge of the
+ * current crop region. Multiply this number by the number of pixels along
+ * shorter edge of the current crop region's width to get a value in pixels.
+ *
+ * <p>
+ * This value has been tested on Nexus 5 and Shamu, but will need to be
+ * tuned per device depending on how its ISP interprets the metering box and weight.
+ * </p>
+ *
+ * <p>
+ * Values prior to L release:
+ * Normal mode: 0.1875 * longest edge
+ * Gcam: Fixed at 300px x 300px.
+ * </p>
*/
private static final float AE_REGION_BOX = 0.3f;
- /** Metering region weight between 0 and 1. */
- private static final float REGION_WEIGHT = 0.25f;
+ /** Metering region weight between 0 and 1.
+ *
+ * <p>
+ * This value has been tested on Nexus 5 and Shamu, but will need to be
+ * tuned per device depending on how its ISP interprets the metering box and weight.
+ * </p>
+ */
+ private static final float REGION_WEIGHT = 0.022f;
/** Duration to hold after manual tap to focus. */
private static final int FOCUS_HOLD_MILLIS = 3000;
+ /**
+ * Width of touch metering region in [0,1] relative to shorter edge of the
+ * current crop region. Multiply this number by the number of pixels along
+ * shorter edge of the current crop region's width to get a value in pixels.
+ *
+ * <p>
+ * This value has been tested on Nexus 5 and Shamu, but will need to be
+ * tuned per device depending on how its ISP interprets the metering box and weight.
+ * </p>
+ *
+ * <p>
+ * Was fixed at 300px x 300px prior to L release.
+ * </p>
+ */
+ private static final float GCAM_METERING_REGION_FRACTION = 0.1225f;
+
+ /**
+ * Weight of a touch metering region, in [0, \inf).
+ *
+ * <p>
+ * This value has been tested on Nexus 5 and Shamu, but will need to be
+ * tuned per device.
+ * </p>
+ *
+ * <p>
+ * Was fixed at 15.0f prior to L release.
+ * </p>
+ */
+ private static final float GCAM_METERING_REGION_WEIGHT = 22.0f;
public static float getAutoFocusRegionWidth() {
return AF_REGION_BOX;
return REGION_WEIGHT;
}
+ public static float getGcamMeteringRegionFraction() {
+ return GCAM_METERING_REGION_FRACTION;
+ }
+
+ public static float getGcamMeteringRegionWeight() {
+ return GCAM_METERING_REGION_WEIGHT;
+ }
+
public static int getFocusHoldMillis() {
return FOCUS_HOLD_MILLIS;
}
package com.android.camera.one.v2;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureResult;
import com.android.camera.debug.Log;
import com.android.camera.one.OneCamera;
import com.android.camera.one.Settings3A;
+import com.android.camera.util.CameraUtil;
/**
* Helper class to implement auto focus and 3A in camera2-based
/** camera2 API metering region weight. */
private static final int CAMERA2_REGION_WEIGHT = (int)
- (((1 - Settings3A.getMeteringRegionWeight()) * MeteringRectangle.METERING_WEIGHT_MIN +
- Settings3A.getMeteringRegionWeight() * MeteringRectangle.METERING_WEIGHT_MAX));
+ (CameraUtil.lerp(MeteringRectangle.METERING_WEIGHT_MIN, MeteringRectangle.METERING_WEIGHT_MAX,
+ Settings3A.getMeteringRegionWeight()));
/** Zero weight 3A region, to reset regions per API. */
private static final MeteringRectangle[] ZERO_WEIGHT_3A_REGION = new MeteringRectangle[]{
));
}
- /** Compute 3A regions for a sensor-referenced touch coordinate. */
- private static MeteringRectangle[] regionsForSensorCoord(int xc, int yc, float width,
- Rect cropRegion) {
- float minCropEdge = (float) Math.min(cropRegion.width(), cropRegion.height());
- int delta = (int) (0.5 * width * minCropEdge);
- Rect region = new Rect(xc - delta, yc - delta, xc + delta, yc + delta);
- // Make sure region is inside the sensor area.
- if (!region.intersect(cropRegion)) {
- region = cropRegion;
- }
- return new MeteringRectangle[]{new MeteringRectangle(region, CAMERA2_REGION_WEIGHT)};
+ /** Compute 3A regions for a sensor-referenced touch coordinate.
+ * Returns a MeteringRectangle[] with length 1.
+ *
+ * @param nx x coordinate of the touch point, in normalized portrait coordinates.
+ * @param ny y coordinate of the touch point, in normalized portrait coordinates.
+ * @param fraction Fraction in [0,1]. Multiplied by min(cropRegion.width(), cropRegion.height())
+ * to determine the side length of the square MeteringRectangle.
+ * @param cropRegion Crop region of the image.
+ * @param sensorOrientation sensor orientation as defined by
+ * CameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION).
+ *
+ * */
+ private static MeteringRectangle[] regionsForNormalizedCoord(float nx, float ny, float fraction,
+ final Rect cropRegion, int sensorOrientation) {
+ // Compute half side length in pixels.
+ int minCropEdge = Math.min(cropRegion.width(), cropRegion.height());
+ int halfSideLength = (int) (0.5f * fraction * minCropEdge);
+
+ // Compute the output MeteringRectangle in sensor space.
+ // nx, ny is normalized to the screen.
+ // Crop region itself is specified in sensor coordinates.
+
+ // Normalized coordinates, now rotated into sensor space.
+ PointF nsc = CameraUtil.normalizedSensorCoordsForNormalizedDisplayCoords(
+ nx, ny, sensorOrientation);
+
+ int xCenterSensor = (int)(cropRegion.left + nsc.x * cropRegion.width());
+ int yCenterSensor = (int)(cropRegion.top + nsc.y * cropRegion.height());
+
+ Rect meteringRegion = new Rect(xCenterSensor - halfSideLength,
+ yCenterSensor - halfSideLength,
+ xCenterSensor + halfSideLength,
+ yCenterSensor + halfSideLength);
+
+ // Clamp meteringRegion to cropRegion.
+ meteringRegion.left = CameraUtil.clamp(meteringRegion.left, cropRegion.left, cropRegion.right);
+ meteringRegion.top = CameraUtil.clamp(meteringRegion.top, cropRegion.top, cropRegion.bottom);
+ meteringRegion.right = CameraUtil.clamp(meteringRegion.right, cropRegion.left, cropRegion.right);
+ meteringRegion.bottom = CameraUtil.clamp(meteringRegion.bottom, cropRegion.top, cropRegion.bottom);
+
+ return new MeteringRectangle[]{new MeteringRectangle(meteringRegion, CAMERA2_REGION_WEIGHT)};
}
/**
* Return AF region(s) for a sensor-referenced touch coordinate.
*
+ * <p>
+ * Normalized coordinates are referenced to portrait preview window with
+ * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
+ * </p>
+ *
* @return AF region(s).
*/
- public static MeteringRectangle[] afRegionsForSensorCoord(int xc, int yc, Rect cropRegion) {
- return regionsForSensorCoord(xc, yc, Settings3A.getAutoFocusRegionWidth(), cropRegion);
+ public static MeteringRectangle[] afRegionsForNormalizedCoord(float nx,
+ float ny, final Rect cropRegion, int sensorOrientation) {
+ return regionsForNormalizedCoord(nx, ny, Settings3A.getAutoFocusRegionWidth(),
+ cropRegion, sensorOrientation);
}
/**
* Return AE region(s) for a sensor-referenced touch coordinate.
*
+ * <p>
+ * Normalized coordinates are referenced to portrait preview window with
+ * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
+ * </p>
+ *
* @return AE region(s).
*/
- public static MeteringRectangle[] aeRegionsForSensorCoord(int xc, int yc, Rect cropRegion) {
- return regionsForSensorCoord(xc, yc, Settings3A.getMeteringRegionWidth(), cropRegion);
+ public static MeteringRectangle[] aeRegionsForNormalizedCoord(float nx,
+ float ny, final Rect cropRegion, int sensorOrientation) {
+ return regionsForNormalizedCoord(nx, ny, Settings3A.getMeteringRegionWidth(),
+ cropRegion, sensorOrientation);
}
/**
@Override
public void triggerFocusAndMeterAtPoint(float nx, float ny) {
- // xc, yc is center of tap point in sensor coordinate system.
- int xc = mCropRegion.left + (int) (mCropRegion.width() * ny);
- int yc = mCropRegion.top + (int) (mCropRegion.height() * (1f - nx));
-
- mAERegions = AutoFocusHelper.aeRegionsForSensorCoord(xc, yc, mCropRegion);
- mAFRegions = AutoFocusHelper.afRegionsForSensorCoord(xc, yc, mCropRegion);
+ int sensorOrientation = mCharacteristics.get(
+ CameraCharacteristics.SENSOR_ORIENTATION);
+ mAERegions = AutoFocusHelper.aeRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
+ mAFRegions = AutoFocusHelper.afRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
sendAutoFocusTriggerCaptureRequest(RequestTag.TAP_TO_FOCUS);
}
*/
@Override
public void triggerFocusAndMeterAtPoint(float nx, float ny) {
- // xc, yc is center of tap point in sensor coordinate system.
- int xc = mCropRegion.left + (int) (mCropRegion.width() * ny);
- int yc = mCropRegion.top + (int) (mCropRegion.height() * (1f - nx));
-
- mAERegions = AutoFocusHelper.aeRegionsForSensorCoord(xc, yc, mCropRegion);
- mAFRegions = AutoFocusHelper.afRegionsForSensorCoord(xc, yc, mCropRegion);
+ int sensorOrientation = mCharacteristics.get(
+ CameraCharacteristics.SENSOR_ORIENTATION);
+ mAERegions = AutoFocusHelper.aeRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
+ mAFRegions = AutoFocusHelper.afRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
startAFCycle();
}
private String[] mCamcorderProfileNames;
private CameraDeviceInfo mInfos;
private final String mPrefKey;
+ private boolean mGetSubPrefAsRoot = true;
// Selected resolutions for the different cameras and sizes.
private SelectedPictureSizes mOldPictureSizesBack;
super.onCreate(savedInstanceState);
Context context = this.getActivity().getApplicationContext();
addPreferencesFromResource(R.xml.camera_preferences);
- // Only add the additional preferences when in the main settings
- // view, and not in the sub-preferences screens.
- if (mPrefKey == null) {
- CameraSettingsActivityHelper.addAdditionalPreferences(this, context);
- }
+
+ // Allow the Helper to edit the full preference hierarchy, not the sub
+ // tree we may show as root. See {@link #getPreferenceScreen()}.
+ mGetSubPrefAsRoot = false;
+ CameraSettingsActivityHelper.addAdditionalPreferences(this, context);
+ mGetSubPrefAsRoot = true;
+
mCamcorderProfileNames = getResources().getStringArray(R.array.camcorder_profile_names);
mInfos = CameraAgentFactory
.getAndroidCameraAgent(context, CameraAgentFactory.CameraApi.API_1)
@Override
public PreferenceScreen getPreferenceScreen() {
PreferenceScreen root = super.getPreferenceScreen();
- if (mPrefKey == null || root == null) {
+ if (!mGetSubPrefAsRoot || mPrefKey == null || root == null) {
return root;
} else {
PreferenceScreen match = findByKey(root, mPrefKey);
* whereas a state list drawable would require a different drawable for each state.
*/
public class ModeIconView extends View {
- private boolean mHighlightIsOn = false;
private final GradientDrawable mBackground;
- private final GradientDrawable mHoverDrawable;
private final int mIconBackgroundSize;
private int mHighlightColor;
private final int mBackgroundDefaultColor;
private final int mIconDrawableSize;
private Drawable mIconDrawable = null;
- private boolean mSelected = false;
public ModeIconView(Context context, AttributeSet attrs) {
super(context, attrs);
mBackground = (GradientDrawable) getResources()
.getDrawable(R.drawable.mode_icon_background).mutate();
mBackground.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize);
- mHoverDrawable = (GradientDrawable) getResources()
- .getDrawable(R.drawable.mode_icon_background).mutate();
- mHoverDrawable.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize);
mIconDrawableSize = getResources().getDimensionPixelSize(
R.dimen.mode_selector_icon_drawable_size);
-
- mHoverDrawable.setColor(getResources().getColor(R.color.mode_icon_hover_highlight));
}
/**
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
- if (mHighlightIsOn && !mSelected) {
- mHoverDrawable.draw(canvas);
- } else {
- mBackground.draw(canvas);
- }
+ mBackground.draw(canvas);
if (mIconDrawable != null) {
mIconDrawable.draw(canvas);
}
public void setSelected(boolean selected) {
if (selected) {
mBackground.setColor(mHighlightColor);
- mHighlightIsOn = false;
} else {
mBackground.setColor(mBackgroundDefaultColor);
}
- mSelected = selected;
- invalidate();
- }
-
- /**
- * This gets called when the highlighted state is changed. When highlighted,
- * a ring shaped drawable of a solid pre-defined color will be drawn on top
- * of the background drawable to indicate highlight state.
- *
- * @param highlighted true when highlighted, false otherwise.
- */
- public void setHighlighted(boolean highlighted) {
- mHighlightIsOn = highlighted;
invalidate();
}
* be revealed through a pinhole animation. After all the animations finish,
* mode list will transition into fully hidden state.
*/
- private class SelectedState extends ModeListState {
+ private class SelectedState extends ModeListState {
public SelectedState(ModeSelectorItem selectedItem) {
final int modeId = selectedItem.getModeId();
// Un-highlight all the modes.
for (int i = 0; i < mModeSelectorItems.length; i++) {
- mModeSelectorItems[i].setHighlighted(false);
mModeSelectorItems[i].setSelected(false);
}
if (mModeSelectorItems != null) {
// When becoming invisible/gone after initializing mode selector items.
for (int i = 0; i < mModeSelectorItems.length; i++) {
- mModeSelectorItems[i].setHighlighted(false);
mModeSelectorItems[i].setSelected(false);
}
}
private int mPeepHoleCenterY = UNSET;
private float mRadius = 0f;
private ValueAnimator mPeepHoleAnimator;
+ private ValueAnimator mFadeOutAlphaAnimator;
+ private ValueAnimator mRevealAlphaAnimator;
private Bitmap mBackground;
private Bitmap mBackgroundOverlay;
mCoverPaint.setColor(0);
mCoverPaint.setAlpha(0);
+
+ setupAnimators();
+ }
+
+ private void setupAnimators() {
+ mFadeOutAlphaAnimator = ValueAnimator.ofInt(0, 255);
+ mFadeOutAlphaAnimator.setDuration(100);
+ mFadeOutAlphaAnimator.setInterpolator(Gusterpolator.INSTANCE);
+ mFadeOutAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mCoverPaint.setAlpha((Integer) animation.getAnimatedValue());
+ invalidate();
+ }
+ });
+ mFadeOutAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Sets a HW layer on the view for the animation.
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Sets the layer type back to NONE as a workaround for b/12594617.
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+ });
+
+ /////////////////
+
+ mRevealAlphaAnimator = ValueAnimator.ofInt(255, 0);
+ mRevealAlphaAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS);
+ mRevealAlphaAnimator.setInterpolator(Gusterpolator.INSTANCE);
+ mRevealAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int alpha = (Integer) animation.getAnimatedValue();
+ mCirclePaint.setAlpha(alpha);
+ mCoverPaint.setAlpha(alpha);
+ }
+ });
+ mRevealAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Sets a HW layer on the view for the animation.
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Sets the layer type back to NONE as a workaround for b/12594617.
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+ });
+
+ ////////////////
+
+ int horizontalDistanceToFarEdge = Math.max(mPeepHoleCenterX, mWidth - mPeepHoleCenterX);
+ int verticalDistanceToFarEdge = Math.max(mPeepHoleCenterY, mHeight - mPeepHoleCenterY);
+ int endRadius = (int) (Math.sqrt(horizontalDistanceToFarEdge * horizontalDistanceToFarEdge
+ + verticalDistanceToFarEdge * verticalDistanceToFarEdge));
+ int startRadius = getResources().getDimensionPixelSize(
+ R.dimen.mode_selector_icon_block_width) / 2;
+
+ mPeepHoleAnimator = ValueAnimator.ofFloat(startRadius, endRadius);
+ mPeepHoleAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS);
+ mPeepHoleAnimator.setInterpolator(Gusterpolator.INSTANCE);
+ mPeepHoleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ // Modify mask by enlarging the hole
+ mRadius = (Float) mPeepHoleAnimator.getAnimatedValue();
+ invalidate();
+ }
+ });
+ mPeepHoleAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Sets a HW layer on the view for the animation.
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Sets the layer type back to NONE as a workaround for b/12594617.
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+ });
+
+ ////////////////
+ int size = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width);
+ mCircleDrawable = new TouchCircleDrawable(getContext().getResources());
+ mCircleDrawable.setSize(size, size);
+ mCircleDrawable.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ invalidate();
+ }
+ });
}
@Override
return true;
}
+ @Override
+ public void drawForeground(Canvas canvas) {
+ // Draw the circle in clear mode
+ if (mPeepHoleAnimator != null) {
+ // Draw a transparent circle using clear mode
+ canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mMaskPaint);
+ canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mCirclePaint);
+ }
+ }
+
public void setAnimationStartingPosition(int x, int y) {
mPeepHoleCenterX = x;
mPeepHoleCenterY = y;
}
@Override
- public void drawForeground(Canvas canvas) {
- // Draw the circle in clear mode
- if (mPeepHoleAnimator != null) {
- // Draw a transparent circle using clear mode
- canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mMaskPaint);
- canvas.drawCircle(mPeepHoleCenterX, mPeepHoleCenterY, mRadius, mCirclePaint);
- }
- }
-
- @Override
public boolean shouldDrawSuper() {
// No need to draw super when mBackgroundOverlay is being drawn, as
// background overlay already contains what's drawn in super.
mCoverPaint.setColor(0);
mCoverPaint.setAlpha(0);
- ValueAnimator alphaAnimator = ValueAnimator.ofInt(0, 255);
- alphaAnimator.setDuration(100);
- alphaAnimator.setInterpolator(Gusterpolator.INSTANCE);
- alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mCoverPaint.setAlpha((Integer) animation.getAnimatedValue());
- invalidate();
- }
- });
- if (listener != null) {
- alphaAnimator.addListener(listener);
- }
-
- int size = getContext().getResources()
- .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width);
- mCircleDrawable = new TouchCircleDrawable(getContext().getResources());
mCircleDrawable.setIconDrawable(
selectedItem.getIcon().getIconDrawableClone(),
selectedItem.getIcon().getIconDrawableSize());
- mCircleDrawable.setSize(size, size);
mCircleDrawable.setCenter(new Point(x, y));
mCircleDrawable.setColor(selectedItem.getHighlightColor());
- mCircleDrawable.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidate();
- }
- });
-
mCircleDrawable.setAnimatorListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
});
}
});
+
+ // add fade out animator to a set, so we can freely add
+ // the listener without having to worry about listener dupes
+ AnimatorSet s = new AnimatorSet();
+ s.play(mFadeOutAlphaAnimator);
+ if (listener != null) {
+ s.addListener(listener);
+ }
mCircleDrawable.animate();
- alphaAnimator.start();
+ s.start();
}
@Override
mPeepHoleCenterY = mHeight / 2;
}
- int horizontalDistanceToFarEdge = Math.max(mPeepHoleCenterX, mWidth - mPeepHoleCenterX);
- int verticalDistanceToFarEdge = Math.max(mPeepHoleCenterY, mHeight - mPeepHoleCenterY);
- int endRadius = (int) (Math.sqrt(horizontalDistanceToFarEdge * horizontalDistanceToFarEdge
- + verticalDistanceToFarEdge * verticalDistanceToFarEdge));
- int startRadius = getResources().getDimensionPixelSize(
- R.dimen.mode_selector_icon_block_width) / 2;
-
- mPeepHoleAnimator = ValueAnimator.ofFloat(startRadius, endRadius);
- mPeepHoleAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS);
- mPeepHoleAnimator.setInterpolator(Gusterpolator.INSTANCE);
- mPeepHoleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- // Modify mask by enlarging the hole
- mRadius = (Float) mPeepHoleAnimator.getAnimatedValue();
- invalidate();
- }
- });
-
- if (listener != null) {
- mPeepHoleAnimator.addListener(listener);
- }
-
- mPeepHoleAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- // Sets a HW layer on the view for the animation.
- setLayerType(LAYER_TYPE_HARDWARE, null);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // Sets the layer type back to NONE as a workaround for b/12594617.
- setLayerType(LAYER_TYPE_NONE, null);
- }
- });
-
mCirclePaint.setAlpha(255);
mCoverPaint.setAlpha(255);
- ValueAnimator alphaAnimator = ValueAnimator.ofInt(255, 0);
- alphaAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS);
- alphaAnimator.setInterpolator(Gusterpolator.INSTANCE);
- alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int alpha = (Integer) animation.getAnimatedValue();
- mCirclePaint.setAlpha(alpha);
- mCoverPaint.setAlpha(alpha);
- }
- });
- alphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- // Sets a HW layer on the view for the animation.
- setLayerType(LAYER_TYPE_HARDWARE, null);
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- // Sets the layer type back to NONE as a workaround for b/12594617.
- setLayerType(LAYER_TYPE_NONE, null);
- }
- });
-
-
- mPeepHoleAnimator.start();
- alphaAnimator.start();
+ // add peephole and reveal animators to a set, so we can
+ // freely add the listener without having to worry about
+ // listener dupes
+ AnimatorSet s = new AnimatorSet();
+ s.play(mPeepHoleAnimator).with(mRevealAlphaAnimator);
+ if (listener != null) {
+ s.addListener(listener);
+ }
+ s.start();
}
@Override
* we display the view partially.
*/
class ModeSelectorItem extends FrameLayout {
- // Drawing modes that defines how the TextView should be drawn when there
- // is not enough space to draw the whole TextView.
- public static final int FLY_IN = 1;
- public static final int FLY_OUT = 2;
-
private TextView mText;
private ModeIconView mIcon;
private int mVisibleWidth = 0;
mListener = listener;
}
- public void setHighlighted(boolean highlighted) {
- mIcon.setHighlighted(highlighted);
- }
-
@Override
public void setSelected(boolean selected) {
mIcon.setSelected(selected);
return false;
}
- @Override
- public void setPressed(boolean pressed) {
- super.setPressed(pressed);
- // When pressed state changes, highlight the icon.
- mIcon.setHighlighted(pressed);
- }
-
/**
* When swiping in, we truncate the end of the item if the visible width
* is not enough to show the whole item. When swiping out, we truncate the
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.camera2.CameraCharacteristics;
}
public static int nextPowerOf2(int n) {
+ // TODO: what happens if n is negative or already a power of 2?
n -= 1;
n |= n >>> 16;
n |= n >>> 8;
return (float) Math.sqrt(dx * dx + dy * dy);
}
+ /**
+ * Clamps x to between min and max (inclusive on both ends, x = min --> min,
+ * x = max --> max).
+ */
public static int clamp(int x, int min, int max) {
if (x > max) {
return max;
return x;
}
+ /**
+ * Clamps x to between min and max (inclusive on both ends, x = min --> min,
+ * x = max --> max).
+ */
public static float clamp(float x, float min, float max) {
if (x > max) {
return max;
}
/**
+ * Given (nx, ny) \in [0, 1]^2, in the display's portrait coordinate system,
+ * returns normalized sensor coordinates \in [0, 1]^2 depending on how
+ * the sensor's orientation \in {0, 90, 180, 270}.
+ *
+ * <p>
+ * Returns null if sensorOrientation is not one of the above.
+ * </p>
+ */
+ public static PointF normalizedSensorCoordsForNormalizedDisplayCoords(
+ float nx, float ny, int sensorOrientation) {
+ switch (sensorOrientation) {
+ case 0:
+ return new PointF(nx, ny);
+ case 90:
+ return new PointF(ny, 1.0f - nx);
+ case 180:
+ return new PointF(1.0f - nx, 1.0f - ny);
+ case 270:
+ return new PointF(1.0f - ny, nx);
+ default:
+ return null;
+ }
+ }
+
+ /**
* Given a size, return the largest size with the given aspectRatio that
* maximally fits into the bounding rectangle of the original Size.
*