Contacts Provider Training Class - Sample App Initial Commit
This is the sample app for the Contacts Provider Android training
class. It's a basic master/detail view with a list of contacts in the
master and contact name, photo and mailing addresses in the detail.
This sample app is backward compatible to API level 7 and also
optimized for all screen sizes.

Change-Id: I83fe6beae9fd4c3fe710426b7dd0863e094cbc89
diff --git a/samples/training/ContactsList/AndroidManifest.xml b/samples/training/ContactsList/AndroidManifest.xml
new file mode 100644
index 0000000..025e9cf
--- /dev/null
+++ b/samples/training/ContactsList/AndroidManifest.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.contactslist"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="5"
+        android:targetSdkVersion="17" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application
+        android:description="@string/app_description"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme"
+        android:allowBackup="true">
+
+        <!-- When the soft keyboard is showing the views of this activity should be resized in the
+             remaining space so that inline searching can take place without having to dismiss the
+             keyboard to see all the content. Therefore windowSoftInputMode is set to
+             adjustResize. -->
+        <activity
+                android:name=".ui.ContactsListActivity"
+                android:label="@string/activity_contacts_list"
+                android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <!-- Add intent-filter for search intent action and specify searchable configuration
+                 via meta-data tag. This allows this activity to receive search intents via the
+                 system hooks. In this sample this is only used on older OS versions (pre-Honeycomb)
+                 via the activity search dialog. See the Search API guide for more information:
+                 http://developer.android.com/guide/topics/search/search-dialog.html -->
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+            </intent-filter>
+            <meta-data android:name="android.app.searchable"
+                       android:resource="@xml/searchable_contacts" />
+        </activity>
+        <activity
+            android:name=".ui.ContactDetailActivity"
+            android:label="@string/activity_contact_detail"
+            android:parentActivityName=".ui.ContactsListActivity">
+            <!-- Define hierarchical parent of this activity, both via the system
+                 parentActivityName attribute (added in API Level 16) and via meta-data annotation.
+                 This allows use of the support library NavUtils class in a way that works over
+                 all Android versions. See the "Tasks and Back Stack" guide for more information:
+                 http://developer.android.com/guide/components/tasks-and-back-stack.html
+            -->
+            <meta-data android:name="android.support.PARENT_ACTIVITY"
+                       android:value=".ui.ContactsListActivity" />
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/training/ContactsList/libs/android-support-v4.jar b/samples/training/ContactsList/libs/android-support-v4.jar
new file mode 100644
index 0000000..65ebaf8
--- /dev/null
+++ b/samples/training/ContactsList/libs/android-support-v4.jar
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_add.png b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_add.png
new file mode 100644
index 0000000..0e4f334
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_edit.png b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_edit.png
new file mode 100644
index 0000000..9d4c934
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_search.png b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_search.png
new file mode 100644
index 0000000..6b7ce8d
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi-v11/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_add.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_add.png
new file mode 100644
index 0000000..644d1c1
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_edit.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_edit.png
new file mode 100644
index 0000000..d423b9e
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_map.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_map.png
new file mode 100644
index 0000000..c233914
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_map.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_action_search.png b/samples/training/ContactsList/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000..1ef4a82
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_180_holo_light.png b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_180_holo_light.png
new file mode 100644
index 0000000..38e4c30
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_180_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_holo_light.png b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_holo_light.png
new file mode 100644
index 0000000..4c0e35e
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_contact_picture_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..2b3a35a
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
new file mode 100644
index 0000000..ee030fb
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
new file mode 100644
index 0000000..7140957
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_add.png b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_add.png
new file mode 100644
index 0000000..86097d8
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_edit.png b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_edit.png
new file mode 100644
index 0000000..71fb427
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_search.png b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_search.png
new file mode 100644
index 0000000..aad37b5
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi-v11/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_add.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_add.png
new file mode 100644
index 0000000..d7ba6fe
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_edit.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_edit.png
new file mode 100644
index 0000000..6a3afe2
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_map.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_map.png
new file mode 100644
index 0000000..68499c5
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_map.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_action_search.png b/samples/training/ContactsList/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000..6b3d131
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_180_holo_light.png b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_180_holo_light.png
new file mode 100644
index 0000000..0b52683
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_180_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_holo_light.png b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_holo_light.png
new file mode 100644
index 0000000..ead9718
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_contact_picture_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..1baf723
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_pressed.9.png b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_pressed.9.png
new file mode 100644
index 0000000..b23e921
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_pressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_unpressed.9.png b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_unpressed.9.png
new file mode 100644
index 0000000..38f14f7
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-mdpi/quickcontact_badge_small_unpressed.9.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_add.png b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_add.png
new file mode 100644
index 0000000..1ebdb43
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_edit.png b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_edit.png
new file mode 100644
index 0000000..6f7e335
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_search.png b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_search.png
new file mode 100644
index 0000000..340031b
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi-v11/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_add.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_add.png
new file mode 100644
index 0000000..b064476
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_add.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_edit.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_edit.png
new file mode 100644
index 0000000..6f2eb59
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_edit.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_map.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_map.png
new file mode 100644
index 0000000..4aed873
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_map.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_action_search.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000..c2b58df
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_action_search.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png
new file mode 100644
index 0000000..f6fd172
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_180_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_holo_light.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_holo_light.png
new file mode 100644
index 0000000..05a65f6
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_contact_picture_holo_light.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xhdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e0b49df
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable-xxhdpi/ic_launcher.png b/samples/training/ContactsList/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e9e1527
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/training/ContactsList/res/drawable/quickcontact_badge_small.xml b/samples/training/ContactsList/res/drawable/quickcontact_badge_small.xml
new file mode 100644
index 0000000..9e68152
--- /dev/null
+++ b/samples/training/ContactsList/res/drawable/quickcontact_badge_small.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<!-- Refer to documentation for the <selector> element. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+            android:state_focused="false"
+            android:state_selected="false"
+            android:state_pressed="false"
+            android:drawable="@drawable/quickcontact_badge_small_unpressed"/>
+
+    <item
+            android:state_pressed="true"
+            android:drawable="@drawable/quickcontact_badge_small_pressed"/>
+
+</selector>
diff --git a/samples/training/ContactsList/res/layout-land/contact_detail_fragment.xml b/samples/training/ContactsList/res/layout-land/contact_detail_fragment.xml
new file mode 100644
index 0000000..a6dd381
--- /dev/null
+++ b/samples/training/ContactsList/res/layout-land/contact_detail_fragment.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:weightSum="100">
+
+        <ImageView
+                android:id="@+id/contact_image"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="@integer/contact_detail_photo_percent"
+                android:scaleType="centerCrop"
+                android:src="@drawable/ic_contact_picture_180_holo_light"
+                android:contentDescription="@string/imageview_description"/>
+
+        <LinearLayout
+                android:layout_height="match_parent"
+                android:layout_width="0dp"
+                android:layout_weight="@integer/contact_detail_info_percent"
+                android:orientation="vertical">
+
+            <TextView android:id="@+id/contact_name"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingLeft="@dimen/padding"
+                      android:paddingRight="@dimen/padding"
+                      android:paddingTop="@dimen/padding"
+                      android:visibility="gone"
+                      android:textAppearance="@style/contactNameTitle"/>
+
+            <ScrollView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+
+                <LinearLayout
+                        android:id="@+id/contact_details_layout"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:paddingBottom="@dimen/padding"
+                        android:orientation="vertical">
+                </LinearLayout>
+
+            </ScrollView>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <!-- This view will be displayed when the views above are hidden. That happens when in two-pane
+         layout mode and no contact is currently selected and therefore the this fragment will
+         simply show a text message instead of contact details. -->
+    <TextView android:id="@id/android:empty"
+              android:gravity="center"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:text="@string/no_contact_selected"
+              android:fontFamily="sans-serif-light"
+              android:visibility="gone"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/training/ContactsList/res/layout/activity_main.xml b/samples/training/ContactsList/res/layout/activity_main.xml
new file mode 100644
index 0000000..20fe26b
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/activity_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- Main Activity single pane layout. This layout contains a single ContactsListFragment that
+     displays a list of contacts. Tapping on a contact will start a new activity to display the
+     contact details. -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+          android:name="com.example.android.contactslist.ui.ContactsListFragment"
+          android:id="@+id/contact_list"
+          android:layout_height="match_parent"
+          android:layout_width="match_parent"/>
diff --git a/samples/training/ContactsList/res/layout/activity_main_twopanes.xml b/samples/training/ContactsList/res/layout/activity_main_twopanes.xml
new file mode 100644
index 0000000..d67f548
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/activity_main_twopanes.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- Main Activity two-pane layout. This layout has two panes, a ContactsListFragment on the left
+     and a ContactDetailFragment on the right. Tapping on a contact in the list loads the details
+     of that contact on the right. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="horizontal"
+              android:showDividers="middle"
+              android:divider="?android:attr/listDivider"
+              android:weightSum="100"
+              android:baselineAligned="false">
+
+    <fragment class="com.example.android.contactslist.ui.ContactsListFragment"
+              android:id="@+id/contact_list"
+              android:layout_height="match_parent"
+              android:layout_width="0dp"
+              android:layout_weight="@integer/contact_list_percent"/>
+
+    <fragment class="com.example.android.contactslist.ui.ContactDetailFragment"
+              android:id="@+id/contact_detail"
+              android:layout_height="match_parent"
+              android:layout_width="0dp"
+              android:layout_weight="@integer/contact_detail_percent"/>
+
+</LinearLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_detail_fragment.xml b/samples/training/ContactsList/res/layout/contact_detail_fragment.xml
new file mode 100644
index 0000000..1676e2e
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_detail_fragment.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- This layout is used by ContactDetailFragment to show contact details: contact photo, contact
+     display name and a dynamic number of addresses (if the contact has any) inside a ScrollView.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:weightSum="100">
+
+        <ImageView
+                android:id="@+id/contact_image"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="@integer/contact_detail_photo_percent"
+                android:scaleType="centerCrop"
+                android:src="@drawable/ic_contact_picture_180_holo_light"
+                android:contentDescription="@string/imageview_description"/>
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="@integer/contact_detail_info_percent"
+                android:orientation="vertical">
+
+            <TextView android:id="@+id/contact_name"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingLeft="@dimen/padding"
+                      android:paddingRight="@dimen/padding"
+                      android:paddingTop="@dimen/padding"
+                      android:visibility="gone"
+                      style="@style/contactNameTitle"/>
+
+            <ScrollView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+
+                <LinearLayout
+                        android:id="@+id/contact_details_layout"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:paddingBottom="@dimen/padding"
+                        android:orientation="vertical">
+                </LinearLayout>
+
+            </ScrollView>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <!-- This view will be displayed when the views above are hidden. That happens when in two-pane
+         layout mode and no contact is currently selected and therefore the this fragment will
+         simply show a text message instead of contact details. -->
+    <TextView android:id="@id/android:empty"
+              android:gravity="center"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:text="@string/no_contact_selected"
+              android:fontFamily="sans-serif-light"
+              android:visibility="gone"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_detail_item.xml b/samples/training/ContactsList/res/layout/contact_detail_item.xml
new file mode 100644
index 0000000..b1e832a
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_detail_item.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- This layout is used to display a single mailing address for a contact. In the case of multiple
+     mailing addresses it could be inflated multiple times and displayed in a ScrollView container
+     to let the user more easily scroll over all addresses. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:paddingTop="@dimen/padding"
+              android:paddingLeft="@dimen/padding"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+            android:id="@+id/contact_detail_header"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            style="@style/addressHeader"/>
+
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:showDividers="middle"
+                  android:dividerPadding="12dp"
+                  android:minHeight="48dp"
+                  android:divider="?android:attr/listDivider">
+
+        <TextView
+                android:id="@+id/contact_detail_item"
+                android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:paddingRight="@dimen/padding"
+                android:layout_gravity="center"
+                style="@style/addressDetail"/>
+
+        <ImageButton
+            android:id="@+id/button_view_address"
+            android:src="@drawable/ic_action_map"
+            android:layout_height="match_parent"
+            android:layout_width="wrap_content"
+            android:paddingTop="8dp"
+            android:paddingBottom="8dp"
+            android:layout_gravity="center"
+            android:contentDescription="@string/address_button_description"
+            style="@style/addressButton"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_list_fragment.xml b/samples/training/ContactsList/res/layout/contact_list_fragment.xml
new file mode 100644
index 0000000..3fc2ae4
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_list_fragment.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
+
+    <!-- Use standard android.R class list id instead of app specific id. This is just useful for
+         consistency. -->
+    <ListView android:id="@id/android:list"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              style="@style/ContactListView"/>
+
+    <!-- Use standard android.R class empty id instead of app specific id. This is just useful for
+     consistency. -->
+    <TextView android:id="@id/android:empty"
+              android:gravity="center"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:text="@string/no_contacts"
+              android:fontFamily="sans-serif-light"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</FrameLayout>
diff --git a/samples/training/ContactsList/res/layout/contact_list_item.xml b/samples/training/ContactsList/res/layout/contact_list_item.xml
new file mode 100644
index 0000000..1ca24ad
--- /dev/null
+++ b/samples/training/ContactsList/res/layout/contact_list_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="match_parent"
+                android:layout_height="?android:attr/listPreferredItemHeight"
+                style="@style/listViewActivatedStyle">
+
+    <!-- Use standard android.R class icon id instead of app specific id. This is just useful for
+         consistency. Use scaleType=centerCrop to give a nice full cropped image in the assigned
+         space -->
+    <QuickContactBadge android:id="@android:id/icon"
+                       android:layout_height="?android:attr/listPreferredItemHeight"
+                       android:layout_width="?android:attr/listPreferredItemHeight"
+                       android:scaleType="centerCrop"
+                       style="@style/quickContactBadgeStyle"
+                       android:src="@drawable/ic_contact_picture_holo_light"/>
+
+    <!-- Use standard android.R class text2 id instead of app specific id. This is just useful for
+         consistency. This is secondary text and not always visible so by default is has its
+         visibility set to gone -->
+    <TextView android:id="@android:id/text2"
+              android:paddingLeft="@dimen/listview_item_padding"
+              android:paddingRight="@dimen/listview_item_padding"
+              android:layout_width="match_parent"
+              android:layout_height="26dp"
+              android:layout_toRightOf="@android:id/icon"
+              android:layout_alignParentBottom="true"
+              android:layout_alignParentRight="true"
+              android:fontFamily="sans-serif"
+              android:singleLine="true"
+              android:ellipsize="marquee"
+              android:visibility="gone"
+              android:text="@string/search_match_other"
+              android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+    <!-- Use standard android.R class text1 id instead of app specific id. This is just useful for
+         consistency. This view also sets layout_alignWithParentIfMissing=true which lets the view
+         align with the parent view if the text2 view is not part of the view hierarchy (which is
+         its initial state). -->
+    <TextView android:id="@android:id/text1"
+              android:paddingLeft="@dimen/listview_item_padding"
+              android:paddingRight="@dimen/listview_item_padding"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_above="@android:id/text2"
+              android:layout_toRightOf="@android:id/icon"
+              android:gravity="center_vertical"
+              android:layout_alignParentRight="true"
+              android:layout_alignParentTop="true"
+              android:layout_alignWithParentIfMissing="true"
+              android:fontFamily="sans-serif-light"
+              android:singleLine="true"
+              android:ellipsize="marquee"
+              android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</RelativeLayout>
diff --git a/samples/training/ContactsList/res/menu/contact_detail_menu.xml b/samples/training/ContactsList/res/menu/contact_detail_menu.xml
new file mode 100644
index 0000000..f2c17df
--- /dev/null
+++ b/samples/training/ContactsList/res/menu/contact_detail_menu.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+            android:id="@+id/menu_edit_contact"
+            android:title="@string/menu_edit_contact"
+            android:icon="@drawable/ic_action_edit"
+            android:showAsAction="ifRoom"/>
+
+</menu>
diff --git a/samples/training/ContactsList/res/menu/contact_list_menu.xml b/samples/training/ContactsList/res/menu/contact_list_menu.xml
new file mode 100644
index 0000000..e784054
--- /dev/null
+++ b/samples/training/ContactsList/res/menu/contact_list_menu.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- The search menu item. Honeycomb and above uses an ActionView or specifically a SearchView
+         which expands within the Action Bar directly. Note the initial collapsed state set using
+         collapseActionView in the showAsAction attribute. -->
+    <item
+            android:id="@+id/menu_search"
+            android:title="@string/menu_search"
+            android:icon="@drawable/ic_action_search"
+            android:showAsAction="ifRoom|collapseActionView"
+            android:actionViewClass="android.widget.SearchView"/>
+
+    <item
+            android:id="@+id/menu_add_contact"
+            android:title="@string/menu_add_contact"
+            android:icon="@drawable/ic_action_add"
+            android:showAsAction="ifRoom"/>
+
+</menu>
diff --git a/samples/training/ContactsList/res/values-sw360dp/styles.xml b/samples/training/ContactsList/res/values-sw360dp/styles.xml
new file mode 100644
index 0000000..eb25832
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw360dp/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- This style bumps up the address details font size to large on devices that have a smallest
+         width of 360dp (larger phones). -->
+    <style name="addressDetail" parent="@android:style/TextAppearance.Large">
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:textIsSelectable">true</item>
+    </style>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp-port/integers.xml b/samples/training/ContactsList/res/values-sw600dp-port/integers.xml
new file mode 100644
index 0000000..cb09a2b
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp-port/integers.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- On devices with a smallest width of 600dp or more in portrait orientation, the two-pane
+         layout should allocate equal space to each fragment. -->
+    <integer name="contact_list_percent">50</integer>
+    <integer name="contact_detail_percent">50</integer>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/bools.xml b/samples/training/ContactsList/res/values-sw600dp/bools.xml
new file mode 100644
index 0000000..69ce942
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/bools.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- On devices with a smallest width of 600dp or more, switch to a two-pane layout.-->
+    <bool name="has_two_panes">true</bool>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/integers.xml b/samples/training/ContactsList/res/values-sw600dp/integers.xml
new file mode 100644
index 0000000..eef8547
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/integers.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- On devices with a smallest width of 600dp or more, the two-pane layout should allocate
+         a larger portion of the screen to the detail fragment. -->
+    <integer name="contact_list_percent">35</integer>
+    <integer name="contact_detail_percent">65</integer>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/layout.xml b/samples/training/ContactsList/res/values-sw600dp/layout.xml
new file mode 100644
index 0000000..c9f8848
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- Create a layout alias so that devices with a minimum width of 600dp or more will use the
+         two-pane layout when referring to the activity_main layout identifier. -->
+    <item name="activity_main" type="layout">@layout/activity_main_twopanes</item>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw600dp/styles.xml b/samples/training/ContactsList/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..980fd97
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw600dp/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <style name="ContactListView">
+        <item name="android:verticalScrollbarPosition">left</item>
+        <item name="android:fastScrollAlwaysVisible">true</item>
+        <item name="android:scrollbarStyle">outsideInset</item>
+    </style>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-sw720dp/dimens.xml b/samples/training/ContactsList/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..2184cd0
--- /dev/null
+++ b/samples/training/ContactsList/res/values-sw720dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- On devices with much larger screen sizes, such as a 10" tablet like Nexus 10, bump up the
+         common padding value to add some extra white space which makes the layouts feel more
+         suitable for the larger screen. -->
+    <dimen name="padding">32dp</dimen>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values-v11/styles.xml b/samples/training/ContactsList/res/values-v11/styles.xml
new file mode 100644
index 0000000..d22ffc9
--- /dev/null
+++ b/samples/training/ContactsList/res/values-v11/styles.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- API Level 11 and above specific resource files. Some of these styles allow us to use new
+     system styles or attributes introduced in API Level 11 and others allow overriding already
+     defined style that are only suitable for older OS versions (such as quickContactBadgeStyle).-->
+
+<resources>
+
+    <style name="AppTheme" parent="@android:style/Theme.Holo.Light"/>
+
+    <style name="listViewActivatedStyle">
+        <item name="android:background">?android:attr/activatedBackgroundIndicator</item>
+    </style>
+
+    <style name="quickContactBadgeStyle"/>
+
+    <style name="addressHeader" parent="@android:style/TextAppearance.Small">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:textColor">@color/holo_blue</item>
+    </style>
+
+    <style name="addressButton" parent="@android:style/Widget.Holo.Button.Borderless"/>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/bools.xml b/samples/training/ContactsList/res/values/bools.xml
new file mode 100644
index 0000000..1197eaa
--- /dev/null
+++ b/samples/training/ContactsList/res/values/bools.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- Default is to use a single pane layout -->
+    <bool name="has_two_panes">false</bool>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/colors.xml b/samples/training/ContactsList/res/values/colors.xml
new file mode 100644
index 0000000..e078dcf
--- /dev/null
+++ b/samples/training/ContactsList/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- Define a standard holo blue color. Useful as we can refer to it from various other
+         resource files or even code and it only needs to be updated in one place if we wanted
+         to change it. -->
+    <color name="holo_blue">#FF33B5E5</color>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/dimens.xml b/samples/training/ContactsList/res/values/dimens.xml
new file mode 100644
index 0000000..25db89d
--- /dev/null
+++ b/samples/training/ContactsList/res/values/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- Define some key view padding values. This is useful because the values can be used in
+         multiple views and changed from one central location. It also gives us the ability to
+         provide alternate values for different device configurations using resource directory
+         qualifiers. -->
+    <dimen name="padding">16dp</dimen>
+    <dimen name="listview_item_padding">16dp</dimen>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/integers.xml b/samples/training/ContactsList/res/values/integers.xml
new file mode 100644
index 0000000..10596a6
--- /dev/null
+++ b/samples/training/ContactsList/res/values/integers.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <!-- These are the default percent values that the contact photo and information should take up
+         in the ContactDetailFragment. -->
+    <integer name="contact_detail_photo_percent">45</integer>
+    <integer name="contact_detail_info_percent">55</integer>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/strings.xml b/samples/training/ContactsList/res/values/strings.xml
new file mode 100644
index 0000000..3253e22
--- /dev/null
+++ b/samples/training/ContactsList/res/values/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+<resources>
+
+    <string name="app_name">Contacts List</string>
+    <string name="activity_contacts_list">Contacts List</string>
+    <string name="activity_contact_detail">Contact Detail</string>
+    <string name="contacts_list_search_results_title">Contacts List Search for \"%s\"</string>
+    <string name="app_description">This is a sample app, demonstrating use of the Android system Contacts Provider.</string>
+    <string name="imageview_description">Contact Thumbnail</string>
+    <string name="address_button_description">View Address</string>
+    <string name="menu_search">Search</string>
+    <string name="menu_add_contact">Add Contact</string>
+    <string name="menu_edit_contact">Edit Contact</string>
+    <string name="no_contacts">No Contacts Found</string>
+    <string name="no_contact_selected">No Contact Selected</string>
+    <string name="search_hint">Find contacts</string>
+
+    <!-- Used for the AlphabetIndexer in ContactsListFragment to provide quick navigation by
+         alphabet using ListView fast scroll. -->
+    <string name="alphabet">ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
+
+    <!-- When using ContactsContract.Contacts#CONTENT_FILTER_URI to search contacts, a match occurs
+         when using a number of different fields, such as name, e-mail address, address, phone
+         number, etc. When a match occurs that is not the name, there is currently no way to tell
+         which other field was matched. This string is displayed in the secondary display text in
+         ContactsListFragment when a search query match occurs that is not the display name.
+         -->
+    <string name="search_match_other">Matches Other Field</string>
+
+    <string name="no_address">No addresses found</string>
+    <string name="no_intent_found">No application found to handle this action</string>
+
+</resources>
diff --git a/samples/training/ContactsList/res/values/styles.xml b/samples/training/ContactsList/res/values/styles.xml
new file mode 100644
index 0000000..18d85f5
--- /dev/null
+++ b/samples/training/ContactsList/res/values/styles.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- This file defines various styles for the application. As this file is located in the /values
+     subdirectory the styles defined here will be used by default unless the styles are redefined
+     inside a more specific resource directory such as /values-sw600dp. -->
+
+<resources>
+
+    <style name="AppTheme" parent="@android:style/Theme"/>
+
+    <style name="ContactListView">
+        <item name="android:verticalScrollbarPosition">right</item>
+        <item name="android:fastScrollAlwaysVisible">true</item>
+        <item name="android:scrollbarStyle">outsideInset</item>
+    </style>
+
+    <style name="listViewActivatedStyle"/>
+
+    <style name="quickContactBadgeStyle">
+        <item name="android:background">@drawable/quickcontact_badge_small</item>
+    </style>
+
+    <style name="searchTextHiglight">
+        <item name="android:textColor">@color/holo_blue</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="addressHeader" parent="@android:style/TextAppearance.Small">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="addressDetail" parent="@android:style/TextAppearance.Medium">
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:textIsSelectable">true</item>
+    </style>
+
+    <style name="contactNameTitle" parent="@android:style/TextAppearance.Large">
+        <item name="android:textSize">38sp</item>
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:textIsSelectable">true</item>
+    </style>
+
+    <style name="addressButton"/>
+
+</resources>
diff --git a/samples/training/ContactsList/res/xml/searchable_contacts.xml b/samples/training/ContactsList/res/xml/searchable_contacts.xml
new file mode 100644
index 0000000..ce52eec
--- /dev/null
+++ b/samples/training/ContactsList/res/xml/searchable_contacts.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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.
+-->
+
+<!-- Define a searchable configuration. See the docs for more information:
+     http://developer.android.com/guide/topics/search/searchable-config.html -->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+            android:label="@string/app_name"
+            android:inputType="textPersonName"
+            android:hint="@string/search_hint"/>
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailActivity.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailActivity.java
new file mode 100644
index 0000000..d53017f
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 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.contactslist.ui;
+
+import android.annotation.TargetApi;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.NavUtils;
+import android.view.MenuItem;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.util.Utils;
+
+/**
+ * This class defines a simple FragmentActivity as the parent of {@link ContactDetailFragment}.
+ */
+public class ContactDetailActivity extends FragmentActivity {
+    // Defines a tag for identifying the single fragment that this activity holds
+    private static final String TAG = "ContactDetailActivity";
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (BuildConfig.DEBUG) {
+            // Enable strict mode checks when in debug modes
+            Utils.enableStrictMode();
+        }
+        super.onCreate(savedInstanceState);
+
+        // This activity expects to receive an intent that contains the uri of a contact
+        if (getIntent() != null) {
+
+            // For OS versions honeycomb and higher use action bar
+            if (Utils.hasHoneycomb()) {
+                // Enables action bar "up" navigation
+                getActionBar().setDisplayHomeAsUpEnabled(true);
+            }
+
+            // Fetch the data Uri from the intent provided to this activity
+            final Uri uri = getIntent().getData();
+
+            // Checks to see if fragment has already been added, otherwise adds a new
+            // ContactDetailFragment with the Uri provided in the intent
+            if (getSupportFragmentManager().findFragmentByTag(TAG) == null) {
+                final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
+                // Adds a newly created ContactDetailFragment that is instantiated with the
+                // data Uri
+                ft.add(android.R.id.content, ContactDetailFragment.newInstance(uri), TAG);
+                ft.commit();
+            }
+        } else {
+            // No intent provided, nothing to do so finish()
+            finish();
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                // Tapping on top left ActionBar icon navigates "up" to hierarchical parent screen.
+                // The parent is defined in the AndroidManifest entry for this activity via the
+                // parentActivityName attribute (and via meta-data tag for OS versions before API
+                // Level 16). See the "Tasks and Back Stack" guide for more information:
+                // http://developer.android.com/guide/components/tasks-and-back-stack.html
+                NavUtils.navigateUpFromSameTask(this);
+                return true;
+        }
+        // Otherwise, pass the item to the super implementation for handling, as described in the
+        // documentation.
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailFragment.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailFragment.java
new file mode 100644
index 0000000..94d477c
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactDetailFragment.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2013 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.contactslist.ui;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.Photo;
+import android.provider.ContactsContract.Data;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.util.DisplayMetrics;
+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.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.R;
+import com.example.android.contactslist.util.ImageLoader;
+import com.example.android.contactslist.util.Utils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * This fragment displays details of a specific contact from the contacts provider. It shows the
+ * contact's display photo, name and all its mailing addresses. You can also modify this fragment
+ * to show other information, such as phone numbers, email addresses and so forth.
+ *
+ * This fragment appears full-screen in an activity on devices with small screen sizes, and as
+ * part of a two-pane layout on devices with larger screens, alongside the
+ * {@link ContactsListFragment}.
+ *
+ * To create an instance of this fragment, use the factory method
+ * {@link ContactDetailFragment#newInstance(android.net.Uri)}, passing as an argument the contact
+ * Uri for the contact you want to display.
+ */
+public class ContactDetailFragment extends Fragment implements
+        LoaderManager.LoaderCallbacks<Cursor> {
+
+    public static final String EXTRA_CONTACT_URI =
+            "com.example.android.contactslist.ui.EXTRA_CONTACT_URI";
+
+    // Defines a tag for identifying log entries
+    private static final String TAG = "ContactDetailFragment";
+
+    // The geo Uri scheme prefix, used with Intent.ACTION_VIEW to form a geographical address
+    // intent that will trigger available apps to handle viewing a location (such as Maps)
+    private static final String GEO_URI_SCHEME_PREFIX = "geo:0,0?q=";
+
+    // Whether or not this fragment is showing in a two pane layout
+    private boolean mIsTwoPaneLayout;
+
+    private Uri mContactUri; // Stores the contact Uri for this fragment instance
+    private ImageLoader mImageLoader; // Handles loading the contact image in a background thread
+
+    // Used to store references to key views, layouts and menu items as these need to be updated
+    // in multiple methods throughout this class.
+    private ImageView mImageView;
+    private LinearLayout mDetailsLayout;
+    private TextView mEmptyView;
+    private TextView mContactName;
+    private MenuItem mEditContactMenuItem;
+
+    /**
+     * Factory method to generate a new instance of the fragment given a contact Uri. A factory
+     * method is preferable to simply using the constructor as it handles creating the bundle and
+     * setting the bundle as an argument.
+     *
+     * @param contactUri The contact Uri to load
+     * @return A new instance of {@link ContactDetailFragment}
+     */
+    public static ContactDetailFragment newInstance(Uri contactUri) {
+        // Create new instance of this fragment
+        final ContactDetailFragment fragment = new ContactDetailFragment();
+
+        // Create and populate the args bundle
+        final Bundle args = new Bundle();
+        args.putParcelable(EXTRA_CONTACT_URI, contactUri);
+
+        // Assign the args bundle to the new fragment
+        fragment.setArguments(args);
+
+        // Return fragment
+        return fragment;
+    }
+
+    /**
+     * Fragments require an empty constructor.
+     */
+    public ContactDetailFragment() {}
+
+    /**
+     * Sets the contact that this Fragment displays, or clears the display if the contact argument
+     * is null. This will re-initialize all the views and start the queries to the system contacts
+     * provider to populate the contact information.
+     *
+     * @param contactLookupUri The contact lookup Uri to load and display in this fragment. Passing
+     *                         null is valid and the fragment will display a message that no
+     *                         contact is currently selected instead.
+     */
+    public void setContact(Uri contactLookupUri) {
+
+        // In version 3.0 and later, stores the provided contact lookup Uri in a class field. This
+        // Uri is then used at various points in this class to map to the provided contact.
+        if (Utils.hasHoneycomb()) {
+            mContactUri = contactLookupUri;
+        } else {
+            // For versions earlier than Android 3.0, stores a contact Uri that's constructed from
+            // contactLookupUri. Later on, the resulting Uri is combined with
+            // Contacts.Data.CONTENT_DIRECTORY to map to the provided contact. It's done
+            // differently for these earlier versions because Contacts.Data.CONTENT_DIRECTORY works
+            // differently for Android versions before 3.0.
+            mContactUri = Contacts.lookupContact(getActivity().getContentResolver(),
+                    contactLookupUri);
+        }
+
+        // If the Uri contains data, load the contact's image and load contact details.
+        if (contactLookupUri != null) {
+            // Asynchronously loads the contact image
+            mImageLoader.loadImage(mContactUri, mImageView);
+
+            // Shows the contact photo ImageView and hides the empty view
+            mImageView.setVisibility(View.VISIBLE);
+            mEmptyView.setVisibility(View.GONE);
+
+            // Shows the edit contact action/menu item
+            if (mEditContactMenuItem != null) {
+                mEditContactMenuItem.setVisible(true);
+            }
+
+            // Starts two queries to to retrieve contact information from the Contacts Provider.
+            // restartLoader() is used instead of initLoader() as this method may be called
+            // multiple times.
+            getLoaderManager().restartLoader(ContactDetailQuery.QUERY_ID, null, this);
+            getLoaderManager().restartLoader(ContactAddressQuery.QUERY_ID, null, this);
+        } else {
+            // If contactLookupUri is null, then the method was called when no contact was selected
+            // in the contacts list. This should only happen in a two-pane layout when the user
+            // hasn't yet selected a contact. Don't display an image for the contact, and don't
+            // account for the view's space in the layout. Turn on the TextView that appears when
+            // the layout is empty, and set the contact name to the empty string. Turn off any menu
+            // items that are visible.
+            mImageView.setVisibility(View.GONE);
+            mEmptyView.setVisibility(View.VISIBLE);
+            mDetailsLayout.removeAllViews();
+            if (mContactName != null) {
+                mContactName.setText("");
+            }
+            if (mEditContactMenuItem != null) {
+                mEditContactMenuItem.setVisible(false);
+            }
+        }
+    }
+
+    /**
+     * When the Fragment is first created, this callback is invoked. It initializes some key
+     * class fields.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Check if this fragment is part of a two pane set up or a single pane
+        mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
+
+        // Let this fragment contribute menu items
+        setHasOptionsMenu(true);
+
+        /*
+         * The ImageLoader takes care of loading and resizing images asynchronously into the
+         * ImageView. More thorough sample code demonstrating background image loading as well as
+         * details on how it works can be found in the following Android Training class:
+         * http://developer.android.com/training/displaying-bitmaps/
+         */
+        mImageLoader = new ImageLoader(getActivity(), getLargestScreenDimension()) {
+            @Override
+            protected Bitmap processBitmap(Object data) {
+                // This gets called in a background thread and passed the data from
+                // ImageLoader.loadImage().
+                return loadContactPhoto((Uri) data, getImageSize());
+
+            }
+        };
+
+        // Set a placeholder loading image for the image loader
+        mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_180_holo_light);
+
+        // Tell the image loader to set the image directly when it's finished loading
+        // rather than fading in
+        mImageLoader.setImageFadeIn(false);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+
+        // Inflates the main layout to be used by this fragment
+        final View detailView =
+                inflater.inflate(R.layout.contact_detail_fragment, container, false);
+
+        // Gets handles to view objects in the layout
+        mImageView = (ImageView) detailView.findViewById(R.id.contact_image);
+        mDetailsLayout = (LinearLayout) detailView.findViewById(R.id.contact_details_layout);
+        mEmptyView = (TextView) detailView.findViewById(android.R.id.empty);
+
+        if (mIsTwoPaneLayout) {
+            // If this is a two pane view, the following code changes the visibility of the contact
+            // name in details. For a one-pane view, the contact name is displayed as a title.
+            mContactName = (TextView) detailView.findViewById(R.id.contact_name);
+            mContactName.setVisibility(View.VISIBLE);
+        }
+
+        return detailView;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        // If not being created from a previous state
+        if (savedInstanceState == null) {
+            // Sets the argument extra as the currently displayed contact
+            setContact(getArguments() != null ?
+                    (Uri) getArguments().getParcelable(EXTRA_CONTACT_URI) : null);
+        } else {
+            // If being recreated from a saved state, sets the contact from the incoming
+            // savedInstanceState Bundle
+            setContact((Uri) savedInstanceState.getParcelable(EXTRA_CONTACT_URI));
+        }
+    }
+
+    /**
+     * When the Fragment is being saved in order to change activity state, save the
+     * currently-selected contact.
+     */
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        // Saves the contact Uri
+        outState.putParcelable(EXTRA_CONTACT_URI, mContactUri);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            // When "edit" menu option selected
+            case R.id.menu_edit_contact:
+                // Standard system edit contact intent
+                Intent intent = new Intent(Intent.ACTION_EDIT, mContactUri);
+
+                // Because of an issue in Android 4.0 (API level 14), clicking Done or Back in the
+                // People app doesn't return the user to your app; instead, it displays the People
+                // app's contact list. A workaround, introduced in Android 4.0.3 (API level 15) is
+                // to set a special flag in the extended data for the Intent you send to the People
+                // app. The issue is does not appear in versions prior to Android 4.0. You can use
+                // the flag with any version of the People app; if the workaround isn't needed,
+                // the flag is ignored.
+                intent.putExtra("finishActivityOnSaveCompleted", true);
+
+                // Start the edit activity
+                startActivity(intent);
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+
+        // Inflates the options menu for this fragment
+        inflater.inflate(R.menu.contact_detail_menu, menu);
+
+        // Gets a handle to the "find" menu item
+        mEditContactMenuItem = menu.findItem(R.id.menu_edit_contact);
+
+        // If contactUri is null the edit menu item should be hidden, otherwise
+        // it is visible.
+        mEditContactMenuItem.setVisible(mContactUri != null);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            // Two main queries to load the required information
+            case ContactDetailQuery.QUERY_ID:
+                // This query loads main contact details, see
+                // ContactDetailQuery for more information.
+                return new CursorLoader(getActivity(), mContactUri,
+                        ContactDetailQuery.PROJECTION,
+                        null, null, null);
+            case ContactAddressQuery.QUERY_ID:
+                // This query loads contact address details, see
+                // ContactAddressQuery for more information.
+                final Uri uri = Uri.withAppendedPath(mContactUri, Contacts.Data.CONTENT_DIRECTORY);
+                return new CursorLoader(getActivity(), uri,
+                        ContactAddressQuery.PROJECTION,
+                        ContactAddressQuery.SELECTION,
+                        null, null);
+        }
+        return null;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+
+        // If this fragment was cleared while the query was running
+        // eg. from from a call like setContact(uri) then don't do
+        // anything.
+        if (mContactUri == null) {
+            return;
+        }
+
+        switch (loader.getId()) {
+            case ContactDetailQuery.QUERY_ID:
+                // Moves to the first row in the Cursor
+                if (data.moveToFirst()) {
+                    // For the contact details query, fetches the contact display name.
+                    // ContactDetailQuery.DISPLAY_NAME maps to the appropriate display
+                    // name field based on OS version.
+                    final String contactName = data.getString(ContactDetailQuery.DISPLAY_NAME);
+                    if (mIsTwoPaneLayout && mContactName != null) {
+                        // In the two pane layout, there is a dedicated TextView
+                        // that holds the contact name.
+                        mContactName.setText(contactName);
+                    } else {
+                        // In the single pane layout, sets the activity title
+                        // to the contact name. On HC+ this will be set as
+                        // the ActionBar title text.
+                        getActivity().setTitle(contactName);
+                    }
+                }
+                break;
+            case ContactAddressQuery.QUERY_ID:
+                // This query loads the contact address details. More than
+                // one contact address is possible, so move each one to a
+                // LinearLayout in a Scrollview so multiple addresses can
+                // be scrolled by the user.
+
+                // Each LinearLayout has the same LayoutParams so this can
+                // be created once and used for each address.
+                final LinearLayout.LayoutParams layoutParams =
+                        new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                                ViewGroup.LayoutParams.WRAP_CONTENT);
+
+                // Clears out the details layout first in case the details
+                // layout has addresses from a previous data load still
+                // added as children.
+                mDetailsLayout.removeAllViews();
+
+                // Loops through all the rows in the Cursor
+                if (data.moveToFirst()) {
+                    do {
+                        // Builds the address layout
+                        final LinearLayout layout = buildAddressLayout(
+                                data.getInt(ContactAddressQuery.TYPE),
+                                data.getString(ContactAddressQuery.LABEL),
+                                data.getString(ContactAddressQuery.ADDRESS));
+                        // Adds the new address layout to the details layout
+                        mDetailsLayout.addView(layout, layoutParams);
+                    } while (data.moveToNext());
+                } else {
+                    // If nothing found, adds an empty address layout
+                    mDetailsLayout.addView(buildEmptyAddressLayout(), layoutParams);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // Nothing to do here. The Cursor does not need to be released as it was never directly
+        // bound to anything (like an adapter).
+    }
+
+    /**
+     * Builds an empty address layout that just shows that no addresses
+     * were found for this contact.
+     *
+     * @return A LinearLayout to add to the contact details layout
+     */
+    private LinearLayout buildEmptyAddressLayout() {
+        return buildAddressLayout(0, null, null);
+    }
+
+    /**
+     * Builds an address LinearLayout based on address information from the Contacts Provider.
+     * Each address for the contact gets its own LinearLayout object; for example, if the contact
+     * has three postal addresses, then 3 LinearLayouts are generated.
+     *
+     * @param addressType From
+     * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#TYPE}
+     * @param addressTypeLabel From
+     * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#LABEL}
+     * @param address From
+     * {@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#FORMATTED_ADDRESS}
+     * @return A LinearLayout to add to the contact details layout,
+     *         populated with the provided address details.
+     */
+    private LinearLayout buildAddressLayout(int addressType, String addressTypeLabel,
+            final String address) {
+
+        // Inflates the address layout
+        final LinearLayout addressLayout =
+                (LinearLayout) LayoutInflater.from(getActivity()).inflate(
+                        R.layout.contact_detail_item, mDetailsLayout, false);
+
+        // Gets handles to the view objects in the layout
+        final TextView headerTextView =
+                (TextView) addressLayout.findViewById(R.id.contact_detail_header);
+        final TextView addressTextView =
+                (TextView) addressLayout.findViewById(R.id.contact_detail_item);
+        final ImageButton viewAddressButton =
+                (ImageButton) addressLayout.findViewById(R.id.button_view_address);
+
+        // If there's no addresses for the contact, shows the empty view and message, and hides the
+        // header and button.
+        if (addressTypeLabel == null && addressType == 0) {
+            headerTextView.setVisibility(View.GONE);
+            viewAddressButton.setVisibility(View.GONE);
+            addressTextView.setText(R.string.no_address);
+        } else {
+            // Gets postal address label type
+            CharSequence label =
+                    StructuredPostal.getTypeLabel(getResources(), addressType, addressTypeLabel);
+
+            // Sets TextView objects in the layout
+            headerTextView.setText(label);
+            addressTextView.setText(address);
+
+            // Defines an onClickListener object for the address button
+            viewAddressButton.setOnClickListener(new View.OnClickListener() {
+                // Defines what to do when users click the address button
+                @Override
+                public void onClick(View view) {
+
+                    final Intent viewIntent =
+                            new Intent(Intent.ACTION_VIEW, constructGeoUri(address));
+
+                    // A PackageManager instance is needed to verify that there's a default app
+                    // that handles ACTION_VIEW and a geo Uri.
+                    final PackageManager packageManager = getActivity().getPackageManager();
+
+                    // Checks for an activity that can handle this intent. Preferred in this
+                    // case over Intent.createChooser() as it will still let the user choose
+                    // a default (or use a previously set default) for geo Uris.
+                    if (packageManager.resolveActivity(
+                            viewIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
+                        startActivity(viewIntent);
+                    } else {
+                        // If no default is found, displays a message that no activity can handle
+                        // the view button.
+                        Toast.makeText(getActivity(),
+                                R.string.no_intent_found, Toast.LENGTH_SHORT).show();
+                    }
+                }
+            });
+
+        }
+        return addressLayout;
+    }
+
+    /**
+     * Constructs a geo scheme Uri from a postal address.
+     *
+     * @param postalAddress A postal address.
+     * @return the geo:// Uri for the postal address.
+     */
+    private Uri constructGeoUri(String postalAddress) {
+        // Concatenates the geo:// prefix to the postal address. The postal address must be
+        // converted to Uri format and encoded for special characters.
+        return Uri.parse(GEO_URI_SCHEME_PREFIX + Uri.encode(postalAddress));
+    }
+
+    /**
+     * Fetches the width or height of the screen in pixels, whichever is larger. This is used to
+     * set a maximum size limit on the contact photo that is retrieved from the Contacts Provider.
+     * This limit prevents the app from trying to decode and load an image that is much larger than
+     * the available screen area.
+     *
+     * @return The largest screen dimension in pixels.
+     */
+    private int getLargestScreenDimension() {
+        // Gets a DisplayMetrics object, which is used to retrieve the display's pixel height and
+        // width
+        final DisplayMetrics displayMetrics = new DisplayMetrics();
+
+        // Retrieves a displayMetrics object for the device's default display
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+        final int height = displayMetrics.heightPixels;
+        final int width = displayMetrics.widthPixels;
+
+        // Returns the larger of the two values
+        return height > width ? height : width;
+    }
+
+    /**
+     * Decodes and returns the contact's thumbnail image.
+     * @param contactUri The Uri of the contact containing the image.
+     * @param imageSize The desired target width and height of the output image in pixels.
+     * @return If a thumbnail image exists for the contact, a Bitmap image, otherwise null.
+     */
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private Bitmap loadContactPhoto(Uri contactUri, int imageSize) {
+
+        // Ensures the Fragment is still added to an activity. As this method is called in a
+        // background thread, there's the possibility the Fragment is no longer attached and
+        // added to an activity. If so, no need to spend resources loading the contact photo.
+        if (!isAdded() || getActivity() == null) {
+            return null;
+        }
+
+        // Instantiates a ContentResolver for retrieving the Uri of the image
+        final ContentResolver contentResolver = getActivity().getContentResolver();
+
+        // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the
+        // ContentResolver can return an AssetFileDescriptor for the file.
+        AssetFileDescriptor afd = null;
+
+        if (Utils.hasICS()) {
+            // On platforms running Android 4.0 (API version 14) and later, a high resolution image
+            // is available from Photo.DISPLAY_PHOTO.
+            try {
+                // Constructs the content Uri for the image
+                Uri displayImageUri = Uri.withAppendedPath(contactUri, Photo.DISPLAY_PHOTO);
+
+                // Retrieves an AssetFileDescriptor from the Contacts Provider, using the
+                // constructed Uri
+                afd = contentResolver.openAssetFileDescriptor(displayImageUri, "r");
+                // If the file exists
+                if (afd != null) {
+                    // Reads and decodes the file to a Bitmap and scales it to the desired size
+                    return ImageLoader.decodeSampledBitmapFromDescriptor(
+                            afd.getFileDescriptor(), imageSize, imageSize);
+                }
+            } catch (FileNotFoundException e) {
+                // Catches file not found exceptions
+                if (BuildConfig.DEBUG) {
+                    // Log debug message, this is not an error message as this exception is thrown
+                    // when a contact is legitimately missing a contact photo (which will be quite
+                    // frequently in a long contacts list).
+                    Log.d(TAG, "Contact photo not found for contact " + contactUri.toString()
+                            + ": " + e.toString());
+                }
+            } finally {
+                // Once the decode is complete, this closes the file. You must do this each time
+                // you access an AssetFileDescriptor; otherwise, every image load you do will open
+                // a new descriptor.
+                if (afd != null) {
+                    try {
+                        afd.close();
+                    } catch (IOException e) {
+                        // Closing a file descriptor might cause an IOException if the file is
+                        // already closed. Nothing extra is needed to handle this.
+                    }
+                }
+            }
+        }
+
+        // If the platform version is less than Android 4.0 (API Level 14), use the only available
+        // image URI, which points to a normal-sized image.
+        try {
+            // Constructs the image Uri from the contact Uri and the directory twig from the
+            // Contacts.Photo table
+            Uri imageUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
+
+            // Retrieves an AssetFileDescriptor from the Contacts Provider, using the constructed
+            // Uri
+            afd = getActivity().getContentResolver().openAssetFileDescriptor(imageUri, "r");
+
+            // If the file exists
+            if (afd != null) {
+                // Reads the image from the file, decodes it, and scales it to the available screen
+                // area
+                return ImageLoader.decodeSampledBitmapFromDescriptor(
+                        afd.getFileDescriptor(), imageSize, imageSize);
+            }
+        } catch (FileNotFoundException e) {
+            // Catches file not found exceptions
+            if (BuildConfig.DEBUG) {
+                // Log debug message, this is not an error message as this exception is thrown
+                // when a contact is legitimately missing a contact photo (which will be quite
+                // frequently in a long contacts list).
+                Log.d(TAG, "Contact photo not found for contact " + contactUri.toString()
+                        + ": " + e.toString());
+            }
+        } finally {
+            // Once the decode is complete, this closes the file. You must do this each time you
+            // access an AssetFileDescriptor; otherwise, every image load you do will open a new
+            // descriptor.
+            if (afd != null) {
+                try {
+                    afd.close();
+                } catch (IOException e) {
+                    // Closing a file descriptor might cause an IOException if the file is
+                    // already closed. Ignore this.
+                }
+            }
+        }
+
+        // If none of the case selectors match, returns null.
+        return null;
+    }
+
+    /**
+     * This interface defines constants used by contact retrieval queries.
+     */
+    public interface ContactDetailQuery {
+        // A unique query ID to distinguish queries being run by the
+        // LoaderManager.
+        final static int QUERY_ID = 1;
+
+        // The query projection (columns to fetch from the provider)
+        @SuppressLint("InlinedApi")
+        final static String[] PROJECTION = {
+                Contacts._ID,
+                Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
+        };
+
+        // The query column numbers which map to each value in the projection
+        final static int ID = 0;
+        final static int DISPLAY_NAME = 1;
+    }
+
+    /**
+     * This interface defines constants used by address retrieval queries.
+     */
+    public interface ContactAddressQuery {
+        // A unique query ID to distinguish queries being run by the
+        // LoaderManager.
+        final static int QUERY_ID = 2;
+
+        // The query projection (columns to fetch from the provider)
+        final static String[] PROJECTION = {
+                StructuredPostal._ID,
+                StructuredPostal.FORMATTED_ADDRESS,
+                StructuredPostal.TYPE,
+                StructuredPostal.LABEL,
+        };
+
+        // The query selection criteria. In this case matching against the
+        // StructuredPostal content mime type.
+        final static String SELECTION =
+                Data.MIMETYPE + "='" + StructuredPostal.CONTENT_ITEM_TYPE + "'";
+
+        // The query column numbers which map to each value in the projection
+        final static int ID = 0;
+        final static int ADDRESS = 1;
+        final static int TYPE = 2;
+        final static int LABEL = 3;
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListActivity.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListActivity.java
new file mode 100644
index 0000000..3a6cab2
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2013 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.contactslist.ui;
+
+import android.app.SearchManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.R;
+import com.example.android.contactslist.util.Utils;
+
+/**
+ * FragmentActivity to hold the main {@link ContactsListFragment}. On larger screen devices which
+ * can fit two panes also load {@link ContactDetailFragment}.
+ */
+public class ContactsListActivity extends FragmentActivity implements
+        ContactsListFragment.OnContactsInteractionListener {
+
+    // Defines a tag for identifying log entries
+    private static final String TAG = "ContactsListActivity";
+
+    private ContactDetailFragment mContactDetailFragment;
+
+    // If true, this is a larger screen device which fits two panes
+    private boolean isTwoPaneLayout;
+
+    // True if this activity instance is a search result view (used on pre-HC devices that load
+    // search results in a separate instance of the activity rather than loading results in-line
+    // as the query is typed.
+    private boolean isSearchResultView = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (BuildConfig.DEBUG) {
+            Utils.enableStrictMode();
+        }
+        super.onCreate(savedInstanceState);
+
+        // Set main content view. On smaller screen devices this is a single pane view with one
+        // fragment. One larger screen devices this is a two pane view with two fragments.
+        setContentView(R.layout.activity_main);
+
+        // Check if two pane bool is set based on resource directories
+        isTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
+
+        // Check if this activity instance has been triggered as a result of a search query. This
+        // will only happen on pre-HC OS versions as from HC onward search is carried out using
+        // an ActionBar SearchView which carries out the search in-line without loading a new
+        // Activity.
+        if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
+
+            // Fetch query from intent and notify the fragment that it should display search
+            // results instead of all contacts.
+            String searchQuery = getIntent().getStringExtra(SearchManager.QUERY);
+            ContactsListFragment mContactsListFragment = (ContactsListFragment)
+                    getSupportFragmentManager().findFragmentById(R.id.contact_list);
+
+            // This flag notes that the Activity is doing a search, and so the result will be
+            // search results rather than all contacts. This prevents the Activity and Fragment
+            // from trying to a search on search results.
+            isSearchResultView = true;
+            mContactsListFragment.setSearchQuery(searchQuery);
+
+            // Set special title for search results
+            String title = getString(R.string.contacts_list_search_results_title, searchQuery);
+            setTitle(title);
+        }
+
+        if (isTwoPaneLayout) {
+            // If two pane layout, locate the contact detail fragment
+            mContactDetailFragment = (ContactDetailFragment)
+                    getSupportFragmentManager().findFragmentById(R.id.contact_detail);
+        }
+    }
+
+    /**
+     * This interface callback lets the main contacts list fragment notify
+     * this activity that a contact has been selected.
+     *
+     * @param contactUri The contact Uri to the selected contact.
+     */
+    @Override
+    public void onContactSelected(Uri contactUri) {
+        if (isTwoPaneLayout && mContactDetailFragment != null) {
+            // If two pane layout then update the detail fragment to show the selected contact
+            mContactDetailFragment.setContact(contactUri);
+        } else {
+            // Otherwise single pane layout, start a new ContactDetailActivity with
+            // the contact Uri
+            Intent intent = new Intent(this, ContactDetailActivity.class);
+            intent.setData(contactUri);
+            startActivity(intent);
+        }
+    }
+
+    /**
+     * This interface callback lets the main contacts list fragment notify
+     * this activity that a contact is no longer selected.
+     */
+    @Override
+    public void onSelectionCleared() {
+        if (isTwoPaneLayout && mContactDetailFragment != null) {
+            mContactDetailFragment.setContact(null);
+        }
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        // Don't allow another search if this activity instance is already showing
+        // search results. Only used pre-HC.
+        return !isSearchResultView && super.onSearchRequested();
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListFragment.java b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListFragment.java
new file mode 100644
index 0000000..c3a8a66
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/ui/ContactsListFragment.java
@@ -0,0 +1,935 @@
+/*
+ * Copyright (C) 2013 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.contactslist.ui;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.Photo;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.widget.CursorAdapter;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+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.AbsListView;
+import android.widget.AdapterView;
+import android.widget.AlphabetIndexer;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+import android.widget.SearchView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+import com.example.android.contactslist.BuildConfig;
+import com.example.android.contactslist.R;
+import com.example.android.contactslist.util.ImageLoader;
+import com.example.android.contactslist.util.Utils;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * This fragment displays a list of contacts stored in the Contacts Provider. Each item in the list
+ * shows the contact's thumbnail photo and display name. On devices with large screens, this
+ * fragment's UI appears as part of a two-pane layout, along with the UI of
+ * {@link ContactDetailFragment}. On smaller screens, this fragment's UI appears as a single pane.
+ *
+ * This Fragment retrieves contacts based on a search string. If the user doesn't enter a search
+ * string, then the list contains all the contacts in the Contacts Provider. If the user enters a
+ * search string, then the list contains only those contacts whose data matches the string. The
+ * Contacts Provider itself controls the matching algorithm, which is a "substring" search: if the
+ * search string is a substring of any of the contacts data, then there is a match.
+ *
+ * On newer API platforms, the search is implemented in a SearchView in the ActionBar; as the user
+ * types the search string, the list automatically refreshes to display results ("type to filter").
+ * On older platforms, the user must enter the full string and trigger the search. In response, the
+ * trigger starts a new Activity which loads a fresh instance of this fragment. The resulting UI
+ * displays the filtered list and disables the search feature to prevent furthering searching.
+ */
+public class ContactsListFragment extends ListFragment implements
+        AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> {
+
+    // Defines a tag for identifying log entries
+    private static final String TAG = "ContactsListFragment";
+
+    // Bundle key for saving previously selected search result item
+    private static final String STATE_PREVIOUSLY_SELECTED_KEY =
+            "com.example.android.contactslist.ui.SELECTED_ITEM";
+
+    private ContactsAdapter mAdapter; // The main query adapter
+    private ImageLoader mImageLoader; // Handles loading the contact image in a background thread
+    private String mSearchTerm; // Stores the current search query term
+
+    // Contact selected listener that allows the activity holding this fragment to be notified of
+    // a contact being selected
+    private OnContactsInteractionListener mOnContactSelectedListener;
+
+    // Stores the previously selected search item so that on a configuration change the same item
+    // can be reselected again
+    private int mPreviouslySelectedSearchItem = 0;
+
+    // Whether or not the search query has changed since the last time the loader was refreshed
+    private boolean mSearchQueryChanged;
+
+    // Whether or not this fragment is showing in a two-pane layout
+    private boolean mIsTwoPaneLayout;
+
+    // Whether or not this is a search result view of this fragment, only used on pre-honeycomb
+    // OS versions as search results are shown in-line via Action Bar search from honeycomb onward
+    private boolean mIsSearchResultView = false;
+
+    /**
+     * Fragments require an empty constructor.
+     */
+    public ContactsListFragment() {}
+
+    /**
+     * In platform versions prior to Android 3.0, the ActionBar and SearchView are not supported,
+     * and the UI gets the search string from an EditText. However, the fragment doesn't allow
+     * another search when search results are already showing. This would confuse the user, because
+     * the resulting search would re-query the Contacts Provider instead of searching the listed
+     * results. This method sets the search query and also a boolean that tracks if this Fragment
+     * should be displayed as a search result view or not.
+     *
+     * @param query The contacts search query.
+     */
+    public void setSearchQuery(String query) {
+        if (TextUtils.isEmpty(query)) {
+            mIsSearchResultView = false;
+        } else {
+            mSearchTerm = query;
+            mIsSearchResultView = true;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Check if this fragment is part of a two-pane set up or a single pane by reading a
+        // boolean from the application resource directories. This lets allows us to easily specify
+        // which screen sizes should use a two-pane layout by setting this boolean in the
+        // corresponding resource size-qualified directory.
+        mIsTwoPaneLayout = getResources().getBoolean(R.bool.has_two_panes);
+
+        // Let this fragment contribute menu items
+        setHasOptionsMenu(true);
+
+        // Create the main contacts adapter
+        mAdapter = new ContactsAdapter(getActivity());
+
+        if (savedInstanceState != null) {
+            // If we're restoring state after this fragment was recreated then
+            // retrieve previous search term and previously selected search
+            // result.
+            mSearchTerm = savedInstanceState.getString(SearchManager.QUERY);
+            mPreviouslySelectedSearchItem =
+                    savedInstanceState.getInt(STATE_PREVIOUSLY_SELECTED_KEY, 0);
+        }
+
+        /*
+         * An ImageLoader object loads and resizes an image in the background and binds it to the
+         * QuickContactBadge in each item layout of the ListView. ImageLoader implements memory
+         * caching for each image, which substantially improves refreshes of the ListView as the
+         * user scrolls through it.
+         *
+         * To learn more about downloading images asynchronously and caching the results, read the
+         * Android training class Displaying Bitmaps Efficiently.
+         *
+         * http://developer.android.com/training/displaying-bitmaps/
+         */
+        mImageLoader = new ImageLoader(getActivity(), getListPreferredItemHeight()) {
+            @Override
+            protected Bitmap processBitmap(Object data) {
+                // This gets called in a background thread and passed the data from
+                // ImageLoader.loadImage().
+                return loadContactPhotoThumbnail((String) data, getImageSize());
+            }
+        };
+
+        // Set a placeholder loading image for the image loader
+        mImageLoader.setLoadingImage(R.drawable.ic_contact_picture_holo_light);
+
+        // Add a cache to the image loader
+        mImageLoader.addImageCache(getActivity().getSupportFragmentManager(), 0.1f);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Inflate the list fragment layout
+        return inflater.inflate(R.layout.contact_list_fragment, container, false);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Set up ListView, assign adapter and set some listeners. The adapter was previously
+        // created in onCreate().
+        setListAdapter(mAdapter);
+        getListView().setOnItemClickListener(this);
+        getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
+                // Pause image loader to ensure smoother scrolling when flinging
+                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+                    mImageLoader.setPauseWork(true);
+                } else {
+                    mImageLoader.setPauseWork(false);
+                }
+            }
+
+            @Override
+            public void onScroll(AbsListView absListView, int i, int i1, int i2) {}
+        });
+
+        if (mIsTwoPaneLayout) {
+            // In a two-pane layout, set choice mode to single as there will be two panes
+            // when an item in the ListView is selected it should remain highlighted while
+            // the content shows in the second pane.
+            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        }
+
+        // If there's a previously selected search item from a saved state then don't bother
+        // initializing the loader as it will be restarted later when the query is populated into
+        // the action bar search view (see onQueryTextChange() in onCreateOptionsMenu()).
+        if (mPreviouslySelectedSearchItem == 0) {
+            // Initialize the loader, and create a loader identified by ContactsQuery.QUERY_ID
+            getLoaderManager().initLoader(ContactsQuery.QUERY_ID, null, this);
+        }
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            // Assign callback listener which the holding activity must implement. This is used
+            // so that when a contact item is interacted with (selected by the user) the holding
+            // activity will be notified and can take further action such as populating the contact
+            // detail pane (if in multi-pane layout) or starting a new activity with the contact
+            // details (single pane layout).
+            mOnContactSelectedListener = (OnContactsInteractionListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement OnContactsInteractionListener");
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        // In the case onPause() is called during a fling the image loader is
+        // un-paused to let any remaining background work complete.
+        mImageLoader.setPauseWork(false);
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+        // Gets the Cursor object currently bound to the ListView
+        final Cursor cursor = mAdapter.getCursor();
+
+        // Moves to the Cursor row corresponding to the ListView item that was clicked
+        cursor.moveToPosition(position);
+
+        // Creates a contact lookup Uri from contact ID and lookup_key
+        final Uri uri = Contacts.getLookupUri(
+                cursor.getLong(ContactsQuery.ID),
+                cursor.getString(ContactsQuery.LOOKUP_KEY));
+
+        // Notifies the parent activity that the user selected a contact. In a two-pane layout, the
+        // parent activity loads a ContactDetailFragment that displays the details for the selected
+        // contact. In a single-pane layout, the parent activity starts a new activity that
+        // displays contact details in its own Fragment.
+        mOnContactSelectedListener.onContactSelected(uri);
+
+        // If two-pane layout sets the selected item to checked so it remains highlighted. In a
+        // single-pane layout a new activity is started so this is not needed.
+        if (mIsTwoPaneLayout) {
+            getListView().setItemChecked(position, true);
+        }
+    }
+
+    /**
+     * Called when ListView selection is cleared, for example
+     * when search mode is finished and the currently selected
+     * contact should no longer be selected.
+     */
+    private void onSelectionCleared() {
+        // Uses callback to notify activity this contains this fragment
+        mOnContactSelectedListener.onSelectionCleared();
+
+        // Clears currently checked item
+        getListView().clearChoices();
+    }
+
+    // This method uses APIs from newer OS versions than the minimum that this app supports. This
+    // annotation tells Android lint that they are properly guarded so they won't run on older OS
+    // versions and can be ignored by lint.
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+
+        // Inflate the menu items
+        inflater.inflate(R.menu.contact_list_menu, menu);
+        // Locate the search item
+        MenuItem searchItem = menu.findItem(R.id.menu_search);
+
+        // In versions prior to Android 3.0, hides the search item to prevent additional
+        // searches. In Android 3.0 and later, searching is done via a SearchView in the ActionBar.
+        // Since the search doesn't create a new Activity to do the searching, the menu item
+        // doesn't need to be turned off.
+        if (mIsSearchResultView) {
+            searchItem.setVisible(false);
+        }
+
+        // In version 3.0 and later, sets up and configures the ActionBar SearchView
+        if (Utils.hasHoneycomb()) {
+
+            // Retrieves the system search manager service
+            final SearchManager searchManager =
+                    (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
+
+            // Retrieves the SearchView from the search menu item
+            final SearchView searchView = (SearchView) searchItem.getActionView();
+
+            // Assign searchable info to SearchView
+            searchView.setSearchableInfo(
+                    searchManager.getSearchableInfo(getActivity().getComponentName()));
+
+            // Set listeners for SearchView
+            searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+                @Override
+                public boolean onQueryTextSubmit(String queryText) {
+                    // Nothing needs to happen when the user submits the search string
+                    return true;
+                }
+
+                @Override
+                public boolean onQueryTextChange(String newText) {
+                    // Called when the action bar search text has changed.  Updates
+                    // the search filter, and restarts the loader to do a new query
+                    // using the new search string.
+                    String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
+
+                    // Don't do anything if the filter is empty
+                    if (mSearchTerm == null && newFilter == null) {
+                        return true;
+                    }
+
+                    // Don't do anything if the new filter is the same as the current filter
+                    if (mSearchTerm != null && mSearchTerm.equals(newFilter)) {
+                        return true;
+                    }
+
+                    // Updates current filter to new filter
+                    mSearchTerm = newFilter;
+
+                    // Restarts the loader. This triggers onCreateLoader(), which builds the
+                    // necessary content Uri from mSearchTerm.
+                    mSearchQueryChanged = true;
+                    getLoaderManager().restartLoader(
+                            ContactsQuery.QUERY_ID, null, ContactsListFragment.this);
+                    return true;
+                }
+            });
+
+            if (Utils.hasICS()) {
+                // This listener added in ICS
+                searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+                    @Override
+                    public boolean onMenuItemActionExpand(MenuItem menuItem) {
+                        // Nothing to do when the action item is expanded
+                        return true;
+                    }
+
+                    @Override
+                    public boolean onMenuItemActionCollapse(MenuItem menuItem) {
+                        // When the user collapses the SearchView the current search string is
+                        // cleared and the loader restarted.
+                        if (!TextUtils.isEmpty(mSearchTerm)) {
+                            onSelectionCleared();
+                        }
+                        mSearchTerm = null;
+                        getLoaderManager().restartLoader(
+                                ContactsQuery.QUERY_ID, null, ContactsListFragment.this);
+                        return true;
+                    }
+                });
+            }
+
+            if (mSearchTerm != null) {
+                // If search term is already set here then this fragment is
+                // being restored from a saved state and the search menu item
+                // needs to be expanded and populated again.
+
+                // Stores the search term (as it will be wiped out by
+                // onQueryTextChange() when the menu item is expanded).
+                final String savedSearchTerm = mSearchTerm;
+
+                // Expands the search menu item
+                if (Utils.hasICS()) {
+                    searchItem.expandActionView();
+                }
+
+                // Sets the SearchView to the previous search string
+                searchView.setQuery(savedSearchTerm, false);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (!TextUtils.isEmpty(mSearchTerm)) {
+            // Saves the current search string
+            outState.putString(SearchManager.QUERY, mSearchTerm);
+
+            // Saves the currently selected contact
+            outState.putInt(STATE_PREVIOUSLY_SELECTED_KEY, getListView().getCheckedItemPosition());
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            // Sends a request to the People app to display the create contact screen
+            case R.id.menu_add_contact:
+                final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+                startActivity(intent);
+                break;
+            // For platforms earlier than Android 3.0, triggers the search activity
+            case R.id.menu_search:
+                if (!Utils.hasHoneycomb()) {
+                    getActivity().onSearchRequested();
+                }
+                break;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+
+        // If this is the loader for finding contacts in the Contacts Provider
+        // (the only one supported)
+        if (id == ContactsQuery.QUERY_ID) {
+            Uri contentUri;
+
+            // There are two types of searches, one which displays all contacts and
+            // one which filters contacts by a search query. If mSearchTerm is set
+            // then a search query has been entered and the latter should be used.
+
+            if (mSearchTerm == null) {
+                // Since there's no search string, use the content URI that searches the entire
+                // Contacts table
+                contentUri = ContactsQuery.CONTENT_URI;
+            } else {
+                // Since there's a search string, use the special content Uri that searches the
+                // Contacts table. The URI consists of a base Uri and the search string.
+                contentUri =
+                        Uri.withAppendedPath(ContactsQuery.FILTER_URI, Uri.encode(mSearchTerm));
+            }
+
+            // Returns a new CursorLoader for querying the Contacts table. No arguments are used
+            // for the selection clause. The search string is either encoded onto the content URI,
+            // or no contacts search string is used. The other search criteria are constants. See
+            // the ContactsQuery interface.
+            return new CursorLoader(getActivity(),
+                    contentUri,
+                    ContactsQuery.PROJECTION,
+                    ContactsQuery.SELECTION,
+                    null,
+                    ContactsQuery.SORT_ORDER);
+        }
+
+        Log.e(TAG, "onCreateLoader - incorrect ID provided (" + id + ")");
+        return null;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        // This swaps the new cursor into the adapter.
+        if (loader.getId() == ContactsQuery.QUERY_ID) {
+            mAdapter.swapCursor(data);
+
+            // If this is a two-pane layout and there is a search query then
+            // there is some additional work to do around default selected
+            // search item.
+            if (mIsTwoPaneLayout && !TextUtils.isEmpty(mSearchTerm) && mSearchQueryChanged) {
+                // Selects the first item in results, unless this fragment has
+                // been restored from a saved state (like orientation change)
+                // in which case it selects the previously selected search item.
+                if (data != null && data.moveToPosition(mPreviouslySelectedSearchItem)) {
+                    // Creates the content Uri for the previously selected contact by appending the
+                    // contact's ID to the Contacts table content Uri
+                    final Uri uri = Uri.withAppendedPath(
+                            Contacts.CONTENT_URI, String.valueOf(data.getLong(ContactsQuery.ID)));
+                    mOnContactSelectedListener.onContactSelected(uri);
+                    getListView().setItemChecked(mPreviouslySelectedSearchItem, true);
+                } else {
+                    // No results, clear selection.
+                    onSelectionCleared();
+                }
+                // Only restore from saved state one time. Next time fall back
+                // to selecting first item. If the fragment state is saved again
+                // then the currently selected item will once again be saved.
+                mPreviouslySelectedSearchItem = 0;
+                mSearchQueryChanged = false;
+            }
+        }
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        if (loader.getId() == ContactsQuery.QUERY_ID) {
+            // When the loader is being reset, clear the cursor from the adapter. This allows the
+            // cursor resources to be freed.
+            mAdapter.swapCursor(null);
+        }
+    }
+
+    /**
+     * Gets the preferred height for each item in the ListView, in pixels, after accounting for
+     * screen density. ImageLoader uses this value to resize thumbnail images to match the ListView
+     * item height.
+     *
+     * @return The preferred height in pixels, based on the current theme.
+     */
+    private int getListPreferredItemHeight() {
+        final TypedValue typedValue = new TypedValue();
+
+        // Resolve list item preferred height theme attribute into typedValue
+        getActivity().getTheme().resolveAttribute(
+                android.R.attr.listPreferredItemHeight, typedValue, true);
+
+        // Create a new DisplayMetrics object
+        final DisplayMetrics metrics = new android.util.DisplayMetrics();
+
+        // Populate the DisplayMetrics
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+        // Return theme value based on DisplayMetrics
+        return (int) typedValue.getDimension(metrics);
+    }
+
+    /**
+     * Decodes and scales a contact's image from a file pointed to by a Uri in the contact's data,
+     * and returns the result as a Bitmap. The column that contains the Uri varies according to the
+     * platform version.
+     *
+     * @param photoData For platforms prior to Android 3.0, provide the Contact._ID column value.
+     *                  For Android 3.0 and later, provide the Contact.PHOTO_THUMBNAIL_URI value.
+     * @param imageSize The desired target width and height of the output image in pixels.
+     * @return A Bitmap containing the contact's image, resized to fit the provided image size. If
+     * no thumbnail exists, returns null.
+     */
+    private Bitmap loadContactPhotoThumbnail(String photoData, int imageSize) {
+
+        // Ensures the Fragment is still added to an activity. As this method is called in a
+        // background thread, there's the possibility the Fragment is no longer attached and
+        // added to an activity. If so, no need to spend resources loading the contact photo.
+        if (!isAdded() || getActivity() == null) {
+            return null;
+        }
+
+        // Instantiates an AssetFileDescriptor. Given a content Uri pointing to an image file, the
+        // ContentResolver can return an AssetFileDescriptor for the file.
+        AssetFileDescriptor afd = null;
+
+        // This "try" block catches an Exception if the file descriptor returned from the Contacts
+        // Provider doesn't point to an existing file.
+        try {
+            Uri thumbUri;
+            // If Android 3.0 or later, converts the Uri passed as a string to a Uri object.
+            if (Utils.hasHoneycomb()) {
+                thumbUri = Uri.parse(photoData);
+            } else {
+                // For versions prior to Android 3.0, appends the string argument to the content
+                // Uri for the Contacts table.
+                final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_URI, photoData);
+
+                // Appends the content Uri for the Contacts.Photo table to the previously
+                // constructed contact Uri to yield a content URI for the thumbnail image
+                thumbUri = Uri.withAppendedPath(contactUri, Photo.CONTENT_DIRECTORY);
+            }
+            // Retrieves a file descriptor from the Contacts Provider. To learn more about this
+            // feature, read the reference documentation for
+            // ContentResolver#openAssetFileDescriptor.
+            afd = getActivity().getContentResolver().openAssetFileDescriptor(thumbUri, "r");
+
+            // Gets a FileDescriptor from the AssetFileDescriptor. A BitmapFactory object can
+            // decode the contents of a file pointed to by a FileDescriptor into a Bitmap.
+            FileDescriptor fileDescriptor = afd.getFileDescriptor();
+
+            if (fileDescriptor != null) {
+                // Decodes a Bitmap from the image pointed to by the FileDescriptor, and scales it
+                // to the specified width and height
+                return ImageLoader.decodeSampledBitmapFromDescriptor(
+                        fileDescriptor, imageSize, imageSize);
+            }
+        } catch (FileNotFoundException e) {
+            // If the file pointed to by the thumbnail URI doesn't exist, or the file can't be
+            // opened in "read" mode, ContentResolver.openAssetFileDescriptor throws a
+            // FileNotFoundException.
+            if (BuildConfig.DEBUG) {
+                Log.d(TAG, "Contact photo thumbnail not found for contact " + photoData
+                        + ": " + e.toString());
+            }
+        } finally {
+            // If an AssetFileDescriptor was returned, try to close it
+            if (afd != null) {
+                try {
+                    afd.close();
+                } catch (IOException e) {
+                    // Closing a file descriptor might cause an IOException if the file is
+                    // already closed. Nothing extra is needed to handle this.
+                }
+            }
+        }
+
+        // If the decoding failed, returns null
+        return null;
+    }
+
+    /**
+     * This is a subclass of CursorAdapter that supports binding Cursor columns to a view layout.
+     * If those items are part of search results, the search string is marked by highlighting the
+     * query text. An {@link AlphabetIndexer} is used to allow quicker navigation up and down the
+     * ListView.
+     */
+    private class ContactsAdapter extends CursorAdapter implements SectionIndexer {
+        private LayoutInflater mInflater; // Stores the layout inflater
+        private AlphabetIndexer mAlphabetIndexer; // Stores the AlphabetIndexer instance
+        private TextAppearanceSpan highlightTextSpan; // Stores the highlight text appearance style
+
+        /**
+         * Instantiates a new Contacts Adapter.
+         * @param context A context that has access to the app's layout.
+         */
+        public ContactsAdapter(Context context) {
+            super(context, null, 0);
+
+            // Stores inflater for use later
+            mInflater = LayoutInflater.from(context);
+
+            // Loads a string containing the English alphabet. To fully localize the app, provide a
+            // strings.xml file in res/values-<x> directories, where <x> is a locale. In the file,
+            // define a string with android:name="alphabet" and contents set to all of the
+            // alphabetic characters in the language in their proper sort order, in upper case if
+            // applicable.
+            final String alphabet = context.getString(R.string.alphabet);
+
+            // Instantiates a new AlphabetIndexer bound to the column used to sort contact names.
+            // The cursor is left null, because it has not yet been retrieved.
+            mAlphabetIndexer = new AlphabetIndexer(null, ContactsQuery.SORT_KEY, alphabet);
+
+            // Defines a span for highlighting the part of a display name that matches the search
+            // string
+            highlightTextSpan = new TextAppearanceSpan(getActivity(), R.style.searchTextHiglight);
+        }
+
+        /**
+         * Identifies the start of the search string in the display name column of a Cursor row.
+         * E.g. If displayName was "Adam" and search query (mSearchTerm) was "da" this would
+         * return 1.
+         *
+         * @param displayName The contact display name.
+         * @return The starting position of the search string in the display name, 0-based. The
+         * method returns -1 if the string is not found in the display name, or if the search
+         * string is empty or null.
+         */
+        private int indexOfSearchQuery(String displayName) {
+            if (!TextUtils.isEmpty(mSearchTerm)) {
+                return displayName.toLowerCase(Locale.getDefault()).indexOf(
+                        mSearchTerm.toLowerCase(Locale.getDefault()));
+            }
+            return -1;
+        }
+
+        /**
+         * Overrides newView() to inflate the list item views.
+         */
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
+            // Inflates the list item layout.
+            final View itemLayout =
+                    mInflater.inflate(R.layout.contact_list_item, viewGroup, false);
+
+            // Creates a new ViewHolder in which to store handles to each view resource. This
+            // allows bindView() to retrieve stored references instead of calling findViewById for
+            // each instance of the layout.
+            final ViewHolder holder = new ViewHolder();
+            holder.text1 = (TextView) itemLayout.findViewById(android.R.id.text1);
+            holder.text2 = (TextView) itemLayout.findViewById(android.R.id.text2);
+            holder.icon = (QuickContactBadge) itemLayout.findViewById(android.R.id.icon);
+
+            // Stores the resourceHolder instance in itemLayout. This makes resourceHolder
+            // available to bindView and other methods that receive a handle to the item view.
+            itemLayout.setTag(holder);
+
+            // Returns the item layout view
+            return itemLayout;
+        }
+
+        /**
+         * Binds data from the Cursor to the provided view.
+         */
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            // Gets handles to individual view resources
+            final ViewHolder holder = (ViewHolder) view.getTag();
+
+            // For Android 3.0 and later, gets the thumbnail image Uri from the current Cursor row.
+            // For platforms earlier than 3.0, this isn't necessary, because the thumbnail is
+            // generated from the other fields in the row.
+            final String photoUri = cursor.getString(ContactsQuery.PHOTO_THUMBNAIL_DATA);
+
+            final String displayName = cursor.getString(ContactsQuery.DISPLAY_NAME);
+
+            final int startIndex = indexOfSearchQuery(displayName);
+
+            if (startIndex == -1) {
+                // If the user didn't do a search, or the search string didn't match a display
+                // name, show the display name without highlighting
+                holder.text1.setText(displayName);
+
+                if (TextUtils.isEmpty(mSearchTerm)) {
+                    // If the search search is empty, hide the second line of text
+                    holder.text2.setVisibility(View.GONE);
+                } else {
+                    // Shows a second line of text that indicates the search string matched
+                    // something other than the display name
+                    holder.text2.setVisibility(View.VISIBLE);
+                }
+            } else {
+                // If the search string matched the display name, applies a SpannableString to
+                // highlight the search string with the displayed display name
+
+                // Wraps the display name in the SpannableString
+                final SpannableString highlightedName = new SpannableString(displayName);
+
+                // Sets the span to start at the starting point of the match and end at "length"
+                // characters beyond the starting point
+                highlightedName.setSpan(highlightTextSpan, startIndex,
+                        startIndex + mSearchTerm.length(), 0);
+
+                // Binds the SpannableString to the display name View object
+                holder.text1.setText(highlightedName);
+
+                // Since the search string matched the name, this hides the secondary message
+                holder.text2.setVisibility(View.GONE);
+            }
+
+            // Processes the QuickContactBadge. A QuickContactBadge first appears as a contact's
+            // thumbnail image with styling that indicates it can be touched for additional
+            // information. When the user clicks the image, the badge expands into a dialog box
+            // containing the contact's details and icons for the built-in apps that can handle
+            // each detail type.
+
+            // Generates the contact lookup Uri
+            final Uri contactUri = Contacts.getLookupUri(
+                    cursor.getLong(ContactsQuery.ID),
+                    cursor.getString(ContactsQuery.LOOKUP_KEY));
+
+            // Binds the contact's lookup Uri to the QuickContactBadge
+            holder.icon.assignContactUri(contactUri);
+
+            // Loads the thumbnail image pointed to by photoUri into the QuickContactBadge in a
+            // background worker thread
+            mImageLoader.loadImage(photoUri, holder.icon);
+        }
+
+        /**
+         * Overrides swapCursor to move the new Cursor into the AlphabetIndex as well as the
+         * CursorAdapter.
+         */
+        @Override
+        public Cursor swapCursor(Cursor newCursor) {
+            // Update the AlphabetIndexer with new cursor as well
+            mAlphabetIndexer.setCursor(newCursor);
+            return super.swapCursor(newCursor);
+        }
+
+        /**
+         * An override of getCount that simplifies accessing the Cursor. If the Cursor is null,
+         * getCount returns zero. As a result, no test for Cursor == null is needed.
+         */
+        @Override
+        public int getCount() {
+            if (getCursor() == null) {
+                return 0;
+            }
+            return super.getCount();
+        }
+
+        /**
+         * Defines the SectionIndexer.getSections() interface.
+         */
+        @Override
+        public Object[] getSections() {
+            return mAlphabetIndexer.getSections();
+        }
+
+        /**
+         * Defines the SectionIndexer.getPositionForSection() interface.
+         */
+        @Override
+        public int getPositionForSection(int i) {
+            if (getCursor() == null) {
+                return 0;
+            }
+            return mAlphabetIndexer.getPositionForSection(i);
+        }
+
+        /**
+         * Defines the SectionIndexer.getSectionForPosition() interface.
+         */
+        @Override
+        public int getSectionForPosition(int i) {
+            if (getCursor() == null) {
+                return 0;
+            }
+            return mAlphabetIndexer.getSectionForPosition(i);
+        }
+
+        /**
+         * A class that defines fields for each resource ID in the list item layout. This allows
+         * ContactsAdapter.newView() to store the IDs once, when it inflates the layout, instead of
+         * calling findViewById in each iteration of bindView.
+         */
+        private class ViewHolder {
+            TextView text1;
+            TextView text2;
+            QuickContactBadge icon;
+        }
+    }
+
+    /**
+     * This interface must be implemented by any activity that loads this fragment. When an
+     * interaction occurs, such as touching an item from the ListView, these callbacks will
+     * be invoked to communicate the event back to the activity.
+     */
+    public interface OnContactsInteractionListener {
+        /**
+         * Called when a contact is selected from the ListView.
+         * @param contactUri The contact Uri.
+         */
+        public void onContactSelected(Uri contactUri);
+
+        /**
+         * Called when the ListView selection is cleared like when
+         * a contact search is taking place or is finishing.
+         */
+        public void onSelectionCleared();
+    }
+
+    /**
+     * This interface defines constants for the Cursor and CursorLoader, based on constants defined
+     * in the {@link android.provider.ContactsContract.Contacts} class.
+     */
+    public interface ContactsQuery {
+
+        // An identifier for the loader
+        final static int QUERY_ID = 1;
+
+        // A content URI for the Contacts table
+        final static Uri CONTENT_URI = Contacts.CONTENT_URI;
+
+        // The search/filter query Uri
+        final static Uri FILTER_URI = Contacts.CONTENT_FILTER_URI;
+
+        // The selection clause for the CursorLoader query. The search criteria defined here
+        // restrict results to contacts that have a display name and are linked to visible groups.
+        // Notice that the search on the string provided by the user is implemented by appending
+        // the search string to CONTENT_FILTER_URI.
+        @SuppressLint("InlinedApi")
+        final static String SELECTION =
+                (Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME) +
+                "<>''" + " AND " + Contacts.IN_VISIBLE_GROUP + "=1";
+
+        // The desired sort order for the returned Cursor. In Android 3.0 and later, the primary
+        // sort key allows for localization. In earlier versions. use the display name as the sort
+        // key.
+        @SuppressLint("InlinedApi")
+        final static String SORT_ORDER =
+                Utils.hasHoneycomb() ? Contacts.SORT_KEY_PRIMARY : Contacts.DISPLAY_NAME;
+
+        // The projection for the CursorLoader query. This is a list of columns that the Contacts
+        // Provider should return in the Cursor.
+        @SuppressLint("InlinedApi")
+        final static String[] PROJECTION = {
+
+                // The contact's row id
+                Contacts._ID,
+
+                // A pointer to the contact that is guaranteed to be more permanent than _ID. Given
+                // a contact's current _ID value and LOOKUP_KEY, the Contacts Provider can generate
+                // a "permanent" contact URI.
+                Contacts.LOOKUP_KEY,
+
+                // In platform version 3.0 and later, the Contacts table contains
+                // DISPLAY_NAME_PRIMARY, which either contains the contact's displayable name or
+                // some other useful identifier such as an email address. This column isn't
+                // available in earlier versions of Android, so you must use Contacts.DISPLAY_NAME
+                // instead.
+                Utils.hasHoneycomb() ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME,
+
+                // In Android 3.0 and later, the thumbnail image is pointed to by
+                // PHOTO_THUMBNAIL_URI. In earlier versions, there is no direct pointer; instead,
+                // you generate the pointer from the contact's ID value and constants defined in
+                // android.provider.ContactsContract.Contacts.
+                Utils.hasHoneycomb() ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID,
+
+                // The sort order column for the returned Cursor, used by the AlphabetIndexer
+                SORT_ORDER,
+        };
+
+        // The query column numbers which map to each value in the projection
+        final static int ID = 0;
+        final static int LOOKUP_KEY = 1;
+        final static int DISPLAY_NAME = 2;
+        final static int PHOTO_THUMBNAIL_DATA = 3;
+        final static int SORT_KEY = 4;
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageCache.java b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageCache.java
new file mode 100644
index 0000000..7e86018
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageCache.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2013 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.contactslist.util;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.util.LruCache;
+import android.util.Log;
+
+import com.example.android.contactslist.BuildConfig;
+
+/**
+ * This class holds our bitmap caches (memory and disk).
+ */
+public class ImageCache {
+    private static final String TAG = "ImageCache";
+    private LruCache<String, Bitmap> mMemoryCache;
+
+    /**
+     * Creating a new ImageCache object using the specified parameters.
+     *
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     */
+    private ImageCache(float memCacheSizePercent) {
+        init(memCacheSizePercent);
+    }
+
+    /**
+     * Find and return an existing ImageCache stored in a {@link RetainFragment}, if not found a new
+     * one is created using the supplied params and saved to a {@link RetainFragment}.
+     *
+     * @param fragmentManager The fragment manager to use when dealing with the retained fragment.
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     * @return An existing retained ImageCache object or a new one if one did not exist
+     */
+    public static ImageCache getInstance(
+            FragmentManager fragmentManager, float memCacheSizePercent) {
+
+        // Search for, or create an instance of the non-UI RetainFragment
+        final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
+
+        // See if we already have an ImageCache stored in RetainFragment
+        ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
+
+        // No existing ImageCache, create one and store it in RetainFragment
+        if (imageCache == null) {
+            imageCache = new ImageCache(memCacheSizePercent);
+            mRetainFragment.setObject(imageCache);
+        }
+
+        return imageCache;
+    }
+
+    /**
+     * Initialize the cache.
+     *
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     */
+    private void init(float memCacheSizePercent) {
+        int memCacheSize = calculateMemCacheSize(memCacheSizePercent);
+
+        // Set up memory cache
+        if (BuildConfig.DEBUG) {
+            Log.d(TAG, "Memory cache created (size = " + memCacheSize + ")");
+        }
+        mMemoryCache = new LruCache<String, Bitmap>(memCacheSize) {
+            /**
+             * Measure item size in kilobytes rather than units which is more practical
+             * for a bitmap cache
+             */
+            @Override
+            protected int sizeOf(String key, Bitmap bitmap) {
+                final int bitmapSize = getBitmapSize(bitmap) / 1024;
+                return bitmapSize == 0 ? 1 : bitmapSize;
+            }
+        };
+    }
+
+    /**
+     * Adds a bitmap to both memory and disk cache.
+     * @param data Unique identifier for the bitmap to store
+     * @param bitmap The bitmap to store
+     */
+    public void addBitmapToCache(String data, Bitmap bitmap) {
+        if (data == null || bitmap == null) {
+            return;
+        }
+
+        // Add to memory cache
+        if (mMemoryCache != null && mMemoryCache.get(data) == null) {
+            mMemoryCache.put(data, bitmap);
+        }
+    }
+
+    /**
+     * Get from memory cache.
+     *
+     * @param data Unique identifier for which item to get
+     * @return The bitmap if found in cache, null otherwise
+     */
+    public Bitmap getBitmapFromMemCache(String data) {
+        if (mMemoryCache != null) {
+            final Bitmap memBitmap = mMemoryCache.get(data);
+            if (memBitmap != null) {
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "Memory cache hit");
+                }
+                return memBitmap;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the size in bytes of a bitmap.
+     *
+     * @param bitmap The bitmap to calculate the size of.
+     * @return size of bitmap in bytes.
+     */
+    @TargetApi(12)
+    public static int getBitmapSize(Bitmap bitmap) {
+        if (Utils.hasHoneycombMR1()) {
+            return bitmap.getByteCount();
+        }
+        // Pre HC-MR1
+        return bitmap.getRowBytes() * bitmap.getHeight();
+    }
+
+    /**
+     * Calculates the memory cache size based on a percentage of the max available VM memory.
+     * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
+     * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8.
+     * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
+     * to construct a LruCache which takes an int in its constructor.
+     *
+     * This value should be chosen carefully based on a number of factors
+     * Refer to the corresponding Android Training class for more discussion:
+     * http://developer.android.com/training/displaying-bitmaps/
+     *
+     * @param percent Percent of available app memory to use to size memory cache.
+     */
+    public static int calculateMemCacheSize(float percent) {
+        if (percent < 0.05f || percent > 0.8f) {
+            throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+                    + "between 0.05 and 0.8 (inclusive)");
+        }
+        return Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
+    }
+
+    /**
+     * Locate an existing instance of this Fragment or if not found, create and
+     * add it using FragmentManager.
+     *
+     * @param fm The FragmentManager manager to use.
+     * @return The existing instance of the Fragment or the new instance if just
+     *         created.
+     */
+    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
+        // Check to see if we have retained the worker fragment.
+        RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
+
+        // If not retained (or first time running), we need to create and add it.
+        if (mRetainFragment == null) {
+            mRetainFragment = new RetainFragment();
+            fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
+        }
+
+        return mRetainFragment;
+    }
+
+    /**
+     * A simple non-UI Fragment that stores a single Object and is retained over configuration
+     * changes. It will be used to retain the ImageCache object.
+     */
+    public static class RetainFragment extends Fragment {
+        private Object mObject;
+
+        /**
+         * Empty constructor as per the Fragment documentation
+         */
+        public RetainFragment() {}
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            // Make sure this Fragment is retained over a configuration change
+            setRetainInstance(true);
+        }
+
+        /**
+         * Store a single object in this Fragment.
+         *
+         * @param object The object to store
+         */
+        public void setObject(Object object) {
+            mObject = object;
+        }
+
+        /**
+         * Get the stored object.
+         *
+         * @return The stored object
+         */
+        public Object getObject() {
+            return mObject;
+        }
+    }
+
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageLoader.java b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageLoader.java
new file mode 100644
index 0000000..7d915bb
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/util/ImageLoader.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2013 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.contactslist.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.AsyncTask;
+import android.support.v4.app.FragmentManager;
+import android.util.Log;
+import android.widget.ImageView;
+
+import com.example.android.contactslist.BuildConfig;
+
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+
+/**
+ * This class wraps up completing some arbitrary long running work when loading a bitmap to an
+ * ImageView. It handles things like using a memory and disk cache, running the work in a background
+ * thread and setting a placeholder image.
+ */
+public abstract class ImageLoader {
+    private static final String TAG = "ImageLoader";
+    private static final int FADE_IN_TIME = 200;
+
+    private ImageCache mImageCache;
+    private Bitmap mLoadingBitmap;
+    private boolean mFadeInBitmap = true;
+    private boolean mPauseWork = false;
+    private final Object mPauseWorkLock = new Object();
+    private int mImageSize;
+    private Resources mResources;
+
+    protected ImageLoader(Context context, int imageSize) {
+        mResources = context.getResources();
+        mImageSize = imageSize;
+    }
+
+    public int getImageSize() {
+        return mImageSize;
+    }
+
+    /**
+     * Load an image specified by the data parameter into an ImageView (override
+     * {@link ImageLoader#processBitmap(Object)} to define the processing logic). If the image is
+     * found in the memory cache, it is set immediately, otherwise an {@link AsyncTask} will be
+     * created to asynchronously load the bitmap.
+     *
+     * @param data The URL of the image to download.
+     * @param imageView The ImageView to bind the downloaded image to.
+     */
+    public void loadImage(Object data, ImageView imageView) {
+        if (data == null) {
+            imageView.setImageBitmap(mLoadingBitmap);
+            return;
+        }
+
+        Bitmap bitmap = null;
+
+        if (mImageCache != null) {
+            bitmap = mImageCache.getBitmapFromMemCache(String.valueOf(data));
+        }
+
+        if (bitmap != null) {
+            // Bitmap found in memory cache
+            imageView.setImageBitmap(bitmap);
+        } else if (cancelPotentialWork(data, imageView)) {
+            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+            final AsyncDrawable asyncDrawable =
+                    new AsyncDrawable(mResources, mLoadingBitmap, task);
+            imageView.setImageDrawable(asyncDrawable);
+            task.execute(data);
+        }
+    }
+
+    /**
+     * Set placeholder bitmap that shows when the the background thread is running.
+     *
+     * @param resId Resource ID of loading image.
+     */
+    public void setLoadingImage(int resId) {
+        mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
+    }
+
+    /**
+     * Adds an {@link ImageCache} to this image loader.
+     *
+     * @param fragmentManager A FragmentManager to use to retain the cache over configuration
+     *                        changes such as an orientation change.
+     * @param memCacheSizePercent The cache size as a percent of available app memory.
+     */
+    public void addImageCache(FragmentManager fragmentManager, float memCacheSizePercent) {
+        mImageCache = ImageCache.getInstance(fragmentManager, memCacheSizePercent);
+    }
+
+    /**
+     * If set to true, the image will fade-in once it has been loaded by the background thread.
+     */
+    public void setImageFadeIn(boolean fadeIn) {
+        mFadeInBitmap = fadeIn;
+    }
+
+    /**
+     * Subclasses should override this to define any processing or work that must happen to produce
+     * the final bitmap. This will be executed in a background thread and be long running. For
+     * example, you could resize a large bitmap here, or pull down an image from the network.
+     *
+     * @param data The data to identify which image to process, as provided by
+     *            {@link ImageLoader#loadImage(Object, ImageView)}
+     * @return The processed bitmap
+     */
+    protected abstract Bitmap processBitmap(Object data);
+
+    /**
+     * Cancels any pending work attached to the provided ImageView.
+     */
+    public static void cancelWork(ImageView imageView) {
+        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+        if (bitmapWorkerTask != null) {
+            bitmapWorkerTask.cancel(true);
+            if (BuildConfig.DEBUG) {
+                final Object bitmapData = bitmapWorkerTask.data;
+                Log.d(TAG, "cancelWork - cancelled work for " + bitmapData);
+            }
+        }
+    }
+
+    /**
+     * Returns true if the current work has been canceled or if there was no work in
+     * progress on this image view.
+     * Returns false if the work in progress deals with the same data. The work is not
+     * stopped in that case.
+     */
+    public static boolean cancelPotentialWork(Object data, ImageView imageView) {
+        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+        if (bitmapWorkerTask != null) {
+            final Object bitmapData = bitmapWorkerTask.data;
+            if (bitmapData == null || !bitmapData.equals(data)) {
+                bitmapWorkerTask.cancel(true);
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "cancelPotentialWork - cancelled work for " + data);
+                }
+            } else {
+                // The same work is already in progress.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @param imageView Any imageView
+     * @return Retrieve the currently active work task (if any) associated with this imageView.
+     * null if there is no such task.
+     */
+    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+        if (imageView != null) {
+            final Drawable drawable = imageView.getDrawable();
+            if (drawable instanceof AsyncDrawable) {
+                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+                return asyncDrawable.getBitmapWorkerTask();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * The actual AsyncTask that will asynchronously process the image.
+     */
+    private class BitmapWorkerTask extends AsyncTask<Object, Void, Bitmap> {
+        private Object data;
+        private final WeakReference<ImageView> imageViewReference;
+
+        public BitmapWorkerTask(ImageView imageView) {
+            imageViewReference = new WeakReference<ImageView>(imageView);
+        }
+
+        /**
+         * Background processing.
+         */
+        @Override
+        protected Bitmap doInBackground(Object... params) {
+            if (BuildConfig.DEBUG) {
+                Log.d(TAG, "doInBackground - starting work");
+            }
+
+            data = params[0];
+            final String dataString = String.valueOf(data);
+            Bitmap bitmap = null;
+
+            // Wait here if work is paused and the task is not cancelled
+            synchronized (mPauseWorkLock) {
+                while (mPauseWork && !isCancelled()) {
+                    try {
+                        mPauseWorkLock.wait();
+                    } catch (InterruptedException e) {}
+                }
+            }
+
+            // If the task has not been cancelled by another thread and the ImageView that was
+            // originally bound to this task is still bound back to this task and our "exit early"
+            // flag is not set, then call the main process method (as implemented by a subclass)
+            if (!isCancelled() && getAttachedImageView() != null) {
+                bitmap = processBitmap(params[0]);
+            }
+
+            // If the bitmap was processed and the image cache is available, then add the processed
+            // bitmap to the cache for future use. Note we don't check if the task was cancelled
+            // here, if it was, and the thread is still running, we may as well add the processed
+            // bitmap to our cache as it might be used again in the future
+            if (bitmap != null && mImageCache != null) {
+                mImageCache.addBitmapToCache(dataString, bitmap);
+            }
+
+            if (BuildConfig.DEBUG) {
+                Log.d(TAG, "doInBackground - finished work");
+            }
+
+            return bitmap;
+        }
+
+        /**
+         * Once the image is processed, associates it to the imageView
+         */
+        @Override
+        protected void onPostExecute(Bitmap bitmap) {
+            // if cancel was called on this task or the "exit early" flag is set then we're done
+            if (isCancelled()) {
+                bitmap = null;
+            }
+
+            final ImageView imageView = getAttachedImageView();
+            if (bitmap != null && imageView != null) {
+                if (BuildConfig.DEBUG) {
+                    Log.d(TAG, "onPostExecute - setting bitmap");
+                }
+                setImageBitmap(imageView, bitmap);
+            }
+        }
+
+        @Override
+        protected void onCancelled(Bitmap bitmap) {
+            super.onCancelled(bitmap);
+            synchronized (mPauseWorkLock) {
+                mPauseWorkLock.notifyAll();
+            }
+        }
+
+        /**
+         * Returns the ImageView associated with this task as long as the ImageView's task still
+         * points to this task as well. Returns null otherwise.
+         */
+        private ImageView getAttachedImageView() {
+            final ImageView imageView = imageViewReference.get();
+            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+            if (this == bitmapWorkerTask) {
+                return imageView;
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * A custom Drawable that will be attached to the imageView while the work is in progress.
+     * Contains a reference to the actual worker task, so that it can be stopped if a new binding is
+     * required, and makes sure that only the last started worker process can bind its result,
+     * independently of the finish order.
+     */
+    private static class AsyncDrawable extends BitmapDrawable {
+        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+        public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+            super(res, bitmap);
+            bitmapWorkerTaskReference =
+                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
+        }
+
+        public BitmapWorkerTask getBitmapWorkerTask() {
+            return bitmapWorkerTaskReference.get();
+        }
+    }
+
+    /**
+     * Called when the processing is complete and the final bitmap should be set on the ImageView.
+     *
+     * @param imageView The ImageView to set the bitmap to.
+     * @param bitmap The new bitmap to set.
+     */
+    private void setImageBitmap(ImageView imageView, Bitmap bitmap) {
+        if (mFadeInBitmap) {
+            // Transition drawable to fade from loading bitmap to final bitmap
+            final TransitionDrawable td =
+                    new TransitionDrawable(new Drawable[] {
+                            new ColorDrawable(android.R.color.transparent),
+                            new BitmapDrawable(mResources, bitmap)
+                    });
+            imageView.setBackgroundDrawable(imageView.getDrawable());
+            imageView.setImageDrawable(td);
+            td.startTransition(FADE_IN_TIME);
+        } else {
+            imageView.setImageBitmap(bitmap);
+        }
+    }
+
+    /**
+     * Pause any ongoing background work. This can be used as a temporary
+     * measure to improve performance. For example background work could
+     * be paused when a ListView or GridView is being scrolled using a
+     * {@link android.widget.AbsListView.OnScrollListener} to keep
+     * scrolling smooth.
+     * <p>
+     * If work is paused, be sure setPauseWork(false) is called again
+     * before your fragment or activity is destroyed (for example during
+     * {@link android.app.Activity#onPause()}), or there is a risk the
+     * background thread will never finish.
+     */
+    public void setPauseWork(boolean pauseWork) {
+        synchronized (mPauseWorkLock) {
+            mPauseWork = pauseWork;
+            if (!mPauseWork) {
+                mPauseWorkLock.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Decode and sample down a bitmap from a file input stream to the requested width and height.
+     *
+     * @param fileDescriptor The file descriptor to read from
+     * @param reqWidth The requested width of the resulting bitmap
+     * @param reqHeight The requested height of the resulting bitmap
+     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
+     *         that are equal to or greater than the requested width and height
+     */
+    public static Bitmap decodeSampledBitmapFromDescriptor(
+            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
+
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+    }
+
+    /**
+     * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
+     * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
+     * the closest inSampleSize that will result in the final decoded bitmap having a width and
+     * height equal to or larger than the requested width and height. This implementation does not
+     * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
+     * results in a larger bitmap which isn't as useful for caching purposes.
+     *
+     * @param options An options object with out* params already populated (run through a decode*
+     *            method with inJustDecodeBounds==true
+     * @param reqWidth The requested width of the resulting bitmap
+     * @param reqHeight The requested height of the resulting bitmap
+     * @return The value to be used for inSampleSize
+     */
+    public static int calculateInSampleSize(BitmapFactory.Options options,
+            int reqWidth, int reqHeight) {
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+
+            // Calculate ratios of height and width to requested height and width
+            final int heightRatio = Math.round((float) height / (float) reqHeight);
+            final int widthRatio = Math.round((float) width / (float) reqWidth);
+
+            // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
+            // with both dimensions larger than or equal to the requested height and width.
+            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
+
+            // This offers some additional logic in case the image has a strange
+            // aspect ratio. For example, a panorama may have a much larger
+            // width than height. In these cases the total pixels might still
+            // end up being too large to fit comfortably in memory, so we should
+            // be more aggressive with sample down the image (=larger inSampleSize).
+
+            final float totalPixels = width * height;
+
+            // Anything more than 2x the requested pixels we'll sample down further
+            final float totalReqPixelsCap = reqWidth * reqHeight * 2;
+
+            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
+                inSampleSize++;
+            }
+        }
+        return inSampleSize;
+    }
+}
diff --git a/samples/training/ContactsList/src/com/example/android/contactslist/util/Utils.java b/samples/training/ContactsList/src/com/example/android/contactslist/util/Utils.java
new file mode 100644
index 0000000..9c0cc44
--- /dev/null
+++ b/samples/training/ContactsList/src/com/example/android/contactslist/util/Utils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 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.contactslist.util;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.StrictMode;
+
+import com.example.android.contactslist.ui.ContactDetailActivity;
+import com.example.android.contactslist.ui.ContactsListActivity;
+
+/**
+ * This class contains static utility methods.
+ */
+public class Utils {
+
+    // Prevents instantiation.
+    private Utils() {}
+
+    /**
+     * Enables strict mode. This should only be called when debugging the application and is useful
+     * for finding some potential bugs or best practice violations.
+     */
+    @TargetApi(11)
+    public static void enableStrictMode() {
+        // Strict mode is only available on gingerbread or later
+        if (Utils.hasGingerbread()) {
+
+            // Enable all thread strict mode policies
+            StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
+                    new StrictMode.ThreadPolicy.Builder()
+                            .detectAll()
+                            .penaltyLog();
+
+            // Enable all VM strict mode policies
+            StrictMode.VmPolicy.Builder vmPolicyBuilder =
+                    new StrictMode.VmPolicy.Builder()
+                            .detectAll()
+                            .penaltyLog();
+
+            // Honeycomb introduced some additional strict mode features
+            if (Utils.hasHoneycomb()) {
+                // Flash screen when thread policy is violated
+                threadPolicyBuilder.penaltyFlashScreen();
+                // For each activity class, set an instance limit of 1. Any more instances and
+                // there could be a memory leak.
+                vmPolicyBuilder
+                        .setClassInstanceLimit(ContactsListActivity.class, 1)
+                        .setClassInstanceLimit(ContactDetailActivity.class, 1);
+            }
+
+            // Use builders to enable strict mode policies
+            StrictMode.setThreadPolicy(threadPolicyBuilder.build());
+            StrictMode.setVmPolicy(vmPolicyBuilder.build());
+        }
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is Gingerbread or
+     * later.
+     */
+    public static boolean hasGingerbread() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is Honeycomb or
+     * later.
+     */
+    public static boolean hasHoneycomb() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is Honeycomb MR1 or
+     * later.
+     */
+    public static boolean hasHoneycombMR1() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
+    }
+
+    /**
+     * Uses static final constants to detect if the device's platform version is ICS or
+     * later.
+     */
+    public static boolean hasICS() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+    }
+}