Merge "Import translations. DO NOT MERGE"
diff --git a/res/layout/quickcontact_activity.xml b/res/layout/quickcontact_activity.xml
index 8f78811..13b8d9b 100644
--- a/res/layout/quickcontact_activity.xml
+++ b/res/layout/quickcontact_activity.xml
@@ -24,10 +24,14 @@
android:focusableInTouchMode="true"
android:descendantFocusability="afterDescendants" >
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/quickcontact_starting_empty_height"
+ android:id="@+id/transparent_view" />
+
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="@dimen/quickcontact_starting_empty_height"
android:background="@color/card_margin_color"
android:id="@+id/toolbar_parent">
diff --git a/src/com/android/contacts/interactions/CalendarInteractionsLoader.java b/src/com/android/contacts/interactions/CalendarInteractionsLoader.java
index 8ab6c37..941698c 100644
--- a/src/com/android/contacts/interactions/CalendarInteractionsLoader.java
+++ b/src/com/android/contacts/interactions/CalendarInteractionsLoader.java
@@ -63,10 +63,8 @@
}
// Perform separate calendar queries for events in the past and future.
Cursor cursor = getSharedEventsCursor(/* isFuture= */ true, mMaxFutureToRetrieve);
- Log.v(TAG, "future cursor.count() " + cursor.getCount());
List<ContactInteraction> interactions = getInteractionsFromEventsCursor(cursor);
cursor = getSharedEventsCursor(/* isFuture= */ false, mMaxPastToRetrieve);
- Log.v(TAG, "past cursor.count() " + cursor.getCount());
List<ContactInteraction> interactions2 = getInteractionsFromEventsCursor(cursor);
ArrayList<ContactInteraction> allInteractions = new ArrayList<ContactInteraction>(
@@ -74,6 +72,7 @@
allInteractions.addAll(interactions);
allInteractions.addAll(interactions2);
+ Log.v(TAG, "# ContactInteraction Loaded: " + allInteractions.size());
return allInteractions;
}
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 9533d75..b186038 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -16,6 +16,9 @@
package com.android.contacts.quickcontact;
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.app.Activity;
@@ -80,7 +83,6 @@
import com.android.contacts.common.model.dataitem.PhoneDataItem;
import com.android.contacts.common.util.DataStatus;
import com.android.contacts.detail.ContactDetailDisplayUtils;
-import com.android.contacts.common.util.UriUtils;
import com.android.contacts.interactions.CalendarInteractionsLoader;
import com.android.contacts.interactions.CallLogInteractionsLoader;
import com.android.contacts.interactions.ContactDeletionInteraction;
@@ -97,7 +99,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -142,11 +143,15 @@
private boolean mHasAlreadyBeenOpened;
private ImageView mPhotoView;
+ private View mTransparentView;
private ExpandingEntryCardView mCommunicationCard;
private ExpandingEntryCardView mRecentCard;
private MultiShrinkScroller mScroller;
private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
private AsyncTask<Void, Void, Void> mEntriesAndActionsTask;
+ private ColorDrawable mWindowShim;
+ private boolean mIsWaitingForOtherPieceOfExitAnimation;
+ private boolean mIsExitAnimationInProgress;
private static final int MIN_NUM_COMMUNICATION_ENTRIES_SHOWN = 3;
private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
@@ -269,7 +274,11 @@
= new MultiShrinkScrollerListener() {
@Override
public void onScrolledOffBottom() {
- onBackPressed();
+ if (!mIsWaitingForOtherPieceOfExitAnimation) {
+ finish();
+ return;
+ }
+ mIsWaitingForOtherPieceOfExitAnimation = false;
}
@Override
@@ -281,6 +290,28 @@
public void onExitFullscreen() {
updateStatusBarColor();
}
+
+ @Override
+ public void onStartScrollOffBottom() {
+ // Remove the window shim now that we are starting an Activity exit animation.
+ final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
+ final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowShim, "alpha", 0xFF, 0);
+ animator.addListener(mExitWindowShimAnimationListener);
+ animator.setDuration(duration).start();
+ mIsWaitingForOtherPieceOfExitAnimation = true;
+ mIsExitAnimationInProgress = true;
+ }
+ };
+
+ final AnimatorListener mExitWindowShimAnimationListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mIsWaitingForOtherPieceOfExitAnimation) {
+ finish();
+ return;
+ }
+ mIsWaitingForOtherPieceOfExitAnimation = false;
+ }
};
@Override
@@ -314,6 +345,15 @@
mRecentCard.setTitle(getResources().getString(R.string.recent_card_title));
mPhotoView = (ImageView) findViewById(R.id.photo);
+ mTransparentView = findViewById(R.id.transparent_view);
+ if (mScroller != null) {
+ mTransparentView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mScroller.scrollOffBottom();
+ }
+ });
+ }
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setActionBar(toolbar);
@@ -321,11 +361,11 @@
mHasAlreadyBeenOpened = savedInstanceState != null;
- final ColorDrawable windowShim = new ColorDrawable(SHIM_COLOR);
- getWindow().setBackgroundDrawable(windowShim);
+ mWindowShim = new ColorDrawable(SHIM_COLOR);
+ getWindow().setBackgroundDrawable(mWindowShim);
if (!mHasAlreadyBeenOpened) {
final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
- ObjectAnimator.ofInt(windowShim, "alpha", 0, 0xFF).setDuration(duration).start();
+ ObjectAnimator.ofInt(mWindowShim, "alpha", 0, 0xFF).setDuration(duration).start();
}
if (mScroller != null) {
@@ -878,8 +918,9 @@
@Override
public void onBackPressed() {
if (mScroller != null) {
- // TODO: implement exit animation if the scroller isn't already off the screen
- finish();
+ if (!mIsExitAnimationInProgress) {
+ mScroller.scrollOffBottom();
+ }
} else {
super.onBackPressed();
}
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 53179f5..b38f5cf 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -6,19 +6,22 @@
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.hardware.display.DisplayManagerGlobal;
import android.util.AttributeSet;
+import android.view.Display;
+import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
import android.widget.ImageView;
@@ -46,6 +49,11 @@
*/
private static final int PIXELS_PER_SECOND = 1000;
+ /**
+ * Length of the acceleration animations. This value was taken from ValueAnimator.java.
+ */
+ private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
+
private float[] mLastEventPosition = { 0, 0 };
private VelocityTracker mVelocityTracker;
private boolean mIsBeingDragged = false;
@@ -56,6 +64,7 @@
private View mToolbar;
private ImageView mPhotoView;
private View mPhotoViewContainer;
+ private View mTransparentView;
private MultiShrinkScrollerListener mListener;
private int mHeaderTintColor;
private int mMaximumHeaderHeight;
@@ -75,25 +84,31 @@
public interface MultiShrinkScrollerListener {
void onScrolledOffBottom();
+ void onStartScrollOffBottom();
+
void onEnterFullscreen();
void onExitFullscreen();
}
- private final AnimatorListener mHeaderExpandAnimationListener = new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {}
-
+ private final AnimatorListener mHeaderExpandAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mPhotoView.setClickable(true);
}
+ };
+ private final AnimatorListener mSnapToBottomListener = new AnimatorListenerAdapter() {
@Override
- public void onAnimationCancel(Animator animation) {}
-
- @Override
- public void onAnimationRepeat(Animator animation) {}
+ public void onAnimationEnd(Animator animation) {
+ if (getScrollUntilOffBottom() > 0 && mListener != null) {
+ // Due to a rounding error, after the animation finished we haven't fully scrolled
+ // off the screen. Lie to the listener: tell it that we did scroll off the screen.
+ mListener.onScrolledOffBottom();
+ // No other messages need to be sent to the listener.
+ mListener = null;
+ }
+ }
};
/**
@@ -156,6 +171,7 @@
mScrollViewChild = findViewById(R.id.card_container);
mToolbar = findViewById(R.id.toolbar_parent);
mPhotoViewContainer = findViewById(R.id.toolbar_parent);
+ mTransparentView = findViewById(R.id.transparent_view);
mListener = listener;
mPhotoView = (ImageView) findViewById(R.id.photo);
@@ -354,12 +370,24 @@
*/
private void snapToBottom(int flingDelta) {
if (-getScroll_ignoreOversizedHeader() - flingDelta > 0) {
- mScroller.forceFinished(true);
- ObjectAnimator translateAnimation = ObjectAnimator.ofInt(this, "scroll",
- getScroll() - getScrollUntilOffBottom());
- translateAnimation.setRepeatCount(0);
- translateAnimation.setInterpolator(new AccelerateInterpolator());
- translateAnimation.start();
+ scrollOffBottom();
+ }
+ }
+
+ public void scrollOffBottom() {
+ final Interpolator interpolator = new AcceleratingFlingInterpolator(
+ EXIT_FLING_ANIMATION_DURATION_MS, getCurrentVelocity(),
+ getScrollUntilOffBottom());
+ mScroller.forceFinished(true);
+ ObjectAnimator translateAnimation = ObjectAnimator.ofInt(this, "scroll",
+ getScroll() - getScrollUntilOffBottom());
+ translateAnimation.setRepeatCount(0);
+ translateAnimation.setInterpolator(interpolator);
+ translateAnimation.setDuration(EXIT_FLING_ANIMATION_DURATION_MS);
+ translateAnimation.addListener(mSnapToBottomListener);
+ translateAnimation.start();
+ if (mListener != null) {
+ mListener.onStartScrollOffBottom();
}
}
@@ -413,7 +441,7 @@
public int getScroll() {
final LinearLayout.LayoutParams toolbarLayoutParams
= (LayoutParams) mToolbar.getLayoutParams();
- return mTransparentStartHeight - toolbarLayoutParams.topMargin
+ return mTransparentStartHeight - getTransparentViewHeight()
+ mIntermediateHeaderHeight - toolbarLayoutParams.height + mScrollView.getScrollY();
}
@@ -425,7 +453,7 @@
public int getScroll_ignoreOversizedHeader() {
final LinearLayout.LayoutParams toolbarLayoutParams
= (LayoutParams) mToolbar.getLayoutParams();
- return mTransparentStartHeight - toolbarLayoutParams.topMargin
+ return mTransparentStartHeight - getTransparentViewHeight()
+ Math.max(mIntermediateHeaderHeight - toolbarLayoutParams.height, 0)
+ mScrollView.getScrollY();
}
@@ -434,9 +462,7 @@
* Amount of transparent space above the header/toolbar.
*/
public int getScrollNeededToBeFullScreen() {
- final LinearLayout.LayoutParams toolbarLayoutParams
- = (LayoutParams) mToolbar.getLayoutParams();
- return toolbarLayoutParams.topMargin;
+ return getTransparentViewHeight();
}
/**
@@ -495,6 +521,9 @@
}
private float getCurrentVelocity() {
+ if (mVelocityTracker == null) {
+ return 0;
+ }
mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, mMaximumVelocity);
return mVelocityTracker.getYVelocity();
}
@@ -515,15 +544,24 @@
+ Math.max(0, mScrollViewChild.getHeight() - getHeight() + mMinimumHeaderHeight);
}
+ private int getTransparentViewHeight() {
+ return mTransparentView.getLayoutParams().height;
+ }
+
+ private void setTransparentViewHeight(int height) {
+ mTransparentView.getLayoutParams().height = height;
+ mTransparentView.setLayoutParams(mTransparentView.getLayoutParams());
+ }
+
private void scrollUp(int delta) {
- LinearLayout.LayoutParams toolbarLayoutParams = (LayoutParams) mToolbar.getLayoutParams();
- if (toolbarLayoutParams.topMargin != 0) {
- final int originalValue = toolbarLayoutParams.topMargin;
- toolbarLayoutParams.topMargin -= delta;
- toolbarLayoutParams.topMargin = Math.max(toolbarLayoutParams.topMargin, 0);
- mToolbar.setLayoutParams(toolbarLayoutParams);
- delta -= originalValue - toolbarLayoutParams.topMargin;
+ if (getTransparentViewHeight() != 0) {
+ final int originalValue = getTransparentViewHeight();
+ setTransparentViewHeight(getTransparentViewHeight() - delta);
+ setTransparentViewHeight(Math.max(0, getTransparentViewHeight()));
+ delta -= originalValue - getTransparentViewHeight();
}
+ final LinearLayout.LayoutParams toolbarLayoutParams
+ = (LayoutParams) mToolbar.getLayoutParams();
if (toolbarLayoutParams.height != mMinimumHeaderHeight) {
final int originalValue = toolbarLayoutParams.height;
toolbarLayoutParams.height -= delta;
@@ -535,12 +573,13 @@
}
private void scrollDown(int delta) {
- LinearLayout.LayoutParams toolbarLayoutParams = (LayoutParams) mToolbar.getLayoutParams();
if (mScrollView.getScrollY() > 0) {
final int originalValue = mScrollView.getScrollY();
mScrollView.scrollBy(0, delta);
delta -= mScrollView.getScrollY() - originalValue;
}
+ final LinearLayout.LayoutParams toolbarLayoutParams
+ = (LayoutParams) mToolbar.getLayoutParams();
if (toolbarLayoutParams.height < mIntermediateHeaderHeight) {
final int originalValue = toolbarLayoutParams.height;
toolbarLayoutParams.height -= delta;
@@ -549,14 +588,17 @@
mToolbar.setLayoutParams(toolbarLayoutParams);
delta -= originalValue - toolbarLayoutParams.height;
}
- toolbarLayoutParams.topMargin -= delta;
- mToolbar.setLayoutParams(toolbarLayoutParams);
+ setTransparentViewHeight(getTransparentViewHeight() - delta);
- if (mListener != null && getScrollUntilOffBottom() <= 0) {
+ if (getScrollUntilOffBottom() <= 0) {
post(new Runnable() {
@Override
public void run() {
- mListener.onScrolledOffBottom();
+ if (mListener != null) {
+ mListener.onScrolledOffBottom();
+ // No other messages need to be sent to the listener.
+ mListener = null;
+ }
}
});
}
@@ -568,7 +610,7 @@
final int toolbarHeight = mToolbar.getLayoutParams().height;
// Reuse an existing mColorFilter (to avoid GC pauses) to change the photo's tint.
mPhotoView.clearColorFilter();
- if (toolbarHeight >= mIntermediateHeaderHeight) {
+ if (toolbarHeight >= mMaximumHeaderHeight) {
mPhotoViewContainer.setElevation(0);
return;
}
@@ -576,14 +618,13 @@
mColorFilter.setColor(mHeaderTintColor);
mPhotoView.setColorFilter(mColorFilter);
mPhotoViewContainer.setElevation(mToolbarElevation);
- } else if (toolbarHeight <= mIntermediateHeaderHeight) {
- mPhotoViewContainer.setElevation(0);
- final int alphaBits = 0xff - 0xff * (toolbarHeight - mMinimumHeaderHeight)
- / (mIntermediateHeaderHeight - mMinimumHeaderHeight);
- final int color = alphaBits << 24 | (mHeaderTintColor & 0xffffff);
- mColorFilter.setColor(color);
- mPhotoView.setColorFilter(mColorFilter);
}
+ mPhotoViewContainer.setElevation(0);
+ final int alphaBits = 0xff - 0xff * (toolbarHeight - mMinimumHeaderHeight)
+ / (mMaximumHeaderHeight - mMinimumHeaderHeight);
+ final int color = alphaBits << 24 | (mHeaderTintColor & 0xffffff);
+ mColorFilter.setColor(color);
+ mPhotoView.setColorFilter(mColorFilter);
}
private void updateLastEventPosition(MotionEvent event) {
@@ -616,4 +657,53 @@
mScroller.startScroll(0, getScroll(), 0, delta);
invalidate();
}
+
+ /**
+ * Interpolator that enforces a specific starting velocity. This is useful to avoid a
+ * discontinuity between dragging speed and flinging speed.
+ *
+ * Similar to a {@link android.view.animation.AccelerateInterpolator} in the sense that
+ * getInterpolation() is a quadratic function.
+ */
+ private static class AcceleratingFlingInterpolator implements Interpolator {
+
+ private final float mStartingSpeedPixelsPerFrame;
+ private final float mDurationMs;
+ private final int mPixelsDelta;
+ private final float mNumberFrames;
+
+ public AcceleratingFlingInterpolator(int durationMs, float startingSpeedPixelsPerSecond,
+ int pixelsDelta) {
+ mStartingSpeedPixelsPerFrame = startingSpeedPixelsPerSecond / getRefreshRate();
+ mDurationMs = durationMs;
+ mPixelsDelta = pixelsDelta;
+ mNumberFrames = mDurationMs / getFrameIntervalMs();
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ final float animationIntervalNumber = mNumberFrames * input;
+ final float linearDelta = (animationIntervalNumber * mStartingSpeedPixelsPerFrame)
+ / mPixelsDelta;
+ // Add the results of a linear interpolator (with the initial speed) with the
+ // results of a AccelerateInterpolator.
+ if (mStartingSpeedPixelsPerFrame > 0) {
+ return Math.min(input * input + linearDelta, 1);
+ } else {
+ // Initial fling was in the wrong direction, make sure that the quadratic component
+ // grows faster in order to make up for this.
+ return Math.min(input * (input - linearDelta) + linearDelta, 1);
+ }
+ }
+
+ private float getRefreshRate() {
+ DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+ Display.DEFAULT_DISPLAY);
+ return di.refreshRate;
+ }
+
+ public long getFrameIntervalMs() {
+ return (long)(1000 / getRefreshRate());
+ }
+ }
}