Add pulsating effect to header label when switching view.

Bug: 8570362
Change-Id: Ie8534f5be227605ee17d14e558f3398f9d7c6ed7
diff --git a/res/layout-land/time_picker_dialog.xml b/res/layout-land/time_picker_dialog.xml
index 4501d19..0d21f20 100644
--- a/res/layout-land/time_picker_dialog.xml
+++ b/res/layout-land/time_picker_dialog.xml
@@ -25,18 +25,18 @@
     android:layout_marginTop="@dimen/minimum_margin_top_bottom"
     android:layout_marginBottom="@dimen/minimum_margin_top_bottom" >
     <LinearLayout
-        android:layout_width="wrap_content"
+        android:layout_width="@dimen/left_side_width"
         android:layout_height="match_parent"
         android:orientation="vertical">
         <FrameLayout
-            android:layout_width="@dimen/left_side_width"
+            android:layout_width="match_parent"
             android:layout_height="0dip"
             android:layout_weight="1"
             android:background="@color/white" >
             <include
                 layout="@layout/time_header_label"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/header_height"
                 android:layout_gravity="center" />
         </FrameLayout>
         <View
diff --git a/res/layout/time_header_label.xml b/res/layout/time_header_label.xml
index 8f64583..62a2ac5 100644
--- a/res/layout/time_header_label.xml
+++ b/res/layout/time_header_label.xml
@@ -16,23 +16,43 @@
   -->
   <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center" >
+      android:layout_height="match_parent"
+      android:layout_gravity="center"
+      android:background="@color/white" >
         <View
             android:id="@+id/empty_view"
             android:layout_width="1dp"
             android:layout_height="1dp"
             android:background="#00000000"
             android:layout_centerInParent="true" />
-        <com.android.datetimepicker.FakeButton
-            android:id="@+id/hours"
+        <TextView
+            android:id="@+id/hour_space"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/time_placeholder"
-            android:textColor="@color/blue"
             android:layout_toLeftOf="@+id/separator"
             android:layout_centerVertical="true"
-            style="@style/time_label" />
+            android:visibility="invisible"
+            style="@style/time_label"
+            android:importantForAccessibility="no" />
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_alignRight="@+id/hour_space"
+            android:layout_alignLeft="@+id/hour_space"
+            android:layout_marginLeft="@dimen/extra_time_label_margin"
+            android:layout_marginRight="@dimen/extra_time_label_margin"
+            android:layout_centerVertical="true" >
+            <com.android.datetimepicker.FakeButton
+                android:id="@+id/hours"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/time_placeholder"
+                android:textColor="@color/blue"
+                android:gravity="center_horizontal"
+                android:layout_gravity="center"
+                style="@style/time_label" />
+            </FrameLayout>
         <TextView
             android:id="@+id/separator"
             android:layout_width="wrap_content"
@@ -44,20 +64,39 @@
             android:layout_centerVertical="true"
             style="@style/time_label"
             android:importantForAccessibility="no" />
-        <com.android.datetimepicker.FakeButton
-            android:id="@+id/minutes"
+        <TextView
+            android:id="@+id/minutes_space"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/time_placeholder"
             android:layout_toRightOf="@+id/separator"
             android:layout_centerVertical="true"
-            style="@style/time_label" />
+            android:visibility="invisible"
+            style="@style/time_label"
+            android:importantForAccessibility="no" />
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_alignRight="@+id/minutes_space"
+            android:layout_alignLeft="@+id/minutes_space"
+            android:layout_marginLeft="@dimen/extra_time_label_margin"
+            android:layout_marginRight="@dimen/extra_time_label_margin"
+            android:layout_centerVertical="true" >
+            <com.android.datetimepicker.FakeButton
+                android:id="@+id/minutes"
+                style="@style/time_label"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:text="@string/time_placeholder"
+                android:layout_gravity="center" />
+        </FrameLayout>
         <com.android.datetimepicker.FakeButton
             android:id="@+id/ampm_hitspace"
             android:layout_width="@dimen/ampm_label_size"
             android:layout_height="wrap_content"
-            android:layout_alignTop="@+id/minutes"
-            android:layout_alignBottom="@+id/minutes"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentBottom="true"
             android:layout_alignLeft="@+id/ampm_label"
             android:layout_alignRight="@+id/ampm_label" />
         <TextView
@@ -67,8 +106,8 @@
             android:text="@string/time_placeholder"
             android:paddingLeft="@dimen/ampm_left_padding"
             android:paddingRight="@dimen/ampm_left_padding"
-            android:layout_toRightOf="@+id/minutes"
-            android:layout_alignBaseline="@+id/minutes"
+            android:layout_toRightOf="@+id/minutes_space"
+            android:layout_alignBaseline="@+id/separator"
             style="@style/ampm_label"
             android:importantForAccessibility="no" />
-  </RelativeLayout>
\ No newline at end of file
+  </RelativeLayout>
diff --git a/res/layout/time_picker_dialog.xml b/res/layout/time_picker_dialog.xml
index 67f5977..07867d1 100644
--- a/res/layout/time_picker_dialog.xml
+++ b/res/layout/time_picker_dialog.xml
@@ -23,12 +23,12 @@
     android:focusable="true" >
     <FrameLayout
         android:layout_width="match_parent"
-        android:layout_height="@dimen/header_height"
+        android:layout_height="wrap_content"
         android:background="@color/white" >
         <include
             layout="@layout/time_header_label"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
+            android:layout_height="@dimen/header_height"
             android:layout_gravity="center" />
     </FrameLayout>
     <com.android.datetimepicker.time.RadialPickerLayout
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 03ddbca..68dbc70 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -40,6 +40,7 @@
     <dimen name="month_day_label_text_size">15sp</dimen>
 
     <dimen name="time_label_size">75sp</dimen>
+    <dimen name="extra_time_label_margin">-50dp</dimen>
     <dimen name="ampm_label_size">20sp</dimen>
     <dimen name="ampm_left_padding">8dip</dimen>
     <dimen name="separator_padding">5dip</dimen>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 621cbf7..691ab0f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -30,6 +30,7 @@
 
 
     <dimen name="time_label_size">60sp</dimen>
+    <dimen name="extra_time_label_margin">-30dp</dimen>
     <dimen name="ampm_label_size">16sp</dimen>
     <dimen name="done_label_size">16sp</dimen>
     <dimen name="ampm_left_padding">6dip</dimen>
diff --git a/src/com/android/datetimepicker/Utils.java b/src/com/android/datetimepicker/Utils.java
index 9bc5952..ae3087b 100644
--- a/src/com/android/datetimepicker/Utils.java
+++ b/src/com/android/datetimepicker/Utils.java
@@ -16,8 +16,12 @@
 
 package com.android.datetimepicker;
 
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.os.Build;
 import android.text.format.Time;
+import android.view.View;
 
 import java.util.Calendar;
 
@@ -27,6 +31,7 @@
 public class Utils {
 
     public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
+    public static final int PULSE_ANIMATOR_DURATION = 600;
 
     static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
 
@@ -92,4 +97,24 @@
         int refDay = Time.EPOCH_JULIAN_DAY - diff;
         return (julianDay - refDay) / 7;
     }
+
+    /**
+     * Render an animator to pulsate a view in place.
+     * @param labelToAnimate the view to pulsate.
+     * @return The animator object. Use .start() to begin.
+     */
+    public static ObjectAnimator getPulseAnimator(View labelToAnimate) {
+        Keyframe k0 = Keyframe.ofFloat(0f, 1f);
+        Keyframe k1 = Keyframe.ofFloat(0.25f, 0.85f);
+        Keyframe k2 = Keyframe.ofFloat(0.625f, 1.1f);
+        Keyframe k3 = Keyframe.ofFloat(1f, 1f);
+
+        PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3);
+        PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3);
+        ObjectAnimator pulseAnimator =
+                ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
+        pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
+
+        return pulseAnimator;
+    }
 }
diff --git a/src/com/android/datetimepicker/time/TimePickerDialog.java b/src/com/android/datetimepicker/time/TimePickerDialog.java
index 57ce93a..a5ef395 100644
--- a/src/com/android/datetimepicker/time/TimePickerDialog.java
+++ b/src/com/android/datetimepicker/time/TimePickerDialog.java
@@ -16,6 +16,7 @@
 
 package com.android.datetimepicker.time;
 
+import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.app.ActionBar.LayoutParams;
 import android.app.DialogFragment;
@@ -65,11 +66,16 @@
     public static final int AM = 0;
     public static final int PM = 1;
 
+    // Delay before starting the pulse animation, in ms.
+    private static final int PULSE_ANIMATOR_DELAY = 300;
+
     private OnTimeSetListener mCallback;
 
     private TextView mDoneButton;
     private TextView mHourView;
+    private TextView mHourSpaceView;
     private TextView mMinuteView;
+    private TextView mMinuteSpaceView;
     private TextView mAmPmTextView;
     private View mAmPmHitspace;
     private RadialPickerLayout mTimePicker;
@@ -181,6 +187,8 @@
 
         mHourView = (TextView) view.findViewById(R.id.hours);
         mHourView.setOnKeyListener(keyboardListener);
+        mHourSpaceView = (TextView) view.findViewById(R.id.hour_space);
+        mMinuteSpaceView = (TextView) view.findViewById(R.id.minutes_space);
         mMinuteView = (TextView) view.findViewById(R.id.minutes);
         mMinuteView.setOnKeyListener(keyboardListener);
         mAmPmTextView = (TextView) view.findViewById(R.id.ampm_label);
@@ -198,20 +206,20 @@
                 savedInstanceState.containsKey(KEY_CURRENT_ITEM_SHOWING)) {
             currentItemShowing = savedInstanceState.getInt(KEY_CURRENT_ITEM_SHOWING);
         }
-        setCurrentItemShowing(currentItemShowing, false, true);
+        setCurrentItemShowing(currentItemShowing, false, true, true);
         mTimePicker.invalidate();
 
         mHourView.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                setCurrentItemShowing(HOUR_INDEX, true, true);
+                setCurrentItemShowing(HOUR_INDEX, true, false, true);
                 mTimePicker.tryVibrate();
             }
         });
         mMinuteView.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                setCurrentItemShowing(MINUTE_INDEX, true, true);
+                setCurrentItemShowing(MINUTE_INDEX, true, false, true);
                 mTimePicker.tryVibrate();
             }
         });
@@ -320,7 +328,7 @@
             setHour(newValue, false);
             String announcement = String.format("%d", newValue);
             if (mAllowAutoAdvance && autoAdvance) {
-                setCurrentItemShowing(MINUTE_INDEX, true, false);
+                setCurrentItemShowing(MINUTE_INDEX, true, true, false);
                 announcement += ". " + mSelectMinutes;
             }
             tryAccessibilityAnnounce(announcement);
@@ -350,6 +358,7 @@
 
         CharSequence text = String.format(format, value);
         mHourView.setText(text);
+        mHourSpaceView.setText(text);
         if (announce) {
             tryAccessibilityAnnounce(text);
         }
@@ -362,12 +371,15 @@
         CharSequence text = String.format(Locale.getDefault(), "%02d", value);
         tryAccessibilityAnnounce(text);
         mMinuteView.setText(text);
+        mMinuteSpaceView.setText(text);
     }
 
     // Show either Hours or Minutes.
-    private void setCurrentItemShowing(int index, boolean animate, boolean announce) {
-        mTimePicker.setCurrentItemShowing(index, animate);
+    private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
+            boolean announce) {
+        mTimePicker.setCurrentItemShowing(index, animateCircle);
 
+        TextView labelToAnimate;
         if (index == HOUR_INDEX) {
             int hours = mTimePicker.getHours();
             if (!mIs24HourMode) {
@@ -377,18 +389,26 @@
             if (announce) {
                 tryAccessibilityAnnounce(mSelectHours);
             }
+            labelToAnimate = mHourView;
         } else {
             int minutes = mTimePicker.getMinutes();
             mTimePicker.setContentDescription(mMinutePickerDescription+": "+minutes);
             if (announce) {
                 tryAccessibilityAnnounce(mSelectMinutes);
             }
+            labelToAnimate = mMinuteView;
         }
 
         int hourColor = (index == HOUR_INDEX)? mBlue : mBlack;
         int minuteColor = (index == MINUTE_INDEX)? mBlue : mBlack;
         mHourView.setTextColor(hourColor);
         mMinuteView.setTextColor(minuteColor);
+
+        ObjectAnimator pulseAnimator = Utils.getPulseAnimator(labelToAnimate);
+        if (delayLabelAnimate) {
+            pulseAnimator.setStartDelay(PULSE_ANIMATOR_DELAY);
+        }
+        pulseAnimator.start();
     }
 
     /**
@@ -581,7 +601,7 @@
             if (!mIs24HourMode) {
                 updateAmPmDisplay(hour < 12? AM : PM);
             }
-            setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true);
+            setCurrentItemShowing(mTimePicker.getCurrentItemShowing(), true, true, true);
             mDoneButton.setEnabled(true);
         } else {
             Boolean[] enteredZeros = {false, false};