Merge "Adding resizeMode to widget samples." into honeycomb-mr2
diff --git a/build/sdk.atree b/build/sdk.atree
index 97317c4..c8f1bf5 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -166,6 +166,7 @@
 development/samples/LunarLander                samples/${PLATFORM_NAME}/LunarLander
 development/samples/MultiResolution            samples/${PLATFORM_NAME}/MultiResolution
 development/samples/NotePad                    samples/${PLATFORM_NAME}/NotePad
+development/samples/RandomMusicPlayer          samples/${PLATFORM_NAME}/RandomMusicPlayer
 development/samples/SampleSyncAdapter          samples/${PLATFORM_NAME}/SampleSyncAdapter
 development/samples/SearchableDictionary       samples/${PLATFORM_NAME}/SearchableDictionary
 development/samples/SipDemo                    samples/${PLATFORM_NAME}/SipDemo
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/RandomMusicPlayer/AndroidManifest.xml b/samples/RandomMusicPlayer/AndroidManifest.xml
new file mode 100644
index 0000000..5a0fc28
--- /dev/null
+++ b/samples/RandomMusicPlayer/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.example.android.musicplayer"
+      android:versionCode="1"
+      android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="8" />
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+    <application android:icon="@drawable/ic_launcher" android:label="Random Music Player">
+        <activity android:name=".MainActivity"
+                  android:label="Random Music Player"
+                  android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service android:exported="false" android:name=".MusicService">
+            <intent-filter>
+                <action android:name="com.example.android.musicplayer.action.PLAY" />
+                <action android:name="com.example.android.musicplayer.action.PAUSE" />
+                <action android:name="com.example.android.musicplayer.action.SKIP" />
+                <action android:name="com.example.android.musicplayer.action.REWIND" />
+                <action android:name="com.example.android.musicplayer.action.STOP" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.example.android.musicplayer.action.URL" />
+                <data android:scheme="http" />
+            </intent-filter>
+        </service>
+
+        <receiver android:name=".MusicIntentReceiver">
+            <intent-filter>
+                <action android:name="android.media.AUDIO_BECOMING_NOISY" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/samples/RandomMusicPlayer/_index.html b/samples/RandomMusicPlayer/_index.html
new file mode 100644
index 0000000..657e6c4
--- /dev/null
+++ b/samples/RandomMusicPlayer/_index.html
@@ -0,0 +1,8 @@
+<p>A simple music player that illustrates how to make a multimedia application
+that manages media playback from a service. It allows the user to play music
+available on the device or specify a URL from which the media should be
+streamed.  It also illustrates how to use the notification system to indicate
+an ongoing task and how to deal with audio focus changes.</p>
+
+<img alt="" src="../images/randommusicplayer.png" />
+
diff --git a/samples/RandomMusicPlayer/default.properties b/samples/RandomMusicPlayer/default.properties
new file mode 100644
index 0000000..e2e8061
--- /dev/null
+++ b/samples/RandomMusicPlayer/default.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-8
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi-v9/ic_stat_playing.png b/samples/RandomMusicPlayer/res/drawable-hdpi-v9/ic_stat_playing.png
new file mode 100644
index 0000000..d111aab
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi-v9/ic_stat_playing.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/eject.png b/samples/RandomMusicPlayer/res/drawable-hdpi/eject.png
new file mode 100644
index 0000000..650b38a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/eject.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/eject_pressed.png b/samples/RandomMusicPlayer/res/drawable-hdpi/eject_pressed.png
new file mode 100644
index 0000000..065b30d
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/eject_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/ff.png b/samples/RandomMusicPlayer/res/drawable-hdpi/ff.png
new file mode 100644
index 0000000..508f741
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/ff.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/ff_pressed.png b/samples/RandomMusicPlayer/res/drawable-hdpi/ff_pressed.png
new file mode 100644
index 0000000..468ae8e
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/ff_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/ic_launcher.png b/samples/RandomMusicPlayer/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..abd9055
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/ic_stat_playing.png b/samples/RandomMusicPlayer/res/drawable-hdpi/ic_stat_playing.png
new file mode 100644
index 0000000..c1dd9da
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/ic_stat_playing.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/pause.png b/samples/RandomMusicPlayer/res/drawable-hdpi/pause.png
new file mode 100644
index 0000000..13581de
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/pause.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/pause_pressed.png b/samples/RandomMusicPlayer/res/drawable-hdpi/pause_pressed.png
new file mode 100644
index 0000000..9ddd07d
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/pause_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/play.png b/samples/RandomMusicPlayer/res/drawable-hdpi/play.png
new file mode 100644
index 0000000..e34b48e
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/play.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/play_pressed.png b/samples/RandomMusicPlayer/res/drawable-hdpi/play_pressed.png
new file mode 100644
index 0000000..790cd29
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/play_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/rew.png b/samples/RandomMusicPlayer/res/drawable-hdpi/rew.png
new file mode 100644
index 0000000..26864b7
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/rew.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/rew_pressed.png b/samples/RandomMusicPlayer/res/drawable-hdpi/rew_pressed.png
new file mode 100644
index 0000000..54c38a7
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/rew_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/selector_eject.xml b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_eject.xml
new file mode 100644
index 0000000..300e75a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_eject.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/eject_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/eject_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/eject" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/selector_ff.xml b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_ff.xml
new file mode 100644
index 0000000..2d399b4
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_ff.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/ff_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/ff_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/ff" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/selector_pause.xml b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_pause.xml
new file mode 100644
index 0000000..2d6c4be
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_pause.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/pause_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/pause_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/pause" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/selector_play.xml b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_play.xml
new file mode 100644
index 0000000..d2eea02
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_play.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/play_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/play_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/play" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/selector_rew.xml b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_rew.xml
new file mode 100644
index 0000000..5f5f88a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_rew.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/rew_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/rew_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/rew" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/selector_stop.xml b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_stop.xml
new file mode 100644
index 0000000..5778417
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/selector_stop.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/stop_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/stop_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/stop" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/stop.png b/samples/RandomMusicPlayer/res/drawable-hdpi/stop.png
new file mode 100644
index 0000000..45eff23
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/stop.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-hdpi/stop_pressed.png b/samples/RandomMusicPlayer/res/drawable-hdpi/stop_pressed.png
new file mode 100644
index 0000000..c7bda81
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-hdpi/stop_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-ldpi-v9/ic_stat_playing.png b/samples/RandomMusicPlayer/res/drawable-ldpi-v9/ic_stat_playing.png
new file mode 100644
index 0000000..6a40823
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-ldpi-v9/ic_stat_playing.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-ldpi/ic_launcher.png b/samples/RandomMusicPlayer/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..6f1277a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-ldpi/ic_stat_playing.png b/samples/RandomMusicPlayer/res/drawable-ldpi/ic_stat_playing.png
new file mode 100644
index 0000000..fb21884
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-ldpi/ic_stat_playing.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi-v9/ic_stat_playing.png b/samples/RandomMusicPlayer/res/drawable-mdpi-v9/ic_stat_playing.png
new file mode 100644
index 0000000..b5a66df
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi-v9/ic_stat_playing.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/eject.png b/samples/RandomMusicPlayer/res/drawable-mdpi/eject.png
new file mode 100644
index 0000000..650b38a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/eject.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/eject_pressed.png b/samples/RandomMusicPlayer/res/drawable-mdpi/eject_pressed.png
new file mode 100644
index 0000000..065b30d
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/eject_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/ff.png b/samples/RandomMusicPlayer/res/drawable-mdpi/ff.png
new file mode 100644
index 0000000..508f741
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/ff.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/ff_pressed.png b/samples/RandomMusicPlayer/res/drawable-mdpi/ff_pressed.png
new file mode 100644
index 0000000..468ae8e
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/ff_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/ic_launcher.png b/samples/RandomMusicPlayer/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..abd9055
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/ic_stat_playing.png b/samples/RandomMusicPlayer/res/drawable-mdpi/ic_stat_playing.png
new file mode 100644
index 0000000..c1dd9da
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/ic_stat_playing.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/pause.png b/samples/RandomMusicPlayer/res/drawable-mdpi/pause.png
new file mode 100644
index 0000000..13581de
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/pause.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/pause_pressed.png b/samples/RandomMusicPlayer/res/drawable-mdpi/pause_pressed.png
new file mode 100644
index 0000000..9ddd07d
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/pause_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/play.png b/samples/RandomMusicPlayer/res/drawable-mdpi/play.png
new file mode 100644
index 0000000..e34b48e
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/play.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/play_pressed.png b/samples/RandomMusicPlayer/res/drawable-mdpi/play_pressed.png
new file mode 100644
index 0000000..790cd29
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/play_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/rew.png b/samples/RandomMusicPlayer/res/drawable-mdpi/rew.png
new file mode 100644
index 0000000..26864b7
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/rew.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/rew_pressed.png b/samples/RandomMusicPlayer/res/drawable-mdpi/rew_pressed.png
new file mode 100644
index 0000000..54c38a7
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/rew_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/selector_eject.xml b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_eject.xml
new file mode 100644
index 0000000..300e75a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_eject.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/eject_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/eject_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/eject" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/selector_ff.xml b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_ff.xml
new file mode 100644
index 0000000..2d399b4
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_ff.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/ff_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/ff_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/ff" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/selector_pause.xml b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_pause.xml
new file mode 100644
index 0000000..2d6c4be
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_pause.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/pause_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/pause_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/pause" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/selector_play.xml b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_play.xml
new file mode 100644
index 0000000..d2eea02
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_play.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/play_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/play_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/play" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/selector_rew.xml b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_rew.xml
new file mode 100644
index 0000000..5f5f88a
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_rew.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/rew_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/rew_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/rew" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/selector_stop.xml b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_stop.xml
new file mode 100644
index 0000000..5778417
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/selector_stop.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+     <item android:state_pressed="true"
+           android:drawable="@drawable/stop_pressed" /> <!-- pressed -->
+     <item android:state_focused="true"
+           android:drawable="@drawable/stop_pressed" /> <!-- focused -->
+     <item android:drawable="@drawable/stop" /> <!-- default -->
+ </selector>
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/stop.png b/samples/RandomMusicPlayer/res/drawable-mdpi/stop.png
new file mode 100644
index 0000000..45eff23
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/stop.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/drawable-mdpi/stop_pressed.png b/samples/RandomMusicPlayer/res/drawable-mdpi/stop_pressed.png
new file mode 100644
index 0000000..c7bda81
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/drawable-mdpi/stop_pressed.png
Binary files differ
diff --git a/samples/RandomMusicPlayer/res/layout-land/main.xml b/samples/RandomMusicPlayer/res/layout-land/main.xml
new file mode 100644
index 0000000..c9072bf
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/layout-land/main.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center"
+    android:background="#000040"
+    >
+
+<TextView android:text="Random Music Player"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:padding="20dp"
+          android:textColor="#ffffff"
+          android:textSize="20sp"
+          android:textStyle="bold"
+          />
+
+<LinearLayout
+    android:orientation="horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    >
+<Button
+    android:id="@+id/rewindbutton"
+    android:background="@drawable/selector_rew"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/playbutton"
+    android:background="@drawable/selector_play"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/pausebutton"
+    android:background="@drawable/selector_pause"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/skipbutton"
+    android:background="@drawable/selector_ff"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/stopbutton"
+    android:background="@drawable/selector_stop"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/ejectbutton"
+    android:background="@drawable/selector_eject"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+
+</LinearLayout>
+</LinearLayout>
diff --git a/samples/RandomMusicPlayer/res/layout-port/main.xml b/samples/RandomMusicPlayer/res/layout-port/main.xml
new file mode 100644
index 0000000..ab86ae5
--- /dev/null
+++ b/samples/RandomMusicPlayer/res/layout-port/main.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center"
+    android:background="#000040"
+    >
+
+<TextView android:text="Random Music Player"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:padding="20dp"
+          android:textColor="#ffffff"
+          android:textSize="20sp"
+          android:textStyle="bold"
+          />
+
+<LinearLayout
+    android:orientation="horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:layout_margin="10dp"
+    >
+<Button
+    android:id="@+id/rewindbutton"
+    android:background="@drawable/selector_rew"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/playbutton"
+    android:background="@drawable/selector_play"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/pausebutton"
+    android:background="@drawable/selector_pause"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/skipbutton"
+    android:background="@drawable/selector_ff"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+</LinearLayout>
+
+<LinearLayout
+    android:orientation="horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:layout_margin="10dp"
+    >
+<Button
+    android:id="@+id/stopbutton"
+    android:background="@drawable/selector_stop"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+<Button
+    android:id="@+id/ejectbutton"
+    android:background="@drawable/selector_eject"
+    android:layout_width="64dp"
+    android:layout_height="64dp"
+    android:layout_margin="5dp"
+    />
+
+</LinearLayout>
+</LinearLayout>
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/AudioFocusHelper.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/AudioFocusHelper.java
new file mode 100644
index 0000000..4b8b54a
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/AudioFocusHelper.java
@@ -0,0 +1,71 @@
+/*   
+ * 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.musicplayer;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+/** 
+ * Convenience class to deal with audio focus. This class deals with everything related to audio
+ * focus: it can request and abandon focus, and will intercept focus change events and deliver
+ * them to a MusicFocusable interface (which, in our case, is implemented by {@link MusicService}).
+ *
+ * This class can only be used on SDK level 8 and above, since it uses API features that are not
+ * available on previous SDK's.
+ */
+public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
+    AudioManager mAM;
+    MusicFocusable mFocusable;
+
+    public AudioFocusHelper(Context ctx, MusicFocusable focusable) {
+        mAM = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
+        mFocusable = focusable;
+    }
+
+    /** Requests audio focus. Returns whether request was successful or not. */
+    public boolean requestFocus() {
+        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
+            mAM.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+    }
+
+    /** Abandons audio focus. Returns whether request was successful or not. */
+    public boolean abandonFocus() {
+        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM.abandonAudioFocus(this);
+    }
+
+    /** 
+     * Called by AudioManager on audio focus changes. We implement this by calling our
+     * MusicFocusable appropriately to relay the message.
+     */
+    @Override
+    public void onAudioFocusChange(int focusChange) {
+        if (mFocusable == null) return;
+        switch (focusChange) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                mFocusable.onGainedAudioFocus();
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS:
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+                mFocusable.onLostAudioFocus(false);
+                break;
+            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                mFocusable.onLostAudioFocus(true);
+                break;
+             default:
+        }
+    }
+}
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MainActivity.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MainActivity.java
new file mode 100644
index 0000000..4974a21
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MainActivity.java
@@ -0,0 +1,122 @@
+/*   
+ * 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.musicplayer;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+/** 
+ * Main activity: shows media player buttons. This activity shows the media player buttons and
+ * lets the user click them. No media handling is done here -- everything is done by passing
+ * Intents to our {@link MusicService}.
+ * */
+public class MainActivity extends Activity implements OnClickListener {
+    /**
+     * The URL we suggest as default when adding by URL. This is just so that the user doesn't
+     * have to find an URL to test this sample.
+     */
+    final String SUGGESTED_URL = "http://www.vorbis.com/music/Epoq-Lepidoptera.ogg";
+
+    Button mPlayButton;
+    Button mPauseButton;
+    Button mSkipButton;
+    Button mRewindButton;
+    Button mStopButton;
+    Button mEjectButton;
+
+    /**
+     * Called when the activity is first created. Here, we simply set the event listeners and
+     * start the background service ({@link MusicService}) that will handle the actual media
+     * playback.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        mPlayButton = (Button) findViewById(R.id.playbutton);
+        mPauseButton = (Button) findViewById(R.id.pausebutton);
+        mSkipButton = (Button) findViewById(R.id.skipbutton);
+        mRewindButton = (Button) findViewById(R.id.rewindbutton);
+        mStopButton = (Button) findViewById(R.id.stopbutton);
+        mEjectButton = (Button) findViewById(R.id.ejectbutton);
+
+        mPlayButton.setOnClickListener(this);
+        mPauseButton.setOnClickListener(this);
+        mSkipButton.setOnClickListener(this);
+        mRewindButton.setOnClickListener(this);
+        mStopButton.setOnClickListener(this);
+        mEjectButton.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View target) {
+        // Send the correct intent to the MusicService, according to the button that was clicked
+        if (target == mPlayButton)
+            startService(new Intent(MusicService.ACTION_PLAY));
+        else if (target == mPauseButton)
+            startService(new Intent(MusicService.ACTION_PAUSE));
+        else if (target == mSkipButton)
+            startService(new Intent(MusicService.ACTION_SKIP));
+        else if (target == mRewindButton)
+            startService(new Intent(MusicService.ACTION_REWIND));
+        else if (target == mStopButton)
+            startService(new Intent(MusicService.ACTION_STOP));
+        else if (target == mEjectButton) {
+            showUrlDialog();
+        }
+    }
+
+    /** 
+     * Shows an alert dialog where the user can input a URL. After showing the dialog, if the user
+     * confirms, sends the appropriate intent to the {@link MusicService} to cause that URL to be
+     * played.
+     */
+    void showUrlDialog() {
+        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
+        alertBuilder.setTitle("Manual Input");
+        alertBuilder.setMessage("Enter a URL (must be http://)");
+        final EditText input = new EditText(this);
+        alertBuilder.setView(input);
+
+        input.setText(SUGGESTED_URL);
+
+        alertBuilder.setPositiveButton("Play!", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dlg, int whichButton) {
+                // Send an intent with the URL of the song to play. This is expected by
+                // MusicService.
+                Intent i = new Intent(MusicService.ACTION_URL);
+                Uri uri = Uri.parse(input.getText().toString());
+                i.setData(uri);
+                startService(i);
+            }
+        });
+        alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dlg, int whichButton) {}
+        });
+
+        alertBuilder.show();
+    }
+}
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicFocusable.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicFocusable.java
new file mode 100644
index 0000000..aea8b49
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicFocusable.java
@@ -0,0 +1,35 @@
+/*   
+ * 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.musicplayer;
+
+/**
+ * Represents something that can react to audio focus events. We implement this instead of just
+ * using AudioManager.OnAudioFocusChangeListener because that interface is only available in SDK
+ * level 8 and above, and we want our application to work on previous SDKs.
+ */
+public interface MusicFocusable {
+    /** Signals that audio focus was gained. */
+    public void onGainedAudioFocus();
+
+    /**
+     * Signals that audio focus was lost.
+     *
+     * @param canDuck If true, audio can continue in "ducked" mode (low volume). Otherwise, all
+     * audio must stop.
+     */
+    public void onLostAudioFocus(boolean canDuck);
+}
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicIntentReceiver.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicIntentReceiver.java
new file mode 100644
index 0000000..cc03d5e
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicIntentReceiver.java
@@ -0,0 +1,40 @@
+/*   
+ * 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.musicplayer;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Toast;
+
+/**
+ * Receives broadcasted intents. In particular, we are interested in the
+ * android.media.AUDIO_BECOMING_NOISY intent, which is broadcast, for example, when the user
+ * disconnects the headphones. This class works because we are declaring it in a &lt;receiver&gt;
+ * tag in AndroidManifest.xml.
+ */
+public class MusicIntentReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context ctx, Intent intent) {
+        if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
+            Toast.makeText(ctx, "Headphones disconnected.", Toast.LENGTH_SHORT).show();
+
+            // send an intent to our MusicService to telling it to pause the audio
+            ctx.startService(new Intent(MusicService.ACTION_PAUSE));
+        }
+    }
+}
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicRetriever.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicRetriever.java
new file mode 100644
index 0000000..44d6447
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicRetriever.java
@@ -0,0 +1,118 @@
+/*   
+ * 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.musicplayer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ * Retrieves and organizes media to play. Before being used, you must call {@link #prepare()},
+ * which will retrieve all of the music on the user's device (by performing a query on a content
+ * resolver). After that, it's ready to retrieve a random song, with its title and URI, upon
+ * request.
+ */
+public class MusicRetriever {
+    final String TAG = "MusicRetriever";
+
+    ContentResolver mContentResolver;
+
+    // the items (songs) we have queried
+    List<Item> mItems = new ArrayList<Item>();
+
+    Random mRandom = new Random();
+
+    public MusicRetriever(ContentResolver cr) {
+        mContentResolver = cr;
+    }
+
+    /**
+     * Loads music data. This method may take long, so be sure to call it asynchronously without
+     * blocking the main thread.
+     */
+    public void prepare() {
+        Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+        Log.i(TAG, "Querying media...");
+        Log.i(TAG, "URI: " + uri.toString());
+
+        // Perform a query on the content resolver. The URI we're passing specifies that we
+        // want to query for all audio media on external storage (e.g. SD card)
+        Cursor cur = mContentResolver.query(uri, null, null, null, null);
+        Log.i(TAG, "Query finished. " + (cur == null ? "Returned NULL." : "Returned a cursor."));
+
+        if (cur == null) {
+            // Query failed...
+            Log.e(TAG, "Failed to retrieve music: cursor is null :-(");
+            return;
+        }
+        if (!cur.moveToFirst()) {
+            // Nothing to query. There is no music on the device. How boring.
+            Log.e(TAG, "Failed to move cursor to first row (no query results).");
+            return;
+        }
+
+        Log.i(TAG, "Listing...");
+
+        // retrieve the indices of the columns where the ID and title of the song are
+        int titleColumn = cur.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
+        int idColumn = cur.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
+
+        Log.i(TAG, "Title column index: " + String.valueOf(titleColumn));
+        Log.i(TAG, "ID column index: " + String.valueOf(titleColumn));
+
+        // add each song to mItems
+        do {
+            Log.i(TAG, "ID: " + cur.getString(idColumn) + " Title: " + cur.getString(titleColumn));
+            mItems.add(new Item(cur.getLong(idColumn), cur.getString(titleColumn)));
+        } while (cur.moveToNext());
+
+        Log.i(TAG, "Done querying media. MusicRetriever is ready.");
+    }
+
+    public ContentResolver getContentResolver() {
+        return mContentResolver;
+    }
+
+    /** Returns a random Item. If there are no items available, returns null. */
+    public Item getRandomItem() {
+        if (mItems.size() <= 0) return null;
+        return mItems.get(mRandom.nextInt(mItems.size()));
+    }
+
+    public class Item {
+        long id;
+        String title;
+
+        public Item(long id, String title) {
+            this.id = id;
+            this.title = title;
+        }
+
+        public long getId() { return id; }
+        public String getTitle() { return title; }
+        public Uri getURI() {
+            return ContentUris.withAppendedId(
+                    android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
+        }
+    }
+}
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicService.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicService.java
new file mode 100644
index 0000000..9bd1251
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/MusicService.java
@@ -0,0 +1,511 @@
+/*   
+ * 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.musicplayer;
+
+import java.io.IOException;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.util.Log;
+import android.widget.Toast;
+
+/**
+ * Service that handles media playback. This is the Service through which we perform all the media
+ * handling in our application. Upon initialization, it starts a {@link MediaRetriever} to scan
+ * the user's media. Then, it waits for Intents (which come from our main activity,
+ * {@link MainActivity}, which signal the service to perform specific operations: Play, Pause,
+ * Rewind, Skip, etc.
+ */
+public class MusicService extends Service implements OnCompletionListener, OnPreparedListener,
+                OnErrorListener, MusicFocusable,
+                PrepareMusicRetrieverTask.MusicRetrieverPreparedListener {
+
+    NotificationManager mNotificationManager;
+
+    // our media player
+    MediaPlayer mPlayer = null;
+
+    // our AudioFocusHelper object, if it's available (it's available on SDK level >= 8)
+    // If not available, this will be null. Always check for null before using!
+    AudioFocusHelper mAudioFocusHelper = null;
+
+    // indicates the state our service:
+    enum State {
+        Retrieving, // the MediaRetriever is retrieving music
+        Stopped,    // media player is stopped and not prepared to play
+        Preparing,  // media player is preparing...
+        Playing,    // playback active (media player ready!). (but the media player may actually be
+                    // paused in this state if we don't have audio focus. But we stay in this state
+                    // so that we know we have to resume playback once we get focus back)
+        Paused      // playback paused (media player ready!)
+    };
+
+    State mState = State.Retrieving;
+
+    // if in Retrieving mode, this flag indicates whether we should start playing immediately
+    // when we are ready or not.
+    boolean mStartPlayingAfterRetrieve = false;
+
+    // if mStartPlayingAfterRetrieve is true, this variable indicates the URL that we should
+    // start playing when we are ready. If null, we should play a random song from the device
+    Uri mWhatToPlayAfterRetrieve = null;
+
+    enum PauseReason {
+        UserRequest,  // paused by user request
+        FocusLoss,    // paused because of audio focus loss
+    };
+
+    // why did we pause? (only relevant if mState == State.Paused)
+    PauseReason mPauseReason = PauseReason.UserRequest;
+
+    // do we have audio focus?
+    enum AudioFocus {
+        NoFocusNoDuck,    // we don't have audio focus, and can't duck
+        NoFocusCanDuck,   // we don't have focus, but can play at a low volume ("ducking")
+        Focused           // we have full audio focus
+    }
+    AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck;
+
+    // title of the song we are currently playing
+    String mSongTitle = "";
+
+    // whether the song we are playing is streaming from the network
+    boolean mIsStreaming = false;
+
+    // Wifi lock that we hold when streaming files from the internet, in order to prevent the
+    // device from shutting off the Wifi radio
+    WifiLock mWifiLock;
+
+    // The tag we put on debug messages
+    final static String TAG = "RandomMusicPlayer";
+
+    // These are the Intent actions that we are prepared to handle. Notice that the fact these
+    // constants exist in our class is a mere convenience: what really defines the actions our
+    // service can handle are the <action> tags in the <intent-filters> tag for our service in
+    // AndroidManifest.xml.
+    public static final String ACTION_PLAY = "com.example.android.musicplayer.action.PLAY";
+    public static final String ACTION_PAUSE = "com.example.android.musicplayer.action.PAUSE";
+    public static final String ACTION_STOP = "com.example.android.musicplayer.action.STOP";
+    public static final String ACTION_SKIP = "com.example.android.musicplayer.action.SKIP";
+    public static final String ACTION_REWIND = "com.example.android.musicplayer.action.REWIND";
+    public static final String ACTION_URL = "com.example.android.musicplayer.action.URL";
+
+    // The volume we set the media player to when we lose audio focus, but are allowed to reduce
+    // the volume instead of stopping playback.
+    public final float DUCK_VOLUME = 0.1f;
+
+    // The ID we use for the notification (the onscreen alert that appears at the notification
+    // area at the top of the screen as an icon -- and as text as well if the user expands the
+    // notification area).
+    final int NOTIFICATION_ID = 1;
+
+    // Our instance of our MusicRetriever, which handles scanning for media and
+    // providing titles and URIs as we need.
+    MusicRetriever mRetriever;
+
+    Notification mNotification = null;
+
+    /**
+     * Makes sure the media player exists and has been reset. This will create the media player
+     * if needed, or reset the existing media player if one already exists.
+     */
+    void createMediaPlayerIfNeeded() {
+        if (mPlayer == null) {
+            mPlayer = new MediaPlayer();
+
+            // Make sure the media player will acquire a wake-lock while playing. If we don't do
+            // that, the CPU might go to sleep while the song is playing, causing playback to stop.
+            //
+            // Remember that to use this, we have to declare the android.permission.WAKE_LOCK
+            // permission in AndroidManifest.xml.
+            mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
+
+            // we want the media player to notify us when it's ready preparing, and when it's done
+            // playing:
+            mPlayer.setOnPreparedListener(this);
+            mPlayer.setOnCompletionListener(this);
+            mPlayer.setOnErrorListener(this);
+        }
+        else
+            mPlayer.reset();
+    }
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "debug: Creating service");
+
+        // Create the Wifi lock (this does not acquire the lock, this just creates it)
+        mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
+                        .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
+
+        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+        // Create the retriever and start an asynchronous task that will prepare it.
+        mRetriever = new MusicRetriever(getContentResolver());
+        (new PrepareMusicRetrieverTask(mRetriever,this)).execute();
+
+        // create the Audio Focus Helper, if the Audio Focus feature is available (SDK 8 or above)
+        if (android.os.Build.VERSION.SDK_INT >= 8)
+            mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
+        else
+            mAudioFocus = AudioFocus.Focused; // no focus feature, so we always "have" audio focus
+    }
+
+    /**
+     * Called when we receive an Intent. When we receive an intent sent to us via startService(),
+     * this is the method that gets called. So here we react appropriately depending on the
+     * Intent's action, which specifies what is being requested of us.
+     */
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        String action = intent.getAction();
+        if (action.equals(ACTION_PLAY)) processPlayRequest();
+        else if (action.equals(ACTION_PAUSE)) processPauseRequest();
+        else if (action.equals(ACTION_SKIP)) processSkipRequest();
+        else if (action.equals(ACTION_STOP)) processStopRequest();
+        else if (action.equals(ACTION_REWIND)) processRewindRequest();
+        else if (action.equals(ACTION_URL)) processAddRequest(intent);
+
+        return START_NOT_STICKY; // Means we started the service, but don't want it to
+                                 // restart in case it's killed.
+    }
+
+    void processPlayRequest() {
+        if (mState == State.Retrieving) {
+            // If we are still retrieving media, just set the flag to start playing when we're
+            // ready
+            mWhatToPlayAfterRetrieve = null; // play a random song
+            mStartPlayingAfterRetrieve = true;
+            return;
+        }
+
+        tryToGetAudioFocus();
+
+        if (mState == State.Stopped) {
+            // If we're stopped, just go ahead to the next song and start playing
+            playNextSong(null);
+        }
+        else if (mState == State.Paused) {
+            // If we're paused, just continue playback and restore the 'foreground service' state.
+            mState = State.Playing;
+            setUpAsForeground(mSongTitle + " (playing)");
+            configAndStartMediaPlayer();
+        }
+    }
+
+    void processPauseRequest() {
+        if (mState == State.Retrieving) {
+            // If we are still retrieving media, clear the flag that indicates we should start
+            // playing when we're ready
+            mStartPlayingAfterRetrieve = false;
+            return;
+        }
+
+        if (mState == State.Playing) {
+            // Pause media player and cancel the 'foreground service' state.
+            mState = State.Paused;
+            mPlayer.pause();
+            relaxResources(false); // while paused, we always retain the MediaPlayer
+            giveUpAudioFocus();
+        }
+    }
+
+    void processRewindRequest() {
+        if (mState == State.Playing || mState == State.Paused)
+            mPlayer.seekTo(0);
+    }
+
+    void processSkipRequest() {
+        if (mState == State.Playing || mState == State.Paused) {
+            tryToGetAudioFocus();
+            playNextSong(null);
+        }
+    }
+
+    void processStopRequest() {
+        if (mState == State.Playing || mState == State.Paused) {
+            mState = State.Stopped;
+
+            // let go of all resources...
+            relaxResources(true);
+            giveUpAudioFocus();
+
+            // service is no longer necessary. Will be started again if needed.
+            stopSelf();
+        }
+    }
+
+    /**
+     * Releases resources used by the service for playback. This includes the "foreground service"
+     * status and notification, the wake locks and possibly the MediaPlayer.
+     *
+     * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
+     */
+    void relaxResources(boolean releaseMediaPlayer) {
+        // stop being a foreground service
+        stopForeground(true);
+
+        // stop and release the Media Player, if it's available
+        if (releaseMediaPlayer && mPlayer != null) {
+            mPlayer.reset();
+            mPlayer.release();
+            mPlayer = null;
+        }
+
+        // we can also release the Wifi lock, if we're holding it
+        if (mWifiLock.isHeld()) mWifiLock.release();
+    }
+
+    void giveUpAudioFocus() {
+        if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null
+                                && mAudioFocusHelper.abandonFocus())
+            mAudioFocus = AudioFocus.NoFocusNoDuck;
+    }
+
+    /**
+     * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This
+     * method starts/restarts the MediaPlayer respecting the current audio focus state. So if
+     * we have focus, it will play normally; if we don't have focus, it will either leave the
+     * MediaPlayer paused or set it to a low volume, depending on what is allowed by the
+     * current focus settings. This method assumes mPlayer != null, so if you are calling it,
+     * you have to do so from a context where you are sure this is the case.
+     */
+    void configAndStartMediaPlayer() {
+        if (mAudioFocus == AudioFocus.NoFocusNoDuck) {
+            // If we don't have audio focus and can't duck, we have to pause, even if mState
+            // is State.Playing. But we stay in the Playing state so that we know we have to resume
+            // playback once we get the focus back.
+            if (mPlayer.isPlaying()) mPlayer.pause();
+            return;
+        }
+        else if (mAudioFocus == AudioFocus.NoFocusCanDuck)
+            mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);  // we'll be relatively quiet
+        else
+            mPlayer.setVolume(1.0f, 1.0f); // we can be loud
+
+        if (!mPlayer.isPlaying()) mPlayer.start();
+    }
+
+    void processAddRequest(Intent intent) {
+        // user wants to play a song directly by URL or path. The URL or path comes in the "data"
+        // part of the Intent. This Intent is sent by {@link MainActivity} after the user
+        // specifies the URL/path via an alert box.
+        if (mState == State.Retrieving) {
+            // we'll play the requested URL right after we finish retrieving
+            mWhatToPlayAfterRetrieve = intent.getData();
+            mStartPlayingAfterRetrieve = true;
+        }
+        else if (mState == State.Playing || mState == State.Paused || mState == State.Stopped) {
+            Log.i(TAG, "Playing from URL/path: " + intent.getData().toString());
+            tryToGetAudioFocus();
+            playNextSong(intent.getData().toString());
+        }
+    }
+
+    /**
+     * Shortcut to making and displaying a toast. Seemed cleaner than repeating
+     * this code everywhere, at least for this sample.
+     */
+    void say(String message) {
+        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+    }
+
+    void tryToGetAudioFocus() {
+        if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null
+                        && mAudioFocusHelper.requestFocus())
+            mAudioFocus = AudioFocus.Focused;
+    }
+
+    /**
+     * Starts playing the next song. If manualUrl is null, the next song will be randomly selected
+     * from our Media Retriever (that is, it will be a random song in the user's device). If
+     * manualUrl is non-null, then it specifies the URL or path to the song that will be played
+     * next.
+     */
+    void playNextSong(String manualUrl) {
+        mState = State.Stopped;
+        relaxResources(false); // release everything except MediaPlayer
+
+        try {
+            if (manualUrl != null) {
+                // set the source of the media player to a manual URL or path
+                createMediaPlayerIfNeeded();
+                mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mPlayer.setDataSource(manualUrl);
+                mSongTitle = manualUrl;
+                mIsStreaming = manualUrl.startsWith("http:") || manualUrl.startsWith("https:");
+            }
+            else {
+                mIsStreaming = false; // playing a locally available song
+
+                MusicRetriever.Item item = mRetriever.getRandomItem();
+                if (item == null) {
+                    say("No song to play :-(");
+                    return;
+                }
+
+                // set the source of the media player a a content URI
+                createMediaPlayerIfNeeded();
+                mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mPlayer.setDataSource(getApplicationContext(), item.getURI());
+                mSongTitle = item.getTitle();
+            }
+
+
+            mState = State.Preparing;
+            setUpAsForeground(mSongTitle + " (loading)");
+
+            // starts preparing the media player in the background. When it's done, it will call
+            // our OnPreparedListener (that is, the onPrepared() method on this class, since we set
+            // the listener to 'this').
+            //
+            // Until the media player is prepared, we *cannot* call start() on it!
+            mPlayer.prepareAsync();
+
+            // If we are streaming from the internet, we want to hold a Wifi lock, which prevents
+            // the Wifi radio from going to sleep while the song is playing. If, on the other hand,
+            // we are *not* streaming, we want to release the lock if we were holding it before.
+            if (mIsStreaming) mWifiLock.acquire();
+            else if (mWifiLock.isHeld()) mWifiLock.release();
+        }
+        catch (IOException ex) {
+            Log.e("MusicService", "IOException playing next song: " + ex.getMessage());
+            ex.printStackTrace();
+        }
+    }
+
+    /** Called when media player is done playing current song. */
+    @Override
+    public void onCompletion(MediaPlayer player) {
+        // The media player finished playing the current song, so we go ahead and start the next.
+        playNextSong(null);
+    }
+
+    /** Called when media player is done preparing. */
+    @Override
+    public void onPrepared(MediaPlayer player) {
+        // The media player is done preparing. That means we can start playing!
+        mState = State.Playing;
+        updateNotification(mSongTitle + " (playing)");
+        configAndStartMediaPlayer();
+    }
+
+    /** Updates the notification. */
+    void updateNotification(String text) {
+        PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
+                new Intent(getApplicationContext(), MainActivity.class),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        mNotification.setLatestEventInfo(getApplicationContext(), "RandomMusicPlayer", text, pi);
+        mNotificationManager.notify(NOTIFICATION_ID, mNotification);
+    }
+
+    /**
+     * Configures service as a foreground service. A foreground service is a service that's doing
+     * something the user is actively aware of (such as playing music), and must appear to the
+     * user as a notification. That's why we create the notification here.
+     */
+    void setUpAsForeground(String text) {
+        PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
+                new Intent(getApplicationContext(), MainActivity.class),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        mNotification = new Notification();
+        mNotification.tickerText = text;
+        mNotification.icon = R.drawable.ic_stat_playing;
+        mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
+        mNotification.setLatestEventInfo(getApplicationContext(), "RandomMusicPlayer",
+                text, pi);
+        startForeground(NOTIFICATION_ID, mNotification);
+    }
+
+    /**
+     * Called when there's an error playing media. When this happens, the media player goes to
+     * the Error state. We warn the user about the error and reset the media player.
+     */
+    @Override
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        Toast.makeText(getApplicationContext(), "Media player error! Resetting.",
+            Toast.LENGTH_SHORT).show();
+        Log.e(TAG, "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra));
+
+        mState = State.Stopped;
+        relaxResources(true);
+        giveUpAudioFocus();
+        return true; // true indicates we handled the error
+    }
+
+    @Override
+    public void onGainedAudioFocus() {
+        Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show();
+        mAudioFocus = AudioFocus.Focused;
+
+        // restart media player with new focus settings
+        if (mState == State.Playing)
+            configAndStartMediaPlayer();
+    }
+
+    @Override
+    public void onLostAudioFocus(boolean canDuck) {
+        Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" :
+            "no duck"), Toast.LENGTH_SHORT).show();
+        mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck;
+
+        // start/restart/pause media player with new focus settings
+        if (mPlayer != null && mPlayer.isPlaying())
+            configAndStartMediaPlayer();
+    }
+
+    @Override
+    public void onMusicRetrieverPrepared() {
+        // Done retrieving!
+        mState = State.Stopped;
+
+        // If the flag indicates we should start playing after retrieving, let's do that now.
+        if (mStartPlayingAfterRetrieve) {
+            tryToGetAudioFocus();
+            playNextSong(mWhatToPlayAfterRetrieve == null ?
+                    null : mWhatToPlayAfterRetrieve.toString());
+        }
+    }
+
+
+    @Override
+    public void onDestroy() {
+        // Service is being killed, so make sure we release our resources
+        mState = State.Stopped;
+        relaxResources(true);
+        giveUpAudioFocus();
+    }
+
+    @Override
+    public IBinder onBind(Intent arg0) {
+        return null;
+    }
+}
diff --git a/samples/RandomMusicPlayer/src/com/example/android/musicplayer/PrepareMusicRetrieverTask.java b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/PrepareMusicRetrieverTask.java
new file mode 100644
index 0000000..fd114c8
--- /dev/null
+++ b/samples/RandomMusicPlayer/src/com/example/android/musicplayer/PrepareMusicRetrieverTask.java
@@ -0,0 +1,50 @@
+/*   
+ * 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.musicplayer;
+
+import android.os.AsyncTask;
+
+/**
+ * Asynchronous task that prepares a MusicRetriever. This asynchronous task essentially calls
+ * {@link MusicRetriever#prepare()} on a {@link MusicRetriever}, which may take some time to
+ * run. Upon finishing, it notifies the indicated {@MusicRetrieverPreparedListener}.
+ */
+public class PrepareMusicRetrieverTask extends AsyncTask<Void, Void, Void> {
+    MusicRetriever mRetriever;
+    MusicRetrieverPreparedListener mListener;
+
+    public PrepareMusicRetrieverTask(MusicRetriever retriever,
+            MusicRetrieverPreparedListener listener) {
+        mRetriever = retriever;
+        mListener = listener;
+    }
+
+    @Override
+    protected Void doInBackground(Void... arg0) {
+        mRetriever.prepare();
+        return null;
+    }
+
+    @Override
+    protected void onPostExecute(Void result) {
+        mListener.onMusicRetrieverPrepared();
+    }
+
+    public interface MusicRetrieverPreparedListener {
+        public void onMusicRetrieverPrepared();
+    }
+}
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