Adding workaround for page indicators to support many pages.

- Tweaking assets until we get something proper

Change-Id: Ie07946acb529ff747d76896ff38837f8db6a7258
diff --git a/res/drawable-hdpi/active_page.png b/res/drawable-hdpi/active_page.png
index ce2d5b1..58aa7f9 100644
--- a/res/drawable-hdpi/active_page.png
+++ b/res/drawable-hdpi/active_page.png
Binary files differ
diff --git a/res/drawable-hdpi/inactive_page.png b/res/drawable-hdpi/inactive_page.png
index 2186f51..b70d9f4 100644
--- a/res/drawable-hdpi/inactive_page.png
+++ b/res/drawable-hdpi/inactive_page.png
Binary files differ
diff --git a/res/drawable-mdpi/active_page.png b/res/drawable-mdpi/active_page.png
index 9e23ecc..296a9a6 100644
--- a/res/drawable-mdpi/active_page.png
+++ b/res/drawable-mdpi/active_page.png
Binary files differ
diff --git a/res/drawable-mdpi/inactive_page.png b/res/drawable-mdpi/inactive_page.png
index 9468a62..2225d25 100644
--- a/res/drawable-mdpi/inactive_page.png
+++ b/res/drawable-mdpi/inactive_page.png
Binary files differ
diff --git a/res/drawable-xhdpi/active_page.png b/res/drawable-xhdpi/active_page.png
index c43e67c..a1cfc35 100644
--- a/res/drawable-xhdpi/active_page.png
+++ b/res/drawable-xhdpi/active_page.png
Binary files differ
diff --git a/res/drawable-xhdpi/inactive_page.png b/res/drawable-xhdpi/inactive_page.png
index ae3f988..177b253 100644
--- a/res/drawable-xhdpi/inactive_page.png
+++ b/res/drawable-xhdpi/inactive_page.png
Binary files differ
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
index 8aae752..14eff75 100644
--- a/res/layout/page_indicator.xml
+++ b/res/layout/page_indicator.xml
@@ -16,5 +16,6 @@
 <com.android.launcher3.PageIndicator
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
-    android:animateLayoutChanges="true">
+    android:animateLayoutChanges="true"
+    launcher:windowSize="@integer/config_maxNumberOfPageIndicatorsToShow">
 </com.android.launcher3.PageIndicator>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 1545083..09b8804 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -32,6 +32,13 @@
         <attr name="drawIdentifier" format="string"  />
     </declare-styleable>
 
+    <!-- Page Indicator specific attributes. These attributes are used to customize
+         the cling in XML files. -->
+    <declare-styleable name="PageIndicator">
+        <!-- Used to identify how to draw the cling bg -->
+        <attr name="windowSize" format="integer"  />
+    </declare-styleable>
+
     <!-- Workspace specific attributes. These attributes are used to customize
          the workspace in XML files. -->
     <declare-styleable name="Workspace">
diff --git a/res/values/config.xml b/res/values/config.xml
index 6aaca1a..cc3d4f0 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -3,6 +3,9 @@
     <bool name="is_large_screen">false</bool>
     <bool name="allow_rotation">false</bool>
 
+    <!-- Max number of page indicators to show -->
+    <integer name="config_maxNumberOfPageIndicatorsToShow">21</integer>
+
 <!-- DragController -->
     <integer name="config_flingToDeleteMinVelocity">-1500</integer>
 
diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/PageIndicator.java
index ecaa8f5..d7778fb 100644
--- a/src/com/android/launcher3/PageIndicator.java
+++ b/src/com/android/launcher3/PageIndicator.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.LayoutTransition;
+import android.animation.TimeInterpolator;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -37,8 +38,22 @@
 public class PageIndicator extends LinearLayout {
     @SuppressWarnings("unused")
     private static final String TAG = "PageIndicator";
+    // Want this to look good? Keep it odd
+    private static final boolean MODULATE_ALPHA_ENABLED = false;
 
     private LayoutInflater mLayoutInflater;
+    private int[] mWindowRange = new int[2];
+    private int mMaxWindowSize;
+
+    private ArrayList<PageIndicatorMarker> mMarkers =
+            new ArrayList<PageIndicatorMarker>();
+    private int mActiveMarkerIndex;
+
+    private TimeInterpolator mAlphaInterpolator = new TimeInterpolator() {
+        public float getInterpolation(float t) {
+            return t;
+        }
+    };
 
     public PageIndicator(Context context) {
         this(context, null);
@@ -50,16 +65,110 @@
 
     public PageIndicator(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.PageIndicator, defStyle, 0);
+        mMaxWindowSize = a.getInteger(R.styleable.PageIndicator_windowSize, 15);
+        mWindowRange[0] = 0;
+        mWindowRange[1] = 0;
         mLayoutInflater = LayoutInflater.from(context);
+        a.recycle();
 
+        // Set the layout transition properties
         LayoutTransition transition = getLayoutTransition();
-        transition.setDuration(250);
+        transition.setDuration(175);
+    }
+
+    private void enableLayoutTransitions() {
+        LayoutTransition transition = getLayoutTransition();
+        transition.enableTransitionType(LayoutTransition.APPEARING);
+        transition.enableTransitionType(LayoutTransition.DISAPPEARING);
+        transition.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        transition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+    }
+
+    private void disableLayoutTransitions() {
+        LayoutTransition transition = getLayoutTransition();
+        transition.disableTransitionType(LayoutTransition.APPEARING);
+        transition.disableTransitionType(LayoutTransition.DISAPPEARING);
+        transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+    }
+
+    void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) {
+        if (activeIndex < 0) {
+            new Throwable().printStackTrace();
+        }
+        int windowSize = Math.min(mMarkers.size(), mMaxWindowSize);
+        int hWindowSize = (int) windowSize / 2;
+        float hfWindowSize = windowSize / 2f;
+        int windowStart = Math.max(0, activeIndex - hWindowSize);
+        int windowEnd = Math.min(mMarkers.size(), windowStart + mMaxWindowSize);
+        windowStart = windowEnd - Math.min(mMarkers.size(), windowSize);
+        int windowMid = windowStart + (windowEnd - windowStart) / 2;
+        boolean windowAtStart = (windowStart == 0);
+        boolean windowAtEnd = (windowEnd == mMarkers.size());
+        boolean windowMoved = (mWindowRange[0] != windowStart) ||
+                (mWindowRange[1] != windowEnd);
+
+        if (!allowAnimations) {
+            disableLayoutTransitions();
+        }
+
+        // Remove all the previous children that are no longer in the window
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i);
+            int markerIndex = mMarkers.indexOf(marker);
+            if (markerIndex < windowStart || markerIndex >= windowEnd) {
+                removeView(marker);
+            }
+        }
+
+        // Add all the new children that belong in the window
+        for (int i = 0; i < mMarkers.size(); ++i) {
+            PageIndicatorMarker marker = (PageIndicatorMarker) mMarkers.get(i);
+            if (windowStart <= i && i < windowEnd) {
+                if (indexOfChild(marker) < 0) {
+                    addView(marker, i - windowStart);
+                }
+                if (i == activeIndex) {
+                    marker.activate(windowMoved);
+                } else {
+                    marker.inactivate(windowMoved);
+                }
+            } else {
+                marker.inactivate(true);
+            }
+
+            if (MODULATE_ALPHA_ENABLED) {
+                // Update the marker's alpha
+                float alpha = 1f;
+                if (mMarkers.size() > windowSize) {
+                    if ((windowAtStart && i > hWindowSize) ||
+                        (windowAtEnd && i < (mMarkers.size() - hWindowSize)) ||
+                        (!windowAtStart && !windowAtEnd)) {
+                        alpha = 1f - Math.abs((i - windowMid) / hfWindowSize);
+                    }
+                }
+                marker.animate().alpha(alpha).setDuration(500).start();
+            }
+        }
+
+        if (!allowAnimations) {
+            enableLayoutTransitions();
+        }
+
+        mWindowRange[0] = windowStart;
+        mWindowRange[1] = windowEnd;
     }
 
     void addMarker(int index) {
-        index = Math.max(0, Math.min(index, getChildCount()));
-        View marker = mLayoutInflater.inflate(R.layout.page_indicator_marker, this, false);
-        addView(marker, index);
+        index = Math.max(0, Math.min(index, mMarkers.size()));
+
+        int mLayoutId = R.layout.page_indicator_marker;
+        PageIndicatorMarker marker =
+            (PageIndicatorMarker) mLayoutInflater.inflate(mLayoutId, this, false);
+        mMarkers.add(index, marker);
+        offsetWindowCenterTo(mActiveMarkerIndex, true);
     }
     void addMarkers(int count) {
         for (int i = 0; i < count; ++i) {
@@ -68,25 +177,37 @@
     }
 
     void removeMarker(int index) {
-        if (getChildCount() > 0) {
-            index = Math.max(0, Math.min(index, getChildCount() - 1));
-            removeViewAt(index);
+        if (mMarkers.size() > 0) {
+            index = Math.max(0, Math.min(mMarkers.size() - 1, index));
+            mMarkers.remove(index);
+            offsetWindowCenterTo(mActiveMarkerIndex, true);
         }
     }
     void removeAllMarkers() {
-        while (getChildCount() > 0) {
+        while (mMarkers.size() > 0) {
             removeMarker(Integer.MAX_VALUE);
         }
     }
 
     void setActiveMarker(int index) {
-        for (int i = 0; i < getChildCount(); ++i) {
-            PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i);
-            if (index == i) {
-                marker.activate();
-            } else {
-                marker.inactivate();
-            }
+        // Center the active marker
+        mActiveMarkerIndex = index;
+        offsetWindowCenterTo(index, false);
+    }
+
+    void dumpState(String txt) {
+        System.out.println(txt);
+        System.out.println("\tmMarkers: " + mMarkers.size());
+        for (int i = 0; i < mMarkers.size(); ++i) {
+            PageIndicatorMarker m = mMarkers.get(i);
+            System.out.println("\t\t(" + i + ") " + m);
         }
+        System.out.println("\twindow: [" + mWindowRange[0] + ", " + mWindowRange[1] + "]");
+        System.out.println("\tchildren: " + getChildCount());
+        for (int i = 0; i < getChildCount(); ++i) {
+            PageIndicatorMarker m = (PageIndicatorMarker) getChildAt(i);
+            System.out.println("\t\t(" + i + ") " + m);
+        }
+        System.out.println("\tactive: " + mActiveMarkerIndex);
     }
 }
diff --git a/src/com/android/launcher3/PageIndicatorMarker.java b/src/com/android/launcher3/PageIndicatorMarker.java
index 852ee51..f64c14f 100644
--- a/src/com/android/launcher3/PageIndicatorMarker.java
+++ b/src/com/android/launcher3/PageIndicatorMarker.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.animation.AnimatorListenerAdapter;
 import android.animation.LayoutTransition;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -29,10 +30,11 @@
     @SuppressWarnings("unused")
     private static final String TAG = "PageIndicator";
 
-    private static final int MARKER_FADE_DURATION = 150;
+    private static final int MARKER_FADE_DURATION = 175;
 
     private View mActiveMarker;
     private View mInactiveMarker;
+    private boolean mIsActive = false;
 
     public PageIndicatorMarker(Context context) {
         this(context, null);
@@ -51,16 +53,36 @@
         mInactiveMarker = findViewById(R.id.inactive);
     }
 
-    public void activate() {
-        mActiveMarker.animate().alpha(1f)
-                .setDuration(MARKER_FADE_DURATION).start();
-        mInactiveMarker.animate().alpha(0f)
-                .setDuration(MARKER_FADE_DURATION).start();
+    void activate(boolean immediate) {
+        if (immediate) {
+            mActiveMarker.animate().cancel();
+            mActiveMarker.setAlpha(1f);
+            mInactiveMarker.animate().cancel();
+            mInactiveMarker.setAlpha(0f);
+        } else {
+            mActiveMarker.animate().alpha(1f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+            mInactiveMarker.animate().alpha(0f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+        }
+        mIsActive = true;
     }
-    public void inactivate() {
-        mInactiveMarker.animate().alpha(1f)
-                .setDuration(MARKER_FADE_DURATION).start();
-        mActiveMarker.animate().alpha(0f)
-                .setDuration(MARKER_FADE_DURATION).start();
+    void inactivate(boolean immediate) {
+        if (immediate) {
+            mInactiveMarker.animate().cancel();
+            mInactiveMarker.setAlpha(1f);
+            mActiveMarker.animate().cancel();
+            mActiveMarker.setAlpha(0f);
+        } else {
+            mInactiveMarker.animate().alpha(1f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+            mActiveMarker.animate().alpha(0f)
+                    .setDuration(MARKER_FADE_DURATION).start();
+        }
+        mIsActive = false;
+    }
+
+    boolean isActive() {
+        return mIsActive;
     }
 }