New API demos showing use of tabs with fragments.

Also various cleanup in other demos.

Change-Id: I4f5669117e28312bcd4b28795c5eca5f4ab6dcfb
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index e487d09..9199c16 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -38,7 +38,10 @@
 
     <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="11" />
 
-    <!-- This app has not been optimized for large screens. -->
+    <!-- The smallest screen this app works on is a phone.  The app will
+         scale its UI to larger screens but doesn't make good use of them
+         so allow the compatibility mode button to be shown (mostly because
+         this is just convenient for testing). -->
     <supports-screens android:requiresSmallestWidthDp="320"
             android:compatibleWidthLimitDp="480" />
 
@@ -361,6 +364,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.FragmentTabs"
+                android:label="@string/fragment_tabs"
+                android:enabled="@bool/atLeastHoneycomb">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <!-- Loader Samples -->
 
         <activity android:name=".app.LoaderCursor"
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index f95d8f2..da20a8f 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -150,6 +150,8 @@
     <string name="fragment_stack">App/Fragment/Stack</string>
     <string name="new_fragment">New fragment</string>
 
+    <string name="fragment_tabs">App/Fragment/Tabs</string>
+
     <string name="loader_cursor">App/Loader/Cursor</string>
 
     <string name="loader_custom">App/Loader/Custom</string>
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java
index 8a7bf51..11c1bc2 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java
@@ -78,6 +78,11 @@
      * to it, it will be committed at the end of the full tab switch operation.
      * This lets tab switches be atomic without the app needing to track
      * the interactions between different tabs.
+     *
+     * NOTE: This is a very simple implementation that does not retain
+     * fragment state of the non-visible tabs across activity instances.
+     * Look at the FragmentTabs example for how to do a more complete
+     * implementation.
      */
     private class TabListener implements ActionBar.TabListener {
         private TabContentFragment mFragment;
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java
new file mode 100644
index 0000000..baaca49
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java
@@ -0,0 +1,116 @@
+/*
+ * 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 com.example.android.apis.app;
+
+import com.example.android.apis.R;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.widget.Toast;
+
+/**
+ * This demonstrates the use of action bar tabs and how they interact
+ * with other action bar features.
+ */
+public class FragmentTabs extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final ActionBar bar = getActionBar();
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
+
+        bar.addTab(bar.newTab()
+                .setText("Simple")
+                .setTabListener(new TabListener<FragmentStack.CountingFragment>(
+                        this, "simple", FragmentStack.CountingFragment.class)));
+        bar.addTab(bar.newTab()
+                .setText("Contacts")
+                .setTabListener(new TabListener<LoaderCursor.CursorLoaderListFragment>(
+                        this, "contacts", LoaderCursor.CursorLoaderListFragment.class)));
+        bar.addTab(bar.newTab()
+                .setText("Apps")
+                .setTabListener(new TabListener<LoaderCustom.AppListFragment>(
+                        this, "apps", LoaderCustom.AppListFragment.class)));
+        bar.addTab(bar.newTab()
+                .setText("Throttle")
+                .setTabListener(new TabListener<LoaderThrottle.ThrottledLoaderListFragment>(
+                        this, "throttle", LoaderThrottle.ThrottledLoaderListFragment.class)));
+
+        if (savedInstanceState != null) {
+            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
+        private final Activity mActivity;
+        private final String mTag;
+        private final Class<T> mClass;
+        private final Bundle mArgs;
+        private Fragment mFragment;
+
+        public TabListener(Activity activity, String tag, Class<T> clz) {
+            this(activity, tag, clz, null);
+        }
+
+        public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
+            mActivity = activity;
+            mTag = tag;
+            mClass = clz;
+            mArgs = args;
+
+            // Check to see if we already have a fragment for this tab, probably
+            // from a previously saved state.  If so, deactivate it, because our
+            // initial state is that a tab isn't shown.
+            mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
+            if (mFragment != null && !mFragment.isDetached()) {
+                FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
+                ft.detach(mFragment);
+                ft.commit();
+            }
+        }
+
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            if (mFragment == null) {
+                mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
+                ft.add(android.R.id.content, mFragment, mTag);
+            } else {
+                ft.attach(mFragment);
+            }
+        }
+
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+            if (mFragment != null) {
+                ft.detach(mFragment);
+            }
+        }
+
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+            Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
index a8ac0d4..fd2fa68 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
@@ -159,7 +159,11 @@
             mAdapter.swapCursor(data);
 
             // The list should now be shown.
-            setListShown(true);
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
         }
 
         public void onLoaderReset(Loader<Cursor> loader) {
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
index 883ab14..e1e77a9 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -466,7 +466,11 @@
             mAdapter.setData(data);
 
             // The list should now be shown.
-            setListShown(true);
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
         }
 
         @Override public void onLoaderReset(Loader<List<AppEntry>> loader) {
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java
index 1c4c839..af674c0 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java
+++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java
@@ -409,6 +409,9 @@
                     new int[] { android.R.id.text1 }, 0);
             setListAdapter(mAdapter);
 
+            // Start out with a progress indicator.
+            setListShown(false);
+
             // Prepare the loader.  Either re-connect with an existing one,
             // or start a new one.
             getLoaderManager().initLoader(0, null, this);
@@ -492,6 +495,13 @@
 
         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
             mAdapter.swapCursor(data);
+
+            // The list should now be shown.
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
         }
 
         public void onLoaderReset(Loader<Cursor> loader) {
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/_index.html b/samples/ApiDemos/src/com/example/android/apis/app/_index.html
index 71ccb54..e9d1cf5 100644
--- a/samples/ApiDemos/src/com/example/android/apis/app/_index.html
+++ b/samples/ApiDemos/src/com/example/android/apis/app/_index.html
@@ -134,6 +134,10 @@
   <dd>Demonstrates creating a stack of Fragment instances similar to the
   traditional stack of activities.</dd>
   
+  <dt><a href="FragmentTabs.html">Fragment Tabs</a></dt>
+  <dd>Demonstrates implementing ActionBar tabs by switching between
+  Fragments.</dd>
+
 </dl>
 
 
@@ -145,9 +149,10 @@
 Action Bar in a more idiomatic manner.</dd>
   <dt><a href="ActionBarTabs.html">Action Bar Tabs</a></dt>
   <dd>Demonstrates the use of Action Bar tabs and how they interact with other action bar
-features.</dd>
+features.  Also see the <a href="FragmentTabs.html">Fragment Tabs</a> for a more
+complete example of how to switch between fragments.</dd>
   <dt><a href="ActionBarUsage.html">Action Bar Usage</a></dt>
-  <dd>Demonstrates imple usage of the Action Bar, including a SearchView as an action item. The
+  <dd>Demonstrates simple usage of the Action Bar, including a SearchView as an action item. The
 default Honeycomb theme includes the Action Bar by default and a menu resource is used to populate
 the menu data itself. If you'd like to see how these things work under the hood, see
 Mechanics.</dd>
@@ -162,6 +167,10 @@
   <dd>Demonstrates use of LoaderManager to perform a query for a Cursor that
   populates a ListFragment.</dd>
 
+  <dt><a href="LoaderCustom.html">Loader Custom</a></dt>
+  <dd>Demonstrates implementation and use of a custom Loader class.  The
+  custom class here "loads" the currently installed applications.</dd>
+
   <dt><a href="LoaderThrottle.html">Loader Throttle</a></dt>
   <dd>Complete end-to-end demonstration of a simple content provider that
   populates data in a list through a cursor loader.  The UI allows the list
diff --git a/samples/Support13Demos/AndroidManifest.xml b/samples/Support13Demos/AndroidManifest.xml
index e54bf7f..bc32d09 100644
--- a/samples/Support13Demos/AndroidManifest.xml
+++ b/samples/Support13Demos/AndroidManifest.xml
@@ -26,7 +26,10 @@
 
     <uses-sdk android:minSdkVersion="13" />
 
-    <!-- This app has not been optimized for large screens. -->
+    <!-- The smallest screen this app works on is a phone.  The app will
+         scale its UI to larger screens but doesn't make good use of them
+         so allow the compatibility mode button to be shown (mostly because
+         this is just convenient for testing). -->
     <supports-screens android:requiresSmallestWidthDp="320"
             android:compatibleWidthLimitDp="480" />
 
@@ -60,5 +63,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.ActionBarTabsPager"
+                android:label="@string/action_bar_tabs_pager">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/samples/Support13Demos/res/values/strings.xml b/samples/Support13Demos/res/values/strings.xml
index 818597c..2fd12d4 100644
--- a/samples/Support13Demos/res/values/strings.xml
+++ b/samples/Support13Demos/res/values/strings.xml
@@ -29,4 +29,5 @@
 
     <string name="fragment_state_pager_support">Fragment/State Pager</string>
 
+    <string name="action_bar_tabs_pager">Fragment/Action Bar Tabs Pager</string>
 </resources>
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java b/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
new file mode 100644
index 0000000..6c0f803
--- /dev/null
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
@@ -0,0 +1,155 @@
+/*
+ * 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 com.example.android.supportv13.app;
+
+import java.util.ArrayList;
+
+import com.example.android.supportv13.R;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+
+/**
+ * This demonstrates the use of action bar tabs and how they interact
+ * with other action bar features.
+ */
+public class ActionBarTabsPager extends Activity {
+    ViewPager mViewPager;
+    TabsAdapter mTabsAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mViewPager = new ViewPager(this);
+        mViewPager.setId(R.id.pager);
+        setContentView(mViewPager);
+
+        final ActionBar bar = getActionBar();
+        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
+
+        mTabsAdapter = new TabsAdapter(this, mViewPager);
+        mTabsAdapter.addTab(bar.newTab().setText("Simple"),
+                CountingFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("List"),
+                FragmentPagerSupport.ArrayListFragment.class, null);
+        mTabsAdapter.addTab(bar.newTab().setText("Cursor"),
+                CursorFragment.class, null);
+
+        if (savedInstanceState != null) {
+            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    /**
+     * This is a helper class that implements the management of tabs and all
+     * details of connecting a ViewPager with associated TabHost.  It relies on a
+     * trick.  Normally a tab host has a simple API for supplying a View or
+     * Intent that each tab will show.  This is not sufficient for switching
+     * between pages.  So instead we make the content part of the tab host
+     * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
+     * view to show as the tab content.  It listens to changes in tabs, and takes
+     * care of switch to the correct paged in the ViewPager whenever the selected
+     * tab changes.
+     */
+    public static class TabsAdapter extends FragmentPagerAdapter
+            implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
+        private final Context mContext;
+        private final ActionBar mActionBar;
+        private final ViewPager mViewPager;
+        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+        static final class TabInfo {
+            private final Class<?> clss;
+            private final Bundle args;
+
+            TabInfo(Class<?> _class, Bundle _args) {
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        public TabsAdapter(Activity activity, ViewPager pager) {
+            super(activity.getFragmentManager());
+            mContext = activity;
+            mActionBar = activity.getActionBar();
+            mViewPager = pager;
+            mViewPager.setAdapter(this);
+            mViewPager.setOnPageChangeListener(this);
+        }
+
+        public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
+            TabInfo info = new TabInfo(clss, args);
+            tab.setTag(info);
+            tab.setTabListener(this);
+            mTabs.add(info);
+            mActionBar.addTab(tab);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mTabs.size();
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            TabInfo info = mTabs.get(position);
+            return Fragment.instantiate(mContext, info.clss.getName(), info.args);
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            mActionBar.setSelectedNavigationItem(position);
+        }
+
+        @Override
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            Object tag = tab.getTag();
+            for (int i=0; i<mTabs.size(); i++) {
+                if (mTabs.get(i) == tag) {
+                    mViewPager.setCurrentItem(i);
+                }
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+        }
+
+        @Override
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+        }
+    }
+}
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java
new file mode 100644
index 0000000..8672ed2
--- /dev/null
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.example.android.supportv13.app;
+
+import com.example.android.supportv13.R;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class CountingFragment extends Fragment {
+    int mNum;
+
+    /**
+     * Create a new instance of CountingFragment, providing "num"
+     * as an argument.
+     */
+    static CountingFragment newInstance(int num) {
+        CountingFragment f = new CountingFragment();
+
+        // Supply num input as an argument.
+        Bundle args = new Bundle();
+        args.putInt("num", num);
+        f.setArguments(args);
+
+        return f;
+    }
+
+    /**
+     * When creating, retrieve this instance's number from its arguments.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mNum = getArguments() != null ? getArguments().getInt("num") : 1;
+    }
+
+    /**
+     * The Fragment's UI is just a simple text view showing its
+     * instance number.
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.hello_world, container, false);
+        View tv = v.findViewById(R.id.text);
+        ((TextView)tv).setText("Fragment #" + mNum);
+        tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
+        return v;
+    }
+}
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java
new file mode 100644
index 0000000..38be247
--- /dev/null
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.example.android.supportv13.app;
+
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.SearchView.OnQueryTextListener;
+
+
+public class CursorFragment extends ListFragment
+        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // This is the Adapter being used to display the list's data.
+    SimpleCursorAdapter mAdapter;
+
+    // If non-null, this is the current filter the user has provided.
+    String mCurFilter;
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No phone numbers");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new SimpleCursorAdapter(getActivity(),
+                android.R.layout.simple_list_item_2, null,
+                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
+                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
+        setListAdapter(mAdapter);
+
+        // Start out with a progress indicator.
+        setListShown(false);
+
+        // Prepare the loader.  Either re-connect with an existing one,
+        // or start a new one.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Place an action bar item for searching.
+        MenuItem item = menu.add("Search");
+        item.setIcon(android.R.drawable.ic_menu_search);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        SearchView sv = new SearchView(getActivity());
+        sv.setOnQueryTextListener(this);
+        item.setActionView(sv);
+    }
+
+    public boolean onQueryTextChange(String newText) {
+        // Called when the action bar search text has changed.  Update
+        // the search filter, and restart the loader to do a new query
+        // with this filter.
+        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+        getLoaderManager().restartLoader(0, null, this);
+        return true;
+    }
+
+    @Override public boolean onQueryTextSubmit(String query) {
+        // Don't care about this.
+        return true;
+    }
+
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        // Insert desired behavior here.
+        Log.i("FragmentComplexList", "Item clicked: " + id);
+    }
+
+    // These are the Contacts rows that we will retrieve.
+    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.DISPLAY_NAME,
+        Contacts.CONTACT_STATUS,
+        Contacts.CONTACT_PRESENCE,
+        Contacts.PHOTO_ID,
+        Contacts.LOOKUP_KEY,
+    };
+
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // This is called when a new Loader needs to be created.  This
+        // sample only has one Loader, so we don't care about the ID.
+        // First, pick the base URI to use depending on whether we are
+        // currently filtering.
+        Uri baseUri;
+        if (mCurFilter != null) {
+            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    Uri.encode(mCurFilter));
+        } else {
+            baseUri = Contacts.CONTENT_URI;
+        }
+
+        // Now create and return a CursorLoader that will take care of
+        // creating a Cursor for the data being displayed.
+        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+                + Contacts.DISPLAY_NAME + " != '' ))";
+        return new CursorLoader(getActivity(), baseUri,
+                CONTACTS_SUMMARY_PROJECTION, select, null,
+                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
+    }
+
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // Swap the new cursor in.  (The framework will take care of closing the
+        // old cursor once we return.)
+        mAdapter.swapCursor(data);
+
+        // The list should now be shown.
+        if (isResumed()) {
+            setListShown(true);
+        } else {
+            setListShownNoAnimation(true);
+        }
+    }
+
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // This is called when the last Cursor provided to onLoadFinished()
+        // above is about to be closed.  We need to make sure we are no
+        // longer using it.
+        mAdapter.swapCursor(null);
+    }
+}
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html b/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html
index 47673da..832d60e 100644
--- a/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html
@@ -8,10 +8,16 @@
 
 <h3 id="Fragment">Fragment</h3>
 <dl>
+  <dt><a href="ActionBarTabsPager.html">Action Bar Tabs Pager</a></dt>
+  <dd>Demonstrates the use of fragments to implement switching between
+  ActionBar tabs, using a ViewPager to manager the fragments so that
+  the user can also fling left and right to switch tabs.</dd>
+
   <dt><a href="FragmentPagerSupport.html">Fragment Pager Support</a></dt>
   <dd>Demonstrates the use of the v4 support class ViewPager with a
   FragmentPagerAdapter to build a user interface where the user can fling
   left or right to switch between fragments.</dd>
+
   <dt><a href="FragmentStatePagerSupport.html">Fragment State Pager Support</a></dt>
   <dd>Demonstrates the use of the v4 support class ViewPager with a
   FragmentStatePagerAdapter to build a user interface where the user can fling
diff --git a/samples/Support4Demos/AndroidManifest.xml b/samples/Support4Demos/AndroidManifest.xml
index db4dbd1..5c74954 100644
--- a/samples/Support4Demos/AndroidManifest.xml
+++ b/samples/Support4Demos/AndroidManifest.xml
@@ -26,7 +26,10 @@
 
     <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="13" />
 
-    <!-- This app has not been optimized for large screens. -->
+    <!-- The smallest screen this app works on is a phone.  The app will
+         scale its UI to larger screens but doesn't make good use of them
+         so allow the compatibility mode button to be shown (mostly because
+         this is just convenient for testing). -->
     <supports-screens android:requiresSmallestWidthDp="320"
             android:compatibleWidthLimitDp="480" />
 
@@ -147,6 +150,22 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.FragmentTabs"
+                android:label="@string/fragment_tabs">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".app.FragmentTabsPager"
+                android:label="@string/fragment_tabs_pager">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.FragmentPagerSupport"
                 android:label="@string/fragment_pager_support">
             <intent-filter>
@@ -171,6 +190,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.LoaderCustomSupport"
+                android:label="@string/loader_custom_support">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.LoaderThrottleSupport"
                 android:label="@string/loader_throttle_support">
             <intent-filter>
diff --git a/samples/Support4Demos/res/layout/fragment_tabs.xml b/samples/Support4Demos/res/layout/fragment_tabs.xml
new file mode 100644
index 0000000..0d62ef6
--- /dev/null
+++ b/samples/Support4Demos/res/layout/fragment_tabs.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/tab_content.xml
+**
+** Copyright 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.
+*/
+-->
+
+<TabHost
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/tabhost"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TabWidget
+            android:id="@android:id/tabs"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0"/>
+
+        <FrameLayout
+            android:id="@android:id/tabcontent"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="0"/>
+
+        <FrameLayout
+            android:id="@+android:id/realtabcontent"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
+    </LinearLayout>
+</TabHost>
diff --git a/samples/Support4Demos/res/layout/fragment_tabs_pager.xml b/samples/Support4Demos/res/layout/fragment_tabs_pager.xml
new file mode 100644
index 0000000..c36cf3c
--- /dev/null
+++ b/samples/Support4Demos/res/layout/fragment_tabs_pager.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/layout/tab_content.xml
+**
+** Copyright 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.
+*/
+-->
+
+<TabHost
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/tabhost"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TabWidget
+            android:id="@android:id/tabs"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0"/>
+
+        <FrameLayout
+            android:id="@android:id/tabcontent"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="0"/>
+
+        <android.support.v4.view.ViewPager
+            android:id="@+id/pager"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
+    </LinearLayout>
+</TabHost>
diff --git a/samples/Support4Demos/res/layout/list_item_icon_text.xml b/samples/Support4Demos/res/layout/list_item_icon_text.xml
new file mode 100644
index 0000000..c3825b7
--- /dev/null
+++ b/samples/Support4Demos/res/layout/list_item_icon_text.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="48dip"
+        android:layout_height="48dip" />
+
+    <TextView android:id="@+id/text"
+        android:layout_gravity="center_vertical"
+        android:layout_width="0dip"
+        android:layout_weight="1.0"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/samples/Support4Demos/res/values/strings.xml b/samples/Support4Demos/res/values/strings.xml
index 477cd9b..fa437db 100644
--- a/samples/Support4Demos/res/values/strings.xml
+++ b/samples/Support4Demos/res/values/strings.xml
@@ -77,6 +77,10 @@
     <string name="fragment_stack_support">Fragment/Stack</string>
     <string name="new_fragment">New fragment</string>
 
+    <string name="fragment_tabs">Fragment/Tabs</string>
+
+    <string name="fragment_tabs_pager">Fragment/Tabs and Pager</string>
+
     <string name="fragment_pager_support">Fragment/Pager</string>
     <string name="first">First</string>
     <string name="last">Last</string>
@@ -85,6 +89,8 @@
 
     <string name="loader_cursor_support">Loader/Cursor</string>
     
+    <string name="loader_custom_support">Loader/Custom</string>
+
     <string name="loader_throttle_support">Loader/Throttle</string>
 
 </resources>
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
new file mode 100644
index 0000000..44bce31
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
@@ -0,0 +1,169 @@
+/*
+ * 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 com.example.android.supportv4.app;
+
+import java.util.HashMap;
+
+import com.example.android.supportv4.R;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+import android.widget.TabHost;
+
+/**
+ * This demonstrates how you can implement switching between the tabs of a
+ * TabHost through fragments.  It uses a trick (see the code below) to allow
+ * the tabs to switch between fragments instead of simple views.
+ */
+public class FragmentTabs extends FragmentActivity {
+    TabHost mTabHost;
+    TabManager mTabManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.fragment_tabs);
+        mTabHost = (TabHost)findViewById(android.R.id.tabhost);
+        mTabHost.setup();
+
+        mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);
+
+        mTabManager.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
+                FragmentStackSupport.CountingFragment.class, null);
+        mTabManager.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
+                LoaderCursorSupport.CursorLoaderListFragment.class, null);
+        mTabManager.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
+                LoaderCustomSupport.AppListFragment.class, null);
+        mTabManager.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
+                LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
+
+        if (savedInstanceState != null) {
+            mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString("tab", mTabHost.getCurrentTabTag());
+    }
+
+    /**
+     * This is a helper class that implements a generic mechanism for
+     * associating fragments with the tabs in a tab host.  It relies on a
+     * trick.  Normally a tab host has a simple API for supplying a View or
+     * Intent that each tab will show.  This is not sufficient for switching
+     * between fragments.  So instead we make the content part of the tab host
+     * 0dp high (it is not shown) and the TabManager supplies its own dummy
+     * view to show as the tab content.  It listens to changes in tabs, and takes
+     * care of switch to the correct fragment shown in a separate content area
+     * whenever the selected tab changes.
+     */
+    public static class TabManager implements TabHost.OnTabChangeListener {
+        private final FragmentActivity mActivity;
+        private final TabHost mTabHost;
+        private final int mContainerId;
+        private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
+        TabInfo mLastTab;
+
+        static final class TabInfo {
+            private final String tag;
+            private final Class<?> clss;
+            private final Bundle args;
+            private Fragment fragment;
+
+            TabInfo(String _tag, Class<?> _class, Bundle _args) {
+                tag = _tag;
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        static class DummyTabFactory implements TabHost.TabContentFactory {
+            private final Context mContext;
+
+            public DummyTabFactory(Context context) {
+                mContext = context;
+            }
+
+            @Override
+            public View createTabContent(String tag) {
+                View v = new View(mContext);
+                v.setMinimumWidth(0);
+                v.setMinimumHeight(0);
+                return v;
+            }
+        }
+
+        public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
+            mActivity = activity;
+            mTabHost = tabHost;
+            mContainerId = containerId;
+            mTabHost.setOnTabChangedListener(this);
+        }
+
+        public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
+            tabSpec.setContent(new DummyTabFactory(mActivity));
+            String tag = tabSpec.getTag();
+
+            TabInfo info = new TabInfo(tag, clss, args);
+
+            // Check to see if we already have a fragment for this tab, probably
+            // from a previously saved state.  If so, deactivate it, because our
+            // initial state is that a tab isn't shown.
+            info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
+            if (info.fragment != null && !info.fragment.isDetached()) {
+                FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
+                ft.detach(info.fragment);
+                ft.commit();
+            }
+
+            mTabs.put(tag, info);
+            mTabHost.addTab(tabSpec);
+        }
+
+        @Override
+        public void onTabChanged(String tabId) {
+            TabInfo newTab = mTabs.get(tabId);
+            if (mLastTab != newTab) {
+                FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
+                if (mLastTab != null) {
+                    if (mLastTab.fragment != null) {
+                        ft.detach(mLastTab.fragment);
+                    }
+                }
+                if (newTab != null) {
+                    if (newTab.fragment == null) {
+                        newTab.fragment = Fragment.instantiate(mActivity,
+                                newTab.clss.getName(), newTab.args);
+                        ft.add(mContainerId, newTab.fragment, newTab.tag);
+                    } else {
+                        ft.attach(newTab.fragment);
+                    }
+                }
+
+                mLastTab = newTab;
+                ft.commit();
+                mActivity.getSupportFragmentManager().executePendingTransactions();
+            }
+        }
+    }
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.java
new file mode 100644
index 0000000..6db9d3c
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.java
@@ -0,0 +1,165 @@
+/*
+ * 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 com.example.android.supportv4.app;
+
+import java.util.ArrayList;
+
+import com.example.android.supportv4.R;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.widget.TabHost;
+
+/**
+ * Demonstrates combining a TabHost with a ViewPager to implement a tab UI
+ * that switches between tabs and also allows the user to perform horizontal
+ * flicks to move between the tabs.
+ */
+public class FragmentTabsPager extends FragmentActivity {
+    TabHost mTabHost;
+    ViewPager  mViewPager;
+    TabsAdapter mTabsAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.fragment_tabs_pager);
+        mTabHost = (TabHost)findViewById(android.R.id.tabhost);
+        mTabHost.setup();
+
+        mViewPager = (ViewPager)findViewById(R.id.pager);
+
+        mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager);
+
+        mTabsAdapter.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
+                FragmentStackSupport.CountingFragment.class, null);
+        mTabsAdapter.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
+                LoaderCursorSupport.CursorLoaderListFragment.class, null);
+        mTabsAdapter.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
+                LoaderCustomSupport.AppListFragment.class, null);
+        mTabsAdapter.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
+                LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
+
+        if (savedInstanceState != null) {
+            mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString("tab", mTabHost.getCurrentTabTag());
+    }
+
+    /**
+     * This is a helper class that implements the management of tabs and all
+     * details of connecting a ViewPager with associated TabHost.  It relies on a
+     * trick.  Normally a tab host has a simple API for supplying a View or
+     * Intent that each tab will show.  This is not sufficient for switching
+     * between pages.  So instead we make the content part of the tab host
+     * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
+     * view to show as the tab content.  It listens to changes in tabs, and takes
+     * care of switch to the correct paged in the ViewPager whenever the selected
+     * tab changes.
+     */
+    public static class TabsAdapter extends FragmentPagerAdapter
+            implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
+        private final Context mContext;
+        private final TabHost mTabHost;
+        private final ViewPager mViewPager;
+        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+        static final class TabInfo {
+            private final String tag;
+            private final Class<?> clss;
+            private final Bundle args;
+
+            TabInfo(String _tag, Class<?> _class, Bundle _args) {
+                tag = _tag;
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        static class DummyTabFactory implements TabHost.TabContentFactory {
+            private final Context mContext;
+
+            public DummyTabFactory(Context context) {
+                mContext = context;
+            }
+
+            @Override
+            public View createTabContent(String tag) {
+                View v = new View(mContext);
+                v.setMinimumWidth(0);
+                v.setMinimumHeight(0);
+                return v;
+            }
+        }
+
+        public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
+            super(activity.getSupportFragmentManager());
+            mContext = activity;
+            mTabHost = tabHost;
+            mViewPager = pager;
+            mTabHost.setOnTabChangedListener(this);
+            mViewPager.setAdapter(this);
+            mViewPager.setOnPageChangeListener(this);
+        }
+
+        public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
+            tabSpec.setContent(new DummyTabFactory(mContext));
+            String tag = tabSpec.getTag();
+
+            TabInfo info = new TabInfo(tag, clss, args);
+            mTabs.add(info);
+            mTabHost.addTab(tabSpec);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mTabs.size();
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            TabInfo info = mTabs.get(position);
+            return Fragment.instantiate(mContext, info.clss.getName(), info.args);
+        }
+
+        @Override
+        public void onTabChanged(String tabId) {
+            int position = mTabHost.getCurrentTab();
+            mViewPager.setCurrentItem(position);
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            mTabHost.setCurrentTab(position);
+        }
+    }
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java
index 07b9309..096316c 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java
@@ -81,6 +81,9 @@
                     new int[] { android.R.id.text1, android.R.id.text2 }, 0);
             setListAdapter(mAdapter);
 
+            // Start out with a progress indicator.
+            setListShown(false);
+
             // Prepare the loader.  Either re-connect with an existing one,
             // or start a new one.
             getLoaderManager().initLoader(0, null, this);
@@ -147,6 +150,13 @@
             // Swap the new cursor in.  (The framework will take care of closing the
             // old cursor once we return.)
             mAdapter.swapCursor(data);
+
+            // The list should now be shown.
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
         }
 
         public void onLoaderReset(Loader<Cursor> loader) {
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java
new file mode 100644
index 0000000..b222a20
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2010 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 com.example.android.supportv4.app;
+
+import com.example.android.supportv4.R;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+import android.widget.SearchView.OnQueryTextListener;
+
+/**
+ * Demonstration of the implementation of a custom Loader.
+ */
+public class LoaderCustomSupport extends FragmentActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FragmentManager fm = getSupportFragmentManager();
+
+        // Create the list fragment and add it as our sole content.
+        if (fm.findFragmentById(android.R.id.content) == null) {
+            AppListFragment list = new AppListFragment();
+            fm.beginTransaction().add(android.R.id.content, list).commit();
+        }
+    }
+
+//BEGIN_INCLUDE(loader)
+    /**
+     * This class holds the per-item data in our Loader.
+     */
+    public static class AppEntry {
+        public AppEntry(AppListLoader loader, ApplicationInfo info) {
+            mLoader = loader;
+            mInfo = info;
+            mApkFile = new File(info.sourceDir);
+        }
+
+        public ApplicationInfo getApplicationInfo() {
+            return mInfo;
+        }
+
+        public String getLabel() {
+            return mLabel;
+        }
+
+        public Drawable getIcon() {
+            if (mIcon == null) {
+                if (mApkFile.exists()) {
+                    mIcon = mInfo.loadIcon(mLoader.mPm);
+                    return mIcon;
+                } else {
+                    mMounted = false;
+                }
+            } else if (!mMounted) {
+                // If the app wasn't mounted but is now mounted, reload
+                // its icon.
+                if (mApkFile.exists()) {
+                    mMounted = true;
+                    mIcon = mInfo.loadIcon(mLoader.mPm);
+                    return mIcon;
+                }
+            } else {
+                return mIcon;
+            }
+
+            return mLoader.getContext().getResources().getDrawable(
+                    android.R.drawable.sym_def_app_icon);
+        }
+
+        @Override public String toString() {
+            return mLabel;
+        }
+
+        void loadLabel(Context context) {
+            if (mLabel == null || !mMounted) {
+                if (!mApkFile.exists()) {
+                    mMounted = false;
+                    mLabel = mInfo.packageName;
+                } else {
+                    mMounted = true;
+                    CharSequence label = mInfo.loadLabel(context.getPackageManager());
+                    mLabel = label != null ? label.toString() : mInfo.packageName;
+                }
+            }
+        }
+
+        private final AppListLoader mLoader;
+        private final ApplicationInfo mInfo;
+        private final File mApkFile;
+        private String mLabel;
+        private Drawable mIcon;
+        private boolean mMounted;
+    }
+
+    /**
+     * Perform alphabetical comparison of application entry objects.
+     */
+    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
+        private final Collator sCollator = Collator.getInstance();
+        @Override
+        public int compare(AppEntry object1, AppEntry object2) {
+            return sCollator.compare(object1.getLabel(), object2.getLabel());
+        }
+    };
+
+    /**
+     * Helper for determining if the configuration has changed in an interesting
+     * way so we need to rebuild the app list.
+     */
+    public static class InterestingConfigChanges {
+        final Configuration mLastConfiguration = new Configuration();
+        int mLastDensity;
+
+        boolean applyNewConfig(Resources res) {
+            int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+            boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
+            if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
+                    |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
+                mLastDensity = res.getDisplayMetrics().densityDpi;
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Helper class to look for interesting changes to the installed apps
+     * so that the loader can be updated.
+     */
+    public static class PackageIntentReceiver extends BroadcastReceiver {
+        final AppListLoader mLoader;
+
+        public PackageIntentReceiver(AppListLoader loader) {
+            mLoader = loader;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addDataScheme("package");
+            mLoader.getContext().registerReceiver(this, filter);
+            // Register for events related to sdcard installation.
+            IntentFilter sdFilter = new IntentFilter();
+            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+            mLoader.getContext().registerReceiver(this, sdFilter);
+        }
+
+        @Override public void onReceive(Context context, Intent intent) {
+            // Tell the loader about the change.
+            mLoader.onContentChanged();
+        }
+    }
+
+    /**
+     * A custom Loader that loads all of the installed applications.
+     */
+    public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
+        final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
+        final PackageManager mPm;
+
+        List<AppEntry> mApps;
+        PackageIntentReceiver mPackageObserver;
+
+        public AppListLoader(Context context) {
+            super(context);
+
+            // Retrieve the package manager for later use; note we don't
+            // use 'context' directly but instead the save global application
+            // context returned by getContext().
+            mPm = getContext().getPackageManager();
+        }
+
+        /**
+         * This is where the bulk of our work is done.  This function is
+         * called in a background thread and should generate a new set of
+         * data to be published by the loader.
+         */
+        @Override public List<AppEntry> loadInBackground() {
+            // Retrieve all known applications.
+            List<ApplicationInfo> apps = mPm.getInstalledApplications(
+                    PackageManager.GET_UNINSTALLED_PACKAGES |
+                    PackageManager.GET_DISABLED_COMPONENTS);
+            if (apps == null) {
+                apps = new ArrayList<ApplicationInfo>();
+            }
+
+            final Context context = getContext();
+
+            // Create corresponding array of entries and load their labels.
+            List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());
+            for (int i=0; i<apps.size(); i++) {
+                AppEntry entry = new AppEntry(this, apps.get(i));
+                entry.loadLabel(context);
+                entries.add(entry);
+            }
+
+            // Sort the list.
+            Collections.sort(entries, ALPHA_COMPARATOR);
+
+            // Done!
+            return entries;
+        }
+
+        /**
+         * Called when there is new data to deliver to the client.  The
+         * super class will take care of delivering it; the implementation
+         * here just adds a little more logic.
+         */
+        @Override public void deliverResult(List<AppEntry> apps) {
+            if (isReset()) {
+                // An async query came in while the loader is stopped.  We
+                // don't need the result.
+                if (apps != null) {
+                    onReleaseResources(apps);
+                }
+            }
+            List<AppEntry> oldApps = apps;
+            mApps = apps;
+
+            if (isStarted()) {
+                // If the Loader is currently started, we can immediately
+                // deliver its results.
+                super.deliverResult(apps);
+            }
+
+            // At this point we can release the resources associated with
+            // 'oldApps' if needed; now that the new result is delivered we
+            // know that it is no longer in use.
+            if (oldApps != null) {
+                onReleaseResources(oldApps);
+            }
+        }
+
+        /**
+         * Handles a request to start the Loader.
+         */
+        @Override protected void onStartLoading() {
+            if (mApps != null) {
+                // If we currently have a result available, deliver it
+                // immediately.
+                deliverResult(mApps);
+            }
+
+            // Start watching for changes in the app data.
+            if (mPackageObserver == null) {
+                mPackageObserver = new PackageIntentReceiver(this);
+            }
+
+            // Has something interesting in the configuration changed since we
+            // last built the app list?
+            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
+
+            if (takeContentChanged() || mApps == null || configChange) {
+                // If the data has changed since the last time it was loaded
+                // or is not currently available, start a load.
+                forceLoad();
+            }
+        }
+
+        /**
+         * Handles a request to stop the Loader.
+         */
+        @Override protected void onStopLoading() {
+            // Attempt to cancel the current load task if possible.
+            cancelLoad();
+        }
+
+        /**
+         * Handles a request to cancel a load.
+         */
+        @Override public void onCanceled(List<AppEntry> apps) {
+            super.onCanceled(apps);
+
+            // At this point we can release the resources associated with 'apps'
+            // if needed.
+            onReleaseResources(apps);
+        }
+
+        /**
+         * Handles a request to completely reset the Loader.
+         */
+        @Override protected void onReset() {
+            super.onReset();
+
+            // Ensure the loader is stopped
+            onStopLoading();
+
+            // At this point we can release the resources associated with 'apps'
+            // if needed.
+            if (mApps != null) {
+                onReleaseResources(mApps);
+                mApps = null;
+            }
+
+            // Stop monitoring for changes.
+            if (mPackageObserver != null) {
+                getContext().unregisterReceiver(mPackageObserver);
+                mPackageObserver = null;
+            }
+        }
+
+        /**
+         * Helper function to take care of releasing resources associated
+         * with an actively loaded data set.
+         */
+        protected void onReleaseResources(List<AppEntry> apps) {
+            // For a simple List<> there is nothing to do.  For something
+            // like a Cursor, we would close it here.
+        }
+    }
+//END_INCLUDE(loader)
+
+//BEGIN_INCLUDE(fragment)
+    public static class AppListAdapter extends ArrayAdapter<AppEntry> {
+        private final LayoutInflater mInflater;
+
+        public AppListAdapter(Context context) {
+            super(context, android.R.layout.simple_list_item_2);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        public void setData(List<AppEntry> data) {
+            clear();
+            if (data != null) {
+                addAll(data);
+            }
+        }
+
+        /**
+         * Populate new items in the list.
+         */
+        @Override public View getView(int position, View convertView, ViewGroup parent) {
+            View view;
+
+            if (convertView == null) {
+                view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
+            } else {
+                view = convertView;
+            }
+
+            AppEntry item = getItem(position);
+            ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
+            ((TextView)view.findViewById(R.id.text)).setText(item.getLabel());
+
+            return view;
+        }
+    }
+
+    public static class AppListFragment extends ListFragment
+            implements OnQueryTextListener, LoaderManager.LoaderCallbacks<List<AppEntry>> {
+
+        // This is the Adapter being used to display the list's data.
+        AppListAdapter mAdapter;
+
+        // If non-null, this is the current filter the user has provided.
+        String mCurFilter;
+
+        @Override public void onActivityCreated(Bundle savedInstanceState) {
+            super.onActivityCreated(savedInstanceState);
+
+            // Give some text to display if there is no data.  In a real
+            // application this would come from a resource.
+            setEmptyText("No applications");
+
+            // We have a menu item to show in action bar.
+            setHasOptionsMenu(true);
+
+            // Create an empty adapter we will use to display the loaded data.
+            mAdapter = new AppListAdapter(getActivity());
+            setListAdapter(mAdapter);
+
+            // Start out with a progress indicator.
+            setListShown(false);
+
+            // Prepare the loader.  Either re-connect with an existing one,
+            // or start a new one.
+            getLoaderManager().initLoader(0, null, this);
+        }
+
+        @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+            // Place an action bar item for searching.
+            MenuItem item = menu.add("Search");
+            item.setIcon(android.R.drawable.ic_menu_search);
+            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            SearchView sv = new SearchView(getActivity());
+            sv.setOnQueryTextListener(this);
+            item.setActionView(sv);
+        }
+
+        @Override public boolean onQueryTextChange(String newText) {
+            // Called when the action bar search text has changed.  Since this
+            // is a simple array adapter, we can just have it do the filtering.
+            mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
+            mAdapter.getFilter().filter(mCurFilter);
+            return true;
+        }
+
+        @Override public boolean onQueryTextSubmit(String query) {
+            // Don't care about this.
+            return true;
+        }
+
+        @Override public void onListItemClick(ListView l, View v, int position, long id) {
+            // Insert desired behavior here.
+            Log.i("LoaderCustom", "Item clicked: " + id);
+        }
+
+        @Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
+            // This is called when a new Loader needs to be created.  This
+            // sample only has one Loader with no arguments, so it is simple.
+            return new AppListLoader(getActivity());
+        }
+
+        @Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
+            // Set the new data in the adapter.
+            mAdapter.setData(data);
+
+            // The list should now be shown.
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
+        }
+
+        @Override public void onLoaderReset(Loader<List<AppEntry>> loader) {
+            // Clear the data in the adapter.
+            mAdapter.setData(null);
+        }
+    }
+//END_INCLUDE(fragment)
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java
index d16797b..de3f937 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java
@@ -410,6 +410,9 @@
                     new int[] { android.R.id.text1 }, 0);
             setListAdapter(mAdapter);
 
+            // Start out with a progress indicator.
+            setListShown(false);
+
             // Prepare the loader.  Either re-connect with an existing one,
             // or start a new one.
             getLoaderManager().initLoader(0, null, this);
@@ -493,6 +496,13 @@
 
         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
             mAdapter.swapCursor(data);
+
+            // The list should now be shown.
+            if (isResumed()) {
+                setListShown(true);
+            } else {
+                setListShownNoAnimation(true);
+            }
         }
 
         public void onLoaderReset(Loader<Cursor> loader) {
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html b/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html
index 286d4a0..fa9af5a 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html
@@ -67,6 +67,15 @@
   <dd>Demonstrates creating a stack of Fragment instances similar to the
   traditional stack of activities.</dd>
   
+  <dt><a href="FragmentTabs.html">Fragment Tabs</a></dt>
+  <dd>Demonstrates the use of fragments to implement switching between
+  tabs in a TabHost.</dd>
+
+  <dt><a href="FragmentTabsPager.html">Fragment Tabs Pager</a></dt>
+  <dd>Demonstrates the use of fragments to implement switching between
+  tabs in a TabHost, using a ViewPager to manager the fragments so that
+  the user can also fling left and right to switch tabs.</dd>
+
 </dl>
 
 <h3 id="LoaderManager">LoaderManager</h3>
@@ -74,6 +83,10 @@
   <dt><a href="LoaderCursorSupport.html">Loader Cursor</a></dt>
   <dd>Demonstrates use of LoaderManager to perform a query for a Cursor that
   populates a ListFragment.</dd>
+
+  <dt><a href="LoaderCustomSupport.html">Loader Custom</a></dt>
+  <dd>Demonstrates implementation and use of a custom Loader class.  The
+  custom class here "loads" the currently installed applications.</dd>
   
   <dt><a href="LoaderThrottleSupport.html">Loader Throttle</a></dt>
   <dd>Complete end-to-end demonstration of a simple content provider that