DO NOT MERGE.  Integrate support work from master.

First submit of FragmentPager class.

This provides an easy way to build a UI where the user can
swipe left or right through its elements.  The elements are
implemented as fragments, and the class takes care of managing
those fragments as the user navigates through it.

This implementation also introduces a new FragmentManager
concept of a "detached" fragment -- basically a way for you
to put a fragment in the same state as when it is on the
back stack, where the framework is managing its current state
but it is no longer actively running.

Also required the introduction of new compatibility code for
MotionEvent and VelocityTracker for accessing multi-touch data.

Tweak view save/restore state so it will play well with list views.

We need to restore the state *after* the adapter has been set;
setting the adapter clears the state.  To do this, we move the
state restore from immediately after the view is created to after
we call Fragment.onActivityAttached().

Also introduced a new Fragment.onViewCreated() callback which is
nice for fragments that want to do some setup after onCreateView()
but allow for subclasses to override that method.  (ListFragment
I'm looking at you.)
diff --git a/v4/Android.mk b/v4/Android.mk
index c65ca42..c6c1d82 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -17,6 +17,24 @@
 # Note: the source code is in java/, not src/, because this code is also part of
 # the framework library, and build/core/pathmap.mk expects a java/ subdirectory.
 
+# A helper sub-library that makes direct use of Eclair APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-eclair
+LOCAL_SDK_VERSION := 5
+LOCAL_SRC_FILES := $(call all-java-files-under, eclair)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+# A helper sub-library that makes direct use of Froyo APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-froyo
+LOCAL_SDK_VERSION := 8
+LOCAL_SRC_FILES := $(call all-java-files-under, froyo)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 # A helper sub-library that makes direct use of Honeycomb APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4-honeycomb
@@ -31,7 +49,10 @@
 LOCAL_MODULE := android-support-v4
 LOCAL_SDK_VERSION := 4
 LOCAL_SRC_FILES := $(call all-java-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-honeycomb
+LOCAL_STATIC_JAVA_LIBRARIES += \
+        android-support-v4-eclair \
+        android-support-v4-froyo \
+        android-support-v4-honeycomb
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Include this library in the build server's output directory
diff --git a/v4/eclair/android/support/v4/view/MotionEventCompatEclair.java b/v4/eclair/android/support/v4/view/MotionEventCompatEclair.java
new file mode 100644
index 0000000..dedb1d1
--- /dev/null
+++ b/v4/eclair/android/support/v4/view/MotionEventCompatEclair.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.MotionEvent;
+
+/**
+ * Implementation of motion event compatibility that can call Eclair APIs.
+ */
+class MotionEventCompatEclair {
+    public static int findPointerIndex(MotionEvent event, int pointerId) {
+        return event.findPointerIndex(pointerId);
+    }
+    public static int getPointerId(MotionEvent event, int pointerIndex) {
+        return event.getPointerId(pointerIndex);
+    }
+    public static float getX(MotionEvent event, int pointerIndex) {
+        return event.getX(pointerIndex);
+    }
+    public static float getY(MotionEvent event, int pointerIndex) {
+        return event.getY(pointerIndex);
+    }
+}
diff --git a/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java b/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java
new file mode 100644
index 0000000..e97a2a2
--- /dev/null
+++ b/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.ViewConfiguration;
+
+/**
+ * Implementation of menu compatibility that can call Honeycomb APIs.
+ */
+class ViewConfigurationCompatFroyo {
+    public static int getScaledPagingTouchSlop(ViewConfiguration config) {
+        return config.getScaledPagingTouchSlop();
+    }
+}
diff --git a/v4/honeycomb/android/support/v4/view/VelocityTrackerCompatHoneycomb.java b/v4/honeycomb/android/support/v4/view/VelocityTrackerCompatHoneycomb.java
new file mode 100644
index 0000000..4f9d326
--- /dev/null
+++ b/v4/honeycomb/android/support/v4/view/VelocityTrackerCompatHoneycomb.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.VelocityTracker;
+
+/**
+ * Implementation of velocity tracker compatibility that can call Honeycomb APIs.
+ */
+class VelocityTrackerCompatHoneycomb {
+    public static float getXVelocity(VelocityTracker tracker, int pointerId) {
+        return tracker.getXVelocity(pointerId);
+    }
+    public static float getYVelocity(VelocityTracker tracker, int pointerId) {
+        return tracker.getYVelocity(pointerId);
+    }
+}
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index 769c632..49e4351 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -169,6 +169,8 @@
     static final int OP_REMOVE = 3;
     static final int OP_HIDE = 4;
     static final int OP_SHOW = 5;
+    static final int OP_DETACH = 6;
+    static final int OP_ATTACH = 7;
 
     static final class Op {
         Op next;
@@ -401,6 +403,32 @@
         return this;
     }
 
+    public FragmentTransaction detach(Fragment fragment) {
+        //if (fragment.mImmediateActivity == null) {
+        //    throw new IllegalStateException("Fragment not added: " + fragment);
+        //}
+
+        Op op = new Op();
+        op.cmd = OP_DETACH;
+        op.fragment = fragment;
+        addOp(op);
+
+        return this;
+    }
+
+    public FragmentTransaction attach(Fragment fragment) {
+        //if (fragment.mImmediateActivity == null) {
+        //    throw new IllegalStateException("Fragment not added: " + fragment);
+        //}
+
+        Op op = new Op();
+        op.cmd = OP_ATTACH;
+        op.fragment = fragment;
+        addOp(op);
+
+        return this;
+    }
+
     public FragmentTransaction setCustomAnimations(int enter, int exit) {
         mEnterAnim = enter;
         mExitAnim = exit;
@@ -567,6 +595,16 @@
                     f.mNextAnim = op.enterAnim;
                     mManager.showFragment(f, mTransition, mTransitionStyle);
                 } break;
+                case OP_DETACH: {
+                    Fragment f = op.fragment;
+                    f.mNextAnim = op.exitAnim;
+                    mManager.detachFragment(f, mTransition, mTransitionStyle);
+                } break;
+                case OP_ATTACH: {
+                    Fragment f = op.fragment;
+                    f.mNextAnim = op.enterAnim;
+                    mManager.attachFragment(f, mTransition, mTransitionStyle);
+                } break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
@@ -627,6 +665,16 @@
                     mManager.hideFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
                 } break;
+                case OP_DETACH: {
+                    Fragment f = op.fragment;
+                    mManager.attachFragment(f,
+                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                } break;
+                case OP_ATTACH: {
+                    Fragment f = op.fragment;
+                    mManager.detachFragment(f,
+                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                } break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 5f018d3..884c12c 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -52,6 +52,7 @@
     final int mContainerId;
     final String mTag;
     final boolean mRetainInstance;
+    final boolean mDetached;
     final Bundle mArguments;
     
     Bundle mSavedFragmentState;
@@ -66,6 +67,7 @@
         mContainerId = frag.mContainerId;
         mTag = frag.mTag;
         mRetainInstance = frag.mRetainInstance;
+        mDetached = frag.mDetached;
         mArguments = frag.mArguments;
     }
     
@@ -77,6 +79,7 @@
         mContainerId = in.readInt();
         mTag = in.readString();
         mRetainInstance = in.readInt() != 0;
+        mDetached = in.readInt() != 0;
         mArguments = in.readBundle();
         mSavedFragmentState = in.readBundle();
     }
@@ -103,6 +106,7 @@
         mInstance.mContainerId = mContainerId;
         mInstance.mTag = mTag;
         mInstance.mRetainInstance = mRetainInstance;
+        mInstance.mDetached = mDetached;
         mInstance.mFragmentManager = activity.mFragments;
         
         return mInstance;
@@ -120,6 +124,7 @@
         dest.writeInt(mContainerId);
         dest.writeString(mTag);
         dest.writeInt(mRetainInstance ? 1 : 0);
+        dest.writeInt(mDetached ? 1 : 0);
         dest.writeBundle(mArguments);
         dest.writeBundle(mSavedFragmentState);
     }
@@ -150,8 +155,9 @@
     static final int INITIALIZING = 0;     // Not yet created.
     static final int CREATED = 1;          // Created.
     static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
-    static final int STARTED = 3;          // Created and started, not resumed.
-    static final int RESUMED = 4;          // Created started and resumed.
+    static final int STOPPED = 3;          // Fully created, not started.
+    static final int STARTED = 4;          // Created and started, not resumed.
+    static final int RESUMED = 5;          // Created started and resumed.
     
     int mState = INITIALIZING;
     
@@ -233,6 +239,9 @@
     // from the user.
     boolean mHidden;
     
+    // Set to true when the app has requested that this fragment be deactivated.
+    boolean mDetached;
+
     // If set this fragment would like its instance retained across
     // configuration changes.
     boolean mRetainInstance;
@@ -343,23 +352,27 @@
         }
     }
     
-    void restoreViewState() {
+    final void restoreViewState() {
         if (mSavedViewState != null) {
             mInnerView.restoreHierarchyState(mSavedViewState);
             mSavedViewState = null;
         }
     }
     
-    void setIndex(int index) {
+    final void setIndex(int index) {
         mIndex = index;
         mWho = "android:fragment:" + mIndex;
-   }
+    }
     
-    void clearIndex() {
+    final void clearIndex() {
         mIndex = -1;
         mWho = null;
     }
     
+    final boolean isInBackStack() {
+        return mBackStackNesting > 0;
+    }
+
     /**
      * Subclasses can not override equals().
      */
@@ -794,6 +807,19 @@
     }
     
     /**
+     * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
+     * has returned, but before any saved state has been restored in to the view.
+     * This gives subclasses a chance to initialize themselves once
+     * they know their view hierarchy has been completely created.  The fragment's
+     * view hierarchy is not however attached to its parent at this point.
+     * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     * @param savedInstanceState If non-null, this fragment is being re-constructed
+     * from a previous saved state as given here.
+     */
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+    }
+
+    /**
      * Get the root view for the fragment's layout (the one returned by {@link #onCreateView}),
      * if provided.
      * 
@@ -1103,6 +1129,7 @@
                 writer.print(" mFromLayout="); writer.print(mFromLayout);
                 writer.print(" mInLayout="); writer.println(mInLayout);
         writer.print(prefix); writer.print("mHidden="); writer.print(mHidden);
+                writer.print(" mDetached="); writer.print(mDetached);
                 writer.print(" mRetainInstance="); writer.print(mRetainInstance);
                 writer.print(" mRetaining="); writer.print(mRetaining);
                 writer.print(" mHasMenu="); writer.println(mHasMenu);
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index c376038..9aecd8e 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -774,15 +774,15 @@
                         if (f.mView != null) {
                             f.mInnerView = f.mView;
                             f.mView = NoSaveStateFrameLayout.wrap(f.mView);
-                            f.restoreViewState();
                             if (f.mHidden) f.mView.setVisibility(View.GONE); 
+                            f.onViewCreated(f.mView, f.mSavedFragmentState);
                         } else {
                             f.mInnerView = null;
                         }
                     }
                 case Fragment.CREATED:
                     if (newState > Fragment.CREATED) {
-                        if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f);
+                        if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                         if (!f.mFromLayout) {
                             ViewGroup container = null;
                             if (f.mContainerId != 0) {
@@ -806,9 +806,9 @@
                                         f.mView.startAnimation(anim);
                                     }
                                     container.addView(f.mView);
-                                    f.restoreViewState();
                                 }
                                 if (f.mHidden) f.mView.setVisibility(View.GONE); 
+                                f.onViewCreated(f.mView, f.mSavedFragmentState);
                             } else {
                                 f.mInnerView = null;
                             }
@@ -820,10 +820,14 @@
                             throw new SuperNotCalledException("Fragment " + f
                                     + " did not call through to super.onActivityCreated()");
                         }
+                        if (f.mView != null) {
+                            f.restoreViewState();
+                        }
                         f.mSavedFragmentState = null;
                     }
                 case Fragment.ACTIVITY_CREATED:
-                    if (newState > Fragment.ACTIVITY_CREATED) {
+                case Fragment.STOPPED:
+                    if (newState > Fragment.STOPPED) {
                         if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                         f.mCalled = false;
                         f.onStart();
@@ -867,9 +871,10 @@
                                     + " did not call through to super.onStop()");
                         }
                     }
+                case Fragment.STOPPED:
                 case Fragment.ACTIVITY_CREATED:
                     if (newState < Fragment.ACTIVITY_CREATED) {
-                        if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
+                        if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
                         if (f.mView != null) {
                             // Need to save the current view state if not
                             // done already.
@@ -1036,32 +1041,36 @@
         if (mAdded == null) {
             mAdded = new ArrayList<Fragment>();
         }
-        mAdded.add(fragment);
-        makeActive(fragment);
         if (DEBUG) Log.v(TAG, "add: " + fragment);
-        fragment.mAdded = true;
-        fragment.mRemoving = false;
-        if (fragment.mHasMenu) {
-            mNeedMenuInvalidate = true;
-        }
-        if (moveToStateNow) {
-            moveToState(fragment);
+        makeActive(fragment);
+        if (!fragment.mDetached) {
+            mAdded.add(fragment);
+            fragment.mAdded = true;
+            fragment.mRemoving = false;
+            if (fragment.mHasMenu) {
+                mNeedMenuInvalidate = true;
+            }
+            if (moveToStateNow) {
+                moveToState(fragment);
+            }
         }
     }
     
     public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
         if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
-        mAdded.remove(fragment);
-        final boolean inactive = fragment.mBackStackNesting <= 0;
-        if (fragment.mHasMenu) {
-            mNeedMenuInvalidate = true;
-        }
-        fragment.mAdded = false;
-        fragment.mRemoving = true;
-        moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
-                transition, transitionStyle);
-        if (inactive) {
-            makeInactive(fragment);
+        final boolean inactive = !fragment.isInBackStack();
+        if (!fragment.mDetached || inactive) {
+            mAdded.remove(fragment);
+            if (fragment.mHasMenu) {
+                mNeedMenuInvalidate = true;
+            }
+            fragment.mAdded = false;
+            fragment.mRemoving = true;
+            moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
+                    transition, transitionStyle);
+            if (inactive) {
+                makeInactive(fragment);
+            }
         }
     }
     
@@ -1103,6 +1112,39 @@
         }
     }
     
+    public void detachFragment(Fragment fragment, int transition, int transitionStyle) {
+        if (DEBUG) Log.v(TAG, "detach: " + fragment);
+        if (!fragment.mDetached) {
+            fragment.mDetached = true;
+            if (fragment.mAdded) {
+                // We are not already in back stack, so need to remove the fragment.
+                mAdded.remove(fragment);
+                if (fragment.mHasMenu) {
+                    mNeedMenuInvalidate = true;
+                }
+                fragment.mAdded = false;
+                fragment.mRemoving = true;
+                moveToState(fragment, Fragment.CREATED, transition, transitionStyle);
+            }
+        }
+    }
+
+    public void attachFragment(Fragment fragment, int transition, int transitionStyle) {
+        if (DEBUG) Log.v(TAG, "attach: " + fragment);
+        if (fragment.mDetached) {
+            fragment.mDetached = false;
+            if (!fragment.mAdded) {
+                mAdded.add(fragment);
+                fragment.mAdded = true;
+                fragment.mRemoving = false;
+                if (fragment.mHasMenu) {
+                    mNeedMenuInvalidate = true;
+                }
+                moveToState(fragment, mCurState, transition, transitionStyle);
+            }
+        }
+    }
+
     public Fragment findFragmentById(int id) {
         if (mActive != null) {
             // First look through added fragments.
@@ -1660,7 +1702,7 @@
         // them.
         mStateSaved = true;
 
-        moveToState(Fragment.ACTIVITY_CREATED, false);
+        moveToState(Fragment.STOPPED, false);
     }
     
     public void dispatchReallyStop(boolean retaining) {
diff --git a/v4/java/android/support/v4/app/FragmentPager.java b/v4/java/android/support/v4/app/FragmentPager.java
new file mode 100644
index 0000000..1ff7d5e
--- /dev/null
+++ b/v4/java/android/support/v4/app/FragmentPager.java
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.content.Context;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewConfigurationCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.Scroller;
+
+import java.util.ArrayList;
+
+/**
+ * Layout manager that allows the user to flip left and right
+ * through pages of data.  The pages are implemented as fragments;
+ * you need to implement {@link Adapter} to tell this class the
+ * number of pages you have and the fragment to use for each page.
+ */
+public class FragmentPager extends ViewGroup {
+    /**
+     * Special kind of Adapter for supplying fragments to the FragmentPager.
+     */
+    public interface Adapter {
+        /**
+         * Return the number of fragments available.
+         */
+        int getCount();
+
+        /**
+         * Return the Fragment associated with a specified position.
+         */
+        Fragment getItem(int position);
+
+        FragmentManager getSupportFragmentManager();
+    }
+
+    private static final String TAG = "FragmentPager";
+    private static final boolean DEBUG = false;
+
+    private static final boolean USE_CACHE = false;
+
+    static class ItemInfo {
+        Fragment fragment;
+        int position;
+    }
+
+    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
+
+    private Adapter mAdapter;
+    private int mCurItem;   // Index of currently displayed fragment.
+    private int mRestoredCurItem = -1;
+    private Scroller mScroller;
+
+    private int mChildWidthMeasureSpec;
+    private int mChildHeightMeasureSpec;
+    private boolean mInLayout;
+
+    private boolean mScrollingCacheEnabled;
+
+    private boolean mPopulatePending;
+
+    private boolean mIsBeingDragged;
+    private boolean mIsUnableToDrag;
+    private int mTouchSlop;
+    private float mInitialMotionX;
+    /**
+     * Position of the last motion event.
+     */
+    private float mLastMotionX;
+    private float mLastMotionY;
+    /**
+     * ID of the active pointer. This is used to retain consistency during
+     * drags/flings if multiple pointers are used.
+     */
+    private int mActivePointerId = INVALID_POINTER;
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+    private int mMinimumVelocity;
+    private int mMaximumVelocity;
+
+    public FragmentPager(Context context) {
+        super(context);
+        initFragmentPager();
+    }
+
+    public FragmentPager(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initFragmentPager();
+    }
+
+    void initFragmentPager() {
+        setWillNotDraw(false);
+        mScroller = new Scroller(getContext());
+        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
+        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+    }
+
+    public void setAdapter(Adapter adapter) {
+        mAdapter = adapter;
+
+        if (mAdapter != null) {
+            mPopulatePending = false;
+            if (mRestoredCurItem >= 0) {
+                setCurrentItemInternal(mRestoredCurItem, false, true);
+                mRestoredCurItem = -1;
+            } else {
+                populate();
+            }
+        }
+    }
+
+    public Adapter getAdapter() {
+        return mAdapter;
+    }
+
+    public void setCurrentItem(int item) {
+        mPopulatePending = false;
+        setCurrentItemInternal(item, true, false);
+    }
+
+    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
+        if (mAdapter == null || mAdapter.getCount() <= 0) {
+            setScrollingCacheEnabled(false);
+            return;
+        }
+        if (!always && mCurItem == item && mItems.size() != 0) {
+            setScrollingCacheEnabled(false);
+            return;
+        }
+        if (item < 0) {
+            item = 0;
+        } else if (item >= mAdapter.getCount()) {
+            item = mAdapter.getCount() - 1;
+        }
+        mCurItem = item;
+        populate();
+        if (smoothScroll) {
+            smoothScrollTo(getWidth()*item, 0);
+        } else {
+            completeScroll();
+            scrollTo(getWidth()*item, 0);
+        }
+    }
+
+    /**
+     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+     *
+     * @param dx the number of pixels to scroll by on the X axis
+     * @param dy the number of pixels to scroll by on the Y axis
+     */
+    void smoothScrollTo(int x, int y) {
+        if (getChildCount() == 0) {
+            // Nothing to do.
+            setScrollingCacheEnabled(false);
+            return;
+        }
+        setScrollingCacheEnabled(true);
+        int sx = getScrollX();
+        int sy = getScrollY();
+        mScroller.startScroll(sx, sy, x-sx, y-sy);
+        invalidate();
+    }
+
+    String makeFragmentName(int index) {
+        return "android:switcher:" + getId() + ":" + index;
+    }
+
+    FragmentTransaction addNewItem(FragmentManager fm, int position, int index,
+            FragmentTransaction ft) {
+        ItemInfo ii = new ItemInfo();
+        ii.position = position;
+
+        // Do we already have this fragment?
+        if (ft == null) {
+            ft = fm.beginTransaction();
+        }
+
+        String name = makeFragmentName(ii.position);
+        ii.fragment = fm.findFragmentByTag(name);
+        if (ii.fragment != null) {
+            if (DEBUG) Log.v(TAG, "Attaching item #" + ii.position + ": f=" + ii.fragment);
+            ft.attach(ii.fragment);
+        } else {
+            ii.fragment = mAdapter.getItem(position);
+            if (DEBUG) Log.v(TAG, "Adding item #" + ii.position + ": f=" + ii.fragment);
+            ft.add(getId(), ii.fragment, makeFragmentName(ii.position));
+        }
+
+        if (index < 0) {
+            mItems.add(ii);
+        } else {
+            mItems.add(index, ii);
+        }
+
+        return ft;
+    }
+
+    void populate() {
+        if (mAdapter == null) {
+            return;
+        }
+
+        // Bail now if we are waiting to populate.  This is to hold off
+        // on creating views from the time the user releases their finger to
+        // fling to a new position until we have finished the scroll to
+        // that position, avoiding glitches from happening at that point.
+        if (mPopulatePending) {
+            return;
+        }
+
+        final FragmentManager fm = mAdapter.getSupportFragmentManager();
+
+        final int startIdx = mCurItem > 0 ? mCurItem - 1 : mCurItem;
+        final int endIdx = mCurItem+1;
+
+        int lastIdx = 0;
+        FragmentTransaction ft = null;
+
+        for (int i=0; i<mItems.size(); i++) {
+            ItemInfo ii = mItems.get(i);
+            if (ii.position < startIdx || ii.position > endIdx) {
+                mItems.remove(i);
+                i--;
+                if (ft == null) {
+                    ft = fm.beginTransaction();
+                }
+                if (DEBUG) Log.v(TAG, "Detaching item #" + ii.position + ": f=" + ii.fragment
+                        + " v=" + ii.fragment.getView());
+                ft.detach(ii.fragment);
+            } else {
+                lastIdx = ii.position + 1;
+            }
+        }
+
+        if (mItems.size() > 0) {
+            int newStartIdx = mItems.get(0).position;
+            while (newStartIdx > startIdx) {
+                newStartIdx--;
+                ft = addNewItem(fm, newStartIdx, 0, ft);
+            }
+        }
+
+        while (lastIdx <= endIdx) {
+            if (lastIdx < mAdapter.getCount()) {
+                ft = addNewItem(fm, lastIdx, -1, ft);
+            }
+            lastIdx++;
+        }
+
+        if (ft != null) {
+            ft.commit();
+            fm.executePendingTransactions();
+        }
+    }
+
+    public static class SavedState extends BaseSavedState {
+        int position;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(position);
+        }
+
+        @Override
+        public String toString() {
+            return "FragmentPager.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " position=" + position + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+
+        private SavedState(Parcel in) {
+            super(in);
+            position = in.readInt();
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.position = mCurItem;
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        SavedState ss = (SavedState)state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (mAdapter != null) {
+            setCurrentItemInternal(ss.position, false, true);
+        } else {
+            mRestoredCurItem = ss.position;
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, LayoutParams params) {
+        if (mInLayout) {
+            addViewInLayout(child, index, params);
+            child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+        } else {
+            super.addView(child, index, params);
+        }
+
+        if (USE_CACHE) {
+            if (child.getVisibility() != GONE) {
+                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
+            } else {
+                child.setDrawingCacheEnabled(false);
+            }
+        }
+    }
+
+    ItemInfo infoForChild(View child) {
+        for (int i=0; i<mItems.size(); i++) {
+            ItemInfo ii = mItems.get(i);
+            if (ii.fragment.getView() == child) {
+                return ii;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // For simple implementation, or internal size is always 0.
+        // We depend on the container to specify the layout size of
+        // our view.  We can't really know what it is since we will be
+        // adding and removing different arbitrary views and do not
+        // want the layout to change as this happens.
+        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
+                getDefaultSize(0, heightMeasureSpec));
+
+        // Children are just made to fill our space.
+        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
+                getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
+        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
+                getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
+
+        // Make sure we have created all fragments that we need to have shown.
+        mInLayout = true;
+        mPopulatePending = false;
+        populate();
+        mInLayout = false;
+
+        // Make sure all children have been properly measured.
+        final int size = getChildCount();
+        for (int i = 0; i < size; ++i) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
+		        + ": " + mChildWidthMeasureSpec);
+                child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+            }
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        // Make sure scroll position is set correctly.
+        int scrollPos = mCurItem*w;
+        if (scrollPos != getScrollX()) {
+            completeScroll();
+            scrollTo(scrollPos, getScrollY());
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mInLayout = true;
+        mPopulatePending = false;
+        populate();
+        mInLayout = false;
+
+        final int count = getChildCount();
+        final int width = r-l;
+
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            ItemInfo ii;
+            if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
+                int loff = width*ii.position;
+                int childLeft = getPaddingLeft() + loff;
+                int childTop = getPaddingTop();
+                if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.fragment
+		        + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+		        + "x" + child.getMeasuredHeight());
+                child.layout(childLeft, childTop,
+                        childLeft + child.getMeasuredWidth(),
+                        childTop + child.getMeasuredHeight());
+            }
+        }
+    }
+
+    @Override
+    public void computeScroll() {
+        if (!mScroller.isFinished()) {
+            if (mScroller.computeScrollOffset()) {
+                int oldX = getScrollX();
+                int oldY = getScrollY();
+                int x = mScroller.getCurrX();
+                int y = mScroller.getCurrY();
+
+                if (oldX != x || oldY != y) {
+                    scrollTo(x, y);
+                }
+
+                // Keep on drawing until the animation has finished.
+                invalidate();
+            } else {
+                // Done with scroll, clean up state.
+                completeScroll();
+            }
+        }
+    }
+
+    private void completeScroll() {
+        // Done with scroll, no longer want to cache view drawing.
+        setScrollingCacheEnabled(false);
+        if (!mScroller.isFinished()) {
+            mScroller.abortAnimation();
+            int oldX = getScrollX();
+            int oldY = getScrollY();
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+            if (oldX != x || oldY != y) {
+                scrollTo(x, y);
+            }
+        }
+        mPopulatePending = false;
+        populate();
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        /*
+         * This method JUST determines whether we want to intercept the motion.
+         * If we return true, onMotionEvent will be called and we do the actual
+         * scrolling there.
+         */
+
+        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+
+        // Always take care of the touch gesture being complete.
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+            // Release the drag.
+            if (DEBUG) Log.v(TAG, "Intercept done!");
+            mIsBeingDragged = false;
+            mIsUnableToDrag = false;
+            mActivePointerId = INVALID_POINTER;
+            return false;
+        }
+
+        // Nothing more to do here if we have decided whether or not we
+        // are dragging.
+        if (action != MotionEvent.ACTION_DOWN) {
+            if (mIsBeingDragged) {
+                if (DEBUG) Log.v(TAG, "Intercept returning true!");
+                return true;
+            }
+            if (mIsUnableToDrag) {
+                if (DEBUG) Log.v(TAG, "Intercept returning false!");
+                return false;
+            }
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE: {
+                /*
+                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+                 * whether the user has moved far enough from his original down touch.
+                 */
+
+                /*
+                * Locally do absolute value. mLastMotionY is set to the y value
+                * of the down event.
+                */
+                final int activePointerId = mActivePointerId;
+                if (activePointerId == INVALID_POINTER) {
+                    // If we don't have a valid id, the touch down wasn't on content.
+                    break;
+                }
+
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
+                final float x = MotionEventCompat.getX(ev, pointerIndex);
+                final int xDiff = (int) Math.abs(x - mLastMotionX);
+                final float y = MotionEventCompat.getY(ev, pointerIndex);
+                final int yDiff = (int) Math.abs(y - mLastMotionY);
+                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
+                if (xDiff > mTouchSlop && xDiff > yDiff) {
+                    if (DEBUG) Log.v(TAG, "Starting drag!");
+                    mIsBeingDragged = true;
+                    mLastMotionX = x;
+                    setScrollingCacheEnabled(true);
+                } else {
+                    if (yDiff > mTouchSlop) {
+                        // The finger has moved enough in the vertical
+                        // direction to be counted as a drag...  abort
+                        // any attempt to drag horizontally, to work correctly
+                        // with children that have scrolling containers.
+                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
+                        mIsUnableToDrag = true;
+                    }
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_DOWN: {
+                /*
+                 * Remember location of down touch.
+                 * ACTION_DOWN always refers to pointer index 0.
+                 */
+                mLastMotionX = mInitialMotionX = ev.getX();
+                mLastMotionY = ev.getY();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+
+                completeScroll();
+                mIsBeingDragged = false;
+                mIsUnableToDrag = false;
+
+                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+                        + " mIsBeingDragged=" + mIsBeingDragged
+                        + "mIsUnableToDrag=" + mIsUnableToDrag);
+                break;
+            }
+
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+        }
+
+        /*
+        * The only time we want to intercept motion events is if we are in the
+        * drag mode.
+        */
+        return mIsBeingDragged;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+
+        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+            // Don't handle edge touches immediately -- they may actually belong to one of our
+            // descendants.
+            return false;
+        }
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final int action = ev.getAction();
+
+        switch (action & MotionEventCompat.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                /*
+                 * If being flinged and user touches, stop the fling. isFinished
+                 * will be false if being flinged.
+                 */
+                completeScroll();
+
+                // Remember where the motion event started
+                mLastMotionX = mInitialMotionX = ev.getX();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                break;
+            }
+            case MotionEvent.ACTION_MOVE:
+                if (mIsBeingDragged) {
+                    // Scroll to follow the motion event
+                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
+                            ev, mActivePointerId);
+                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
+                    final int deltaX = (int) (mLastMotionX - x);
+                    mLastMotionX = x;
+                    int scrollX = getScrollX() + deltaX;
+                    if (scrollX < 0) {
+                        scrollX = 0;
+                    } else if (scrollX > ((mAdapter.getCount()-1)*getWidth())) {
+                        scrollX = (mAdapter.getCount()-1)*getWidth();
+                    }
+                    scrollTo(scrollX, getScrollY());
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mIsBeingDragged) {
+                    final VelocityTracker velocityTracker = mVelocityTracker;
+                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                    int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
+                            velocityTracker, mActivePointerId);
+                    mPopulatePending = true;
+                    if ((Math.abs(initialVelocity) > mMinimumVelocity)
+                            || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
+                        if (mLastMotionX > mInitialMotionX) {
+                            setCurrentItemInternal(mCurItem-1, true, true);
+                        } else {
+                            setCurrentItemInternal(mCurItem+1, true, true);
+                        }
+                    } else {
+                        setCurrentItemInternal(mCurItem, true, true);
+                    }
+
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                if (mIsBeingDragged) {
+                    setCurrentItemInternal(mCurItem, true, true);
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
+                }
+                break;
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
+                final int index = MotionEventCompat.getActionIndex(ev);
+                final float x = MotionEventCompat.getX(ev, index);
+                mLastMotionX = x;
+                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+                break;
+            }
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                mLastMotionX = MotionEventCompat.getX(ev,
+                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
+                break;
+        }
+        return true;
+    }
+
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
+        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
+            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+    }
+
+    private void endDrag() {
+        mIsBeingDragged = false;
+        mIsUnableToDrag = false;
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private void setScrollingCacheEnabled(boolean enabled) {
+        if (mScrollingCacheEnabled != enabled) {
+            mScrollingCacheEnabled = enabled;
+            if (USE_CACHE) {
+                final int size = getChildCount();
+                for (int i = 0; i < size; ++i) {
+                    final View child = getChildAt(i);
+                    if (child.getVisibility() != GONE) {
+                        child.setDrawingCacheEnabled(enabled);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/app/FragmentTransaction.java b/v4/java/android/support/v4/app/FragmentTransaction.java
index 1ed8c0d..cccf6a4 100644
--- a/v4/java/android/support/v4/app/FragmentTransaction.java
+++ b/v4/java/android/support/v4/app/FragmentTransaction.java
@@ -107,6 +107,31 @@
     public abstract FragmentTransaction show(Fragment fragment);
 
     /**
+     * Detach the given fragment from the UI.  This is the same state as
+     * when it is put on the back stack: the fragment is removed from
+     * the UI, however its state is still being actively managed by the
+     * fragment manager.  When going into this state its view hierarchy
+     * is destroyed.
+     *
+     * @param fragment The fragment to be detached.
+     *
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public abstract FragmentTransaction detach(Fragment fragment);
+
+    /**
+     * Re-attach a fragment after it had previously been deatched from
+     * the UI with {@link #detach(Fragment)}.  This
+     * causes its view hierarchy to be re-created, attached to the UI,
+     * and displayed.
+     *
+     * @param fragment The fragment to be attached.
+     *
+     * @return Returns the same FragmentTransaction instance.
+     */
+    public abstract FragmentTransaction attach(Fragment fragment);
+
+    /**
      * @return <code>true</code> if this transaction contains no operations,
      * <code>false</code> otherwise.
      */
diff --git a/v4/java/android/support/v4/app/ListFragment.java b/v4/java/android/support/v4/app/ListFragment.java
index e7a5ef3..9451126 100644
--- a/v4/java/android/support/v4/app/ListFragment.java
+++ b/v4/java/android/support/v4/app/ListFragment.java
@@ -105,11 +105,11 @@
     }
 
     /**
-     * Attach to list view once Fragment is ready to run.
+     * Attach to list view once the view hierarchy has been created.
      */
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
         ensureList();
     }
 
diff --git a/v4/java/android/support/v4/view/MotionEventCompat.java b/v4/java/android/support/v4/view/MotionEventCompat.java
new file mode 100644
index 0000000..d2fddb4
--- /dev/null
+++ b/v4/java/android/support/v4/view/MotionEventCompat.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.MotionEvent;
+
+/**
+ * Helper for accessing newer features in MotionEvent.
+ */
+public class MotionEventCompat {
+    /**
+     * Interface for the full API.
+     */
+    interface MotionEventVersionImpl {
+        public int findPointerIndex(MotionEvent event, int pointerId);
+        public int getPointerId(MotionEvent event, int pointerIndex);
+        public float getX(MotionEvent event, int pointerIndex);
+        public float getY(MotionEvent event, int pointerIndex);
+    }
+
+    /**
+     * Interface implementation that doesn't use anything about v4 APIs.
+     */
+    static class BaseMotionEventVersionImpl implements MotionEventVersionImpl {
+        @Override
+        public int findPointerIndex(MotionEvent event, int pointerId) {
+            return -1;
+        }
+        @Override
+        public int getPointerId(MotionEvent event, int pointerIndex) {
+            throw new IndexOutOfBoundsException("Pre-Eclair does not support pointers");
+        }
+        @Override
+        public float getX(MotionEvent event, int pointerIndex) {
+            throw new IndexOutOfBoundsException("Pre-Eclair does not support pointers");
+        }
+        @Override
+        public float getY(MotionEvent event, int pointerIndex) {
+            throw new IndexOutOfBoundsException("Pre-Eclair does not support pointers");
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least v11 APIs.
+     */
+    static class EclairMotionEventVersionImpl implements MotionEventVersionImpl {
+        @Override
+        public int findPointerIndex(MotionEvent event, int pointerId) {
+            return MotionEventCompatEclair.findPointerIndex(event, pointerId);
+        }
+        @Override
+        public int getPointerId(MotionEvent event, int pointerIndex) {
+            return MotionEventCompatEclair.getPointerId(event, pointerIndex);
+        }
+        @Override
+        public float getX(MotionEvent event, int pointerIndex) {
+            return MotionEventCompatEclair.getX(event, pointerIndex);
+        }
+        @Override
+        public float getY(MotionEvent event, int pointerIndex) {
+            return MotionEventCompatEclair.getY(event, pointerIndex);
+        }
+    }
+
+    /**
+     * Select the correct implementation to use for the current platform.
+     */
+    static final MotionEventVersionImpl IMPL;
+    static {
+        if (android.os.Build.VERSION.SDK_INT >= 5) {
+            IMPL = new EclairMotionEventVersionImpl();
+        } else {
+            IMPL = new BaseMotionEventVersionImpl();
+        }
+    }
+
+    // -------------------------------------------------------------------
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_MASK}.
+     */
+    public static final int ACTION_MASK = 0xff;
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_POINTER_DOWN}.
+     */
+    public static final int ACTION_POINTER_DOWN = 5;
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_POINTER_UP}.
+     */
+    public static final int ACTION_POINTER_UP = 6;
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_HOVER_MOVE}.
+     */
+    public static final int ACTION_HOVER_MOVE = 7;
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_SCROLL}.
+     */
+    public static final int ACTION_SCROLL = 8;
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_POINTER_INDEX_MASK}.
+     */
+    public static final int ACTION_POINTER_INDEX_MASK  = 0xff00;
+
+    /**
+     * Synonym for {@link MotionEvent#ACTION_POINTER_INDEX_SHIFT}.
+     */
+    public static final int ACTION_POINTER_INDEX_SHIFT = 8;
+
+    /**
+     * Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK}
+     * portion.
+     */
+    public static int getActionMasked(MotionEvent event) {
+        return event.getAction() & ACTION_MASK;
+    }
+
+    /**
+     * Call {@link MotionEvent#getAction}, returning only the pointer index
+     * portion
+     */
+    public static int getActionIndex(MotionEvent event) {
+        return (event.getAction() & ACTION_POINTER_INDEX_MASK)
+                >> ACTION_POINTER_INDEX_SHIFT;
+    }
+
+    /**
+     * Call {@link MotionEvent#findPointerIndex(int)}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#HONEYCOMB} device,
+     * does nothing and returns -1.
+     */
+    public static int findPointerIndex(MotionEvent event, int pointerId) {
+        return IMPL.findPointerIndex(event, pointerId);
+    }
+
+    /**
+     * Call {@link MotionEvent#getPointerId(int)}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#HONEYCOMB} device,
+     * {@link IndexOutOfBoundsException} is thrown.
+     */
+    public static int getPointerId(MotionEvent event, int pointerIndex) {
+        return IMPL.getPointerId(event, pointerIndex);
+    }
+
+    /**
+     * Call {@link MotionEvent#getX(int)}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#HONEYCOMB} device,
+     * {@link IndexOutOfBoundsException} is thrown.
+     */
+    public static float getX(MotionEvent event, int pointerIndex) {
+        return IMPL.getX(event, pointerIndex);
+    }
+
+    /**
+     * Call {@link MotionEvent#getY(int)}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#HONEYCOMB} device,
+     * {@link IndexOutOfBoundsException} is thrown.
+     */
+    public static float getY(MotionEvent event, int pointerIndex) {
+        return IMPL.getY(event, pointerIndex);
+    }
+}
diff --git a/v4/java/android/support/v4/view/VelocityTrackerCompat.java b/v4/java/android/support/v4/view/VelocityTrackerCompat.java
new file mode 100644
index 0000000..8c87490
--- /dev/null
+++ b/v4/java/android/support/v4/view/VelocityTrackerCompat.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.VelocityTracker;
+
+/**
+ * Helper for accessing newer features in VelocityTracker.
+ */
+public class VelocityTrackerCompat {
+    /**
+     * Interface for the full API.
+     */
+    interface VelocityTrackerVersionImpl {
+        public float getXVelocity(VelocityTracker tracker, int pointerId);
+        public float getYVelocity(VelocityTracker tracker, int pointerId);
+    }
+
+    /**
+     * Interface implementation that doesn't use anything about v4 APIs.
+     */
+    static class BaseVelocityTrackerVersionImpl implements VelocityTrackerVersionImpl {
+        @Override
+        public float getXVelocity(VelocityTracker tracker, int pointerId) {
+            return tracker.getXVelocity();
+        }
+        @Override
+        public float getYVelocity(VelocityTracker tracker, int pointerId) {
+            return tracker.getYVelocity();
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least v11 APIs.
+     */
+    static class HoneycombVelocityTrackerVersionImpl implements VelocityTrackerVersionImpl {
+        @Override
+        public float getXVelocity(VelocityTracker tracker, int pointerId) {
+            return VelocityTrackerCompatHoneycomb.getXVelocity(tracker, pointerId);
+        }
+        @Override
+        public float getYVelocity(VelocityTracker tracker, int pointerId) {
+            return VelocityTrackerCompatHoneycomb.getYVelocity(tracker, pointerId);
+        }
+    }
+
+    /**
+     * Select the correct implementation to use for the current platform.
+     */
+    static final VelocityTrackerVersionImpl IMPL;
+    static {
+        if (android.os.Build.VERSION.SDK_INT >= 11) {
+            IMPL = new HoneycombVelocityTrackerVersionImpl();
+        } else {
+            IMPL = new BaseVelocityTrackerVersionImpl();
+        }
+    }
+
+    // -------------------------------------------------------------------
+
+    /**
+     * Call {@link VelocityTracker#getXVelocity(int)}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#HONEYCOMB} device,
+     * returns {@link VelocityTracker#getXVelocity()}.
+     */
+    public static float getXVelocity(VelocityTracker tracker, int pointerId) {
+        return IMPL.getXVelocity(tracker, pointerId);
+    }
+
+    /**
+     * Call {@link VelocityTracker#getYVelocity(int)}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#HONEYCOMB} device,
+     * returns {@link VelocityTracker#getYVelocity()}.
+     */
+    public static float getYVelocity(VelocityTracker tracker, int pointerId) {
+        return IMPL.getYVelocity(tracker, pointerId);
+    }
+}
diff --git a/v4/java/android/support/v4/view/ViewConfigurationCompat.java b/v4/java/android/support/v4/view/ViewConfigurationCompat.java
new file mode 100644
index 0000000..1444662
--- /dev/null
+++ b/v4/java/android/support/v4/view/ViewConfigurationCompat.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.ViewConfiguration;
+
+/**
+ * Helper for accessing newer features in ViewConfiguration.
+ */
+public class ViewConfigurationCompat {
+    /**
+     * Interface for the full API.
+     */
+    interface ViewConfigurationVersionImpl {
+        public int getScaledPagingTouchSlop(ViewConfiguration config);
+    }
+
+    /**
+     * Interface implementation that doesn't use anything about v4 APIs.
+     */
+    static class BaseViewConfigurationVersionImpl implements ViewConfigurationVersionImpl {
+        @Override
+        public int getScaledPagingTouchSlop(ViewConfiguration config) {
+            return config.getScaledTouchSlop();
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least v11 APIs.
+     */
+    static class FroyoViewConfigurationVersionImpl implements ViewConfigurationVersionImpl {
+        @Override
+        public int getScaledPagingTouchSlop(ViewConfiguration config) {
+            return ViewConfigurationCompatFroyo.getScaledPagingTouchSlop(config);
+        }
+    }
+
+    /**
+     * Select the correct implementation to use for the current platform.
+     */
+    static final ViewConfigurationVersionImpl IMPL;
+    static {
+        if (android.os.Build.VERSION.SDK_INT >= 11) {
+            IMPL = new FroyoViewConfigurationVersionImpl();
+        } else {
+            IMPL = new BaseViewConfigurationVersionImpl();
+        }
+    }
+
+    // -------------------------------------------------------------------
+
+    /**
+     * Call {@link ViewConfiguration#getScaledPagingTouchSlop()}.
+     * If running on a pre-{@android.os.Build.VERSION_CODES#FROYO} device,
+     * returns {@link ViewConfiguration#getScaledTouchSlop()}.
+     */
+    public static int getScaledPagingTouchSlop(ViewConfiguration config) {
+        return IMPL.getScaledPagingTouchSlop(config);
+    }
+}