Merge "Import translations. DO NOT MERGE"
diff --git a/res/layout-land/quickcontact_activity.xml b/res/layout-land/quickcontact_activity.xml
index 552f568..65fcd7d 100644
--- a/res/layout-land/quickcontact_activity.xml
+++ b/res/layout-land/quickcontact_activity.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2014 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.
@@ -13,45 +13,29 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout
+<com.android.contacts.widget.MultiShrinkScroller
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:ex="http://schemas.android.com/apk/res-auto"
-    android:id="@android:id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:padding="32dip"
-    android:orientation="horizontal">
-    <view
-        class="com.android.contacts.common.widget.ProportionalLayout"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        ex:ratio="1.0"
-        ex:direction="heightToWidth">
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <ImageView
-                android:id="@+id/photo"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop"
-                android:contentDescription="@string/description_contact_photo" />
-            <!-- Need to set a non null background on Toolbar in order for MenuItem
-                ripples to be drawn on this view, instead of another-->
-            <Toolbar
-                android:layout_width="match_parent"
-                android:layout_height="?android:attr/actionBarSize"
-                android:background="#00000000"
-                android:id="@+id/toolbar"/>
-        </FrameLayout>
-    </view>
-    <com.android.contacts.quickcontact.ExpandingEntryCardView
-        style="@style/ExpandingEntryCardStyle"
-        android:id="@+id/communication_card"
-        android:layout_marginTop="@dimen/communication_card_marginTop"
-        android:visibility="gone" />
-    <com.android.contacts.quickcontact.ExpandingEntryCardView
-        style="@style/ExpandingEntryCardStyle"
-        android:id="@+id/recent_card"
-        android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
+    android:orientation="vertical"
+    android:id="@+id/multiscroller"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:descendantFocusability="afterDescendants" >
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/quickcontact_starting_empty_height"
+        android:id="@+id/transparent_view" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <include layout="@layout/quickcontact_header" />
+
+        <include layout="@layout/quickcontact_content" />
+
+    </LinearLayout>
+
+</com.android.contacts.widget.MultiShrinkScroller>
\ No newline at end of file
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 890f2da..c038d1b 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -40,8 +40,7 @@
         android:layout_alignParentTop="true"
         android:layout_toRightOf="@+id/icon"
         android:singleLine="true"
-        android:textColor="@android:color/black"
-        android:textStyle="bold" />
+        android:textColor="@android:color/black" />
 
     <TextView
         android:id="@+id/sub_header"
diff --git a/res/layout/quickcontact_activity.xml b/res/layout/quickcontact_activity.xml
index 13b8d9b..7b81ea2 100644
--- a/res/layout/quickcontact_activity.xml
+++ b/res/layout/quickcontact_activity.xml
@@ -29,54 +29,8 @@
         android:layout_height="@dimen/quickcontact_starting_empty_height"
         android:id="@+id/transparent_view" />
 
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@color/card_margin_color"
-        android:id="@+id/toolbar_parent">
+    <include layout="@layout/quickcontact_header" />
 
-        <ImageView
-            android:id="@+id/photo"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="centerCrop"
-            android:contentDescription="@string/description_contact_photo" />
-
-        <!-- Need to set a non null background on Toolbar in order for MenuItem
-            ripples to be drawn on this view, instead of another-->
-        <Toolbar
-            android:layout_width="match_parent"
-            android:layout_height="?android:attr/actionBarSize"
-            android:background="#00000000"
-            android:id="@+id/toolbar"/>
-
-    </FrameLayout>
-
-    <com.android.contacts.widget.TouchlessScrollView
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:fillViewport="true"
-        android:id="@+id/content_scroller"
-        android:background="@color/card_margin_color">
-
-        <!-- All the cards should be inserted into this LinearLayout -->
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical"
-            android:id="@+id/card_container">
-            <com.android.contacts.quickcontact.ExpandingEntryCardView
-                style="@style/ExpandingEntryCardStyle"
-                android:id="@+id/communication_card"
-                android:layout_marginTop="@dimen/communication_card_marginTop"
-                android:visibility="gone" />
-
-           <com.android.contacts.quickcontact.ExpandingEntryCardView
-                style="@style/ExpandingEntryCardStyle"
-                android:id="@+id/recent_card"
-                android:visibility="gone" />
-        </LinearLayout>
-
-    </com.android.contacts.widget.TouchlessScrollView>
+    <include layout="@layout/quickcontact_content" />
 
 </com.android.contacts.widget.MultiShrinkScroller>
\ No newline at end of file
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
new file mode 100644
index 0000000..b5b2a83
--- /dev/null
+++ b/res/layout/quickcontact_content.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<com.android.contacts.widget.TouchlessScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true"
+    android:id="@+id/content_scroller"
+    android:background="@color/card_margin_color">
+
+    <!-- All the cards should be inserted into this LinearLayout -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:id="@+id/card_container">
+        <com.android.contacts.quickcontact.ExpandingEntryCardView
+            style="@style/ExpandingEntryCardStyle"
+            android:id="@+id/communication_card"
+            android:layout_marginTop="@dimen/communication_card_marginTop"
+            android:visibility="gone" />
+
+        <com.android.contacts.quickcontact.ExpandingEntryCardView
+            style="@style/ExpandingEntryCardStyle"
+            android:id="@+id/recent_card"
+            android:visibility="gone" />
+    </LinearLayout>
+
+</com.android.contacts.widget.TouchlessScrollView>
\ No newline at end of file
diff --git a/res/layout/quickcontact_header.xml b/res/layout/quickcontact_header.xml
new file mode 100644
index 0000000..55b23c9
--- /dev/null
+++ b/res/layout/quickcontact_header.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/toolbar_parent">
+
+    <com.android.contacts.widget.QuickContactImageView
+        android:id="@+id/photo"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop"
+        android:contentDescription="@string/description_contact_photo" />
+
+    <!-- Need to set a non null background on Toolbar in order for MenuItem
+        ripples to be drawn on this view, instead of another-->
+    <Toolbar
+        android:layout_width="match_parent"
+        android:layout_height="?android:attr/actionBarSize"
+        android:background="#00000000"
+        android:id="@+id/toolbar"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="@color/actionbar_text_color"
+        android:maxLines="@integer/quickcontact_title_lines"
+        android:ellipsize="end"
+        android:layout_gravity="bottom"
+        android:textSize="@dimen/quickcontact_maximum_title_size"
+        android:id="@+id/large_title"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/quickcontact_title_placeholder.xml b/res/layout/quickcontact_title_placeholder.xml
new file mode 100644
index 0000000..31d83ff
--- /dev/null
+++ b/res/layout/quickcontact_title_placeholder.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2014 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent" >
+
+    <!-- Marks the location and size of the Activity title -->
+    <TextView
+        android:id="@+id/placeholder_textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@android:style/TextAppearance.Material.Widget.ActionBar.Title" />
+
+</FrameLayout>
+
+
diff --git a/res/values-land/bools.xml b/res/values-land/bools.xml
new file mode 100644
index 0000000..bd0650f
--- /dev/null
+++ b/res/values-land/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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>
+
+    <bool name="quickcontact_two_panel">true</bool>
+
+</resources>
diff --git a/res/values-land/integers.xml b/res/values-land/integers.xml
index 010a5b7..08e1fe3 100644
--- a/res/values-land/integers.xml
+++ b/res/values-land/integers.xml
@@ -18,4 +18,7 @@
     <integer name="contact_tile_column_count_in_favorites">5</integer>
 
     <integer name="contact_tile_column_count">4</integer>
+
+    <!-- Number of lines the QuickContact title can have -->
+    <integer name="quickcontact_title_lines">2</integer>
 </resources>
diff --git a/res/values-land/vals.xml b/res/values-land/vals.xml
new file mode 100644
index 0000000..ebcae31
--- /dev/null
+++ b/res/values-land/vals.xml
@@ -0,0 +1,19 @@
+<?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>
+    <!-- The ratio of width:height for the contact's photo -->
+    <item name="quickcontact_photo_ratio" type="vals" format="float">0.7</item>
+</resources>
diff --git a/res/values/bools.xml b/res/values/bools.xml
new file mode 100644
index 0000000..663845a
--- /dev/null
+++ b/res/values/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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>
+
+    <bool name="quickcontact_two_panel">false</bool>
+
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 962fe97..89f39b8 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,16 +39,16 @@
     <color name="contacts_accent_color">#00acc1</color>
 
     <!-- Color of the separator between entries in an ExpandingEntryCardView -->
-    <color name="expanding_entry_card_item_separator_color">#eeeeee</color>
+    <color name="expanding_entry_card_item_separator_color">#e4e4e4</color>
 
     <!-- Color of the text on an ExpandingEntryCard button -->
     <color name="expanding_entry_card_button_text_color">@android:color/black</color>
 
     <!-- Background color for an ExpandingEntryCard -->
-    <color name="expanding_entry_card_background_color">#f7f8f9</color>
+    <color name="expanding_entry_card_background_color">#ffffff</color>
 
     <!-- Color of the margin for cards -->
-    <color name="card_margin_color">#ffbbbbbb</color>
+    <color name="card_margin_color">#f4f4f4</color>
 
     <color name="call_arrow_green">#2aad6f</color>
     <color name="call_arrow_red">#ff2e58</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 99bc345..9d8ba70 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -18,8 +18,8 @@
 
     <!-- Initial height of transparent space above QuickContacts -->
     <dimen name="quickcontact_starting_empty_height">150dp</dimen>
-    <!-- Initial height of QuickContact's header/avatar-photo -->
-    <dimen name="quickcontact_starting_header_height">200dp</dimen>
+    <!-- Initial size of QuickContact's title size -->
+    <dimen name="quickcontact_maximum_title_size">36dp</dimen>
 
     <!-- Top padding of the entire contact editor  -->
     <dimen name="editor_padding_top">0dip</dimen>
@@ -147,9 +147,9 @@
     <dimen name="expanding_entry_card_marginBottom">8dp</dimen>
 
     <!-- Elevation of an ExpandingEntryCard, for the sake of shadow casting -->
-    <dimen name="expanding_entry_card_elevation">2dp</dimen>
+    <dimen name="expanding_entry_card_elevation">1dp</dimen>
     <!-- Elevation of the QuickContact's Toolbar, for the sake of shadow casting -->
-    <dimen name="quick_contact_toolbar_elevation">6dp</dimen>
+    <dimen name="quick_contact_toolbar_elevation">4.5dp</dimen>
 
     <!-- Top margin for the communication card, used to add space from header. -->
     <dimen name="communication_card_marginTop">8dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index a6b43d7..ff34d11 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -20,4 +20,7 @@
 
     <!-- Determines the number of columns in a ContactTileRow -->
     <integer name="contact_tile_column_count">2</integer>
+
+    <!-- Number of lines the QuickContact title can have -->
+    <integer name="quickcontact_title_lines">1</integer>
 </resources>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 8688a47..cc687ec 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -829,9 +829,8 @@
 
                 // Don't bother undemoting if this contact is the user's profile.
                 if (id < Profile.MIN_ID) {
-                    values.clear();
-                    values.put(String.valueOf(id), PinnedPositions.UNDEMOTE);
-                    getContentResolver().update(PinnedPositions.UPDATE_URI, values, null, null);
+                    getContentResolver().call(ContactsContract.AUTHORITY_URI,
+                            PinnedPositions.UNDEMOTE_METHOD, String.valueOf(id), null);
                 }
             }
         } finally {
diff --git a/src/com/android/contacts/interactions/CalendarInteraction.java b/src/com/android/contacts/interactions/CalendarInteraction.java
index 68e37f7..4b766f8 100644
--- a/src/com/android/contacts/interactions/CalendarInteraction.java
+++ b/src/com/android/contacts/interactions/CalendarInteraction.java
@@ -61,6 +61,16 @@
         // TODO: build callback to update time zone if different than preferences
         String localTimezone = Time.getCurrentTimezone();
 
+        Long dateEnd = getDtend();
+        Long dateStart = getDtstart();
+        if (dateStart == null && dateEnd == null) {
+            return null;
+        } else if (dateEnd == null) {
+            dateEnd = dateStart;
+        } else if (dateStart == null) {
+            dateStart = dateEnd;
+        }
+
         String displayedDatetime = CalendarInteractionUtils.getDisplayedDatetime(
                 getDtstart(), getDtend(), System.currentTimeMillis(), localTimezone,
                 getAllDay(), context);
@@ -99,39 +109,39 @@
         return mValues.getAsString(Attendees.ATTENDEE_NAME);
     }
 
-    public int getAttendeeRelationship() {
+    public Integer getAttendeeRelationship() {
         return mValues.getAsInteger(Attendees.ATTENDEE_RELATIONSHIP);
     }
 
-    public int getAttendeeStatus() {
+    public Integer getAttendeeStatus() {
         return mValues.getAsInteger(Attendees.ATTENDEE_STATUS);
     }
 
-    public int getAttendeeType() {
+    public Integer getAttendeeType() {
         return mValues.getAsInteger(Attendees.ATTENDEE_TYPE);
     }
 
-    public int getEventId() {
+    public Integer getEventId() {
         return mValues.getAsInteger(Attendees.EVENT_ID);
     }
 
-    public int getAccessLevel() {
+    public Integer getAccessLevel() {
         return mValues.getAsInteger(Attendees.ACCESS_LEVEL);
     }
 
-    public boolean getAllDay() {
+    public Boolean getAllDay() {
         return mValues.getAsBoolean(Attendees.ALL_DAY);
     }
 
-    public int getAvailability() {
+    public Integer getAvailability() {
         return mValues.getAsInteger(Attendees.AVAILABILITY);
     }
 
-    public int getCalendarId() {
+    public Integer getCalendarId() {
         return mValues.getAsInteger(Attendees.CALENDAR_ID);
     }
 
-    public boolean getCanInviteOthers() {
+    public Boolean getCanInviteOthers() {
         return mValues.getAsBoolean(Attendees.CAN_INVITE_OTHERS);
     }
 
@@ -147,15 +157,15 @@
         return mValues.getAsString(Attendees.DESCRIPTION);
     }
 
-    public int getDisplayColor() {
+    public Integer getDisplayColor() {
         return mValues.getAsInteger(Attendees.DISPLAY_COLOR);
     }
 
-    public long getDtend() {
+    public Long getDtend() {
         return mValues.getAsLong(Attendees.DTEND);
     }
 
-    public long getDtstart() {
+    public Long getDtstart() {
         return mValues.getAsLong(Attendees.DTSTART);
     }
 
@@ -163,7 +173,7 @@
         return mValues.getAsString(Attendees.DURATION);
     }
 
-    public int getEventColor() {
+    public Integer getEventColor() {
         return mValues.getAsInteger(Attendees.EVENT_COLOR);
     }
 
@@ -187,27 +197,27 @@
         return mValues.getAsString(Attendees.EXRULE);
     }
 
-    public boolean getGuestsCanInviteOthers() {
+    public Boolean getGuestsCanInviteOthers() {
         return mValues.getAsBoolean(Attendees.GUESTS_CAN_INVITE_OTHERS);
     }
 
-    public boolean getGuestsCanModify() {
+    public Boolean getGuestsCanModify() {
         return mValues.getAsBoolean(Attendees.GUESTS_CAN_MODIFY);
     }
 
-    public boolean getGuestsCanSeeGuests() {
+    public Boolean getGuestsCanSeeGuests() {
         return mValues.getAsBoolean(Attendees.GUESTS_CAN_SEE_GUESTS);
     }
 
-    public boolean getHasAlarm() {
+    public Boolean getHasAlarm() {
         return mValues.getAsBoolean(Attendees.HAS_ALARM);
     }
 
-    public boolean getHasAttendeeData() {
+    public Boolean getHasAttendeeData() {
         return mValues.getAsBoolean(Attendees.HAS_ATTENDEE_DATA);
     }
 
-    public boolean getHasExtendedProperties() {
+    public Boolean getHasExtendedProperties() {
         return mValues.getAsBoolean(Attendees.HAS_EXTENDED_PROPERTIES);
     }
 
@@ -215,11 +225,11 @@
         return mValues.getAsString(Attendees.IS_ORGANIZER);
     }
 
-    public long getLastDate() {
+    public Long getLastDate() {
         return mValues.getAsLong(Attendees.LAST_DATE);
     }
 
-    public boolean getLastSynced() {
+    public Boolean getLastSynced() {
         return mValues.getAsBoolean(Attendees.LAST_SYNCED);
     }
 
@@ -227,7 +237,7 @@
         return mValues.getAsString(Attendees.ORGANIZER);
     }
 
-    public boolean getOriginalAllDay() {
+    public Boolean getOriginalAllDay() {
         return mValues.getAsBoolean(Attendees.ORIGINAL_ALL_DAY);
     }
 
@@ -235,7 +245,7 @@
         return mValues.getAsString(Attendees.ORIGINAL_ID);
     }
 
-    public long getOriginalInstanceTime() {
+    public Long getOriginalInstanceTime() {
         return mValues.getAsLong(Attendees.ORIGINAL_INSTANCE_TIME);
     }
 
@@ -251,11 +261,11 @@
         return mValues.getAsString(Attendees.RRULE);
     }
 
-    public int getSelfAttendeeStatus() {
+    public Integer getSelfAttendeeStatus() {
         return mValues.getAsInteger(Attendees.SELF_ATTENDEE_STATUS);
     }
 
-    public int getStatus() {
+    public Integer getStatus() {
         return mValues.getAsInteger(Attendees.STATUS);
     }
 
diff --git a/src/com/android/contacts/interactions/CallLogInteraction.java b/src/com/android/contacts/interactions/CallLogInteraction.java
index 8607974..28b9655 100644
--- a/src/com/android/contacts/interactions/CallLogInteraction.java
+++ b/src/com/android/contacts/interactions/CallLogInteraction.java
@@ -55,7 +55,9 @@
 
     @Override
     public Intent getIntent() {
-        return new Intent(Intent.ACTION_CALL).setData(Uri.parse(URI_TARGET_PREFIX + getNumber()));
+        String number = getNumber();
+        return number == null ? null : new Intent(Intent.ACTION_CALL).setData(
+                Uri.parse(URI_TARGET_PREFIX + number));
     }
 
     @Override
@@ -65,13 +67,14 @@
 
     @Override
     public long getInteractionDate() {
-        return getDate();
+        Long date = getDate();
+        return date == null ? -1 : date;
     }
 
     @Override
     public String getViewBody(Context context) {
-        int numberType = getCachedNumberType();
-        if (numberType == -1) {
+        Integer numberType = getCachedNumberType();
+        if (numberType == null) {
             return null;
         }
         return Phone.getTypeLabel(context.getResources(), getCachedNumberType(),
@@ -80,7 +83,9 @@
 
     @Override
     public String getViewFooter(Context context) {
-        return ContactInteractionUtil.formatDateStringFromTimestamp(getDate(), context);
+        Long date = getDate();
+        return date == null ? null : ContactInteractionUtil.formatDateStringFromTimestamp(
+                date, context);
     }
 
     @Override
@@ -97,7 +102,11 @@
     public Drawable getFooterIcon(Context context) {
         Drawable callArrow = null;
         Resources res = context.getResources();
-        switch (getType()) {
+        Integer type = getType();
+        if (type == null) {
+            return null;
+        }
+        switch (type) {
             case Calls.INCOMING_TYPE:
                 callArrow = res.getDrawable(CALL_ARROW_ICON_RES);
                 callArrow.setColorFilter(res.getColor(R.color.call_arrow_green),
@@ -125,28 +134,27 @@
         return mValues.getAsString(Calls.CACHED_NUMBER_LABEL);
     }
 
-    public int getCachedNumberType() {
-        Integer type = mValues.getAsInteger(Calls.CACHED_NUMBER_TYPE);
-        return type != null ? type : -1;
+    public Integer getCachedNumberType() {
+        return mValues.getAsInteger(Calls.CACHED_NUMBER_TYPE);
     }
 
-    public long getDate() {
+    public Long getDate() {
         return mValues.getAsLong(Calls.DATE);
     }
 
-    public long getDuration() {
+    public Long getDuration() {
         return mValues.getAsLong(Calls.DURATION);
     }
 
-    public boolean getIsRead() {
+    public Boolean getIsRead() {
         return mValues.getAsBoolean(Calls.IS_READ);
     }
 
-    public int getLimitParamKey() {
+    public Integer getLimitParamKey() {
         return mValues.getAsInteger(Calls.LIMIT_PARAM_KEY);
     }
 
-    public boolean getNew() {
+    public Boolean getNew() {
         return mValues.getAsBoolean(Calls.NEW);
     }
 
@@ -154,15 +162,15 @@
         return mValues.getAsString(Calls.NUMBER);
     }
 
-    public int getNumberPresentation() {
+    public Integer getNumberPresentation() {
         return mValues.getAsInteger(Calls.NUMBER_PRESENTATION);
     }
 
-    public int getOffsetParamKey() {
+    public Integer getOffsetParamKey() {
         return mValues.getAsInteger(Calls.OFFSET_PARAM_KEY);
     }
 
-    public int getType() {
+    public Integer getType() {
         return mValues.getAsInteger(Calls.TYPE);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/interactions/ContactInteractionUtil.java b/src/com/android/contacts/interactions/ContactInteractionUtil.java
index a8a66f3..98d45ee 100644
--- a/src/com/android/contacts/interactions/ContactInteractionUtil.java
+++ b/src/com/android/contacts/interactions/ContactInteractionUtil.java
@@ -26,7 +26,7 @@
 
 import java.util.Calendar;
 
-import com.android.contacts.common.R;
+import com.android.contacts.R;
 
 
 /**
diff --git a/src/com/android/contacts/interactions/SmsInteraction.java b/src/com/android/contacts/interactions/SmsInteraction.java
index c70356e..ac83786 100644
--- a/src/com/android/contacts/interactions/SmsInteraction.java
+++ b/src/com/android/contacts/interactions/SmsInteraction.java
@@ -41,12 +41,15 @@
 
     @Override
     public Intent getIntent() {
-        return new Intent(Intent.ACTION_VIEW).setData(Uri.parse(URI_TARGET_PREFIX + getAddress()));
+        String address = getAddress();
+        return address == null ? null : new Intent(Intent.ACTION_VIEW).setData(
+                Uri.parse(URI_TARGET_PREFIX + address));
     }
 
     @Override
     public long getInteractionDate() {
-        return getDate();
+        Long date = getDate();
+        return date == null ? -1 : date;
     }
 
     @Override
@@ -61,7 +64,9 @@
 
     @Override
     public String getViewFooter(Context context) {
-        return ContactInteractionUtil.formatDateStringFromTimestamp(getDate(), context);
+        Long date = getDate();
+        return date == null ? null : ContactInteractionUtil.formatDateStringFromTimestamp(
+                date, context);
     }
 
     @Override
@@ -87,40 +92,40 @@
         return mValues.getAsString(Sms.BODY);
     }
 
-    public long getDate() {
+    public Long getDate() {
         return mValues.getAsLong(Sms.DATE);
     }
 
 
-    public long getDateSent() {
+    public Long getDateSent() {
         return mValues.getAsLong(Sms.DATE_SENT);
     }
 
-    public int getErrorCode() {
+    public Integer getErrorCode() {
         return mValues.getAsInteger(Sms.ERROR_CODE);
     }
 
-    public boolean getLocked() {
+    public Boolean getLocked() {
         return mValues.getAsBoolean(Sms.LOCKED);
     }
 
-    public int getPerson() {
+    public Integer getPerson() {
         return mValues.getAsInteger(Sms.PERSON);
     }
 
-    public int getProtocol() {
+    public Integer getProtocol() {
         return mValues.getAsInteger(Sms.PROTOCOL);
     }
 
-    public boolean getRead() {
+    public Boolean getRead() {
         return mValues.getAsBoolean(Sms.READ);
     }
 
-    public boolean getReplyPathPresent() {
+    public Boolean getReplyPathPresent() {
         return mValues.getAsBoolean(Sms.REPLY_PATH_PRESENT);
     }
 
-    public boolean getSeen() {
+    public Boolean getSeen() {
         return mValues.getAsBoolean(Sms.SEEN);
     }
 
@@ -128,7 +133,7 @@
         return mValues.getAsString(Sms.SERVICE_CENTER);
     }
 
-    public int getStatus() {
+    public Integer getStatus() {
         return mValues.getAsInteger(Sms.STATUS);
     }
 
@@ -136,11 +141,11 @@
         return mValues.getAsString(Sms.SUBJECT);
     }
 
-    public int getThreadId() {
+    public Integer getThreadId() {
         return mValues.getAsInteger(Sms.THREAD_ID);
     }
 
-    public int getType() {
+    public Integer getType() {
         return mValues.getAsInteger(Sms.TYPE);
     }
 }
diff --git a/src/com/android/contacts/quickcontact/Action.java b/src/com/android/contacts/quickcontact/Action.java
index fa23286..7d904ab 100644
--- a/src/com/android/contacts/quickcontact/Action.java
+++ b/src/com/android/contacts/quickcontact/Action.java
@@ -45,7 +45,10 @@
     public Intent getAlternateIntent();
 
     /** Checks if the contact data for this action is primary. */
-    public Boolean isPrimary();
+    public boolean isPrimary();
+
+    /** Checks if the contact data for this action is super primary. */
+    public boolean isSuperPrimary();
 
     /**
      * Returns a lookup (@link Uri) for the contact data item or null if there is no data item
@@ -61,4 +64,14 @@
 
     /** Returns the presence of this item or -1 if it was never set */
     public int getPresence();
+
+    /**
+     * Returns the number of times this action has been used.
+     */
+    public Integer getTimesUsed();
+
+    /**
+     * Returns the last time this action was used.
+     */
+    public Long getLastTimeUsed();
 }
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index e29b3ef..a0df1e6 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -29,9 +29,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.contacts.R;
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.R;
 import com.android.contacts.common.MoreContactUtils;
 import com.android.contacts.common.model.account.AccountType.EditType;
 import com.android.contacts.common.model.dataitem.DataItem;
@@ -55,6 +55,8 @@
     private final Context mContext;
     private final DataKind mKind;
     private final String mMimeType;
+    private final Integer mTimesUsed;
+    private final Long mLastTimeUsed;
 
     private CharSequence mBody;
     private CharSequence mSubtitle;
@@ -67,6 +69,7 @@
     private Uri mDataUri;
     private long mDataId;
     private boolean mIsPrimary;
+    private boolean mIsSuperPrimary;
 
     /**
      * Create an action from common {@link Data} elements.
@@ -75,6 +78,8 @@
         mContext = context;
         mKind = kind;
         mMimeType = item.getMimeType();
+        mTimesUsed = item.getTimesUsed();
+        mLastTimeUsed = item.getLastTimeUsed();
 
         // Determine type for subtitle
         mSubtitle = "";
@@ -96,7 +101,8 @@
             }
         }
 
-        mIsPrimary = item.isSuperPrimary();
+        mIsPrimary = item.isPrimary();
+        mIsSuperPrimary = item.isSuperPrimary();
         mBody = item.buildDataStringForDisplay(context, kind);
 
         mDataId = item.getId();
@@ -265,11 +271,16 @@
     }
 
     @Override
-    public Boolean isPrimary() {
+    public boolean isPrimary() {
         return mIsPrimary;
     }
 
     @Override
+    public boolean isSuperPrimary() {
+        return mIsSuperPrimary;
+    }
+
+    @Override
     public Drawable getAlternateIcon() {
         if (mAlternateIconRes == 0) return null;
 
@@ -322,4 +333,14 @@
         }
         return true;
     }
+
+    @Override
+    public Integer getTimesUsed() {
+        return mTimesUsed;
+    }
+
+    @Override
+    public Long getLastTimeUsed() {
+        return mLastTimeUsed;
+    }
 }
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 18703d1..209284f 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.ColorFilter;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -122,7 +123,10 @@
     private List<Entry> mEntries;
     private List<View> mEntryViews;
     private LinearLayout mEntriesViewGroup;
+    private final Drawable mCollapseArrowDrawable;
+    private final Drawable mExpandArrowDrawable;
     private int mThemeColor;
+    private ColorFilter mThemeColorFilter;
 
     private final OnClickListener mExpandCollapseButtonListener = new OnClickListener() {
         @Override
@@ -146,6 +150,17 @@
         mEntriesViewGroup = (LinearLayout)
                 expandingEntryCardView.findViewById(R.id.content_area_linear_layout);
         mTitleTextView = (TextView) expandingEntryCardView.findViewById(R.id.title);
+        mCollapseArrowDrawable =
+                getResources().getDrawable(R.drawable.expanding_entry_card_collapse_white_24);
+        mExpandArrowDrawable =
+                getResources().getDrawable(R.drawable.expanding_entry_card_expand_white_24);
+
+        mExpandCollapseButton = inflater.inflate(
+                R.layout.quickcontact_expanding_entry_card_button, this, false);
+        mExpandCollapseTextView = (TextView) mExpandCollapseButton.findViewById(R.id.text);
+        mExpandCollapseButton.setOnClickListener(mExpandCollapseButtonListener);
+
+
     }
 
     /**
@@ -154,19 +169,21 @@
      * @param entries The Entry list to display.
      */
     public void initialize(List<Entry> entries, int numInitialVisibleEntries,
-            boolean isExpanded, int themeColor) {
+            boolean isExpanded) {
         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
         mIsExpanded = isExpanded;
         mEntries = entries;
         mEntryViews = new ArrayList<View>(entries.size());
         mCollapsedEntriesCount = Math.min(numInitialVisibleEntries, entries.size());
-        mThemeColor = themeColor;
 
-        if (mExpandCollapseButton == null) {
-            createExpandButton(layoutInflater);
+        if (mIsExpanded) {
+            updateExpandCollapseButton(getCollapseButtonText());
+        } else {
+            updateExpandCollapseButton(getExpandButtonText());
         }
         inflateViewsIfNeeded(layoutInflater);
         insertEntriesIntoViewGroup();
+        applyColor();
     }
 
     /**
@@ -260,18 +277,6 @@
         }
     }
 
-    private void createExpandButton(LayoutInflater layoutInflater) {
-        mExpandCollapseButton = layoutInflater.inflate(
-                R.layout.quickcontact_expanding_entry_card_button, this, false);
-        mExpandCollapseTextView = (TextView) mExpandCollapseButton.findViewById(R.id.text);
-        if (mIsExpanded) {
-            updateExpandCollapseButton(getCollapseButtonText());
-        } else {
-            updateExpandCollapseButton(getExpandButtonText());
-        }
-        mExpandCollapseButton.setOnClickListener(mExpandCollapseButtonListener);
-    }
-
     /**
      * Lazily inflate the number of views currently needed, and bind data from
      * mEntries into these views.
@@ -283,6 +288,41 @@
         }
     }
 
+    public void setColorAndFilter(int color, ColorFilter colorFilter) {
+        mThemeColor = color;
+        mThemeColorFilter = colorFilter;
+        applyColor();
+    }
+
+    /**
+     * The ColorFilter is passed in along with the color so that a new one only needs to be created
+     * once for the entire activity.
+     * 1. Title
+     * 2. Entry icons
+     * 3. Expand/Collapse Text
+     * 4. Expand/Collapse Button
+     */
+    public void applyColor() {
+        if (mThemeColor != 0 && mThemeColorFilter != null) {
+            // Title
+            if (mTitleTextView != null) {
+                mTitleTextView.setTextColor(mThemeColor);
+            }
+
+            // Entry icons
+            if (mEntries != null) {
+                for (Entry entry : mEntries) {
+                    entry.getIcon().setColorFilter(mThemeColorFilter);
+                }
+            }
+
+            // Expand/Collapse
+            mExpandCollapseTextView.setTextColor(mThemeColor);
+            mCollapseArrowDrawable.setColorFilter(mThemeColorFilter);
+            mExpandArrowDrawable.setColorFilter(mThemeColorFilter);
+        }
+    }
+
     private View createEntryView(LayoutInflater layoutInflater, Entry entry) {
         View view = layoutInflater.inflate(
                 R.layout.expanding_entry_card_item, this, false);
@@ -335,15 +375,12 @@
     }
 
     private void updateExpandCollapseButton(CharSequence buttonText) {
-        int resId = mIsExpanded ? R.drawable.expanding_entry_card_collapse_white_24
-                : R.drawable.expanding_entry_card_expand_white_24;
-        // TODO: apply color theme to the drawable
-        Drawable drawable = getResources().getDrawable(resId);
+        final Drawable arrow = mIsExpanded ? mCollapseArrowDrawable : mExpandArrowDrawable;
         if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable,
+            mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, arrow,
                     null);
         } else {
-            mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null,
+            mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(arrow, null, null,
                     null);
         }
         mExpandCollapseTextView.setText(buttonText);
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index b186038..99d8094 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -93,7 +94,6 @@
 import com.android.contacts.util.SchedulingUtils;
 import com.android.contacts.widget.MultiShrinkScroller;
 import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
-
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 
@@ -123,11 +123,13 @@
 
     private static final String TAG = "QuickContact";
 
+    private static final String KEY_THEME_COLOR = "theme_color";
+
     private static final int ANIMATION_SLIDE_OPEN_DURATION = 250;
-    private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 75;
+    private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
     private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
     private static final float SYSTEM_BAR_BRIGHTNESS_FACTOR = 0.7f;
-    private static final int SHIM_COLOR = Color.argb(0x7F, 0, 0, 0);
+    private static final int SCRIM_COLOR = Color.argb(0xB2, 0, 0, 0);
 
     /** This is the Intent action to install a shortcut in the launcher. */
     private static final String ACTION_INSTALL_SHORTCUT =
@@ -149,18 +151,17 @@
     private MultiShrinkScroller mScroller;
     private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
     private AsyncTask<Void, Void, Void> mEntriesAndActionsTask;
-    private ColorDrawable mWindowShim;
+    private ColorDrawable mWindowScrim;
     private boolean mIsWaitingForOtherPieceOfExitAnimation;
     private boolean mIsExitAnimationInProgress;
+    private boolean mHasComputedThemeColor;
 
     private static final int MIN_NUM_COMMUNICATION_ENTRIES_SHOWN = 3;
     private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
 
     private Contact mContactData;
     private ContactLoader mContactLoader;
-
     private PorterDuffColorFilter mColorFilter;
-    List<Drawable> mDrawablesToTint;
 
     private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
 
@@ -295,7 +296,7 @@
         public void onStartScrollOffBottom() {
             // Remove the window shim now that we are starting an Activity exit animation.
             final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
-            final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowShim, "alpha", 0xFF, 0);
+            final ObjectAnimator animator = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0xFF, 0);
             animator.addListener(mExitWindowShimAnimationListener);
             animator.setDuration(duration).start();
             mIsWaitingForOtherPieceOfExitAnimation = true;
@@ -357,15 +358,18 @@
 
         final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
         setActionBar(toolbar);
-        setHeaderNameText(R.string.missing_name);
+        getActionBar().setTitle(null);
+        // Put a TextView with a known resource id into the ActionBar. This allows us to easily
+        // find the correct TextView location & size later.
+        toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
 
         mHasAlreadyBeenOpened = savedInstanceState != null;
 
-        mWindowShim = new ColorDrawable(SHIM_COLOR);
-        getWindow().setBackgroundDrawable(mWindowShim);
+        mWindowScrim = new ColorDrawable(SCRIM_COLOR);
+        getWindow().setBackgroundDrawable(mWindowScrim);
         if (!mHasAlreadyBeenOpened) {
             final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
-            ObjectAnimator.ofInt(mWindowShim, "alpha", 0, 0xFF).setDuration(duration).start();
+            ObjectAnimator.ofInt(mWindowScrim, "alpha", 0, 0xFF).setDuration(duration).start();
         }
 
         if (mScroller != null) {
@@ -380,7 +384,8 @@
             }
         }
 
-        mDrawablesToTint = new ArrayList<>();
+        setHeaderNameText(R.string.missing_name);
+
         mSelectAccountFragmentListener= (SelectAccountDialogFragmentListener) getFragmentManager()
                 .findFragmentByTag(FRAGMENT_TAG_SELECT_ACCOUNT);
         if (mSelectAccountFragmentListener == null) {
@@ -391,6 +396,21 @@
         }
         mSelectAccountFragmentListener.setQuickContactActivity(this);
 
+        if (savedInstanceState != null) {
+            final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
+            if (color != 0) {
+                // Wait for pre draw. Setting the header tint before the MultiShrinkScroller has
+                // been measured will cause incorrect tinting calculations.
+                SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                setThemeColor(color);
+                            }
+                        });
+            }
+        }
+
         Trace.endSection();
     }
 
@@ -407,9 +427,18 @@
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         mHasAlreadyBeenOpened = true;
+        mHasComputedThemeColor = false;
         processIntent(intent);
     }
 
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        super.onSaveInstanceState(savedInstanceState);
+        if (mColorFilter != null) {
+            savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilter.getColor());
+        }
+    }
+
     private void processIntent(Intent intent) {
         Uri lookupUri = intent.getData();
 
@@ -454,13 +483,17 @@
 
     /** Assign this string to the view if it is not empty. */
     private void setHeaderNameText(int resId) {
-        getActionBar().setTitle(getText(resId));
+        if (mScroller != null) {
+            mScroller.setTitle(String.valueOf(getText(resId)));
+        }
     }
 
     /** Assign this string to the view if it is not empty. */
     private void setHeaderNameText(CharSequence value) {
         if (!TextUtils.isEmpty(value)) {
-            getActionBar().setTitle(value);
+            if (mScroller != null) {
+                mScroller.setTitle(value.toString());
+            }
         }
     }
 
@@ -497,7 +530,6 @@
 
         Trace.endSection();
 
-        final List<String> sortedActionMimeTypes = Lists.newArrayList();
         // Maintain a list of phone numbers to pass into SmsInteractionsLoader
         final Set<String> phoneNumbers = new HashSet<>();
         // Maintain a list of email addresses to pass into CalendarInteractionsLoader
@@ -508,8 +540,7 @@
         mEntriesAndActionsTask = new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                computeEntriesAndActions(data, phoneNumbers, emailAddresses,
-                        sortedActionMimeTypes, entries);
+                computeEntriesAndActions(data, phoneNumbers, emailAddresses, entries);
                 return null;
             }
 
@@ -520,8 +551,7 @@
                 // is still running before binding to UI. A new intent could invalidate
                 // the results, for example.
                 if (data == mContactData && !isCancelled()) {
-                    bindEntriesAndActions(entries, phoneNumbers, emailAddresses,
-                            sortedActionMimeTypes);
+                    bindEntriesAndActions(entries, phoneNumbers, emailAddresses);
                     showActivity();
                 }
             }
@@ -531,8 +561,7 @@
 
     private void bindEntriesAndActions(List<Entry> entries,
             Set<String> phoneNumbers,
-            Set<String> emailAddresses,
-            List<String> sortedActionMimeTypes) {
+            Set<String> emailAddresses) {
         Trace.beginSection("start sms loader");
         final Bundle phonesExtraBundle = new Bundle();
         phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES,
@@ -564,11 +593,10 @@
         if (entries.size() > 0) {
             mCommunicationCard.initialize(entries,
                     /* numInitialVisibleEntries = */ MIN_NUM_COMMUNICATION_ENTRIES_SHOWN,
-                    /* isExpanded = */ false,
-                    /* themeColor = */ 0);
+                    /* isExpanded = */ false);
         }
 
-        final boolean hasData = !sortedActionMimeTypes.isEmpty();
+        final boolean hasData = !entries.isEmpty();
         mCommunicationCard.setVisibility(hasData ? View.VISIBLE : View.GONE);
 
         Trace.endSection();
@@ -588,7 +616,7 @@
     }
 
     private void computeEntriesAndActions(Contact data, Set<String> phoneNumbers,
-            Set<String> emailAddresses, List<String> sortedActionMimeTypes, List<Entry> entries) {
+            Set<String> emailAddresses, List<Entry> entries) {
         Trace.beginSection("inflate entries and actions");
 
         final ResolveCache cache = ResolveCache.getInstance(this);
@@ -655,35 +683,85 @@
         Trace.endSection();
         Trace.beginSection("sort mimetypes");
 
-        // All the mime-types to add.
-        final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
-        // First, add LEADING_MIMETYPES, which are most common.
-        for (String mimeType : LEADING_MIMETYPES) {
-            if (containedTypes.contains(mimeType)) {
-                sortedActionMimeTypes.add(mimeType);
-                containedTypes.remove(mimeType);
-                entries.addAll(actionsToEntries(mActions.get(mimeType)));
-            }
+        /*
+         * Sorting is a multi part step. The end result is to a have a sorted list of the most
+         * used actions, one per mimetype. Then, within each mimetype, the list of actions for that
+         * type is also sorted, based off of {super primary, primary, times used} in that order.
+         */
+        final List<Action> topActions = new ArrayList<>();
+        for (List<Action> mimeTypeActions : mActions.values()) {
+            Collections.sort(mimeTypeActions, new Comparator<Action>() {
+                @Override
+                public int compare(Action lhs, Action rhs) {
+                    /*
+                     * Actions are compared to the same mimetype based off of three qualities:
+                     * 1. Super primary
+                     * 2. Primary
+                     * 3. Times used
+                     */
+                    if (lhs.isSuperPrimary()) {
+                        return -1;
+                    } else if (rhs.isSuperPrimary()) {
+                        return 1;
+                    } else if (lhs.isPrimary() && !rhs.isPrimary()) {
+                        return -1;
+                    } else if (!lhs.isPrimary() && rhs.isPrimary()) {
+                        return 1;
+                    } else {
+                        int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
+                        int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
+
+                        return rhsTimesUsed - lhsTimesUsed;
+                    }
+                }
+            });
+            topActions.add(mimeTypeActions.get(0));
         }
 
-        // Add all the remaining ones that are not TRAILING
-        for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
-            if (!TRAILING_MIMETYPES.contains(mimeType)) {
-                sortedActionMimeTypes.add(mimeType);
-                containedTypes.remove(mimeType);
-                entries.addAll(actionsToEntries(mActions.get(mimeType)));
-            }
-        }
+        // topActions now contains the top action for each mimetype. This list now needs to be
+        // sorted, based off of {times used, last used, statically defined} in that order.
+        Collections.sort(topActions, new Comparator<Action>() {
+            @Override
+            public int compare(Action lhs, Action rhs) {
+                int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
+                int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
+                int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
+                if (timesUsedDifference != 0) {
+                    return timesUsedDifference;
+                }
 
-        // Then, add TRAILING_MIMETYPES, which are least common.
-        for (String mimeType : TRAILING_MIMETYPES) {
-            if (containedTypes.contains(mimeType)) {
-                containedTypes.remove(mimeType);
-                sortedActionMimeTypes.add(mimeType);
-                entries.addAll(actionsToEntries(mActions.get(mimeType)));
-            }
-        }
+                long lhsLastTimeUsed = lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
+                long rhsLastTimeUsed = rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
+                long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
+                if (lastTimeUsedDifference > 0) {
+                    return 1;
+                } else if (lastTimeUsedDifference < 0) {
+                    return -1;
+                }
 
+                // Times used and last time used are the same. Resort to statically defined.
+                String lhsMimeType = lhs.getMimeType();
+                String rhsMimeType = rhs.getMimeType();
+                for (String mimeType : LEADING_MIMETYPES) {
+                    if (lhsMimeType.equals(mimeType)) {
+                        return -1;
+                    } else if (rhsMimeType.equals(mimeType)) {
+                        return 1;
+                    }
+                }
+                // Trailing types come last, so flip the returns
+                for (String mimeType : TRAILING_MIMETYPES) {
+                    if (lhsMimeType.equals(mimeType)) {
+                        return 1;
+                    } else if (rhsMimeType.equals(mimeType)) {
+                        return -1;
+                    }
+                }
+                return 0;
+            }
+        });
+
+        entries.addAll(actionsToEntries(topActions));
         Trace.endSection();
     }
 
@@ -705,24 +783,7 @@
                     return colorFromBitmap(bitmap);
                 }
                 if (imageViewDrawable instanceof LetterTileDrawable) {
-                    // LetterTileDrawable doesn't normally draw unless it is visible. Therefore,
-                    // we need to directly ask it for its color via getColor(). We could directly
-                    // return this color. However, in the future Palette#generate() may incorporate
-                    // saturation boosting. So I want to use Palette#generate() for the sake of
-                    // consistency.
-                    final LetterTileDrawable tileDrawable = (LetterTileDrawable) imageViewDrawable;
-                    final int PALETTE_BITMAP_SIZE = 1;
-                    final Bitmap bitmap = Bitmap.createBitmap(PALETTE_BITMAP_SIZE,
-                            PALETTE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
-                    // If Palette can not extract a primary color, our UX person says we are better
-                    // off using the LetterTileDrawable's non vibrant color than falling back
-                    // to the app's default color.
-                    final int color = colorFromBitmap(bitmap);
-                    if (color == 0) {
-                        return tileDrawable.getColor();
-                    } else {
-                        return color;
-                    }
+                    return ((LetterTileDrawable) imageViewDrawable).getColor();
                 }
                 return 0;
             }
@@ -730,31 +791,46 @@
             @Override
             protected void onPostExecute(Integer color) {
                 super.onPostExecute(color);
-                mColorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
-                // Make sure the color is valid. Also check that the Photo has not changed. If it
-                // has changed, the new tint color needs to be extracted
-                if (color != 0 && imageViewDrawable == mPhotoView.getDrawable()) {
-                    // TODO: animate from the previous tint.
-                    mScroller.setHeaderTintColor(color);
-
-                    // Create a darker version of the actionbar color. HSV is device dependent
-                    // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
-                    // 70% as bright as the action bar color. We can only say: it is a bit darker.
-                    final float hsvComponents[] = new float[3];
-                    Color.colorToHSV(color, hsvComponents);
-                    hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
-                    mStatusBarColor = Color.HSVToColor(hsvComponents);
-
-                    updateStatusBarColor();
-                    for (Drawable drawable : mDrawablesToTint) {
-                        applyThemeColorIfAvailable(drawable);
-                    }
-                    mDrawablesToTint.clear();
+                if (mHasComputedThemeColor) {
+                    // If we had previously computed a theme color from the contact photo,
+                    // then do not update the theme color. Changing the theme color several
+                    // seconds after QC has started, as a result of an updated/upgraded photo,
+                    // is a jarring experience. On the other hand, changing the theme color after
+                    // a rotation or onNewIntent() is perfectly fine.
+                    return;
+                }
+                // Check that the Photo has not changed. If it has changed, the new tint
+                // color needs to be extracted
+                if (imageViewDrawable == mPhotoView.getDrawable()) {
+                    mHasComputedThemeColor = true;
+                    setThemeColor(color);
                 }
             }
         }.execute();
     }
 
+    private void setThemeColor(int color) {
+        // If the color is invalid, use the predefined default
+        if (color == 0) {
+            color = getResources().getColor(R.color.actionbar_background_color);
+        }
+        mScroller.setHeaderTintColor(color);
+
+        // Create a darker version of the actionbar color. HSV is device dependent
+        // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
+        // 70% as bright as the action bar color. We can only say: it is a bit darker.
+        final float hsvComponents[] = new float[3];
+        Color.colorToHSV(color, hsvComponents);
+        hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
+        mStatusBarColor = Color.HSVToColor(hsvComponents);
+        updateStatusBarColor();
+
+        mColorFilter =
+                new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+        mCommunicationCard.setColorAndFilter(color, mColorFilter);
+        mRecentCard.setColorAndFilter(color, mColorFilter);
+    }
+
     private void updateStatusBarColor() {
         if (mScroller == null) {
             return;
@@ -819,20 +895,17 @@
                 case Phone.CONTENT_ITEM_TYPE:
                     header = String.valueOf(action.getBody());
                     footer = String.valueOf(action.getSubtitle());
-                    icon = applyThemeColorIfAvailable(
-                            getResources().getDrawable(R.drawable.ic_phone_24dp));
+                    icon = getResources().getDrawable(R.drawable.ic_phone_24dp);
                     break;
                 case Email.CONTENT_ITEM_TYPE:
                     header = String.valueOf(action.getBody());
                     footer = String.valueOf(action.getSubtitle());
-                    icon = applyThemeColorIfAvailable(
-                            getResources().getDrawable(R.drawable.ic_email_24dp));
+                    icon = getResources().getDrawable(R.drawable.ic_email_24dp);
                     break;
                 case StructuredPostal.CONTENT_ITEM_TYPE:
                     header = String.valueOf(action.getBody());
                     footer = String.valueOf(action.getSubtitle());
-                    icon = applyThemeColorIfAvailable(
-                            getResources().getDrawable(R.drawable.ic_place_24dp));
+                    icon = getResources().getDrawable(R.drawable.ic_place_24dp);
                     break;
                 default:
                     header = String.valueOf(action.getSubtitle());
@@ -844,8 +917,7 @@
 
             // Add SMS in addition to phone calls
             if (action.getMimeType().equals(Phone.CONTENT_ITEM_TYPE)) {
-                entries.add(new Entry(applyThemeColorIfAvailable(getResources().getDrawable(
-                        R.drawable.ic_message_24dp)),
+                entries.add(new Entry(getResources().getDrawable(R.drawable.ic_message_24dp),
                         getResources().getString(R.string.send_message), null, header,
                         action.getAlternateIntent(), /* isEditable = */ false));
             }
@@ -856,7 +928,7 @@
     private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
         List<Entry> entries = new ArrayList<>();
         for (ContactInteraction interaction : interactions) {
-            entries.add(new Entry(applyThemeColorIfAvailable(interaction.getIcon(this)),
+            entries.add(new Entry(interaction.getIcon(this),
                     interaction.getViewHeader(this),
                     interaction.getViewBody(this),
                     interaction.getBodyIcon(this),
@@ -911,7 +983,7 @@
             // contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
             return new ContactLoader(getApplicationContext(), mLookupUri,
                     true /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
-                    false /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
+                    true /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
         }
     };
 
@@ -1012,8 +1084,7 @@
         if (allInteractions.size() > 0) {
             mRecentCard.initialize(contactInteractionsToEntries(allInteractions),
                     /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
-                    /* isExpanded = */ false,
-                    /* themeColor = */ 0);
+                    /* isExpanded = */ false);
             mRecentCard.setVisibility(View.VISIBLE);
         }
     }
@@ -1032,20 +1103,6 @@
     }
 
     /**
-     * Applies the theme color as extracted in
-     * {@link #extractAndApplyTintFromPhotoViewAsynchronously()} if available. If the color is not
-     * available, store a reference to the drawable to tint when a color becomes available.
-     */
-    private Drawable applyThemeColorIfAvailable(Drawable drawable) {
-        if (mColorFilter != null) {
-            drawable.setColorFilter(mColorFilter);
-        } else {
-            mDrawablesToTint.add(drawable);
-        }
-        return drawable;
-    }
-
-    /**
      * Returns true if it is possible to edit the current contact.
      */
     private boolean isContactEditable() {
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index b38f5cf..3666420 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -11,10 +11,14 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
@@ -22,12 +26,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewConfiguration;
+import android.view.WindowManager;
 import android.view.animation.Interpolator;
 import android.widget.EdgeEffect;
-import android.widget.ImageView;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.Scroller;
 import android.widget.ScrollView;
+import android.widget.TextView;
 
 /**
  * A custom {@link ViewGroup} that operates similarly to a {@link ScrollView}, except with multiple
@@ -54,6 +60,11 @@
      */
     private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
 
+    /**
+     * In portrait mode, the height:width ratio of the photo's starting height.
+     */
+    private static final float INTERMEDIATE_HEADER_HEIGHT_RATIO = 0.5f;
+
     private float[] mLastEventPosition = { 0, 0 };
     private VelocityTracker mVelocityTracker;
     private boolean mIsBeingDragged = false;
@@ -62,24 +73,49 @@
     private ScrollView mScrollView;
     private View mScrollViewChild;
     private View mToolbar;
-    private ImageView mPhotoView;
+    private QuickContactImageView mPhotoView;
     private View mPhotoViewContainer;
     private View mTransparentView;
     private MultiShrinkScrollerListener mListener;
+    private TextView mLargeTextView;
+    /** Contains desired location/size of the title, once the header is fully compressed */
+    private TextView mInvisiblePlaceholderTextView;
     private int mHeaderTintColor;
     private int mMaximumHeaderHeight;
+    private int mMinimumHeaderHeight;
+    private int mIntermediateHeaderHeight;
+    private int mMaximumHeaderTextSize;
 
     private final Scroller mScroller;
     private final EdgeEffect mEdgeGlowBottom;
     private final int mTouchSlop;
     private final int mMaximumVelocity;
     private final int mMinimumVelocity;
-    private final int mIntermediateHeaderHeight;
-    private final int mMinimumHeaderHeight;
     private final int mTransparentStartHeight;
     private final float mToolbarElevation;
-    private final PorterDuffColorFilter mColorFilter
-            = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
+    private final boolean mIsTwoPanel;
+    final Rect mLargeTextViewRect = new Rect();
+    final Rect mInvisiblePlaceholderTextViewRect = new Rect();
+
+    // Objects used to perform color filtering on the header. These are stored as fields for
+    // the sole purpose of avoiding "new" operations inside animation loops.
+    private final ColorMatrix mWhitenessColorMatrix = new ColorMatrix();
+    private final  ColorMatrixColorFilter mColorFilter = new ColorMatrixColorFilter(
+            mWhitenessColorMatrix);
+    private final ColorMatrix mColorMatrix = new ColorMatrix();
+    private final float[] mAlphaMatrixValues = {
+            0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0,
+            0, 0, 0, 1, 0
+    };
+    private final ColorMatrix mMultiplyBlendMatrix = new ColorMatrix();
+    private final float[] mMultiplyBlendMatrixValues = {
+            0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0,
+            0, 0, 0, 1, 0
+    };
 
     public interface MultiShrinkScrollerListener {
         void onScrolledOffBottom();
@@ -148,14 +184,11 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mIntermediateHeaderHeight = (int) getResources().getDimension(
-                R.dimen.quickcontact_starting_header_height);
         mTransparentStartHeight = (int) getResources().getDimension(
                 R.dimen.quickcontact_starting_empty_height);
-        mHeaderTintColor = mContext.getResources().getColor(
-                R.color.actionbar_background_color);
         mToolbarElevation = mContext.getResources().getDimension(
                 R.dimen.quick_contact_toolbar_elevation);
+        mIsTwoPanel = mContext.getResources().getBoolean(R.bool.quickcontact_two_panel);
 
         final TypedArray attributeArray = context.obtainStyledAttributes(
                 new int[]{android.R.attr.actionBarSize});
@@ -172,10 +205,11 @@
         mToolbar = findViewById(R.id.toolbar_parent);
         mPhotoViewContainer = findViewById(R.id.toolbar_parent);
         mTransparentView = findViewById(R.id.transparent_view);
+        mLargeTextView = (TextView) findViewById(R.id.large_title);
+        mInvisiblePlaceholderTextView = (TextView) findViewById(R.id.placeholder_textview);
         mListener = listener;
 
-        mPhotoView = (ImageView) findViewById(R.id.photo);
-        setHeaderHeight(mIntermediateHeaderHeight);
+        mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
         mPhotoView.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -183,15 +217,49 @@
             }
         });
 
-        SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ true, new Runnable() {
+        final WindowManager windowManager = (WindowManager) getContext().getSystemService(
+                Context.WINDOW_SERVICE);
+        final Point windowSize = new Point();
+        windowManager.getDefaultDisplay().getSize(windowSize);
+        if (!mIsTwoPanel) {
+            // We never want the height of the photo view to exceed its width.
+            mMaximumHeaderHeight = windowSize.x;
+            mIntermediateHeaderHeight = (int) (mMaximumHeaderHeight
+                    * INTERMEDIATE_HEADER_HEIGHT_RATIO);
+        }
+        setHeaderHeight(mIntermediateHeaderHeight);
+
+        SchedulingUtils.doOnPreDraw(this, /* drawNextFrame = */ false, new Runnable() {
             @Override
             public void run() {
-                // We never want the height of the photo view to exceed its width.
-                mMaximumHeaderHeight = mToolbar.getWidth();
+                mMaximumHeaderTextSize = mLargeTextView.getHeight();
+                // Unlike Window width, we can't know the usable window height until predraw
+                // has occured. Therefore, setting these constraints must be done inside
+                // onPreDraw for the two panel layout. Fortunately, the two panel layout
+                // doesn't need these values anywhere else inside the activity's creation.
+                if (mIsTwoPanel) {
+                    mMaximumHeaderHeight = getHeight();
+                    mMinimumHeaderHeight = mMaximumHeaderHeight;
+                    mIntermediateHeaderHeight = mMaximumHeaderHeight;
+                    final TypedValue photoRatio = new TypedValue();
+                    getResources().getValue(R.vals.quickcontact_photo_ratio, photoRatio,
+                            /* resolveRefs = */ true);
+                    final LayoutParams layoutParams
+                            = (LayoutParams) mPhotoViewContainer.getLayoutParams();
+                    layoutParams.height = mMaximumHeaderHeight;
+                    layoutParams.width = (int) (mMaximumHeaderHeight * photoRatio.getFloat());
+                    mPhotoViewContainer.setLayoutParams(layoutParams);
+                }
+
+                updateHeaderTextSize();
             }
         });
     }
 
+    public void setTitle(String title) {
+        mLargeTextView.setText(title);
+    }
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         // The only time we want to intercept touch events is when we are being dragged.
@@ -259,14 +327,13 @@
                 mReceivedDown = false;
 
                 if (mIsBeingDragged) {
-                    final int heightScrollViewChild = mScrollViewChild.getHeight();
-                    final int pulledToY = mScrollView.getScrollY() + (int) delta;
-                    if (pulledToY > heightScrollViewChild - mScrollView.getHeight()
-                            && mToolbar.getHeight() == mMinimumHeaderHeight) {
+                    final int distanceFromMaxScrolling = getMaximumScrollUpwards() - getScroll();
+                    if (delta > distanceFromMaxScrolling) {
                         // The ScrollView is being pulled upwards while there is no more
                         // content offscreen, and the view port is already fully expanded.
                         mEdgeGlowBottom.onPull(delta / getHeight(), 1 - event.getX() / getWidth());
                     }
+
                     if (!mEdgeGlowBottom.isFinished()) {
                         postInvalidateOnAnimation();
                     }
@@ -287,6 +354,9 @@
     public void setHeaderTintColor(int color) {
         mHeaderTintColor = color;
         updatePhotoTintAndDropShadow();
+        // We want to use the same amount of alpha on the new tint color as the previous tint color.
+        final int edgeEffectAlpha = Color.alpha(mEdgeGlowBottom.getColor());
+        mEdgeGlowBottom.setColor((color & 0xffffff) | Color.argb(edgeEffectAlpha, 0, 0, 0));
     }
 
     /**
@@ -401,6 +471,7 @@
             scrollDown(delta);
         }
         updatePhotoTintAndDropShadow();
+        updateHeaderTextSize();
         final boolean isFullscreen = getScrollNeededToBeFullScreen() <= 0;
         if (mListener != null) {
             if (wasFullscreen && !isFullscreen) {
@@ -421,6 +492,7 @@
         toolbarLayoutParams.height = height;
         mToolbar.setLayoutParams(toolbarLayoutParams);
         updatePhotoTintAndDropShadow();
+        updateHeaderTextSize();
     }
 
     @NeededForReflection
@@ -512,7 +584,12 @@
                     height + getMaximumScrollUpwards() - getScroll());
 
             canvas.rotate(180, width, 0);
-            mEdgeGlowBottom.setSize(width, height);
+            if (mIsTwoPanel) {
+                // Only show the EdgeEffect on the bottom of the ScrollView.
+                mEdgeGlowBottom.setSize(mScrollView.getWidth(), height);
+            } else {
+                mEdgeGlowBottom.setSize(width, height);
+            }
             if (mEdgeGlowBottom.draw(canvas)) {
                 postInvalidateOnAnimation();
             }
@@ -537,11 +614,18 @@
     }
 
     private int getMaximumScrollUpwards() {
-        return mTransparentStartHeight
-                // How much the Header view can compress
-                + mIntermediateHeaderHeight - mMinimumHeaderHeight
-                // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
-                + Math.max(0, mScrollViewChild.getHeight() - getHeight() + mMinimumHeaderHeight);
+        if (!mIsTwoPanel) {
+            return mTransparentStartHeight
+                    // How much the Header view can compress
+                    + mIntermediateHeaderHeight - mMinimumHeaderHeight
+                    // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
+                    + Math.max(0, mScrollViewChild.getHeight() - getHeight()
+                    + mMinimumHeaderHeight);
+        } else {
+            return mTransparentStartHeight
+                    // How much the ScrollView can scroll. 0, if child is smaller than ScrollView.
+                    + Math.max(0, mScrollViewChild.getHeight() - getHeight());
+        }
     }
 
     private int getTransparentViewHeight() {
@@ -604,27 +688,167 @@
         }
     }
 
+    /**
+     * Set the header size and padding, based on the current scroll position.
+     */
+    private void updateHeaderTextSize() {
+        if (mIsTwoPanel) {
+            // The text size stays constant on two panel layouts.
+            return;
+        }
+
+        // The pivot point for scaling should be middle of the starting side.
+        if (isLayoutRtl()) {
+            mLargeTextView.setPivotX(mLargeTextView.getWidth());
+        } else {
+            mLargeTextView.setPivotX(0);
+        }
+        mLargeTextView.setPivotY(mLargeTextView.getHeight() / 2);
+
+        final int START_TEXT_SCALING_THRESHOLD_COEFFICIENT = 2;
+        final int threshold = START_TEXT_SCALING_THRESHOLD_COEFFICIENT * mMinimumHeaderHeight;
+        final int toolbarHeight = mToolbar.getLayoutParams().height;
+        if (toolbarHeight >= threshold) {
+            // Keep the text at maximum size since the header is smaller than threshold.
+            mLargeTextView.setScaleX(1);
+            mLargeTextView.setScaleY(1);
+            configureLargeTitlePadding();
+            return;
+        }
+        final float ratio = (toolbarHeight  - mMinimumHeaderHeight)
+                / (float)(threshold - mMinimumHeaderHeight);
+        final float minimumSize = mInvisiblePlaceholderTextView.getHeight();
+        final float scale = (minimumSize + (mMaximumHeaderTextSize - minimumSize) * ratio)
+                / mMaximumHeaderTextSize;
+
+        mLargeTextView.setScaleX(scale);
+        mLargeTextView.setScaleY(scale);
+        configureLargeTitlePadding();
+    }
+
+    /**
+     * Configure the padding around mLargeTextView so that it will look appropriate once it
+     * finishes moving into its target location/size.
+     */
+    private void configureLargeTitlePadding() {
+        mToolbar.getBoundsOnScreen(mLargeTextViewRect);
+        mInvisiblePlaceholderTextView.getBoundsOnScreen(mInvisiblePlaceholderTextViewRect);
+        final int neededPaddingStart;
+        if (isLayoutRtl()) {
+            neededPaddingStart = mInvisiblePlaceholderTextViewRect.right - mLargeTextViewRect.right;
+        } else {
+            neededPaddingStart = mInvisiblePlaceholderTextViewRect.left - mLargeTextViewRect.left;
+        }
+
+        // Distance between top of toolbar to the center of the target rectangle.
+        final int desiredTopToCenter = (
+                mInvisiblePlaceholderTextViewRect.top + mInvisiblePlaceholderTextViewRect.bottom)
+                / 2 - mLargeTextViewRect.top;
+        // Additional padding needed on the mLargeTextView so that it has the same amount of
+        // padding as the target rectangle.
+        final int additionalBottomPaddingNeeded = desiredTopToCenter - mLargeTextView.getHeight()
+                / 2;
+
+        final FrameLayout.LayoutParams layoutParams
+                = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams();
+        layoutParams.bottomMargin = additionalBottomPaddingNeeded;
+        layoutParams.setMarginStart(neededPaddingStart);
+        mLargeTextView.setLayoutParams(layoutParams);
+    }
+
     private void updatePhotoTintAndDropShadow() {
         // We need to use toolbarLayoutParams to determine the height, since the layout
         // params can be updated before the height change is reflected inside the View#getHeight().
         final int toolbarHeight = mToolbar.getLayoutParams().height;
+
+        if (toolbarHeight <= mMinimumHeaderHeight && !mIsTwoPanel) {
+            mPhotoViewContainer.setElevation(mToolbarElevation);
+        } else {
+            mPhotoViewContainer.setElevation(0);
+        }
+
         // Reuse an existing mColorFilter (to avoid GC pauses) to change the photo's tint.
         mPhotoView.clearColorFilter();
-        if (toolbarHeight >= mMaximumHeaderHeight) {
-            mPhotoViewContainer.setElevation(0);
-            return;
+
+        final float ratio;
+        final float intermediateRatio;
+        if (!mIsTwoPanel) {
+            // Ratio of current size to maximum size of the header.
+            ratio =  (toolbarHeight  - mMinimumHeaderHeight)
+                    / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight) ;
+            // The value that "ratio" will have when the header is at its
+            // starting/intermediate size.
+            intermediateRatio = (mIntermediateHeaderHeight - mMinimumHeaderHeight)
+                    / (float) (mMaximumHeaderHeight - mMinimumHeaderHeight);
+        } else {
+            // Set ratio and intermediateRatio to the same arbitrary value, so that
+            // the math below considers us to be in the intermediate position. The specific
+            // values are not very important.
+            ratio = 0.5f;
+            intermediateRatio = 0.5f;
         }
-        if (toolbarHeight <= mMinimumHeaderHeight) {
-            mColorFilter.setColor(mHeaderTintColor);
-            mPhotoView.setColorFilter(mColorFilter);
-            mPhotoViewContainer.setElevation(mToolbarElevation);
+
+        final float linearBeforeMiddle = Math.max(1 - (1 - ratio) / intermediateRatio, 0);
+
+        // Want a function with a derivative of 0 at x=0. I don't want it to grow too
+        // slowly before x=0.5. x^1.1 satisfies both requirements.
+        final float EXPONENT_ALMOST_ONE = 1.1f;
+        final float semiLinearBeforeMiddle = (float) Math.pow(linearBeforeMiddle,
+                EXPONENT_ALMOST_ONE);
+        mColorMatrix.reset();
+        mColorMatrix.setSaturation(semiLinearBeforeMiddle);
+        mColorMatrix.postConcat(alphaMatrix(ratio, Color.WHITE));
+
+        final float colorAlpha;
+        if (mPhotoView.isBasedOffLetterTile()) {
+            // Since the letter tile only has white and grey, tint it more slowly. Otherwise
+            // it will be completely invisible before we reach the intermediate point.
+            final float SLOWING_FACTOR = 1.6f;
+            float linearBeforeMiddleish = Math.max(1 - (1 - ratio) / intermediateRatio
+                    / SLOWING_FACTOR, 0);
+            colorAlpha = 1 - (float) Math.pow(linearBeforeMiddleish, EXPONENT_ALMOST_ONE);
+            mColorMatrix.postConcat(alphaMatrix(colorAlpha, mHeaderTintColor));
+        } else {
+            colorAlpha = 1 - semiLinearBeforeMiddle;
+            mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, colorAlpha));
         }
-        mPhotoViewContainer.setElevation(0);
-        final int alphaBits = 0xff - 0xff * (toolbarHeight  - mMinimumHeaderHeight)
-                / (mMaximumHeaderHeight - mMinimumHeaderHeight);
-        final int color = alphaBits << 24 | (mHeaderTintColor & 0xffffff);
-        mColorFilter.setColor(color);
+
+        mColorFilter.setColorMatrix(mColorMatrix);
         mPhotoView.setColorFilter(mColorFilter);
+        // Tell the photo view what tint we are trying to achieve. Depending on the type of
+        // drawable used, the photo view may or may not use this tint.
+        mPhotoView.setTint(((int) (0xFF * colorAlpha)) << 24 | (mHeaderTintColor & 0xffffff));
+    }
+
+    /**
+     * Simulates alpha blending an image with {@param color}.
+     */
+    private ColorMatrix alphaMatrix(float alpha, int color) {
+        mAlphaMatrixValues[0] = Color.red(color) * alpha / 255;
+        mAlphaMatrixValues[6] = Color.green(color) * alpha / 255;
+        mAlphaMatrixValues[12] = Color.blue(color) * alpha / 255;
+        mAlphaMatrixValues[4] = 255 * (1 - alpha);
+        mAlphaMatrixValues[9] = 255 * (1 - alpha);
+        mAlphaMatrixValues[14] = 255 * (1 - alpha);
+        mWhitenessColorMatrix.set(mAlphaMatrixValues);
+        return mWhitenessColorMatrix;
+    }
+
+    /**
+     * Simulates multiply blending an image with a single {@param color}.
+     *
+     * Multiply blending is [Sa * Da, Sc * Dc]. See {@link android.graphics.PorterDuff}.
+     */
+    private ColorMatrix multiplyBlendMatrix(int color, float alpha) {
+        mMultiplyBlendMatrixValues[0] = multiplyBlend(Color.red(color), alpha);
+        mMultiplyBlendMatrixValues[6] = multiplyBlend(Color.green(color), alpha);
+        mMultiplyBlendMatrixValues[12] = multiplyBlend(Color.blue(color), alpha);
+        mMultiplyBlendMatrix.set(mMultiplyBlendMatrixValues);
+        return mMultiplyBlendMatrix;
+    }
+
+    private float multiplyBlend(int color, float alpha) {
+        return color * alpha / 255.0f + (1 - alpha);
     }
 
     private void updateLastEventPosition(MotionEvent event) {
diff --git a/src/com/android/contacts/widget/QuickContactImageView.java b/src/com/android/contacts/widget/QuickContactImageView.java
new file mode 100644
index 0000000..9dbf85e
--- /dev/null
+++ b/src/com/android/contacts/widget/QuickContactImageView.java
@@ -0,0 +1,85 @@
+package com.android.contacts.widget;
+
+
+import com.android.contacts.common.lettertiles.LetterTileDrawable;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * An {@link ImageView} designed to display QuickContact's contact photo. In addition to
+ * supporting {@link ImageView#setColorFilter} this also performs a second color blending with
+ * the tint set in {@link #setTint}. This requires a second draw pass.
+ */
+public class QuickContactImageView extends ImageView {
+
+    private Xfermode mMode = new PorterDuffXfermode(Mode.MULTIPLY);
+    private int mTintColor;
+    private BitmapDrawable mBitmapDrawable;
+    private Drawable mOriginalDrawable;
+
+    public QuickContactImageView(Context context) {
+        this(context, null);
+    }
+
+    public QuickContactImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public QuickContactImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public QuickContactImageView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void setTint(int color) {
+        mTintColor = color;
+        postInvalidate();
+    }
+
+    public boolean isBasedOffLetterTile() {
+        return mOriginalDrawable instanceof LetterTileDrawable;
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        // There is no way to avoid all this casting. Blending modes aren't equally
+        // supported for all drawable types.
+        if (drawable == null || drawable instanceof BitmapDrawable) {
+            mBitmapDrawable = (BitmapDrawable) drawable;
+        } else if (drawable instanceof LetterTileDrawable) {
+            // TODO: set a desired hardcoded BitmapDrawable here
+            mBitmapDrawable = null;
+        } else {
+            throw new IllegalArgumentException("Does not support this type of drawable");
+
+        }
+        mOriginalDrawable = drawable;
+        super.setImageDrawable(mBitmapDrawable);
+    }
+
+    @Override
+    public Drawable getDrawable() {
+        return mOriginalDrawable;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (isBasedOffLetterTile()) {
+            // The LetterTileDrawable's bitmaps have a lot of pixels with alpha=0. These
+            // look stupid unless we fill in the background and use a different blending mode.
+            canvas.drawColor(((LetterTileDrawable) mOriginalDrawable).getColor());
+        }
+        super.onDraw(canvas);
+    }
+}