Merge "Fade transitions between messages for voicemails."
diff --git a/res/layout/playback_layout.xml b/res/layout/playback_layout.xml
index 45e7ad9..05b3e43 100644
--- a/res/layout/playback_layout.xml
+++ b/res/layout/playback_layout.xml
@@ -77,6 +77,16 @@
                 android:layout_centerHorizontal="true"
                 android:layout_marginTop="10dip"
             />
+            <TextView
+                android:id="@+id/playback_speed_text"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:textSize="14sp"
+                android:layout_alignParentTop="true"
+                android:layout_centerHorizontal="true"
+                android:layout_marginTop="10dip"
+                android:alpha="0"
+            />
             <ImageButton
                 android:id="@+id/rate_decrease_button"
                 android:src="@drawable/ic_minus_holo_dark"
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
index d306209..fcf99b6 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
@@ -66,7 +66,6 @@
     private SeekBar mPlaybackSeek;
     private ImageButton mStartStopButton;
     private ImageButton mPlaybackSpeakerphone;
-    private TextView mPlaybackPositionText;
     private ImageButton mRateDecreaseButton;
     private ImageButton mRateIncreaseButton;
     private TextViewWithMessagesController mTextController;
@@ -79,10 +78,11 @@
         mPlaybackSeek = (SeekBar) view.findViewById(R.id.playback_seek);
         mStartStopButton = (ImageButton) view.findViewById(R.id.playback_start_stop);
         mPlaybackSpeakerphone = (ImageButton) view.findViewById(R.id.playback_speakerphone);
-        mPlaybackPositionText = (TextView) view.findViewById(R.id.playback_position_text);
         mRateDecreaseButton = (ImageButton) view.findViewById(R.id.rate_decrease_button);
         mRateIncreaseButton = (ImageButton) view.findViewById(R.id.rate_increase_button);
-        mTextController = new TextViewWithMessagesController(mPlaybackPositionText);
+        mTextController = new TextViewWithMessagesController(
+                (TextView) view.findViewById(R.id.playback_position_text),
+                (TextView) view.findViewById(R.id.playback_speed_text));
         return view;
     }
 
@@ -259,30 +259,30 @@
      * All the methods on this class must be called from the ui thread.
      */
     private static final class TextViewWithMessagesController {
+        private static final float VISIBLE = 1;
+        private static final float INVISIBLE = 0;
+        private static final long SHORT_ANIMATION_MS = 200;
+        private static final long LONG_ANIMATION_MS = 400;
         private final Object mLock = new Object();
-        private final TextView mTextView;
-        @GuardedBy("mLock") String mCurrentText = "";
-        @GuardedBy("mLock") Runnable mRunnable;
+        private final TextView mPermanentTextView;
+        private final TextView mTemporaryTextView;
+        @GuardedBy("mLock") private Runnable mRunnable;
 
-        public TextViewWithMessagesController(TextView textView) {
-            mTextView = textView;
+        public TextViewWithMessagesController(TextView permanentTextView,
+                TextView temporaryTextView) {
+            mPermanentTextView = permanentTextView;
+            mTemporaryTextView = temporaryTextView;
         }
 
         public void setPermanentText(String text) {
-            synchronized (mLock) {
-                mCurrentText = text;
-                // If there's currently a Runnable pending, then we don't alter the display
-                // text. The Runnable will use the most recent version of mCurrentText
-                // when it completes.
-                if (mRunnable == null) {
-                    mTextView.setText(text);
-                }
-            }
+            mPermanentTextView.setText(text);
         }
 
         public void setTemporaryText(String text, long duration, TimeUnit units) {
             synchronized (mLock) {
-                mTextView.setText(text);
+                mTemporaryTextView.setText(text);
+                mTemporaryTextView.animate().alpha(VISIBLE).setDuration(SHORT_ANIMATION_MS);
+                mPermanentTextView.animate().alpha(INVISIBLE).setDuration(SHORT_ANIMATION_MS);
                 mRunnable = new Runnable() {
                     @Override
                     public void run() {
@@ -292,12 +292,15 @@
                             // one is now defunct and needs to take no action.
                             if (mRunnable == this) {
                                 mRunnable = null;
-                                mTextView.setText(mCurrentText);
+                                mTemporaryTextView.animate()
+                                        .alpha(INVISIBLE).setDuration(LONG_ANIMATION_MS);
+                                mPermanentTextView.animate()
+                                        .alpha(VISIBLE).setDuration(LONG_ANIMATION_MS);
                             }
                         }
                     }
                 };
-                mTextView.postDelayed(mRunnable, units.toMillis(duration));
+                mTemporaryTextView.postDelayed(mRunnable, units.toMillis(duration));
             }
         }
     }
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
index eac502d..6f3a625 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
@@ -16,6 +16,8 @@
 
 package com.android.contacts.voicemail;
 
+import static android.util.MathUtils.constrain;
+
 import com.android.contacts.R;
 import com.android.ex.variablespeed.MediaPlayerProxy;
 import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
@@ -211,7 +213,7 @@
         @Override
         public void onClick(View v) {
             // Adjust the current rate, then clamp it to the allowed values.
-            mRateIndex = clamp(mRateIndex + (mIncrease ? 1 : -1), 0, PRESET_RATES.length - 1);
+            mRateIndex = constrain(mRateIndex + (mIncrease ? 1 : -1), 0, PRESET_RATES.length - 1);
             // Whether or not we have actually changed the index, call changeRate().
             // This will ensure that we show the "fastest" or "slowest" text on the ui to indicate
             // to the user that it doesn't get any faster or slower.
@@ -219,18 +221,13 @@
         }
     }
 
-    /** Clamp the input value to between min and max inclusive. */
-    private static int clamp(int input, int min, int max) {
-        return Math.max(Math.min(input, max), min);
-    }
-
     private void resetPrepareStartPlaying(int clipPositionInMillis) {
         try {
             mPlayer.reset();
             mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
             mPlayer.prepare();
             mDuration.set(mPlayer.getDuration());
-            int startPosition = clamp(clipPositionInMillis, 0, mDuration.get());
+            int startPosition = constrain(clipPositionInMillis, 0, mDuration.get());
             mView.setClipPosition(startPosition, mDuration.get());
             mPlayer.seekTo(startPosition);
             mPlayer.start();