Move over conversation list items.

Note:
Will have to work out the placement of fwd/reply indicators
and important exchange indicator
This doesn't do anything with labels
This doesn't handle checkboxes or selections yet
This is basically just a port of the coordinates system, xml, and assets
Change-Id: I9b55fab81e7b27ede602d2776ce16537c13bcefb
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a57d8cf..3cd090f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5,19 +5,18 @@
       android:versionName="1.0">
 
     <application android:icon="@mipmap/ic_launcher_mail"
-        android:label="@string/app_name">
+        android:label="@string/app_name"
+        android:theme="@android:style/Theme.Holo.Light">
         <activity android:name=".UnifiedEmail"
-                  android:label="@string/app_name"
-                  android:theme="@android:style/Theme.Holo.Light" >
+                  android:label="@string/app_name">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
-        <activity android:name=".compose.ComposeActivity"
-                  android:theme="@android:style/Theme.Holo.Light" />
-        <activity android:name=".browse.LabelItem"
-                  android:theme="@android:style/Theme.Holo.Light" />
+        <activity android:name=".compose.ComposeActivity" />
+        <activity android:name=".browse.LabelItem" />
+        <activity android:name=".browse.BrowseListActivity" />
     </application>
 </manifest>
diff --git a/res/drawable/conversation_read_selector.xml b/res/drawable/conversation_read_selector.xml
new file mode 100644
index 0000000..705ea09
--- /dev/null
+++ b/res/drawable/conversation_read_selector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@drawable/list_pressed_holo" />
+    <item android:state_focused="true"
+          android:drawable="@drawable/list_focused_holo" />
+    <item android:state_selected="true"
+          android:drawable="@drawable/list_selected_holo" />
+    <item android:drawable="@drawable/list_read_holo" />
+</selector>
diff --git a/res/drawable/conversation_unread_selector.xml b/res/drawable/conversation_unread_selector.xml
new file mode 100644
index 0000000..32f2994
--- /dev/null
+++ b/res/drawable/conversation_unread_selector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@drawable/list_pressed_holo" />
+    <item android:state_focused="true"
+          android:drawable="@drawable/list_focused_holo" />
+    <item android:state_selected="true"
+          android:drawable="@drawable/list_selected_holo" />
+    <item android:drawable="@drawable/list_unread_holo" />
+</selector>
diff --git a/res/drawable/conversation_wide_read_selector.xml b/res/drawable/conversation_wide_read_selector.xml
new file mode 100644
index 0000000..d9ed255
--- /dev/null
+++ b/res/drawable/conversation_wide_read_selector.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+          android:drawable="@drawable/list_conversation_wide_read_focused_holo" />
+    <item android:state_activated="true"
+          android:drawable="@drawable/list_conversation_wide_read_pressed_holo" />
+    <item android:state_pressed="true"
+          android:drawable="@drawable/list_conversation_wide_read_pressed_holo" />
+    <item android:state_selected="true"
+          android:drawable="@drawable/list_conversation_wide_read_selected_holo" />
+    <item android:state_selected="true"
+          android:state_activated="true"
+          android:drawable="@drawable/list_conversation_wide_read_selected_holo" />
+    <item android:drawable="@drawable/list_conversation_wide_read_normal_holo" />
+</selector>
diff --git a/res/drawable/conversation_wide_unread_selector.xml b/res/drawable/conversation_wide_unread_selector.xml
new file mode 100644
index 0000000..8322465
--- /dev/null
+++ b/res/drawable/conversation_wide_unread_selector.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+          android:drawable="@drawable/list_conversation_wide_unread_focused_holo" />
+    <item android:state_activated="true"
+          android:drawable="@drawable/list_conversation_wide_unread_pressed_holo" />
+    <item android:state_pressed="true"
+          android:drawable="@drawable/list_conversation_wide_unread_pressed_holo" />
+    <item android:state_selected="true"
+          android:drawable="@drawable/list_conversation_wide_unread_selected_holo" />
+    <item android:state_selected="true"
+          android:state_activated="true"
+          android:drawable="@drawable/list_conversation_wide_unread_selected_holo" />
+    <item android:drawable="@drawable/list_conversation_wide_unread_normal_holo" />
+</selector>
diff --git a/res/layout/browse_item_view_normal.xml b/res/layout/browse_item_view_normal.xml
new file mode 100644
index 0000000..94119d0
--- /dev/null
+++ b/res/layout/browse_item_view_normal.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011 Google Inc. -->
+
+<!-- This layout is used as a template to create custom view CanvasConversationHeaderView
+    in normal mode. To be able to get the correct measurements, every source field should
+    be populated with data here. E.g:
+    - Text View should set text to a random long string (android:text="@string/long_string")
+    - Image View should set source to a specific asset -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="70sp"
+    android:orientation="vertical">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical">
+        <ImageView
+            android:id="@+id/personal_level"
+            style="@style/PersonalIndicatorStyle"
+            android:src="@drawable/ic_email_caret_single" />
+        <TextView
+            android:id="@+id/senders"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/SendersStyle"
+            android:text="@string/long_string"
+            android:textSize="@dimen/senders_font_size"
+            android:lines="1"
+            android:layout_toRightOf="@id/personal_level" />
+        <LinearLayout
+            android:layout_height="wrap_content"
+            android:layout_width="@dimen/total_conversationbar_width"
+            android:layout_alignParentRight="true">
+            <TextView
+                android:id="@+id/labels"
+                android:layout_width="@dimen/max_total_label_width"
+                android:layout_height="wrap_content"
+                android:text="@string/long_string"
+                android:textSize="@dimen/labels_font_size"
+                android:lines="1"
+                android:paddingTop="1dip"
+                android:layout_marginTop="10sp" />
+            <ImageView
+                android:id="@+id/paperclip"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/ic_attachment_holo_light"
+                android:layout_marginTop="6sp" />
+            <TextView
+                android:id="@+id/date"
+                android:layout_width="0dip"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:text="@string/long_string"
+                android:textSize="@dimen/date_font_size"
+                android:lines="1"
+                android:layout_marginRight="16dip"
+                android:layout_marginTop="10sp" />
+        </LinearLayout>
+    </RelativeLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ImageView
+            style="@style/CheckmarkStyle"
+            android:id="@+id/checkmark"
+            android:src="@drawable/btn_check_on_normal_holo_light"/>
+        <TextView
+            android:id="@+id/subject"
+            android:layout_width="@dimen/subject_width"
+            style="@style/SubjectStyle"
+            android:text="@string/long_string"
+            android:lines="2"/>
+        <ImageView
+            android:id="@+id/star"
+            style="@style/StarStyle"
+            android:src="@drawable/btn_star_off_normal_email_holo_light" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/browse_item_view_wide.xml b/res/layout/browse_item_view_wide.xml
new file mode 100644
index 0000000..d5e3a85
--- /dev/null
+++ b/res/layout/browse_item_view_wide.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011 Google Inc. -->
+
+<!-- This layout is used as a template to create custom view CanvasConversationHeaderView
+    in wide mode. To be able to get the correct measurements, every source field should
+    be populated with data here. E.g:
+    - Text View should set text to a random long string (android:text="@string/long_string")
+    - Image View should set source to a specific asset -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="64sp"
+    android:orientation="horizontal">
+    <ImageView
+        android:id="@+id/checkmark"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16dip"
+        android:layout_marginRight="16dip"
+        android:layout_gravity="center_vertical"
+        android:src="@drawable/btn_check_on_normal_holo_light" />
+    <TextView
+        android:id="@+id/senders"
+        android:layout_width="224dip"
+        android:layout_height="wrap_content"
+        android:text="@string/long_string"
+        android:textSize="@dimen/wide_senders_font_size"
+        android:layout_gravity="center_vertical"
+        android:maxLines="2"
+        android:layout_marginTop="@dimen/wide_senders_margin_top"  />
+    <ImageView
+        android:id="@+id/personal_level"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16dip"
+        android:layout_marginRight="16dip"
+        android:layout_gravity="center_vertical"
+        android:src="@drawable/ic_email_caret_single" />
+    <TextView
+        android:id="@+id/subject"
+        android:layout_width="0dip"
+        android:layout_weight="0.7"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:text="@string/long_string"
+        android:lines="2"
+        android:textColor="@color/subject_text_color_unread"
+        android:textSize="@dimen/wide_subject_font_size"
+        android:layout_marginTop="2sp"
+        android:layout_marginRight="@dimen/wide_subject_margin_right"/>
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/labels"
+            android:layout_width="@dimen/max_total_label_width_wide"
+            android:layout_height="wrap_content"
+            android:paddingTop="2sp"
+            android:text="@string/long_string"
+            android:textSize="@dimen/labels_font_size"
+            android:lines="1"
+            android:layout_gravity="top" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_gravity="center_vertical">
+            <ImageView
+                android:id="@+id/paperclip"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/ic_attachment_holo_light"
+                android:layout_gravity="center_vertical"
+                android:layout_marginTop="@dimen/wide_attachment_margin_top"/>
+            <TextView
+                android:id="@+id/date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/date"
+                android:layout_marginTop="@dimen/wide_date_margin_top" />
+        </LinearLayout>
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/star"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16dip"
+        android:layout_marginRight="16dip"
+        android:layout_gravity="center_vertical"
+        android:src="@drawable/btn_star_off_normal_email_holo_light" />
+</LinearLayout>
diff --git a/res/layout/browse_list_activity.xml b/res/layout/browse_list_activity.xml
new file mode 100644
index 0000000..5ec198a
--- /dev/null
+++ b/res/layout/browse_list_activity.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011 Google Inc. -->
+
+<!-- This layout is used as a template to create custom view CanvasConversationHeaderView
+    in normal mode. To be able to get the correct measurements, every source field should
+    be populated with data here. E.g:
+    - Text View should set text to a random long string (android:text="@string/long_string")
+    - Image View should set source to a specific asset -->
+<ListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_list"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+</ListView>
\ No newline at end of file
diff --git a/res/layout/layout_tests.xml b/res/layout/layout_tests.xml
index a5f8c8c..7fb91a0 100644
--- a/res/layout/layout_tests.xml
+++ b/res/layout/layout_tests.xml
@@ -38,6 +38,11 @@
             android:layout_height="wrap_content"
             android:layout_width="wrap_content"
             android:onClick="labelSpinnerTest"/>
+        <Button android:id="@+id/label_browseitem"
+            android:text="@string/test_browseitems"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:onClick="browseListItemTest"/>
     </LinearLayout>
 
 </ScrollView>
\ No newline at end of file
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
index 0832d99..2db11d6 100644
--- a/res/values-land/styles.xml
+++ b/res/values-land/styles.xml
@@ -18,7 +18,7 @@
  */
 -->
 <resources>
-
+    <!-- Compose styles -->
     <style name="ComposeEditTextView">
         <item name="android:minHeight">40dip</item>
         <item name="android:layout_gravity">center_vertical</item>
@@ -37,6 +37,7 @@
         <item name="android:minHeight">38dip</item>
         <item name="android:paddingTop">0dip</item>
     </style>
+    <!-- End compose styles -->
 
     <!-- Spinner primary text is smaller than usual due to extra vertical padding in spinner asset -->
     <style name="AccountSpinnerAnchorTextPrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
diff --git a/res/values-sw600dp/arrays.xml b/res/values-sw600dp/arrays.xml
new file mode 100644
index 0000000..7646733
--- /dev/null
+++ b/res/values-sw600dp/arrays.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011, Google Inc. -->
+
+<resources>
+    <integer-array name="senders_with_labels_and_attachment_lengths">
+        <item>50</item>
+        <item>25</item>
+    </integer-array>
+    <integer-array name="senders_with_labels_lengths">
+        <item>50</item>
+        <item>27</item>
+    </integer-array>
+    <integer-array name="senders_with_attachment_lengths">
+        <item>50</item>
+        <item>30</item>
+    </integer-array>
+    <integer-array name="senders_lengths">
+        <item>50</item>
+        <item>35</item>
+    </integer-array>
+    <!-- When no recent labels exist, pre-populate with these as defaults -->
+    <string-array name="default_recent_labels">
+        <!-- (empty) -->
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/res/values-sw600dp/constants.xml b/res/values-sw600dp/constants.xml
new file mode 100644
index 0000000..76140bd
--- /dev/null
+++ b/res/values-sw600dp/constants.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to 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>
+    <!-- Boolean value indicating whether the table UI should be used. -->
+    <integer name="use_tablet_ui">1</integer>
+    <integer name="conversation_header_mode">1</integer>
+</resources>
\ No newline at end of file
diff --git a/res/values-sw600dp/dimen.xml b/res/values-sw600dp/dimen.xml
index daeb68b..b58eb01 100644
--- a/res/values-sw600dp/dimen.xml
+++ b/res/values-sw600dp/dimen.xml
@@ -20,4 +20,10 @@
 <resources>
     <dimen name="senders_font_size">18sp</dimen>
     <dimen name="account_dropdown_dropdownwidth">496dip</dimen>
+    <dimen name="wide_senders_margin_top">3sp</dimen>
+    <dimen name="wide_attachment_margin_top">2sp</dimen>
+    <dimen name="wide_date_margin_top">4sp</dimen>
+    <dimen name="max_total_label_width_wide">128dip</dimen>
+    <dimen name="wide_subject_margin_right">28dip</dimen>
+    <dimen name="subject_width">238dip</dimen>
 </resources>
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
index 05baa27..b75a638 100644
--- a/res/values-sw600dp/styles.xml
+++ b/res/values-sw600dp/styles.xml
@@ -19,6 +19,7 @@
 -->
 <resources>
 
+    <!-- Compose styles -->
     <style name="RecipientEditTextViewStyle" parent="@style/RecipientEditTextView">
         <item name="android:minHeight">42dip</item>
         <item name="android:gravity">bottom</item>
@@ -77,6 +78,7 @@
     <style name="RecipientComposeFieldLayout" parent="@style/ComposeFieldLayout">
         <item name="android:orientation">vertical</item>
     </style>
+    <!-- End compose styles -->
 
     <style name="AccountSwitchSpinnerItem">
         <item name="android:layout_width">332dip</item>
@@ -84,4 +86,43 @@
 
     <style name="AccountSpinnerAnchorTextPrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
     </style>
+
+    <!-- Browse list item styles -->
+    <style name="PersonalIndicatorStyle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_marginTop">1sp</item>
+    </style>
+
+    <style name="SendersStyle">
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_marginTop">10sp</item>
+    </style>
+
+    <style name="SubjectStyle">
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_marginTop">-6sp</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:textColor">@color/subject_text_color</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:lines">2</item>
+        <item name="android:layout_width">0dip</item>
+    </style>
+
+    <style name="CheckmarkStyle">
+        <item name="android:layout_marginTop">-6sp</item>
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="StarStyle">
+        <item name="android:layout_marginRight">12dip</item>
+        <item name="android:layout_marginTop">-6sp</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+    <!-- End browse list item styles -->
 </resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..b48769c
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <integer-array name="conversation_heights">
+        <item>64</item>
+        <item>70</item>
+    </integer-array>
+    <integer-array name="senders_with_labels_and_attachment_lengths">
+        <item>20</item>
+        <item>20</item>
+    </integer-array>
+    <integer-array name="senders_with_labels_lengths">
+        <item>22</item>
+        <item>22</item>
+    </integer-array>
+    <integer-array name="senders_with_attachment_lengths">
+        <item>25</item>
+        <item>25</item>
+    </integer-array>
+    <integer-array name="senders_lengths">
+        <item>27</item>
+        <item>27</item>
+    </integer-array>
+    <!-- When no recent labels exist, pre-populate with these as defaults. Do not translate. -->
+    <string-array translatable="false" name="default_recent_labels">
+        <!-- Starred -->
+        <item>^t</item>
+        <!-- Sent -->
+        <item>^f</item>
+        <!-- Drafts -->
+        <item>^r</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 7c2bd78..f17a1fa 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -30,6 +30,11 @@
     <color name="date_text_color_read">@color/senders_text_color_read</color>
     <color name="label_list_heading_text_color">#777777</color>
     <color name="actionbar_secondary">#777777</color>
+    <color name="subject_text_color">#333333</color>
+    <color name="faded_conversation_header">#7f99d6eb</color>
+    <color name="faded_activated_conversation_header">#7faccbf9</color>
+    <color name="drafts">#ff990000</color>
+    <color name="light_text_color">#ff666666</color>
 
     <!-- Compose colors -->
     <color name="compose_label_text">#aaaaaa</color>
diff --git a/res/values/constants.xml b/res/values/constants.xml
new file mode 100644
index 0000000..d6fc4c8
--- /dev/null
+++ b/res/values/constants.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to 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>
+    <!-- Boolean value indicating whether the table UI should be used. -->
+    <integer name="use_tablet_ui">0</integer>
+    <integer name="conversation_list_header_mode">1</integer>
+    <integer name="conversation_header_mode">1</integer>
+</resources>
\ No newline at end of file
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 17ecf65..09d0a89 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -28,4 +28,19 @@
     <dimen name="account_dropdown_dropdownwidth">274dip</dimen>
     <dimen name="actionbar_subject_padding_right">8dp</dimen>
     <dimen name="compose_scrollview_width">700dp</dimen>
+    <dimen name="total_conversationbar_width">150dip</dimen>
+    <dimen name="max_total_label_width">64dip</dimen>
+    <dimen name="max_total_label_width_wide">64dip</dimen>
+    <dimen name="subject_width">0dip</dimen>
+    <dimen name="labels_font_size">12sp</dimen>
+    <dimen name="wide_senders_margin_top">0sp</dimen>
+    <dimen name="wide_attachment_margin_top">2sp</dimen>
+    <dimen name="wide_subject_margin_right">0dip</dimen>
+    <dimen name="wide_date_margin_top">2sp</dimen>
+    <dimen name="date_background_height">17sp</dimen>
+    <dimen name="date_background_padding_left">4dip</dimen>
+    <dimen name="touch_slop">16dip</dimen>
+    <dimen name="standard_scaled_dimen">100sp</dimen>
+    <dimen name="label_cell_width">8dip</dimen>
+    <dimen name="triangle_width">15dip</dimen>
 </resources>
diff --git a/res/values/donottranslate.xml b/res/values/donottranslate.xml
new file mode 100644
index 0000000..71066f2
--- /dev/null
+++ b/res/values/donottranslate.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- A random long string to be used in template XML files browse_item_view_*.xml -->
+    <string name="long_string">looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong</string>
+    <!-- A random date to be used in template XML files browse_item_view_*.xml -->
+    <string name="date">Aug 8, 2011</string>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1e5b56c..3fb5ee8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -115,8 +115,39 @@
     <!-- Solicit feedback string in about screen [CHAR LIMIT=50]-->
     <string name="contextmenu_feedback">Send feedback</string>
 
+    <!-- Browse list item strings -->
+    <!--  Displayed when drag and drop conversations "Move ? conversations" [CHAR LIMIT=50] -->
+    <plurals name="move_conversation">
+        <!-- Move 1 conversation -->
+        <item quantity="one">Move conversation</item>
+        <!-- Move several conversations -->
+        <item quantity="other">Move <xliff:g>%1$d</xliff:g> conversations</item>
+    </plurals>
+    <!-- Formatting string for the content description field of a conversation list item when device is in accessibility mode. [CHAR LIMI=250] -->
+    <string name="content_description">Subject: <xliff:g id="subject">%1$s</xliff:g> Snippet:<xliff:g id="snippet">%2$s</xliff:g></string>
+    <!-- Formatting string. If the subject contains the tag of a mailing-list (text surrounded with
+    return the subject with that tag ellipsized, e.g. "[android-gmail-team] Hello" -> "[andr...] Hello" [CHAR LIMIT=100] -->
+    <string name="filtered_tag"> [<xliff:g id="tag">%1$s</xliff:g>]<xliff:g id="subject">%2$s</xliff:g></string>
+    <!-- Displayed in Conversation Header View and Widget in the form of "subject - snippet"
+         [CHAR LIMIT=5] -->
+    <string name="subject_and_snippet"><xliff:g>%s</xliff:g> \u2014 <xliff:g>%s</xliff:g></string>
+    <!-- Displayed in browse list item when the list item is a draft message instead of showing the subject [CHAR LIMIT=100] -->
+    <plurals name="draft">
+        <!-- Title of the screen when there is exactly one draft -->
+        <item quantity="one">Draft</item>
+        <!-- Title of the screen when there are more than one draft -->
+        <item quantity="other">Drafts</item>
+    </plurals>
+    <!-- Message displayed in a browse list item for one second when message is being sent [CHAR LIMIT=20]-->
+    <string name="sending">Sending\u2026</string>
+    <!-- Message displayed in a browse list item for one second after a send failed [CHAR LIMIT=20]-->
+    <string name="send_failed">Message wasn\'t sent.</string>
+    <!-- Strings used to show myself in a To/Cc list. [CHAR LIMIT=15] -->
+    <string name="me">me</string>
+
     <!-- Layout tests strings -->
     <string name="test_compose" translate="false">Test Compose Layout</string>
     <string name="test_accountspinner" translate="false">Test Account Spinner Layout</string>
     <string name="test_labelspinner" translate="false">Test Label Spinner Layout</string>
+    <string name="test_browseitems" translate="false">Test Browse Items</string>
 </resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5a56e03..ed13de1 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -172,4 +172,43 @@
     <style name="AccountSpinnerAnchorTextSecondary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle">
         <item name="android:textColor">@color/actionbar_secondary</item>
     </style>
+
+    <!-- Browse list item styles -->
+    <style name="PersonalIndicatorStyle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_marginTop">10sp</item>
+    </style>
+
+    <style name="SendersStyle">
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_marginTop">8sp</item>
+    </style>
+
+    <style name="CheckmarkStyle">
+        <item name="android:layout_marginTop">-4sp</item>
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="StarStyle">
+        <item name="android:layout_marginRight">16dip</item>
+        <item name="android:layout_marginTop">-4sp</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="SubjectStyle">
+        <item name="android:layout_marginLeft">8dip</item>
+        <item name="android:layout_marginTop">-4sp</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:textColor">@color/subject_text_color</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:lines">2</item>
+        <item name="android:layout_width">0dip</item>
+    </style>
+    <!-- End browser list item styles -->
 </resources>
diff --git a/src/com/android/email/UnifiedEmail.java b/src/com/android/email/UnifiedEmail.java
index a1fc200..316cab9 100644
--- a/src/com/android/email/UnifiedEmail.java
+++ b/src/com/android/email/UnifiedEmail.java
@@ -16,6 +16,7 @@
 
 package com.android.email;
 
+import com.android.email.browse.BrowseListActivity;
 import com.android.email.browse.LabelItem;
 import com.android.email.compose.ComposeActivity;
 
@@ -49,4 +50,7 @@
     public void composeTest(View v){
         startActivityWithClass(ComposeActivity.class);
     }
+    public void browseListItemTest(View v){
+        startActivityWithClass(BrowseListActivity.class);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/email/ViewMode.java b/src/com/android/email/ViewMode.java
new file mode 100644
index 0000000..ca5bbb2
--- /dev/null
+++ b/src/com/android/email/ViewMode.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2011, Google Inc.
+ *
+ * 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.android.email;
+
+import com.android.email.utils.Utils;
+import com.google.common.collect.Lists;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+
+/**
+ * Represents the view mode for the tablet Gmail activity.
+ * Transitions between modes should be done through this central object, and UI components that are
+ * dependent on the mode should listen to changes on this object.
+ */
+public class ViewMode {
+    // Key used to save this {@link ViewMode}.
+    private static final String VIEW_MODE_KEY = "view-mode";
+
+    public static final int MODE_UNKNOWN = 0;
+    public static final int MODE_LABEL_LIST = 1;
+    public static final int MODE_CONVERSATION_LIST = 2;
+    public static final int MODE_CONVERSATION = 3;
+
+    private int mMode = MODE_UNKNOWN;
+    private final boolean mTwoPane;
+    private final ArrayList<ModeChangeListener> mListeners = Lists.newArrayList();
+
+    public ViewMode(Context context) {
+        mTwoPane = Utils.useTabletUI(context);
+    }
+
+    /**
+     * Requests a transition of the mode to show a conversation as the prominent view.
+     * @return Whether or not a change occured.
+     */
+    public boolean transitionToConversationMode() {
+        return setModeInternal(MODE_CONVERSATION);
+    }
+
+    /**
+     * Requests a transition of the mode to show the conversation list as the prominent view.
+     * @return Whether or not a change occured.
+     */
+    public boolean transitionToConversationListMode() {
+        return setModeInternal(MODE_CONVERSATION_LIST);
+    }
+
+    /**
+     * Requests a transition of the mode to show the label list as the prominent view.
+     * @return Whether or not a change occured.
+     */
+    public boolean transitionToLabelListMode() {
+        return setModeInternal(MODE_LABEL_LIST);
+    }
+
+    /**
+     * Sets the internal mode.
+     * @return Whether or not a change occured.
+     */
+    private boolean setModeInternal(int mode) {
+        if (mMode == mode) {
+            return false;
+        }
+
+        mMode = mode;
+        dispatchModeChange();
+        return true;
+    }
+
+    /**
+     * @return The current mode.
+     */
+    public int getMode() {
+        return mMode;
+    }
+
+    /**
+     * @return Whether or not to display 2 pane.
+     */
+    public boolean isTwoPane() {
+        return mTwoPane;
+    }
+
+    public boolean isConversationMode() {
+        return mMode == MODE_CONVERSATION;
+    }
+
+    public boolean isConversationListMode() {
+        return mMode == MODE_CONVERSATION_LIST;
+    }
+
+    public boolean isLabelListMode() {
+        return mMode == MODE_LABEL_LIST;
+    }
+
+    public void handleSaveInstanceState(Bundle outState) {
+        outState.putInt(VIEW_MODE_KEY, mMode);
+    }
+
+    public void handleRestore(Bundle inState) {
+        int mode = inState.getInt(VIEW_MODE_KEY, MODE_UNKNOWN);
+        setModeInternal(mode);
+    }
+
+    /**
+     * A listener for changes on a ViewMode.
+     */
+    public interface ModeChangeListener {
+        /**
+         * Handles a mode change.
+         */
+        void onViewModeChanged(ViewMode mode);
+    }
+
+    /**
+     * Adds a listener from this view mode.
+     * Must happen in the UI thread.
+     */
+    public void addListener(ModeChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from this view mode.
+     * Must happen in the UI thread.
+     */
+    public void removeListener(ModeChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Dispatches a change event for the mode.
+     * Always happens in the UI thread.
+     */
+    private void dispatchModeChange() {
+        ArrayList<ModeChangeListener> list = new ArrayList<ModeChangeListener>(mListeners);
+        for (ModeChangeListener listener : list) {
+            listener.onViewModeChanged(this);
+        }
+    }
+}
diff --git a/src/com/android/email/browse/BrowseItemView.java b/src/com/android/email/browse/BrowseItemView.java
new file mode 100644
index 0000000..2bc9bb6
--- /dev/null
+++ b/src/com/android/email/browse/BrowseItemView.java
@@ -0,0 +1,776 @@
+/**
+ * Copyright (c) 2011, Google Inc.
+ *
+ * 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.android.email.browse;
+
+import com.android.email.browse.BrowseItemViewModel.SenderFragment;
+import com.android.email.perf.Timer;
+import com.android.email.providers.UIProvider;
+import com.android.email.R;
+import com.android.email.ViewMode;
+import com.android.email.utils.Utils;
+import com.google.common.annotations.VisibleForTesting;
+
+import android.content.ClipData;
+import android.content.ClipData.Item;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.Layout.Alignment;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.TextUtils.TruncateAt;
+import android.text.format.DateUtils;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.util.SparseArray;
+import android.view.DragEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.Map;
+
+public class BrowseItemView extends View {
+    // Timer.
+    private static int sLayoutCount = 0;
+    private static Timer sTimer; // Create the sTimer here if you need to do perf analysis.
+    private static final int PERF_LAYOUT_ITERATIONS = 50;
+    private static final String PERF_TAG_LAYOUT = "CCHV.layout";
+    private static final String PERF_TAG_CALCULATE_TEXTS_BITMAPS = "CCHV.txtsbmps";
+    private static final String PERF_TAG_CALCULATE_SENDER_SUBJECT = "CCHV.sendersubj";
+    private static final String PERF_TAG_CALCULATE_LABELS = "CCHV.labels";
+    private static final String PERF_TAG_CALCULATE_COORDINATES = "CCHV.coordinates";
+
+    // Static bitmaps.
+    private static Bitmap CHECKMARK_OFF;
+    private static Bitmap CHECKMARK_ON;
+    private static Bitmap STAR_OFF;
+    private static Bitmap STAR_ON;
+    private static Bitmap ATTACHMENT;
+    private static Bitmap ONLY_TO_ME;
+    private static Bitmap TO_ME_AND_OTHERS;
+    private static Bitmap IMPORTANT_ONLY_TO_ME;
+    private static Bitmap IMPORTANT_TO_ME_AND_OTHERS;
+    private static Bitmap IMPORTANT_TO_OTHERS;
+    private static Bitmap DATE_BACKGROUND;
+
+    // Static colors.
+    private static int DEFAULT_TEXT_COLOR;
+    private static int ACTIVATED_TEXT_COLOR;
+    private static int LIGHT_TEXT_COLOR;
+    private static int DRAFT_TEXT_COLOR;
+    private static int SUBJECT_TEXT_COLOR_READ;
+    private static int SUBJECT_TEXT_COLOR_UNREAD;
+    private static int SNIPPET_TEXT_COLOR_READ;
+    private static int SNIPPET_TEXT_COLOR_UNREAD;
+    private static int SENDERS_TEXT_COLOR_READ;
+    private static int SENDERS_TEXT_COLOR_UNREAD;
+    private static int DATE_TEXT_COLOR_READ;
+    private static int DATE_TEXT_COLOR_UNREAD;
+    private static int DATE_BACKGROUND_PADDING_LEFT;
+    private static int TOUCH_SLOP;
+    private static int sDateBackgroundHeight;
+    private static int sStandardScaledDimen;
+    private static CharacterStyle sLightTextStyle;
+    private static CharacterStyle sNormalTextStyle;
+
+    // Static paints.
+    private static TextPaint sPaint = new TextPaint();
+    private static TextPaint sLabelsPaint = new TextPaint();
+
+    // Backgrounds for different states.
+    private final SparseArray<Drawable> mBackgrounds = new SparseArray<Drawable>();
+
+    // Dimensions and coordinates.
+    private int mViewWidth = -1;
+    private int mMode = -1;
+    private int mDateX;
+    private int mPaperclipX;
+    private int mLabelsXEnd;
+    private int mSendersWidth;
+
+    /** Whether we're running under test mode. */
+    private boolean mTesting = false;
+
+    @VisibleForTesting
+    BrowseItemViewCoordinates mCoordinates;
+
+    // Current displayed label.
+    private CharSequence mDisplayedLabel;
+
+    private final Context mContext;
+    private static UIProvider sProvider;
+
+    private StarHandler mStarHandler;
+    private String mAccount;
+    private BrowseItemViewModel mHeader;
+    private ViewMode mViewMode;
+    private static int sFadedColor = -1;
+    private static int sFadedActivatedColor = -1;
+
+    static {
+        sPaint.setAntiAlias(true);
+        sLabelsPaint.setAntiAlias(true);
+    }
+
+    /**
+     * This handler will be called when user toggle a star in a conversation
+     * header view. It can be used to update the state of other views to ensure
+     * UI consistency.
+     */
+    public static interface StarHandler {
+        public void toggleStar(boolean toggleOn, long conversationId, long maxMessageId);
+    }
+
+    public BrowseItemView(Context context, String account) {
+        super(context);
+        mContext = context.getApplicationContext();
+        mAccount = account;
+        Resources res = mContext.getResources();
+
+        if (CHECKMARK_OFF == null) {
+            // Initialize static bitmaps.
+            CHECKMARK_OFF = BitmapFactory.decodeResource(res,
+                    R.drawable.btn_check_off_normal_holo_light);
+            CHECKMARK_ON = BitmapFactory.decodeResource(res,
+                    R.drawable.btn_check_on_normal_holo_light);
+            STAR_OFF = BitmapFactory.decodeResource(res,
+                    R.drawable.btn_star_off_normal_email_holo_light);
+            STAR_ON = BitmapFactory.decodeResource(res,
+                    R.drawable.btn_star_on_normal_email_holo_light);
+            ONLY_TO_ME = BitmapFactory.decodeResource(res, R.drawable.ic_email_caret_double);
+            TO_ME_AND_OTHERS = BitmapFactory.decodeResource(res, R.drawable.ic_email_caret_single);
+            IMPORTANT_ONLY_TO_ME = BitmapFactory.decodeResource(res,
+                    R.drawable.ic_email_caret_double_important_unread);
+            IMPORTANT_TO_ME_AND_OTHERS = BitmapFactory.decodeResource(res,
+                    R.drawable.ic_email_caret_single_important_unread);
+            IMPORTANT_TO_OTHERS = BitmapFactory.decodeResource(res,
+                    R.drawable.ic_email_caret_none_important_unread);
+            ATTACHMENT = BitmapFactory.decodeResource(res, R.drawable.ic_attachment_holo_light);
+            DATE_BACKGROUND = BitmapFactory.decodeResource(res, R.drawable.label_bg_holo_light);
+
+            // Initialize colors.
+            DEFAULT_TEXT_COLOR = res.getColor(R.color.default_text_color);
+            ACTIVATED_TEXT_COLOR = res.getColor(android.R.color.white);
+            LIGHT_TEXT_COLOR = res.getColor(R.color.light_text_color);
+            DRAFT_TEXT_COLOR = res.getColor(R.color.drafts);
+            SUBJECT_TEXT_COLOR_READ = res.getColor(R.color.subject_text_color_read);
+            SUBJECT_TEXT_COLOR_UNREAD = res.getColor(R.color.subject_text_color_unread);
+            SNIPPET_TEXT_COLOR_READ = res.getColor(R.color.snippet_text_color_read);
+            SNIPPET_TEXT_COLOR_UNREAD = res.getColor(R.color.snippet_text_color_unread);
+            SENDERS_TEXT_COLOR_READ = res.getColor(R.color.senders_text_color_read);
+            SENDERS_TEXT_COLOR_UNREAD = res.getColor(R.color.senders_text_color_unread);
+            DATE_TEXT_COLOR_READ = res.getColor(R.color.date_text_color_read);
+            DATE_TEXT_COLOR_UNREAD = res.getColor(R.color.date_text_color_unread);
+            DATE_BACKGROUND_PADDING_LEFT = res
+                    .getDimensionPixelSize(R.dimen.date_background_padding_left);
+            TOUCH_SLOP = res.getDimensionPixelSize(R.dimen.touch_slop);
+            sDateBackgroundHeight = res.getDimensionPixelSize(R.dimen.date_background_height);
+            sStandardScaledDimen = res.getDimensionPixelSize(R.dimen.standard_scaled_dimen);
+
+            // Initialize static color.
+            sNormalTextStyle = new StyleSpan(Typeface.NORMAL);
+            sLightTextStyle = new ForegroundColorSpan(LIGHT_TEXT_COLOR);
+
+            // Get a reference to the Gmail content provider mail access
+            sProvider = new UIProvider();
+        }
+    }
+
+    /**
+     * Bind this view to the content of the cursor and request layout.
+     */
+    public void bind(BrowseItemViewModel model, StarHandler starHandler, String account,
+            CharSequence displayedLabel, ViewMode viewMode) {
+        mStarHandler = starHandler;
+        mAccount = account;
+        mDisplayedLabel = displayedLabel;
+        mViewMode = viewMode;
+        mHeader = model;
+        setContentDescription(mHeader.getContentDescription(mContext));
+        requestLayout();
+    }
+
+    /**
+     * Sets the mode. Only used for testing.
+     */
+    @VisibleForTesting
+    void setMode(int mode) {
+        mMode = mode;
+        mTesting = true;
+    }
+
+    private static void startTimer(String tag) {
+        if (sTimer != null) {
+            sTimer.start(tag);
+        }
+    }
+
+    private static void pauseTimer(String tag) {
+        if (sTimer != null) {
+            sTimer.pause(tag);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        startTimer(PERF_TAG_LAYOUT);
+
+        super.onLayout(changed, left, top, right, bottom);
+
+        int width = right - left;
+        if (width != mViewWidth) {
+            mViewWidth = width;
+            if (!mTesting) {
+                mMode = BrowseItemViewCoordinates.getMode(mContext, mViewMode);
+            }
+        }
+        mHeader.viewWidth = mViewWidth;
+        Resources res = getResources();
+        mHeader.standardScaledDimen = res.getDimensionPixelOffset(R.dimen.standard_scaled_dimen);
+        if (mHeader.standardScaledDimen != sStandardScaledDimen) {
+            // Large Text has been toggle on/off. Update the static dimens.
+            sStandardScaledDimen = mHeader.standardScaledDimen;
+            BrowseItemViewCoordinates.refreshConversationHeights(mContext);
+            sDateBackgroundHeight = res.getDimensionPixelSize(R.dimen.date_background_height);
+        }
+        mCoordinates = BrowseItemViewCoordinates.forWidth(mContext, mViewWidth, mMode,
+                mHeader.standardScaledDimen);
+        calculateTextsAndBitmaps();
+        calculateCoordinates();
+        mHeader.validate(mContext);
+
+        pauseTimer(PERF_TAG_LAYOUT);
+        if (sTimer != null && ++sLayoutCount >= PERF_LAYOUT_ITERATIONS) {
+            sTimer.dumpResults();
+            sTimer = new Timer();
+            sLayoutCount = 0;
+        }
+    }
+
+    @Override
+    public void setBackgroundResource(int resourceId) {
+        Drawable drawable = mBackgrounds.get(resourceId);
+        if (drawable == null) {
+            drawable = getResources().getDrawable(resourceId);
+            mBackgrounds.put(resourceId, drawable);
+        }
+        if (getBackground() != drawable) {
+            super.setBackgroundDrawable(drawable);
+        }
+    }
+
+    private void calculateTextsAndBitmaps() {
+        startTimer(PERF_TAG_CALCULATE_TEXTS_BITMAPS);
+
+        // Update font color.
+        int fontColor = getFontColor(DEFAULT_TEXT_COLOR);
+        boolean fontChanged = false;
+        if (mHeader.fontColor != fontColor) {
+            fontChanged = true;
+            mHeader.fontColor = fontColor;
+        }
+
+        boolean isUnread = true;
+
+        final boolean checkboxEnabled = true;
+        if (mHeader.checkboxVisible != checkboxEnabled) {
+            mHeader.checkboxVisible = checkboxEnabled;
+        }
+
+        // Update background.
+        updateBackground(isUnread);
+
+        if (mHeader.isLayoutValid(mContext)) {
+            // Relayout subject if font color has changed.
+            if (fontChanged) {
+                createSubjectSpans(isUnread);
+                layoutSubject();
+            }
+            pauseTimer(PERF_TAG_CALCULATE_TEXTS_BITMAPS);
+            return;
+        }
+
+        // Initialize label displayer.
+        startTimer(PERF_TAG_CALCULATE_LABELS);
+
+        pauseTimer(PERF_TAG_CALCULATE_LABELS);
+
+        // Star.
+        mHeader.starBitmap = mHeader.starred ? STAR_ON : STAR_OFF;
+
+        // Date.
+        mHeader.dateText = DateUtils.getRelativeTimeSpanString(mContext, mHeader.dateMs).toString();
+
+        // Paper clip icon.
+        mHeader.paperclip = null;
+        if (mHeader.hasAttachments) {
+            mHeader.paperclip = ATTACHMENT;
+        }
+
+        // Personal level.
+        mHeader.personalLevelBitmap = null;
+
+        startTimer(PERF_TAG_CALCULATE_SENDER_SUBJECT);
+
+        // Subject.
+        createSubjectSpans(isUnread);
+
+        // Parse senders fragments.
+        parseSendersFragments(isUnread);
+
+        pauseTimer(PERF_TAG_CALCULATE_SENDER_SUBJECT);
+        pauseTimer(PERF_TAG_CALCULATE_TEXTS_BITMAPS);
+    }
+
+    private void createSubjectSpans(boolean isUnread) {
+        String subject = filterTag(mHeader.subject);
+        String snippet = mHeader.snippet;
+        int subjectColor = isUnread ? SUBJECT_TEXT_COLOR_UNREAD : SUBJECT_TEXT_COLOR_READ;
+        int snippetColor = isUnread ? SNIPPET_TEXT_COLOR_UNREAD : SNIPPET_TEXT_COLOR_READ;
+        mHeader.subjectText = new SpannableStringBuilder(mContext.getString(
+                R.string.subject_and_snippet, subject, snippet));
+        if (isUnread) {
+            mHeader.subjectText.setSpan(new StyleSpan(Typeface.BOLD), 0, subject.length(),
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+        int fontColor = getFontColor(subjectColor);
+        mHeader.subjectText.setSpan(new ForegroundColorSpan(fontColor), 0,
+                subject.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        fontColor = getFontColor(snippetColor);
+        mHeader.subjectText.setSpan(new ForegroundColorSpan(fontColor), subject.length() + 1,
+                mHeader.subjectText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    private int getFontColor(int defaultColor) {
+        return isActivated() && mViewMode.isTwoPane() ? ACTIVATED_TEXT_COLOR
+                : defaultColor;
+    }
+
+    private void layoutSubject() {
+        sPaint.setTextSize(mCoordinates.subjectFontSize);
+        sPaint.setColor(mHeader.fontColor);
+        mHeader.subjectLayout = new StaticLayout(mHeader.subjectText, sPaint,
+                mCoordinates.subjectWidth, Alignment.ALIGN_NORMAL, 1, 0, true);
+        if (mCoordinates.subjectLineCount < mHeader.subjectLayout.getLineCount()) {
+            int end = mHeader.subjectLayout.getLineEnd(mCoordinates.subjectLineCount - 1);
+            mHeader.subjectLayout = new StaticLayout(mHeader.subjectText.subSequence(0, end),
+                    sPaint, mCoordinates.subjectWidth, Alignment.ALIGN_NORMAL, 1, 0, true);
+        }
+    }
+
+    /**
+     * Parses senders text into small fragments.
+     */
+    private void parseSendersFragments(boolean isUnread) {
+        SpannableStringBuilder sendersBuilder = new SpannableStringBuilder();
+        SpannableStringBuilder statusBuilder = new SpannableStringBuilder();
+        Utils.getStyledSenderSnippet(mContext, mHeader.fromSnippetInstructions, sendersBuilder,
+                statusBuilder, BrowseItemViewCoordinates.getSubjectLength(mContext, mMode,
+                        false, mHeader.hasAttachments), false,
+                        false, mHeader.hasDraftMessage);
+        mHeader.sendersText = sendersBuilder.toString();
+
+        CharacterStyle[] spans = sendersBuilder.getSpans(0, sendersBuilder.length(),
+                CharacterStyle.class);
+        mHeader.clearSenderFragments();
+        int lastPosition = 0;
+        CharacterStyle style = sNormalTextStyle;
+        if (spans != null) {
+            for (CharacterStyle span : spans) {
+                style = span;
+                int start = sendersBuilder.getSpanStart(style);
+                int end = sendersBuilder.getSpanEnd(style);
+                if (start > lastPosition) {
+                    mHeader.addSenderFragment(lastPosition, start, sNormalTextStyle, false);
+                }
+                // From instructions won't be updated until the next sync. So we
+                // have to override the text style here to be consistent with
+                // the background color.
+                if (isUnread) {
+                    mHeader.addSenderFragment(start, end, style, false);
+                } else {
+                    mHeader.addSenderFragment(start, end, sNormalTextStyle, false);
+                }
+                lastPosition = end;
+            }
+        }
+        if (lastPosition < sendersBuilder.length()) {
+            style = sLightTextStyle;
+            mHeader.addSenderFragment(lastPosition, sendersBuilder.length(), style, true);
+        }
+        if (statusBuilder.length() > 0) {
+            if (mHeader.sendersText.length() > 0) {
+                mHeader.sendersText = mHeader.sendersText.concat(", ");
+
+                // Extend the last fragment to include the comma.
+                int lastIndex = mHeader.senderFragments.size() - 1;
+                int start = mHeader.senderFragments.get(lastIndex).start;
+                int end = mHeader.senderFragments.get(lastIndex).end + 2;
+                style = mHeader.senderFragments.get(lastIndex).style;
+
+                // The new fragment is only fixed if the previous fragment
+                // is fixed.
+                boolean isFixed = mHeader.senderFragments.get(lastIndex).isFixed;
+
+                // Remove the old fragment.
+                mHeader.senderFragments.remove(lastIndex);
+
+                // Add new fragment.
+                mHeader.addSenderFragment(start, end, style, isFixed);
+            }
+            int pos = mHeader.sendersText.length();
+            mHeader.sendersText = mHeader.sendersText.concat(statusBuilder.toString());
+            mHeader.addSenderFragment(pos, mHeader.sendersText.length(), new ForegroundColorSpan(
+                    DRAFT_TEXT_COLOR), true);
+        }
+    }
+
+    private boolean canFitFragment(int width, int line, int fixedWidth) {
+        if (line == mCoordinates.sendersLineCount) {
+            return width + fixedWidth <= mSendersWidth;
+        } else {
+            return width <= mSendersWidth;
+        }
+    }
+
+    private void calculateCoordinates() {
+        startTimer(PERF_TAG_CALCULATE_COORDINATES);
+
+        sPaint.setTextSize(mCoordinates.dateFontSize);
+        sPaint.setTypeface(Typeface.DEFAULT);
+        mDateX = mCoordinates.dateXEnd - (int) sPaint.measureText(mHeader.dateText);
+
+        mPaperclipX = mDateX - ATTACHMENT.getWidth();
+
+        int cellWidth = mContext.getResources().getDimensionPixelSize(R.dimen.label_cell_width);
+
+        if (BrowseItemViewCoordinates.displayLabelsAboveDate(mMode)) {
+            mLabelsXEnd = mCoordinates.dateXEnd;
+            mSendersWidth = mCoordinates.sendersWidth;
+        } else {
+            if (mHeader.paperclip != null) {
+                mLabelsXEnd = mPaperclipX;
+            } else {
+                mLabelsXEnd = mDateX - cellWidth / 2;
+            }
+            mSendersWidth = mLabelsXEnd - mCoordinates.sendersX - 2 * cellWidth;
+        }
+
+        if (mHeader.isLayoutValid(mContext)) {
+            pauseTimer(PERF_TAG_CALCULATE_COORDINATES);
+            return;
+        }
+
+        // Layout subject.
+        layoutSubject();
+
+        // First pass to calculate width of each fragment.
+        int totalWidth = 0;
+        int fixedWidth = 0;
+        sPaint.setTextSize(mCoordinates.sendersFontSize);
+        sPaint.setTypeface(Typeface.DEFAULT);
+        for (SenderFragment senderFragment : mHeader.senderFragments) {
+            CharacterStyle style = senderFragment.style;
+            int start = senderFragment.start;
+            int end = senderFragment.end;
+            style.updateDrawState(sPaint);
+            senderFragment.width = (int) sPaint.measureText(mHeader.sendersText, start, end);
+            boolean isFixed = senderFragment.isFixed;
+            if (isFixed) {
+                fixedWidth += senderFragment.width;
+            }
+            totalWidth += senderFragment.width;
+        }
+
+        // Second pass to layout each fragment.
+        int sendersY = mCoordinates.sendersY - mCoordinates.sendersAscent;
+        if (!BrowseItemViewCoordinates.displaySendersInline(mMode)) {
+            sendersY += totalWidth <= mSendersWidth ? mCoordinates.sendersLineHeight / 2 : 0;
+        }
+        totalWidth = 0;
+        int currentLine = 1;
+        boolean ellipsize = false;
+        for (SenderFragment senderFragment : mHeader.senderFragments) {
+            CharacterStyle style = senderFragment.style;
+            int start = senderFragment.start;
+            int end = senderFragment.end;
+            int width = senderFragment.width;
+            boolean isFixed = senderFragment.isFixed;
+            style.updateDrawState(sPaint);
+
+            // No more width available, we'll only show fixed fragments.
+            if (ellipsize && !isFixed) {
+                senderFragment.shouldDisplay = false;
+                continue;
+            }
+
+            // New line and ellipsize text if needed.
+            senderFragment.ellipsizedText = null;
+            if (isFixed) {
+                fixedWidth -= width;
+            }
+            if (!canFitFragment(totalWidth + width, currentLine, fixedWidth)) {
+                // The text is too long, new line won't help. We have to
+                // ellipsize text.
+                if (totalWidth == 0) {
+                    ellipsize = true;
+                } else {
+                    // New line.
+                    if (currentLine < mCoordinates.sendersLineCount) {
+                        currentLine++;
+                        sendersY += mCoordinates.sendersLineHeight;
+                        totalWidth = 0;
+                        // The text is still too long, we have to ellipsize
+                        // text.
+                        if (totalWidth + width > mSendersWidth) {
+                            ellipsize = true;
+                        }
+                    } else {
+                        ellipsize = true;
+                    }
+                }
+
+                if (ellipsize) {
+                    width = mSendersWidth - totalWidth;
+                    // No more new line, we have to reserve width for fixed
+                    // fragments.
+                    if (currentLine == mCoordinates.sendersLineCount) {
+                        width -= fixedWidth;
+                    }
+                    senderFragment.ellipsizedText = TextUtils.ellipsize(
+                            mHeader.sendersText.substring(start, end), sPaint, width,
+                            TruncateAt.END).toString();
+                    width = (int) sPaint.measureText(senderFragment.ellipsizedText);
+                }
+            }
+            senderFragment.x = mCoordinates.sendersX + totalWidth;
+            senderFragment.y = sendersY;
+            senderFragment.shouldDisplay = true;
+            totalWidth += width;
+        }
+
+        pauseTimer(PERF_TAG_CALCULATE_COORDINATES);
+    }
+
+    /**
+     * If the subject contains the tag of a mailing-list (text surrounded with
+     * []), return the subject with that tag ellipsized, e.g.
+     * "[android-gmail-team] Hello" -> "[andr...] Hello"
+     */
+    private String filterTag(String subject) {
+        String result = subject;
+        String formatString = getContext().getResources().getString(R.string.filtered_tag);
+        if (!TextUtils.isEmpty(subject) && subject.charAt(0) == '[') {
+            int end = subject.indexOf(']');
+            if (end > 0) {
+                String tag = subject.substring(1, end);
+                result = String.format(formatString, Utils.ellipsize(tag, 7),
+                        subject.substring(end + 1));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = measureWidth(widthMeasureSpec);
+        int height = measureHeight(heightMeasureSpec,
+                BrowseItemViewCoordinates.getMode(mContext, mViewMode));
+        setMeasuredDimension(width, height);
+    }
+
+    /**
+     * Determine the width of this view.
+     *
+     * @param measureSpec A measureSpec packed into an int
+     * @return The width of the view, honoring constraints from measureSpec
+     */
+    private int measureWidth(int measureSpec) {
+        int result = 0;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize = MeasureSpec.getSize(measureSpec);
+
+        if (specMode == MeasureSpec.EXACTLY) {
+            // We were told how big to be
+            result = specSize;
+        } else {
+            // Measure the text
+            result = mViewWidth;
+            if (specMode == MeasureSpec.AT_MOST) {
+                // Respect AT_MOST value if that was what is called for by
+                // measureSpec
+                result = Math.min(result, specSize);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Determine the height of this view.
+     *
+     * @param measureSpec A measureSpec packed into an int
+     * @param mode The current mode of this view
+     * @return The height of the view, honoring constraints from measureSpec
+     */
+    private int measureHeight(int measureSpec, int mode) {
+        int result = 0;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize = MeasureSpec.getSize(measureSpec);
+
+        if (specMode == MeasureSpec.EXACTLY) {
+            // We were told how big to be
+            result = specSize;
+        } else {
+            // Measure the text
+            result = BrowseItemViewCoordinates.getHeight(mContext, mode);
+            if (specMode == MeasureSpec.AT_MOST) {
+                // Respect AT_MOST value if that was what is called for by
+                // measureSpec
+                result = Math.min(result, specSize);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // Check mark.
+        if (mHeader.checkboxVisible) {
+            Bitmap checkmark = CHECKMARK_OFF;
+            canvas.drawBitmap(checkmark, mCoordinates.checkmarkX, mCoordinates.checkmarkY, sPaint);
+        }
+
+        // Personal Level.
+        if (mHeader.personalLevelBitmap != null) {
+            canvas.drawBitmap(mHeader.personalLevelBitmap, mCoordinates.personalLevelX,
+                    mCoordinates.personalLevelY, sPaint);
+        }
+
+        // Senders.
+        sPaint.setTextSize(mCoordinates.sendersFontSize);
+        sPaint.setTypeface(Typeface.DEFAULT);
+        boolean isUnread = true;
+        int sendersColor = getFontColor(isUnread ? SENDERS_TEXT_COLOR_UNREAD
+                : SENDERS_TEXT_COLOR_READ);
+        sPaint.setColor(sendersColor);
+        for (SenderFragment fragment : mHeader.senderFragments) {
+            if (fragment.shouldDisplay) {
+                sPaint.setTypeface(Typeface.DEFAULT);
+                fragment.style.updateDrawState(sPaint);
+                if (fragment.ellipsizedText != null) {
+                    canvas.drawText(fragment.ellipsizedText, fragment.x, fragment.y, sPaint);
+                } else {
+                    canvas.drawText(mHeader.sendersText, fragment.start, fragment.end, fragment.x,
+                            fragment.y, sPaint);
+                }
+            }
+        }
+
+        // Subject.
+        sPaint.setTextSize(mCoordinates.subjectFontSize);
+        sPaint.setTypeface(Typeface.DEFAULT);
+        sPaint.setColor(mHeader.fontColor);
+        canvas.save();
+        canvas.translate(mCoordinates.subjectX,
+                mCoordinates.subjectY + mHeader.subjectLayout.getTopPadding());
+        mHeader.subjectLayout.draw(canvas);
+        canvas.restore();
+
+        // Date background: shown when there is an attachment or a visible
+        // label.
+        if (!isActivated()
+                && mHeader.hasAttachments
+                && BrowseItemViewCoordinates.showAttachmentBackground(mMode)) {
+            mHeader.dateBackground = DATE_BACKGROUND;
+            int leftOffset = (mHeader.hasAttachments ? mPaperclipX : mDateX)
+                    - DATE_BACKGROUND_PADDING_LEFT;
+            int top = mCoordinates.labelsY;
+            Rect src = new Rect(0, 0, mHeader.dateBackground.getWidth(), mHeader.dateBackground
+                    .getHeight());
+            Rect dst = new Rect(leftOffset, top, mViewWidth, top + sDateBackgroundHeight);
+            canvas.drawBitmap(mHeader.dateBackground, src, dst, sPaint);
+        } else {
+            mHeader.dateBackground = null;
+        }
+
+        // Date.
+        sPaint.setTextSize(mCoordinates.dateFontSize);
+        sPaint.setTypeface(Typeface.DEFAULT);
+        sPaint.setColor(isUnread ? DATE_TEXT_COLOR_UNREAD : DATE_TEXT_COLOR_READ);
+        drawText(canvas, mHeader.dateText, mDateX, mCoordinates.dateY - mCoordinates.dateAscent,
+                sPaint);
+
+        // Paper clip icon.
+        if (mHeader.paperclip != null) {
+            canvas.drawBitmap(mHeader.paperclip, mPaperclipX, mCoordinates.paperclipY, sPaint);
+        }
+
+        if (mHeader.faded) {
+            int fadedColor = -1;
+            if (sFadedActivatedColor == -1) {
+                sFadedActivatedColor = mContext.getResources().getColor(
+                        R.color.faded_activated_conversation_header);
+            }
+            fadedColor = sFadedActivatedColor;
+            int restoreState = canvas.save();
+            Rect bounds = canvas.getClipBounds();
+            canvas.clipRect(bounds.left, bounds.top, bounds.right
+                    - mContext.getResources().getDimensionPixelSize(R.dimen.triangle_width),
+                    bounds.bottom);
+            canvas.drawARGB(Color.alpha(fadedColor), Color.red(fadedColor),
+                    Color.green(fadedColor), Color.blue(fadedColor));
+            canvas.restoreToCount(restoreState);
+        }
+
+        // Star.
+        canvas.drawBitmap(mHeader.starBitmap, mCoordinates.starX, mCoordinates.starY, sPaint);
+    }
+
+    private void drawText(Canvas canvas, CharSequence s, int x, int y, TextPaint paint) {
+        canvas.drawText(s, 0, s.length(), x, y, paint);
+    }
+
+    private void updateBackground(boolean isUnread) {
+        if (isUnread) {
+            if (mViewMode.isTwoPane() && mViewMode.isConversationListMode()) {
+                setBackgroundResource(R.drawable.conversation_wide_unread_selector);
+            } else {
+                setBackgroundResource(R.drawable.conversation_unread_selector);
+            }
+        } else {
+            if (mViewMode.isTwoPane() && mViewMode.isConversationListMode()) {
+                setBackgroundResource(R.drawable.conversation_wide_read_selector);
+            } else {
+                setBackgroundResource(R.drawable.conversation_read_selector);
+            }
+        }
+    }
+}
diff --git a/src/com/android/email/browse/BrowseItemViewCoordinates.java b/src/com/android/email/browse/BrowseItemViewCoordinates.java
new file mode 100644
index 0000000..3619bba
--- /dev/null
+++ b/src/com/android/email/browse/BrowseItemViewCoordinates.java
@@ -0,0 +1,341 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.android.email.browse;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextPaint;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewParent;
+import android.widget.TextView;
+import com.android.email.R;
+import com.android.email.ViewMode;
+import com.android.email.utils.Utils;
+
+/**
+ * Represents the coordinates of elements inside a CanvasConversationHeaderView
+ * (eg, checkmark, star, subject, sender, labels, etc.) It will inflate a view,
+ * and record the coordinates of each element after layout. This will allows us
+ * to easily improve performance by creating custom view while still defining
+ * layout in XML files.
+ *
+ * @author phamm
+ */
+public class BrowseItemViewCoordinates {
+    // Modes.
+    private static final int WIDE_MODE = 0;
+    private static final int NORMAL_MODE = 1;
+
+    // Static threshold.
+    private static int TOTAL_LABEL_WIDTH = -1;
+    private static int TOTAL_LABEL_WIDTH_WIDE = -1;
+    private static int LABEL_CELL_WIDTH = -1;
+    private static int sConversationHeights[];
+
+    // Checkmark.
+    int checkmarkX;
+    int checkmarkY;
+
+    // Star.
+    int starX;
+    int starY;
+
+    // Personal level.
+    int personalLevelX;
+    int personalLevelY;
+
+    // Senders.
+    int sendersX;
+    int sendersY;
+    int sendersWidth;
+    int sendersLineCount;
+    int sendersLineHeight;
+    int sendersFontSize;
+    int sendersAscent;
+
+    // Subject.
+    int subjectX;
+    int subjectY;
+    int subjectWidth;
+    int subjectLineCount;
+    int subjectFontSize;
+    int subjectAscent;
+
+    // Labels.
+    int labelsXEnd;
+    int labelsY;
+    int labelsHeight;
+    int labelsTopPadding;
+    int labelsFontSize;
+    int labelsAscent;
+
+    // Date.
+    int dateXEnd;
+    int dateY;
+    int dateFontSize;
+    int dateAscent;
+
+    // Paperclip.
+    int paperclipY;
+
+    // Cache to save Coordinates based on view width.
+    private static SparseArray<BrowseItemViewCoordinates> mCache =
+            new SparseArray<BrowseItemViewCoordinates>();
+
+    private static TextPaint sPaint = new TextPaint();
+
+    static {
+        sPaint.setAntiAlias(true);
+    }
+
+    /**
+     * Returns whether to show a background on the attachment icon.
+     * Currently, we don't show a background in wide mode.
+     */
+    public static boolean showAttachmentBackground(int mode) {
+        return mode != WIDE_MODE;
+    }
+
+    /**
+     * Returns the mode of the header view (Wide/Normal/Narrow).
+     */
+    public static int getMode(Context context, ViewMode viewMode) {
+        Resources res = context.getResources();
+        return viewMode.isConversationListMode() ? res
+                .getInteger(R.integer.conversation_list_header_mode) : res
+                .getInteger(R.integer.conversation_header_mode);
+    }
+
+    /**
+     * Returns the layout id to be inflated in this mode.
+     */
+    private static int getLayoutId(int mode) {
+        switch (mode) {
+            case WIDE_MODE:
+                return R.layout.browse_item_view_wide;
+            case NORMAL_MODE:
+                return R.layout.browse_item_view_normal;
+            default:
+                throw new IllegalArgumentException("Unknown conversation header view mode " + mode);
+        }
+    }
+
+    /**
+     * Returns a value array multiplied by the specified density.
+     */
+    public static int[] getDensityDependentArray(int[] values, float density) {
+        int result[] = new int[values.length];
+        for (int i = 0; i < values.length; ++i) {
+            result[i] = (int) (values[i] * density);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the height of the view in this mode.
+     */
+    public static int getHeight(Context context, int mode) {
+        Resources res = context.getResources();
+        float density = res.getDisplayMetrics().scaledDensity;
+        if (sConversationHeights == null) {
+            sConversationHeights = getDensityDependentArray(
+                    res.getIntArray(R.array.conversation_heights), density);
+        }
+        return sConversationHeights[mode];
+    }
+
+    /**
+     * Refreshes the conversation heights array.
+     */
+    public static void refreshConversationHeights(Context context) {
+        Resources res = context.getResources();
+        float density = res.getDisplayMetrics().scaledDensity;
+        sConversationHeights = getDensityDependentArray(
+                res.getIntArray(R.array.conversation_heights), density);
+    }
+
+    /**
+     * Returns the x coordinates of a view by tracing up its hierarchy.
+     */
+    private static int getX(View view) {
+        int x = 0;
+        while (view != null) {
+            x += (int) view.getX();
+            ViewParent parent = view.getParent();
+            view = parent != null ? (View) parent : null;
+        }
+        return x;
+    }
+
+    /**
+     * Returns the y coordinates of a view by tracing up its hierarchy.
+     */
+    private static int getY(View view) {
+        int y = 0;
+        while (view != null) {
+            y += (int) view.getY();
+            ViewParent parent = view.getParent();
+            view = parent != null ? (View) parent : null;
+        }
+        return y;
+    }
+
+    /**
+     * Returns the number of lines of this text view.
+     */
+    private static int getLineCount(TextView textView) {
+        return Math.round(((float) textView.getHeight()) / textView.getLineHeight());
+    }
+
+    /**
+     * Returns the length (maximum of characters) of subject in this mode.
+     */
+    public static int getSubjectLength(Context context, int mode, boolean hasVisibleLabels,
+            boolean hasAttachments) {
+        if (hasVisibleLabels) {
+            if (hasAttachments) {
+                return context.getResources().getIntArray(
+                        R.array.senders_with_labels_and_attachment_lengths)[mode];
+            } else {
+                return context.getResources().getIntArray(
+                        R.array.senders_with_labels_lengths)[mode];
+            }
+        } else {
+            if (hasAttachments) {
+                return context.getResources().getIntArray(
+                        R.array.senders_with_attachment_lengths)[mode];
+            } else {
+                return context.getResources().getIntArray(R.array.senders_lengths)[mode];
+            }
+        }
+    }
+
+    /**
+     * Returns the width available to draw labels in this mode.
+     */
+    public static int getLabelsWidth(Context context, int mode) {
+        Resources res = context.getResources();
+        if (TOTAL_LABEL_WIDTH <= 0) {
+            TOTAL_LABEL_WIDTH = res.getDimensionPixelSize(R.dimen.max_total_label_width);
+            TOTAL_LABEL_WIDTH_WIDE = res.getDimensionPixelSize(R.dimen.max_total_label_width_wide);
+        }
+        switch (mode) {
+            case WIDE_MODE:
+                return TOTAL_LABEL_WIDTH_WIDE;
+            case NORMAL_MODE:
+                return TOTAL_LABEL_WIDTH;
+            default:
+                throw new IllegalArgumentException("Unknown conversation header view mode " + mode);
+        }
+    }
+
+    /**
+     * Returns the width of a cell to draw labels.
+     */
+    public static int getLabelCellWidth(Context context, int mode, int labelsCount) {
+        Resources res = context.getResources();
+        if (LABEL_CELL_WIDTH <= 0) {
+            LABEL_CELL_WIDTH = res.getDimensionPixelSize(R.dimen.label_cell_width);
+        }
+        switch (mode) {
+            case WIDE_MODE:
+            case NORMAL_MODE:
+                return LABEL_CELL_WIDTH;
+            default:
+                throw new IllegalArgumentException("Unknown conversation header view mode " + mode);
+        }
+    }
+
+    public static boolean displaySendersInline(int mode) {
+        switch (mode) {
+            case WIDE_MODE:
+                return false;
+            case NORMAL_MODE:
+                return true;
+            default:
+                throw new IllegalArgumentException("Unknown conversation header view mode " + mode);
+        }
+    }
+
+    /**
+     * Returns coordinates for elements inside a conversation header view given
+     * the view width.
+     */
+    public static BrowseItemViewCoordinates forWidth(Context context, int width, int mode,
+            int standardScaledDimen) {
+        BrowseItemViewCoordinates coordinates = mCache.get(width ^ standardScaledDimen);
+        if (coordinates == null) {
+            coordinates = new BrowseItemViewCoordinates();
+            mCache.put(width ^ standardScaledDimen, coordinates);
+
+            // Layout the appropriate view.
+            int height = getHeight(context, mode);
+            View view = LayoutInflater.from(context).inflate(getLayoutId(mode), null);
+            int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            view.measure(widthSpec, heightSpec);
+            view.layout(0, 0, width, height);
+
+            // Records coordinates.
+            View checkmark = view.findViewById(R.id.checkmark);
+            coordinates.checkmarkX = getX(checkmark);
+            coordinates.checkmarkY = getY(checkmark);
+
+            View star = view.findViewById(R.id.star);
+            coordinates.starX = getX(star);
+            coordinates.starY = getY(star);
+
+            View personalLevel = view.findViewById(R.id.personal_level);
+            coordinates.personalLevelX = getX(personalLevel);
+            coordinates.personalLevelY = getY(personalLevel);
+
+            TextView senders = (TextView) view.findViewById(R.id.senders);
+            coordinates.sendersX = getX(senders);
+            coordinates.sendersY = getY(senders);
+            coordinates.sendersWidth = senders.getWidth();
+            coordinates.sendersLineCount = getLineCount(senders);
+            coordinates.sendersLineHeight = senders.getLineHeight();
+            coordinates.sendersFontSize = (int) senders.getTextSize();
+            sPaint.setTextSize(coordinates.sendersFontSize);
+            coordinates.sendersAscent = (int) sPaint.ascent();
+
+            TextView subject = (TextView) view.findViewById(R.id.subject);
+            coordinates.subjectX = getX(subject);
+            coordinates.subjectY = getY(subject);
+            coordinates.subjectWidth = subject.getWidth();
+            coordinates.subjectLineCount = getLineCount(subject);
+            coordinates.subjectFontSize = (int) subject.getTextSize();
+            sPaint.setTextSize(coordinates.subjectFontSize);
+            coordinates.subjectAscent = (int) sPaint.ascent();
+
+            View labels = view.findViewById(R.id.labels);
+            coordinates.labelsXEnd = getX(labels) + labels.getWidth();
+            coordinates.labelsY = getY(labels);
+            coordinates.labelsHeight = labels.getHeight();
+            coordinates.labelsTopPadding = labels.getPaddingTop();
+            if (labels instanceof TextView) {
+                coordinates.labelsFontSize = (int) ((TextView) labels).getTextSize();
+                sPaint.setTextSize(coordinates.labelsFontSize);
+                coordinates.labelsAscent = (int) sPaint.ascent();
+            }
+
+            TextView date = (TextView) view.findViewById(R.id.date);
+            coordinates.dateXEnd = getX(date) + date.getWidth();
+            coordinates.dateY = getY(date);
+            coordinates.dateFontSize = (int) date.getTextSize();
+            sPaint.setTextSize(coordinates.dateFontSize);
+            coordinates.dateAscent = (int) sPaint.ascent();
+
+            View paperclip = view.findViewById(R.id.paperclip);
+            coordinates.paperclipY = getY(paperclip);
+        }
+        return coordinates;
+    }
+
+    public static boolean displayLabelsAboveDate(int mode) {
+        return mode == WIDE_MODE;
+    }
+}
diff --git a/src/com/android/email/browse/BrowseItemViewModel.java b/src/com/android/email/browse/BrowseItemViewModel.java
new file mode 100644
index 0000000..cfc86f8
--- /dev/null
+++ b/src/com/android/email/browse/BrowseItemViewModel.java
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2011, Google Inc.
+ *
+ * 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.android.email.browse;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import com.android.email.R;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.text.SpannableStringBuilder;
+import android.text.StaticLayout;
+import android.text.style.CharacterStyle;
+import android.util.LruCache;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * This is the view model for the conversation header. It includes all the
+ * information needed to layout a conversation header view. Each view model is
+ * associated with a conversation and is cached to improve the relayout time.
+ */
+public class BrowseItemViewModel {
+    private static final int MAX_CACHE_SIZE = 100;
+
+    boolean faded = false;
+    int fontColor;
+    @VisibleForTesting
+    static LruCache<Pair<String, Long>, BrowseItemViewModel> sConversationHeaderMap
+        = new LruCache<Pair<String, Long>, BrowseItemViewModel>(MAX_CACHE_SIZE);
+
+    // The hashcode used to detect if the conversation has changed.
+    private int mDataHashCode;
+    private int mLayoutHashCode;
+
+    // Star
+    boolean starred;
+
+    Bitmap starBitmap;
+
+    // Date
+    String dateText;
+    Bitmap dateBackground;
+
+    // Personal level
+    Bitmap personalLevelBitmap;
+
+    // Paperclip
+    Bitmap paperclip;
+
+    // Senders
+    String sendersText;
+
+    // A list of all the fragments that cover sendersText
+    final ArrayList<SenderFragment> senderFragments;
+
+    boolean hasAttachments;
+
+    boolean hasDraftMessage;
+
+    // Subject
+    SpannableStringBuilder subjectText;
+
+    StaticLayout subjectLayout;
+
+    // View Width
+    public int viewWidth;
+
+    // Standard scaled dimen used to detect if the scale of text has changed.
+    public int standardScaledDimen;
+
+    public long dateMs;;
+
+    public String subject;
+
+    public String snippet;
+
+    public String fromSnippetInstructions;
+
+    public long conversationId;
+
+    public long maxMessageId;
+
+    public boolean checkboxVisible;
+
+
+    /**
+     * Returns the view model for a conversation. If the model doesn't exist for this conversation
+     * null is returned. Note: this should only be called from the UI thread.
+     *
+     * @param account the account contains this conversation
+     * @param conversationId the Id of this conversation
+     * @return the view model for this conversation, or null
+     */
+    @VisibleForTesting
+    static BrowseItemViewModel forConversationIdOrNull(
+            String account, long conversationId) {
+        final Pair<String, Long> key = new Pair<String, Long>(account, conversationId);
+        synchronized(sConversationHeaderMap) {
+            return sConversationHeaderMap.get(key);
+        }
+    }
+
+    /**
+     * Returns the view model for a conversation. If this is the first time
+     * call, a new view model will be returned. Note: this should only be called
+     * from the UI thread.
+     *
+     * @param account the account contains this conversation
+     * @param conversationId the Id of this conversation
+     * @param cursor the cursor to use in populating/ updating the model.
+     * @return the view model for this conversation
+     */
+    static BrowseItemViewModel forConversationId(String account, long conversationId) {
+        synchronized(sConversationHeaderMap) {
+            BrowseItemViewModel header =
+                    forConversationIdOrNull(account, conversationId);
+            if (header == null) {
+                final Pair<String, Long> key = new Pair<String, Long>(account, conversationId);
+                header = new BrowseItemViewModel();
+                sConversationHeaderMap.put(key, header);
+            }
+            return header;
+        }
+    }
+
+    public BrowseItemViewModel() {
+        senderFragments = Lists.newArrayList();
+    }
+
+    /**
+     * Adds a sender fragment.
+     *
+     * @param start the start position of this fragment
+     * @param end the start position of this fragment
+     * @param style the style of this fragment
+     * @param isFixed whether this fragment is fixed or not
+     */
+    void addSenderFragment(int start, int end, CharacterStyle style, boolean isFixed) {
+        SenderFragment senderFragment = new SenderFragment(start, end, sendersText, style, isFixed);
+        senderFragments.add(senderFragment);
+    }
+
+    /**
+     * Clears all the current sender fragments.
+     */
+    void clearSenderFragments() {
+        senderFragments.clear();
+    }
+
+    /**
+     * Returns the hashcode to compare if the data in the header is valid.
+     */
+    private static int getHashCode(Context context, String dateText, String fromSnippetInstructions) {
+        if (dateText == null) {
+            return -1;
+        }
+        return fromSnippetInstructions.hashCode() ^ dateText.hashCode();
+    }
+
+    /**
+     * Returns the layout hashcode to compare to see if thet layout state has changed.
+     */
+    private int getLayoutHashCode() {
+        return mDataHashCode ^ viewWidth ^ standardScaledDimen ^
+                Boolean.valueOf(checkboxVisible).hashCode();
+    }
+
+    /**
+     * Marks this header as having valid data and layout.
+     */
+    void validate(Context context) {
+        mDataHashCode = getHashCode(context, dateText, fromSnippetInstructions);
+        mLayoutHashCode = getLayoutHashCode();
+    }
+
+    /**
+     * Returns if the data in this model is valid.
+     */
+    boolean isDataValid(Context context) {
+        return mDataHashCode == getHashCode(context, dateText, fromSnippetInstructions);
+    }
+
+    /**
+     * Returns if the layout in this model is valid.
+     */
+    boolean isLayoutValid(Context context) {
+        return isDataValid(context) && mLayoutHashCode == getLayoutHashCode();
+    }
+
+    /**
+     * Describes the style of a Senders fragment.
+     */
+    static class SenderFragment {
+        // Coordinate where the text to be drawn.
+        int x;
+        int y;
+
+        // Indices that determine which substring of mSendersText we are
+        // displaying.
+        int start;
+        int end;
+
+        // The style to apply to the TextPaint object.
+        CharacterStyle style;
+
+        // Width of the fragment.
+        int width;
+
+        // Ellipsized text.
+        String ellipsizedText;
+
+        // Whether the fragment is fixed or not.
+        boolean isFixed;
+
+        // Should the fragment be displayed or not.
+        boolean shouldDisplay;
+
+        SenderFragment(int start, int end, CharSequence sendersText, CharacterStyle style,
+                boolean isFixed) {
+            this.start = start;
+            this.end = end;
+            this.style = style;
+            this.isFixed = isFixed;
+        }
+    }
+
+    /**
+     * Get conversation information to use for accessibility.
+     */
+    public CharSequence getContentDescription(Context context) {
+       return context.getString(R.string.content_description, subject, snippet);
+    }
+}
diff --git a/src/com/android/email/browse/BrowseListActivity.java b/src/com/android/email/browse/BrowseListActivity.java
new file mode 100644
index 0000000..5e0a11b
--- /dev/null
+++ b/src/com/android/email/browse/BrowseListActivity.java
@@ -0,0 +1,77 @@
+package com.android.email.browse;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.email.R;
+import com.android.email.ViewMode;
+
+import java.util.ArrayList;
+
+public class BrowseListActivity extends Activity {
+
+    private ListView mListView;
+    private BrowseItemAdapter mAdapter;
+    private ArrayList<BrowseItemViewModel> mTestBrowseItems = new ArrayList<BrowseItemViewModel>();
+
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.browse_list_activity);
+        mListView = (ListView) findViewById(R.id.browse_list);
+        mAdapter = new BrowseItemAdapter(this, R.layout.browse_item_view_normal);
+        BrowseItemViewModel itemOne = new BrowseItemViewModel();
+        itemOne.subject = "First";
+        itemOne.sendersText = "Mindy, Andy, Paul, Minh";
+        itemOne.conversationId = 1;
+        itemOne.snippet = "first snippet";
+        itemOne.fromSnippetInstructions = "schmindor@gmail.com";
+        mTestBrowseItems.add(itemOne);
+        mTestBrowseItems.add(itemOne);
+        BrowseItemViewModel itemTwo = new BrowseItemViewModel();
+        itemTwo.subject = "Second";
+        itemTwo.sendersText = "Mindy, Andy, Paul, Minh";
+        itemTwo.conversationId = 2;
+        itemTwo.snippet = "second snippet";
+        itemTwo.fromSnippetInstructions = "schmindor@gmail.com";
+        mTestBrowseItems.add(itemTwo);
+        mTestBrowseItems.add(itemTwo);
+        BrowseItemViewModel itemThree = new BrowseItemViewModel();
+        itemThree.subject = "Third";
+        itemThree.sendersText = "Mindy, Andy, Paul, Minh";
+        itemThree.conversationId = 3;
+        itemThree.snippet = "third snippet";
+        itemThree.fromSnippetInstructions = "schmindor@gmail.com";
+        mTestBrowseItems.add(itemThree);
+        mTestBrowseItems.add(itemThree);
+        BrowseItemViewModel itemFour = new BrowseItemViewModel();
+        itemFour.subject = "Fourth";
+        itemFour.sendersText = "Mindy, Andy, Paul, Minh";
+        itemFour.conversationId = 4;
+        itemFour.fromSnippetInstructions = "schmindor@gmail.com";
+        itemFour.snippet = "fourth snippet";
+        mTestBrowseItems.add(itemFour);
+        mTestBrowseItems.add(itemFour);
+        mAdapter.addAll(mTestBrowseItems);
+
+        mListView.setAdapter(mAdapter);
+    }
+
+    class BrowseItemAdapter extends ArrayAdapter<BrowseItemViewModel> {
+
+        public BrowseItemAdapter(Context context, int textViewResourceId) {
+            super(context, textViewResourceId);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            BrowseItemView view = new BrowseItemView(getContext(), "test@testaccount.com");
+            view.bind(mAdapter.getItem(position), null, "test@testaccount.com", null,
+                    new ViewMode(getContext()));
+            return view;
+        }
+    }
+}
diff --git a/src/com/android/email/compose/AttachmentComposeView.java b/src/com/android/email/compose/AttachmentComposeView.java
index a376cd9..87dc4da 100644
--- a/src/com/android/email/compose/AttachmentComposeView.java
+++ b/src/com/android/email/compose/AttachmentComposeView.java
@@ -35,16 +35,17 @@
 class AttachmentComposeView extends LinearLayout {
     private final long mSize;
     private final String mFilename;
+    private final static String LOG_TAG = new LogUtils().getLogTag();
 
     public AttachmentComposeView(Context c, Attachment attachment) {
         super(c);
         mFilename = attachment.getName();
         mSize = attachment.getSize();
 
-        LogUtils.d(Utils.LOG_TAG, ">>>>> Attachment uri: %s", attachment.getOriginExtras());
-        LogUtils.d(Utils.LOG_TAG, ">>>>>           type: %s", attachment.getContentType());
-        LogUtils.d(Utils.LOG_TAG, ">>>>>           name: %s", mFilename);
-        LogUtils.d(Utils.LOG_TAG, ">>>>>           size: %d", mSize);
+        LogUtils.d(LOG_TAG, ">>>>> Attachment uri: %s", attachment.getOriginExtras());
+        LogUtils.d(LOG_TAG, ">>>>>           type: %s", attachment.getContentType());
+        LogUtils.d(LOG_TAG, ">>>>>           name: %s", mFilename);
+        LogUtils.d(LOG_TAG, ">>>>>           size: %d", mSize);
 
         LayoutInflater factory = LayoutInflater.from(getContext());
 
diff --git a/src/com/android/email/perf/EventLogTags.logtags b/src/com/android/email/perf/EventLogTags.logtags
new file mode 100644
index 0000000..0e5efbc
--- /dev/null
+++ b/src/com/android/email/perf/EventLogTags.logtags
@@ -0,0 +1,9 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.google.android.gm.perf
+
+# Gmail performance
+# @param action the gmail action being timed
+# @param cpu_time the number of cpu ms for this action
+# @param wall_time the SystemClock.uptimeMillis delta for this action
+206002 gmail_perf_end (action|3),(cpu_time|1|3),(wall_time|1|3),(num_sub_iteration|1)
diff --git a/src/com/android/email/perf/SimpleTimer.java b/src/com/android/email/perf/SimpleTimer.java
new file mode 100644
index 0000000..d53c302
--- /dev/null
+++ b/src/com/android/email/perf/SimpleTimer.java
@@ -0,0 +1,62 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.android.email.perf;
+
+import android.os.SystemClock;
+
+import com.android.email.utils.LogUtils;
+import com.android.email.utils.Utils;
+
+/**
+ * A simple perf timer class that supports lap-time-style measurements. Once a timer is started,
+ * any number of laps can be marked, but they are all relative to the original start time.
+ *
+ */
+public class SimpleTimer {
+
+    private static final boolean ENABLE_SIMPLE_TIMER = true;
+    private static final String LOG_TAG = new LogUtils().getLogTag();
+
+    private final boolean mEnabled;
+    private long mStartTime;
+    private String mSessionName;
+
+    public SimpleTimer() {
+        this(false);
+    }
+
+    public SimpleTimer(boolean enabled) {
+        mEnabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return ENABLE_SIMPLE_TIMER && LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)
+                && mEnabled;
+    }
+
+    public void start() {
+        start(null);
+    }
+
+    public void start(String sessionName) {
+        mStartTime = SystemClock.uptimeMillis();
+        mSessionName = sessionName;
+    }
+
+    public void mark(String msg) {
+        if (isEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            if (mSessionName != null) {
+                sb.append("(");
+                sb.append(mSessionName);
+                sb.append(") ");
+            }
+            sb.append(msg);
+            sb.append(": ");
+            sb.append(SystemClock.uptimeMillis() - mStartTime);
+            sb.append("ms elapsed");
+            LogUtils.d(LOG_TAG, sb.toString());
+        }
+    }
+
+}
diff --git a/src/com/android/email/perf/Timer.java b/src/com/android/email/perf/Timer.java
new file mode 100644
index 0000000..1cdd31b
--- /dev/null
+++ b/src/com/android/email/perf/Timer.java
@@ -0,0 +1,231 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.email.perf;
+
+import com.google.common.collect.Maps;
+import com.android.email.utils.LogUtils;
+import com.android.email.utils.Utils;
+
+import android.os.Debug;
+import android.os.SystemClock;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Performance timing utilities for Gmail.
+ *
+ * There are two main ways to log performance. For simple, one off events, the static methods
+ * {@link #startTiming} and {@link #stopTiming} are sufficient:
+ *
+ * <pre>
+ * Timer.startTiming("myEvent");
+ * ... code for myEvent ...
+ * Timer.stopTiming("myEvent");
+ * </pre>
+ *
+ * The other way is to instantiate a timer that can be passed around, and started and paused. The
+ * timer will accumulate the results for each tag, and dump the results when asked.
+ *
+ * <pre>
+ * Timer timer = new Timer();
+ *
+ * for (int i = 0; i < lots; i++) {
+ *   timer.start("tagA");
+ *   ... code for tagA ...
+ *   timer.pause("tagA");
+ *   ... code that isn't relevant to timing ...
+ * }
+ *
+ * timer.dumpResults();
+ * </pre>
+ *
+ */
+public class Timer {
+    private static final String LOG_TAG = new LogUtils().getLogTag();
+
+    // set this to false to compile out all timer calls
+    public static final boolean ENABLE_TIMER = false;
+    // Set to true to enable logging of performance stats.
+    private static final boolean LOG_PERFORMANCE_STATS = true;
+
+    /** An internal structure used for performance markers. */
+    private static class PerformancePoint {
+        public final long mCpuTimeNanos;
+        public final long mWallTimeMillis;
+
+        public PerformancePoint() {
+            mCpuTimeNanos = Debug.threadCpuTimeNanos();
+            mWallTimeMillis = SystemClock.uptimeMillis();
+        }
+    }
+
+    private final Map<String, ArrayList<PerformancePoint>> mPoints = Maps.newHashMap();
+    private final Map<String, Integer> mCounts = Maps.newHashMap();
+    private final boolean mEnabled;
+
+    public Timer() {
+        this(false);
+    }
+
+    public Timer(boolean enable) {
+        mEnabled = enable;
+    }
+
+    @SuppressWarnings("unused")
+    public boolean isEnabled() {
+        return ENABLE_TIMER && mEnabled;
+    }
+
+    /**
+     * Starts timing an event indicated by the {@code tag}.
+     */
+    @SuppressWarnings("unused")
+    public void start(String tag) {
+        if (ENABLE_TIMER && mEnabled) {
+            ArrayList<PerformancePoint> values = mPoints.get(tag);
+            if (values == null) {
+                values = new ArrayList<PerformancePoint>();
+                mPoints.put(tag, values);
+            }
+            if (values.size() % 2 == 0) {
+                values.add(new PerformancePoint());
+            }
+        }
+    }
+
+    /**
+     * Stops timing an event indicated by the {@code tag}
+     */
+    @SuppressWarnings("unused")
+    public void pause(String tag) {
+        if (ENABLE_TIMER && mEnabled) {
+            ArrayList<PerformancePoint> values = mPoints.get(tag);
+            if (values != null && values.size() % 2 == 1) {
+                values.add(new PerformancePoint());
+            }
+        }
+    }
+
+    @SuppressWarnings("unused")
+    public void count(String tag) {
+        if (ENABLE_TIMER && mEnabled) {
+            Integer counts = mCounts.get(tag);
+            if (counts == null) {
+                counts = 0;
+            }
+            mCounts.put(tag, counts + 1);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    public void clear() {
+        if (ENABLE_TIMER && mEnabled) {
+            mPoints.clear();
+            mCounts.clear();
+        }
+    }
+
+    /**
+     * Dumps cumulative timing results for all tags recognized by this timer.
+     */
+    @SuppressWarnings("unused")
+    public void dumpResults() {
+        if (ENABLE_TIMER && mEnabled) {
+            for (Map.Entry<String, ArrayList<PerformancePoint>> entry : mPoints.entrySet()) {
+                String tag = entry.getKey();
+                ArrayList<PerformancePoint> values = entry.getValue();
+                long cpuDurationNanos = 0;
+                long wallDurationMillis = 0;
+
+                for (int i = 0; i < values.size() - 1; i += 2) {
+                    PerformancePoint startPoint = values.get(i);
+                    PerformancePoint endPoint = values.get(i + 1);
+
+                    cpuDurationNanos += endPoint.mCpuTimeNanos - startPoint.mCpuTimeNanos;
+                    wallDurationMillis += endPoint.mWallTimeMillis - startPoint.mWallTimeMillis;
+                }
+
+                if (cpuDurationNanos == 0) {
+                    cpuDurationNanos = 1;
+                }
+
+                dumpTimings(tag, 1, cpuDurationNanos, wallDurationMillis);
+            }
+
+            if (LOG_PERFORMANCE_STATS) {
+                for (Map.Entry<String, Integer> entry : mCounts.entrySet()) {
+                    LogUtils.d(LOG_TAG, "Perf %s count: %d", entry.getKey(), entry.getValue());
+                }
+            }
+        }
+    }
+
+    /**
+     * Used for timing one off events.
+     */
+    private static Map<String, PerformancePoint> sPerformanceCollector =
+            new ConcurrentHashMap<String, PerformancePoint>();
+
+    /**
+     * Starts a one-off timer for an event. The event is denoted by {@code tag} and only one event
+     * of that tag may be timed at a given time.
+     */
+    public static void startTiming(String tag) {
+        if (ENABLE_TIMER) {
+            sPerformanceCollector.put(tag, new PerformancePoint());
+        }
+    }
+
+    /**
+     * Stops a one-off timer for an event indicated by {@code tag}.
+     */
+    public static void stopTiming(String tag) {
+        if (ENABLE_TIMER) {
+            stopTiming(tag, 1 /* one subiteration */);
+        }
+    }
+
+    /**
+     * Stops a one-off timer for an event indicated by {@code tag}, and indicates that the event
+     * consisted of {@code numSubIterations} sub-events, so that performance output will be denoted
+     * as such.
+     */
+    public static void stopTiming(String tag, int numSubIterations) {
+        if (ENABLE_TIMER) {
+            PerformancePoint endPoint = new PerformancePoint();
+            PerformancePoint startPoint = sPerformanceCollector.get(tag);
+            if (startPoint == null) {
+                return;
+            }
+            long cpuDurationNanos = endPoint.mCpuTimeNanos - startPoint.mCpuTimeNanos;
+            long wallDurationMillis = endPoint.mWallTimeMillis - startPoint.mWallTimeMillis;
+            // Make sure cpu Duration is non 0
+            if (cpuDurationNanos == 0) {
+                cpuDurationNanos = 1;
+            }
+
+            dumpTimings(tag, numSubIterations, cpuDurationNanos, wallDurationMillis);
+        }
+    }
+
+    private static void dumpTimings(String tag, int numSubIterations,
+            long cpuDurationNanos, long wallDurationMillis) {
+        // TODO(phamm): EventLogTags doens't work when building with SDK.
+//        EventLog.writeEvent(EventLogTags.GMAIL_PERF_END, tag,
+//                cpuDurationNanos / 1000000, wallDurationMillis, numSubIterations);
+
+        if (LOG_PERFORMANCE_STATS) {
+            LogUtils.d(LOG_TAG, "Perf %s wall: %d cpu: %d",
+                    tag, wallDurationMillis, (cpuDurationNanos / 1000000));
+            // print out the average time for each sub iteration
+            if (numSubIterations > 1) {
+                LogUtils.d(LOG_TAG, "Perf/operation %s wall: %d cpu: %d", tag,
+                        (wallDurationMillis / numSubIterations),
+                        ((cpuDurationNanos / 1000000) / numSubIterations));
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/email/providers/UIProvider.java b/src/com/android/email/providers/UIProvider.java
new file mode 100644
index 0000000..c68f9d2
--- /dev/null
+++ b/src/com/android/email/providers/UIProvider.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2011, Google Inc.
+ *
+ * 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.android.email.providers;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+
+public class UIProvider {
+    // This authority is only needed to get to the account list
+    // NOTE: Overlay applications may want to override this authority
+    public static final String AUTHORITY = "com.android.email.providers";
+
+    static final String BASE_URI_STRING = "content://" + AUTHORITY;
+
+    public static final String ACCOUNT_LIST_TYPE =
+            "vnd.android.cursor.dir/vnd.com.android.email.account";
+    public static final String ACCOUNT_TYPE =
+            "vnd.android.cursor.item/vnd.com.android.email.account";
+
+    public static final String[] ACCOUNTS_PROJECTION = {
+            BaseColumns._ID,
+            AccountColumns.NAME,
+            AccountColumns.PROVIDER_VERSION,
+            AccountColumns.URI,
+            AccountColumns.CAPABILITIES,
+            AccountColumns.FOLDER_LIST_URI,
+            AccountColumns.SEARCH_URI,
+            AccountColumns.ACCOUNT_FROM_ADDRESSES_URI,
+    };
+
+    public static final class AccountCapabilities {
+        public static final int SUPPORTS_SYNCABLE_FOLDERS = 0x0001;
+        public static final int SUPPORTS_REPORT_SPAM = 0x0002;
+        public static final int SUPPORTS_ARCHIVE = 0x0004;
+        public static final int SUPPORTS_SERVER_SEARCH = 0x0008;
+        public static final int SUPPORTS_FOLDER_SERVER_SEARCH = 0x00018;
+        public static final int RETURNS_SANITIZED_HTML = 0x0020;
+        public static final int SUPPORTS_DRAFT_SYNCHRONIZATION = 0x0040;
+        public static final int SUPPORTS_MULTIPLE_FROM_ADDRESSES = 0x0080;
+        public static final int SUPPORTS_SMART_REPLY = 0x0100;
+        public static final int SUPPORTS_LOCAL_SEARCH = 0x0200;
+        public static final int SUPPORTS_THREADED_CONVERSATIONS = 0x0400;
+    }
+
+    public static final class AccountColumns {
+        public static final String NAME = "name";
+        public static final String PROVIDER_VERSION = "providerVersion";
+        public static final String URI = "uri";
+        public static final String CAPABILITIES = "capabilities";
+        public static final String FOLDER_LIST_URI = "folderListUri";
+        public static final String SEARCH_URI = "searchUri";
+        public static final String ACCOUNT_FROM_ADDRESSES_URI = "accountFromAddressesUri";
+        public static final String SAVE_NEW_DRAFT_URI = "saveNewDraftUri";
+
+        private AccountColumns() {};
+    }
+
+    /**
+     * Returns a uri that, when queried, will return a cursor with a list of information for the
+     * list of configured accounts.
+     * @return
+     */
+    public static Uri getAccountsUri() {
+        return Uri.parse(BASE_URI_STRING + "/");
+    }
+
+
+    public static final class MessageColumns {
+        public static final String ID = "_id";
+        public static final String URI = "uri";
+        public static final String MESSAGE_ID = "messageId";
+        public static final String CONVERSATION_ID = "conversation";
+        public static final String SUBJECT = "subject";
+        public static final String SNIPPET = "snippet";
+        public static final String FROM = "fromAddress";
+        public static final String TO = "toAddresses";
+        public static final String CC = "ccAddresses";
+        public static final String BCC = "bccAddresses";
+        public static final String REPLY_TO = "replyToAddresses";
+        public static final String DATE_SENT_MS = "dateSentMs";
+        public static final String DATE_RECEIVED_MS = "dateReceivedMs";
+        public static final String LIST_INFO = "listInfo";
+        public static final String BODY = "body";
+        public static final String EMBEDS_EXTERNAL_RESOURCES = "bodyEmbedsExternalResources";
+        public static final String REF_MESSAGE_ID = "refMessageId";
+        public static final String FORWARD = "forward";
+        public static final String INCLUDE_QUOTED_TEXT = "includeQuotedText";
+        public static final String QUOTE_START_POS = "quoteStartPos";
+        public static final String CLIENT_CREATED = "clientCreated";
+        public static final String CUSTOM_FROM_ADDRESS = "customFromAddress";
+
+        // TODO: Add attachments, flags
+
+        private MessageColumns() {}
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/email/utils/LogUtils.java b/src/com/android/email/utils/LogUtils.java
index 8c065a0..3ca7961 100644
--- a/src/com/android/email/utils/LogUtils.java
+++ b/src/com/android/email/utils/LogUtils.java
@@ -22,7 +22,14 @@
 import java.util.List;
 
 public class LogUtils {
-    private static final String TAG = "Gmail";
+    private static String LOG_TAG = "Email";
+
+    /**
+     * Get the log tag to apply to logging.
+     */
+    public String getLogTag() {
+        return LOG_TAG;
+    }
 
     /**
      * Priority constant for the println method; use LogUtils.v.
@@ -84,7 +91,7 @@
         if (sDebugLoggingEnabledForTests != null) {
             return sDebugLoggingEnabledForTests.booleanValue();
         }
-        return Log.isLoggable(TAG, Log.DEBUG);
+        return Log.isLoggable(LOG_TAG, Log.DEBUG);
     }
 
     /**
diff --git a/src/com/android/email/utils/Utils.java b/src/com/android/email/utils/Utils.java
index e12587b..f0bd69a 100644
--- a/src/com/android/email/utils/Utils.java
+++ b/src/com/android/email/utils/Utils.java
@@ -15,15 +15,24 @@
  */
 package com.android.email.utils;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
 
-public class Utils {
-    public static final String LOG_TAG = "Email";
+import com.android.email.R;
 
-    public String getLogTag() {
-        return LOG_TAG;
-    }
+public class Utils {
+    /**
+     * longest extension we recognize is 4 characters (e.g. "html", "docx")
+     */
+    private static final int FILE_EXTENSION_MAX_CHARS = 4;
 
      /**
       * Sets WebView in a restricted mode suitable for email use.
@@ -36,4 +45,86 @@
         webSettings.setJavaScriptEnabled(true);
         webSettings.setSupportZoom(false);
      }
+
+     /**
+      * Format a plural string.
+      * @param resource The identity of the resource, which must be a R.plurals
+      * @param count The number of items.
+      */
+     public static String formatPlural(Context context, int resource, int count) {
+         CharSequence formatString = context.getResources().getQuantityText(resource, count);
+         return String.format(formatString.toString(), count);
+     }
+
+     /**
+      * @return an ellipsized String that's at most maxCharacters long.  If the text passed is
+      * longer, it will be abbreviated.  If it contains a suffix, the ellipses will be inserted
+      * in the middle and the suffix will be preserved.
+      */
+     public static String ellipsize(String text, int maxCharacters) {
+         int length = text.length();
+         if (length < maxCharacters) return text;
+
+         int realMax = Math.min(maxCharacters, length);
+         // Preserve the suffix if any
+         int index = text.lastIndexOf(".");
+         String extension = "\u2026"; // "...";
+         if (index >= 0) {
+             // Limit the suffix to dot + four characters
+             if (length - index <= FILE_EXTENSION_MAX_CHARS + 1) {
+                 extension = extension + text.substring(index + 1);
+             }
+         }
+         realMax -= extension.length();
+         if (realMax < 0) realMax = 0;
+         return text.substring(0, realMax) + extension;
+     }
+
+     private static CharacterStyle sUnreadStyleSpan = null;
+     private static CharacterStyle sReadStyleSpan;
+     private static CharacterStyle sDraftsStyleSpan;
+     private static CharSequence sMeString;
+     private static CharSequence sDraftSingularString;
+     private static CharSequence sDraftPluralString;
+     private static CharSequence sSendingString;
+     private static CharSequence sSendFailedString;
+
+     public static void getStyledSenderSnippet(
+             Context context, String senderInstructions,
+             SpannableStringBuilder senderBuilder,
+             SpannableStringBuilder statusBuilder,
+             int maxChars, boolean forceAllUnread, boolean forceAllRead, boolean allowDraft) {
+         Resources res = context.getResources();
+         if (sUnreadStyleSpan == null) {
+             sUnreadStyleSpan = new StyleSpan(Typeface.BOLD);
+             sReadStyleSpan = new StyleSpan(Typeface.NORMAL);
+             sDraftsStyleSpan = new ForegroundColorSpan(res.getColor(R.color.drafts));
+
+             sMeString = context.getText(R.string.me);
+             sDraftSingularString = res.getQuantityText(R.plurals.draft, 1);
+             sDraftPluralString = res.getQuantityText(R.plurals.draft, 2);
+             SpannableString sendingString = new SpannableString(context.getText(R.string.sending));
+             sendingString.setSpan(
+                     CharacterStyle.wrap(sDraftsStyleSpan), 0, sendingString.length(), 0);
+             sSendingString = sendingString;
+             sSendFailedString = context.getText(R.string.send_failed);
+         }
+
+     /*    Gmail.getSenderSnippet(
+                 senderInstructions, senderBuilder, statusBuilder, maxChars,
+                 sUnreadStyleSpan,
+                 sReadStyleSpan,
+                 sDraftsStyleSpan,
+                 sMeString,
+                 sDraftSingularString, sDraftPluralString,
+                 sSendingString, sSendFailedString,
+                 forceAllUnread, forceAllRead, allowDraft);*/
+     }
+
+     /**
+      * Returns a boolean indicating whether the table UI should be shown.
+      */
+     public static boolean useTabletUI(Context context) {
+         return context.getResources().getInteger(R.integer.use_tablet_ui) != 0;
+     }
 }