ViewPager data set change fixes

Clean up ViewPager data set change operations a bit and update
FragmentPagerAdapter to allow for items changing positions within the
adapter during a data set change.

Bug 6347192

Change-Id: Ib0aaa31190fca561f9b2c6c94f37e0b5eb1d1e90
diff --git a/v13/java/android/support/v13/app/FragmentPagerAdapter.java b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
index dcd6f22..60aa5d0 100644
--- a/v13/java/android/support/v13/app/FragmentPagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
@@ -89,17 +89,19 @@
             mCurTransaction = mFragmentManager.beginTransaction();
         }
 
+        final long itemId = getItemId(position);
+
         // Do we already have this fragment?
-        String name = makeFragmentName(container.getId(), position);
+        String name = makeFragmentName(container.getId(), itemId);
         Fragment fragment = mFragmentManager.findFragmentByTag(name);
         if (fragment != null) {
-            if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
+            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
             mCurTransaction.attach(fragment);
         } else {
             fragment = getItem(position);
-            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
             mCurTransaction.add(container.getId(), fragment,
-                    makeFragmentName(container.getId(), position));
+                    makeFragmentName(container.getId(), itemId));
         }
         if (fragment != mCurrentPrimaryItem) {
             FragmentCompat.setMenuVisibility(fragment, false);
@@ -114,7 +116,7 @@
         if (mCurTransaction == null) {
             mCurTransaction = mFragmentManager.beginTransaction();
         }
-        if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
+        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                 + " v=" + ((Fragment)object).getView());
         mCurTransaction.detach((Fragment)object);
     }
@@ -158,7 +160,20 @@
     public void restoreState(Parcelable state, ClassLoader loader) {
     }
 
-    private static String makeFragmentName(int viewId, int index) {
-        return "android:switcher:" + viewId + ":" + index;
+    /**
+     * Return a unique identifier for the item at the given position.
+     *
+     * <p>The default implementation returns the given position.
+     * Subclasses should override this method if the positions of items can change.</p>
+     *
+     * @param position Position within this adapter
+     * @return Unique identifier for the item at position
+     */
+    public long getItemId(int position) {
+        return position;
+    }
+
+    private static String makeFragmentName(int viewId, long id) {
+        return "android:switcher:" + viewId + ":" + id;
     }
 }
\ No newline at end of file
diff --git a/v4/java/android/support/v4/app/FragmentPagerAdapter.java b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
index 6a5ecc3..01ca411 100644
--- a/v4/java/android/support/v4/app/FragmentPagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
@@ -85,17 +85,19 @@
             mCurTransaction = mFragmentManager.beginTransaction();
         }
 
+        final long itemId = getItemId(position);
+
         // Do we already have this fragment?
-        String name = makeFragmentName(container.getId(), position);
+        String name = makeFragmentName(container.getId(), itemId);
         Fragment fragment = mFragmentManager.findFragmentByTag(name);
         if (fragment != null) {
-            if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment);
+            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
             mCurTransaction.attach(fragment);
         } else {
             fragment = getItem(position);
-            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
             mCurTransaction.add(container.getId(), fragment,
-                    makeFragmentName(container.getId(), position));
+                    makeFragmentName(container.getId(), itemId));
         }
         if (fragment != mCurrentPrimaryItem) {
             fragment.setMenuVisibility(false);
@@ -110,7 +112,7 @@
         if (mCurTransaction == null) {
             mCurTransaction = mFragmentManager.beginTransaction();
         }
-        if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object
+        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                 + " v=" + ((Fragment)object).getView());
         mCurTransaction.detach((Fragment)object);
     }
@@ -154,7 +156,20 @@
     public void restoreState(Parcelable state, ClassLoader loader) {
     }
 
-    private static String makeFragmentName(int viewId, int index) {
-        return "android:switcher:" + viewId + ":" + index;
+    /**
+     * Return a unique identifier for the item at the given position.
+     *
+     * <p>The default implementation returns the given position.
+     * Subclasses should override this method if the positions of items can change.</p>
+     *
+     * @param position Position within this adapter
+     * @return Unique identifier for the item at position
+     */
+    public long getItemId(int position) {
+        return position;
+    }
+
+    private static String makeFragmentName(int viewId, long id) {
+        return "android:switcher:" + viewId + ":" + id;
     }
 }
diff --git a/v4/java/android/support/v4/view/ViewPager.java b/v4/java/android/support/v4/view/ViewPager.java
index 5220d30..73b16b1 100644
--- a/v4/java/android/support/v4/view/ViewPager.java
+++ b/v4/java/android/support/v4/view/ViewPager.java
@@ -685,7 +685,8 @@
     void dataSetChanged() {
         // This method only gets called if our observer is attached, so mAdapter is non-null.
 
-        boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
+        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
+                mItems.size() < mAdapter.getCount();
         int newCurrItem = -1;
 
         boolean isUpdating = false;
@@ -712,6 +713,7 @@
                 if (mCurItem == ii.position) {
                     // Keep the current item in the valid range
                     newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
+                    needPopulate = true;
                 }
                 continue;
             }
@@ -743,15 +745,8 @@
                     lp.widthFactor = 0.f;
                 }
             }
-        }
 
-        if (newCurrItem >= 0) {
-            // TODO This currently causes a jump.
-            setCurrentItemInternal(newCurrItem, false, true);
-            needPopulate = true;
-        }
-        if (needPopulate) {
-            populate();
+            populate(newCurrItem);
             requestLayout();
         }
     }