Merge "Import translations. DO NOT MERGE" into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4175ef4..0457681 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -47,7 +47,7 @@
     <application
         android:name="com.android.contacts.ContactsApplication"
         android:label="@string/contactsList"
-        android:icon="@mipmap/ic_launcher_contacts"
+        android:icon="@mipmap/ic_contacts_clr_48cv_44dp"
         android:taskAffinity="android.task.contacts"
         android:hardwareAccelerated="true"
         android:supportsRtl="true"
diff --git a/res/drawable-hdpi/ic_dialer_sip_black_24dp.png b/res/drawable-hdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..37dabfc
--- /dev/null
+++ b/res/drawable-hdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_dialer_sip_black_24dp.png b/res/drawable-mdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..51d5e13
--- /dev/null
+++ b/res/drawable-mdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dialer_sip_black_24dp.png b/res/drawable-xhdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..619a79f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_dialer_sip_black_24dp.png b/res/drawable-xxhdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..f1466a1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_dialer_sip_black_24dp.png b/res/drawable-xxxhdpi/ic_dialer_sip_black_24dp.png
new file mode 100644
index 0000000..f812810
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_dialer_sip_black_24dp.png
Binary files differ
diff --git a/res/layout/custom_action_bar.xml b/res/layout/custom_action_bar.xml
index af104fe..5b930ea 100644
--- a/res/layout/custom_action_bar.xml
+++ b/res/layout/custom_action_bar.xml
@@ -33,6 +33,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:iconifiedByDefault="false"
-        android:inputType="textFilter" />
+        android:inputType="textFilter"
+        android:theme="@style/ContactPickerSearchTheme" />
 
 </FrameLayout>
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 138a3a5..4710df2 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -90,8 +90,8 @@
         android:layout_alignParentTop="true"
         android:visibility="gone"
         android:background="?android:attr/selectableItemBackgroundBorderless"
-        android:layout_marginEnd="@dimen/expanding_entry_card_item_alternate_icon_margin_end"
-        android:layout_marginTop="@dimen/expanding_entry_card_item_icon_margin_top"
-        android:layout_marginBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom" />
+        android:paddingTop="@dimen/expanding_entry_card_item_icon_margin_top"
+        android:paddingBottom="@dimen/expanding_entry_card_item_alternate_icon_margin_bottom"
+        android:layout_marginStart="@dimen/expanding_entry_card_item_alternate_icon_start_margin" />
 
 </view>
diff --git a/res/layout/quickcontact_content.xml b/res/layout/quickcontact_content.xml
index 5ead702..494b93b 100644
--- a/res/layout/quickcontact_content.xml
+++ b/res/layout/quickcontact_content.xml
@@ -23,7 +23,7 @@
     android:background="@color/card_margin_color">
 
     <!-- All the cards should be inserted into this LinearLayout -->
-    <view class="com.android.contacts.common.widget.ActivityTouchLinearLayout"
+    <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
@@ -54,6 +54,6 @@
             android:visibility="gone"
             cardview:cardCornerRadius="@dimen/expanding_entry_card_card_corner_radius" />
 
-    </view>
+    </LinearLayout>
 
 </com.android.contacts.widget.TouchlessScrollView>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 52918d5..d422627 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -173,7 +173,7 @@
 
     <dimen name="expanding_entry_card_item_icon_margin_top">7dp</dimen>
     <dimen name="expanding_entry_card_item_header_only_margin_top">6dp</dimen>
-    <dimen name="expanding_entry_card_item_alternate_icon_margin_end">0dp</dimen>
+    <dimen name="expanding_entry_card_item_alternate_icon_start_margin">16dp</dimen>
     <dimen name="expanding_entry_card_item_alternate_icon_margin_bottom">10dp</dimen>
 
     <dimen name="expanding_entry_card_badge_separator_margin">8dp</dimen>
@@ -181,6 +181,7 @@
     <dimen name="expanding_entry_card_header_margin_bottom">2dp</dimen>
     <!-- The top margin when the sub header and text views are both gone -->
     <dimen name="expanding_entry_card_item_header_only_margin_bottom">2dp</dimen>
+    <dimen name="expanding_entry_card_item_no_icon_margin_top">6dp</dimen>
 
     <dimen name="people_activity_card_elevation">2dp</dimen>
     <!-- The width the that the tabs occupy in the ActionBar when in landscape mode.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5f17bcd..5497ee2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -702,4 +702,7 @@
     <!-- Content description for directions secondary button [CHAR LIMIT=NONE] -->
     <string name="content_description_directions">directions to location</string>
 
+    <!-- Prefix for messages that you sent [CHAR LIMIT=40] -->
+    <string name="message_from_you_prefix">You: <xliff:g id="sms_body">%s</xliff:g></string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8ff6469..73329b3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -66,6 +66,8 @@
         <item name="android:windowBackground">@color/background_primary</item>
         <item name="android:colorPrimaryDark">@color/primary_color_dark</item>
         <item name="android:colorPrimary">@color/primary_color</item>
+        <item name="android:colorAccent">@color/primary_color</item>
+        <item name="android:alertDialogTheme">@style/ContactsAlertDialogTheme</item>
         <item name="list_item_height">?android:attr/listPreferredItemHeight</item>
         <item name="activated_background">@drawable/list_item_activated_background</item>
         <item name="section_header_background">@drawable/list_title_holo</item>
@@ -128,6 +130,13 @@
         <item name="android:displayOptions"></item>
     </style>
 
+    <style name="ContactPickerSearchTheme" parent="@style/PeopleTheme">
+        <item name="android:textColorPrimary">@android:color/white</item>
+        <item name="android:textColorHint">?android:textColorHintInverse</item>
+        <item name="android:colorControlActivated">?android:textColorHintInverse</item>
+        <item name="android:colorControlNormal">@android:color/white</item>
+    </style>
+
     <!-- Text in the action bar at the top of the screen -->
     <style name="ContactsActionBarTitleText"
            parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
@@ -287,4 +296,10 @@
         <item name="android:textColor">#363636</item>
         <item name="android:fontFamily">sans-serif</item>
     </style>
+
+    <!-- Inherit from Theme.Material.Light.Dialog instead of Theme.Material.Light.Dialog.Alert
+        since the Alert dialog is private. They are identical anyway. -->
+    <style name="ContactsAlertDialogTheme" parent="@android:style/Theme.Material.Light.Dialog">
+        <item name="android:colorAccent">@color/primary_color</item>
+    </style>
 </resources>
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index b4ec7f9..f112847 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -131,6 +131,9 @@
     private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
     private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
     private static final String KEY_RAW_CONTACTS = "rawContacts";
+    private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
+    private static final String KEY_CUSTOM_RINGTONE = "customRingtone";
+    private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
 
     public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
 
@@ -509,7 +512,9 @@
             mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
             mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
                     KEY_RAW_CONTACTS));
-
+            mSendToVoicemailState = savedState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
+            mCustomRingtone =  savedState.getString(KEY_CUSTOM_RINGTONE);
+            mArePhoneOptionsChangable =  savedState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
         }
 
         // mState can still be null because it may not have have finished loading before
@@ -1772,6 +1777,9 @@
         outState.putParcelableArrayList(KEY_RAW_CONTACTS,
                 mRawContacts == null ?
                 Lists.<RawContact> newArrayList() :  Lists.newArrayList(mRawContacts));
+        outState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
+        outState.putString(KEY_CUSTOM_RINGTONE, mCustomRingtone);
+        outState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
 
         super.onSaveInstanceState(outState);
     }
diff --git a/src/com/android/contacts/interactions/SmsInteraction.java b/src/com/android/contacts/interactions/SmsInteraction.java
index ac83786..3f86eef 100644
--- a/src/com/android/contacts/interactions/SmsInteraction.java
+++ b/src/com/android/contacts/interactions/SmsInteraction.java
@@ -54,7 +54,11 @@
 
     @Override
     public String getViewHeader(Context context) {
-        return getBody();
+        String body = getBody();
+        if (getType() == Sms.MESSAGE_TYPE_SENT) {
+            body = context.getResources().getString(R.string.message_from_you_prefix, body);
+        }
+        return body;
     }
 
     @Override
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 7501bb8..9369cbe 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -357,7 +357,9 @@
         if (TextUtils.isEmpty(mTitleTextView.getText()) &&
                 mEntriesViewGroup.getChildCount() == 0) {
             entry.setPadding(entry.getPaddingLeft(),
-                    entry.getPaddingTop() + getResources().getDimensionPixelSize(
+                    getResources().getDimensionPixelSize(
+                            R.dimen.expanding_entry_card_item_padding_top) +
+                    getResources().getDimensionPixelSize(
                             R.dimen.expanding_entry_card_null_title_top_extra_padding),
                     entry.getPaddingRight(),
                     entry.getPaddingBottom());
@@ -431,7 +433,7 @@
                 for (int j = 1; j < entryList.size() && numInflated < mCollapsedEntriesCount &&
                         extraEntries > 0; j++) {
                     entryViewList.add(createEntryView(layoutInflater, entryList.get(j),
-                            /* showIcon = */ View.VISIBLE));
+                            /* showIcon = */ View.INVISIBLE));
                     numInflated++;
                     extraEntries--;
                 }
@@ -577,6 +579,11 @@
             view.setTag(new EntryTag(entry.getId(), entry.getIntent()));
         }
 
+        if (entry.getIntent() == null && entry.getEntryContextMenuInfo() == null) {
+            // Remove the click effect
+            view.setBackground(null);
+        }
+
         // If only the header is visible, add a top margin to match icon's top margin.
         // Also increase the space below the header for visual comfort.
         if (header.getVisibility() == View.VISIBLE && subHeader.getVisibility() == View.GONE &&
@@ -620,13 +627,21 @@
             });
         }
 
-        // Decrease margin for entries that have an invisible icon
-        if (iconVisibility == View.INVISIBLE) {
+        // Adjust the top padding size for entries with an invisible icon. The padding depends on
+        // if there is a sub header or text section
+        if (iconVisibility == View.INVISIBLE &&
+                (!TextUtils.isEmpty(entry.getSubHeader()) || !TextUtils.isEmpty(entry.getText()))) {
+            view.setPaddingRelative(view.getPaddingStart(),
+                    getResources().getDimensionPixelSize(
+                            R.dimen.expanding_entry_card_item_no_icon_margin_top),
+                    view.getPaddingEnd(),
+                    view.getPaddingBottom());
+        } else if (iconVisibility == View.INVISIBLE &&  TextUtils.isEmpty(entry.getSubHeader())
+                && TextUtils.isEmpty(entry.getText())) {
             view.setPaddingRelative(view.getPaddingStart(), 0, view.getPaddingEnd(),
                     view.getPaddingBottom());
         }
 
-
         view.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
 
         return view;
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 357cae6..9bcdde8 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -25,13 +25,13 @@
 import android.content.ActivityNotFoundException;
 import android.content.ContentUris;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.Intent;
 import android.content.Loader;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.BitmapDrawable;
@@ -68,12 +68,12 @@
 import android.telecomm.TelecommManager;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnCreateContextMenuListener;
@@ -169,7 +169,7 @@
 
     private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
     private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
-    private static final int SCRIM_COLOR = Color.argb(0xB2, 0, 0, 0);
+    private static final int SCRIM_COLOR = Color.argb(0xC8, 0, 0, 0);
     private static final String MIMETYPE_SMS = "vnd.android-dir/mms-sms";
 
     /** This is the Intent action to install a shortcut in the launcher. */
@@ -186,6 +186,10 @@
             "vnd.android.cursor.item/vnd.googleplus.profile.comm";
     private static final String INTENT_DATA_HANGOUTS_VIDEO = "Start video call";
 
+    /**
+     * The URI used to load the the Contact. Once the contact is loaded, use Contact#getLookupUri()
+     * instead of referencing this URI.
+     */
     private Uri mLookupUri;
     private String[] mExcludeMimes;
     private int mExtraMode;
@@ -193,26 +197,17 @@
     private boolean mHasAlreadyBeenOpened;
 
     private ImageView mPhotoView;
-    private View mTransparentView;
     private ExpandingEntryCardView mContactCard;
     private ExpandingEntryCardView mNoContactDetailsCard;
     private ExpandingEntryCardView mRecentCard;
     private ExpandingEntryCardView mAboutCard;
-    /**
-     * This list contains all the {@link DataItem}s. Each nested list contains all data items of a
-     * specific mimetype in sorted order, using mWithinMimeTypeDataItemComparator. The mimetype
-     * lists are sorted using mAmongstMimeTypeDataItemComparator.
-     */
-    private List<List<DataItem>> mDataItemsList;
-    /**
-     * A map between a mimetype string and the corresponding list of data items. The data items
-     * are in sorted order using mWithinMimeTypeDataItemComparator.
-     */
-    private Map<String, List<DataItem>> mDataItemsMap;
     private MultiShrinkScroller mScroller;
     private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
-    private AsyncTask<Void, Void, Pair<List<List<DataItem>>, Map<String, List<DataItem>>>>
-            mEntriesAndActionsTask;
+    private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
+    /**
+     * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
+     */
+    private Cp2DataCardModel mCachedCp2DataCardModel;
     /**
      *  This scrim's opacity is controlled in two different ways. 1) Before the initial entrance
      *  animation finishes, the opacity is animated by a value animator. This is designed to
@@ -225,6 +220,12 @@
     private boolean mIsExitAnimationInProgress;
     private boolean mHasComputedThemeColor;
 
+    /**
+     * Used to stop the ExpandingEntry cards from adjusting between an entry click and the intent
+     * being launched.
+     */
+    private boolean mHasIntentLaunched;
+
     private Contact mContactData;
     private ContactLoader mContactLoader;
     private PorterDuffColorFilter mColorFilter;
@@ -336,15 +337,15 @@
 
             // Pass the touch point through the intent for use in the InCallUI
             if (Intent.ACTION_CALL.equals(intent.getAction())) {
-                final Point touchPoint = TouchPointManager.getInstance().getPoint();
-
-                if (touchPoint.x != 0 || touchPoint.y != 0) {
+                if (TouchPointManager.getInstance().hasValidPoint()) {
                     Bundle extras = new Bundle();
-                    extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
+                    extras.putParcelable(TouchPointManager.TOUCH_POINT,
+                            TouchPointManager.getInstance().getPoint());
                     intent.putExtra(TelecommManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
                 }
             }
 
+            mHasIntentLaunched = true;
             startActivity(intent);
         }
     };
@@ -535,6 +536,14 @@
     };
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         Trace.beginSection("onCreate()");
         super.onCreate(savedInstanceState);
@@ -571,9 +580,9 @@
         mAboutCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
 
         mPhotoView = (ImageView) findViewById(R.id.photo);
-        mTransparentView = findViewById(R.id.transparent_view);
+        final View transparentView = findViewById(R.id.transparent_view);
         if (mScroller != null) {
-            mTransparentView.setOnClickListener(new OnClickListener() {
+            transparentView.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     mScroller.scrollOffBottom();
@@ -773,26 +782,22 @@
 
         Trace.endSection();
 
-        mEntriesAndActionsTask = new AsyncTask<Void, Void,
-                Pair<List<List<DataItem>>, Map<String, List<DataItem>>>>() {
+        mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
 
             @Override
-            protected Pair<List<List<DataItem>>, Map<String, List<DataItem>>> doInBackground(
+            protected Cp2DataCardModel doInBackground(
                     Void... params) {
                 return generateDataModelFromContact(data);
             }
 
             @Override
-            protected void onPostExecute(Pair<List<List<DataItem>>,
-                    Map<String, List<DataItem>>> dataItemsPair) {
-                super.onPostExecute(dataItemsPair);
-                mDataItemsList = dataItemsPair.first;
-                mDataItemsMap = dataItemsPair.second;
+            protected void onPostExecute(Cp2DataCardModel cardDataModel) {
+                super.onPostExecute(cardDataModel);
                 // Check that original AsyncTask parameters are still valid and the activity
                 // is still running before binding to UI. A new intent could invalidate
                 // the results, for example.
                 if (data == mContactData && !isCancelled()) {
-                    bindDataToCards();
+                    bindDataToCards(cardDataModel);
                     showActivity();
                 }
             }
@@ -800,13 +805,14 @@
         mEntriesAndActionsTask.execute();
     }
 
-    private void bindDataToCards() {
-        startInteractionLoaders();
-        populateContactAndAboutCard();
+    private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
+        startInteractionLoaders(cp2DataCardModel);
+        populateContactAndAboutCard(cp2DataCardModel);
     }
 
-    private void startInteractionLoaders() {
-        final List<DataItem> phoneDataItems = mDataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
+    private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
+        final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
+        final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
         String[] phoneNumbers = null;
         if (phoneDataItems != null) {
             phoneNumbers = new String[phoneDataItems.size()];
@@ -833,7 +839,7 @@
 
 
         Trace.beginSection("start calendar loader");
-        final List<DataItem> emailDataItems = mDataItemsMap.get(Email.CONTENT_ITEM_TYPE);
+        final List<DataItem> emailDataItems = dataItemsMap.get(Email.CONTENT_ITEM_TYPE);
         String[] emailAddresses = null;
         if (emailDataItems != null) {
             emailAddresses = new String[emailDataItems.size()];
@@ -863,14 +869,17 @@
         }
     }
 
-    private List<List<Entry>> buildAboutCardEntries() {
+    private List<List<Entry>> buildAboutCardEntries(Map<String, List<DataItem>> dataItemsMap) {
         final List<List<Entry>> aboutCardEntries = new ArrayList<>();
         for (String mimetype : SORTED_ABOUT_CARD_MIMETYPES) {
-            final List<DataItem> mimeTypeItems = mDataItemsMap.get(mimetype);
+            final List<DataItem> mimeTypeItems = dataItemsMap.get(mimetype);
             if (mimeTypeItems == null) {
                 continue;
             }
-            final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems);
+            // Set aboutCardTitleOut = null, since SORTED_ABOUT_CARD_MIMETYPES doesn't contain
+            // the name mimetype.
+            final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems,
+                    /* aboutCardTitleOut = */ null);
             if (aboutEntries.size() > 0) {
                 aboutCardEntries.add(aboutEntries);
             }
@@ -878,25 +887,26 @@
         return aboutCardEntries;
     }
 
-    private void populateContactAndAboutCard() {
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // If returning from a launched activity, repopulate the contact and about card
+        if (mHasIntentLaunched) {
+            mHasIntentLaunched = false;
+            populateContactAndAboutCard(mCachedCp2DataCardModel);
+        }
+    }
+
+    private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel) {
+        mCachedCp2DataCardModel = cp2DataCardModel;
+        if (mHasIntentLaunched || cp2DataCardModel == null) {
+            return;
+        }
         Trace.beginSection("bind contact card");
 
-        final List<List<Entry>> contactCardEntries = new ArrayList<>();
-        final List<List<Entry>> aboutCardEntries = buildAboutCardEntries();
-
-        for (int i = 0; i < mDataItemsList.size(); ++i) {
-            final List<DataItem> dataItemsByMimeType = mDataItemsList.get(i);
-            final DataItem topDataItem = dataItemsByMimeType.get(0);
-            if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
-                // About card mimetypes are built in buildAboutCardEntries, skip here
-                continue;
-            } else {
-                List<Entry> contactEntries = dataItemsToEntries(mDataItemsList.get(i));
-                if (contactEntries.size() > 0) {
-                    contactCardEntries.add(contactEntries);
-                }
-            }
-        }
+        final List<List<Entry>> contactCardEntries = cp2DataCardModel.contactCardEntries;
+        final List<List<Entry>> aboutCardEntries = cp2DataCardModel.aboutCardEntries;
+        final String customAboutCardName = cp2DataCardModel.customAboutCardName;
 
         if (contactCardEntries.size() > 0) {
             mContactCard.initialize(contactCardEntries,
@@ -939,6 +949,10 @@
             }
         }
 
+        if (!TextUtils.isEmpty(customAboutCardName)) {
+            mAboutCard.setTitle(customAboutCardName);
+        }
+
         mAboutCard.initialize(aboutCardEntries,
                 /* numInitialVisibleEntries = */ 1,
                 /* isExpanded = */ true,
@@ -1006,7 +1020,7 @@
      *  amongst mimetype. The map goes from mimetype string to the sorted list of data items within
      *  mimetype
      */
-    private Pair<List<List<DataItem>>, Map<String, List<DataItem>>> generateDataModelFromContact(
+    private Cp2DataCardModel generateDataModelFromContact(
             Contact data) {
         Trace.beginSection("Build data items map");
 
@@ -1065,16 +1079,69 @@
         Collections.sort(dataItemsList, mAmongstMimeTypeDataItemComparator);
         Trace.endSection();
 
-        return new Pair<>(dataItemsList, dataItemsMap);
+        Trace.beginSection("cp2 data items to entries");
+
+        final List<List<Entry>> contactCardEntries = new ArrayList<>();
+        final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
+        final MutableString aboutCardName = new MutableString();
+
+        for (int i = 0; i < dataItemsList.size(); ++i) {
+            final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
+            final DataItem topDataItem = dataItemsByMimeType.get(0);
+            if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
+                // About card mimetypes are built in buildAboutCardEntries, skip here
+                continue;
+            } else {
+                List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
+                        aboutCardName);
+                if (contactEntries.size() > 0) {
+                    contactCardEntries.add(contactEntries);
+                }
+            }
+        }
+
+        Trace.endSection();
+
+        final Cp2DataCardModel dataModel = new Cp2DataCardModel();
+        dataModel.customAboutCardName = aboutCardName.value;
+        dataModel.aboutCardEntries = aboutCardEntries;
+        dataModel.contactCardEntries = contactCardEntries;
+        dataModel.dataItemsMap = dataItemsMap;
+        return dataModel;
+    }
+
+    /**
+     * Class used to hold the About card and Contact cards' data model that gets generated
+     * on a background thread. All data is from CP2.
+     */
+    private static class Cp2DataCardModel {
+        /**
+         * A map between a mimetype string and the corresponding list of data items. The data items
+         * are in sorted order using mWithinMimeTypeDataItemComparator.
+         */
+        public Map<String, List<DataItem>> dataItemsMap;
+        public List<List<Entry>> aboutCardEntries;
+        public List<List<Entry>> contactCardEntries;
+        public String customAboutCardName;
+    }
+
+    private static class MutableString {
+        public String value;
     }
 
     /**
      * Converts a {@link DataItem} into an {@link ExpandingEntryCardView.Entry} for display.
      * If the {@link ExpandingEntryCardView.Entry} has no visual elements, null is returned.
+     *
+     * This runs on a background thread. This is set as static to avoid accidentally adding
+     * additional dependencies on unsafe things (like the Activity).
+     *
      * @param dataItem The {@link DataItem} to convert.
      * @return The {@link ExpandingEntryCardView.Entry}, or null if no visual elements are present.
      */
-    private Entry dataItemToEntry(DataItem dataItem) {
+    private static Entry dataItemToEntry(DataItem dataItem,
+            Context context, Contact contactData,
+            final MutableString aboutCardName) {
         Drawable icon = null;
         String header = null;
         String subHeader = null;
@@ -1089,11 +1156,12 @@
         final boolean isEditable = false;
         EntryContextMenuInfo entryContextMenuInfo = null;
 
+        context = context.getApplicationContext();
         DataKind kind = dataItem.getDataKind();
 
         if (dataItem instanceof ImDataItem) {
             final ImDataItem im = (ImDataItem) dataItem;
-            intent = ContactsUtils.buildImIntent(this, im).first;
+            intent = ContactsUtils.buildImIntent(context, im).first;
             final boolean isEmail = im.isCreatedFromEmail();
             final int protocol;
             if (!im.isProtocolValid()) {
@@ -1104,19 +1172,19 @@
             if (protocol == Im.PROTOCOL_CUSTOM) {
                 // If the protocol is custom, display the "IM" entry header as well to distinguish
                 // this entry from other ones
-                header = getResources().getString(R.string.header_im_entry);
-                subHeader = Im.getProtocolLabel(getResources(), protocol,
+                header = context.getResources().getString(R.string.header_im_entry);
+                subHeader = Im.getProtocolLabel(context.getResources(), protocol,
                         im.getCustomProtocol()).toString();
                 text = im.getData();
             } else {
-                header = Im.getProtocolLabel(getResources(), protocol,
+                header = Im.getProtocolLabel(context.getResources(), protocol,
                         im.getCustomProtocol()).toString();
                 subHeader = im.getData();
             }
             entryContextMenuInfo = new EntryContextMenuInfo(im.getData(), header);
         } else if (dataItem instanceof OrganizationDataItem) {
             final OrganizationDataItem organization = (OrganizationDataItem) dataItem;
-            header = getResources().getString(R.string.header_organization_entry);
+            header = context.getResources().getString(R.string.header_organization_entry);
             subHeader = organization.getCompany();
             entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
             text = organization.getTitle();
@@ -1124,36 +1192,37 @@
             final NicknameDataItem nickname = (NicknameDataItem) dataItem;
             // Build nickname entries
             final boolean isNameRawContact =
-                (mContactData.getNameRawContactId() == dataItem.getRawContactId());
+                (contactData.getNameRawContactId() == dataItem.getRawContactId());
 
             final boolean duplicatesTitle =
                 isNameRawContact
-                && mContactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
+                && contactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
 
             if (!duplicatesTitle) {
-                header = getResources().getString(R.string.header_nickname_entry);
+                header = context.getResources().getString(R.string.header_nickname_entry);
                 subHeader = nickname.getName();
                 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
             }
         } else if (dataItem instanceof NoteDataItem) {
             final NoteDataItem note = (NoteDataItem) dataItem;
-            header = getResources().getString(R.string.header_note_entry);
+            header = context.getResources().getString(R.string.header_note_entry);
             subHeader = note.getNote();
             entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
         } else if (dataItem instanceof WebsiteDataItem) {
             final WebsiteDataItem website = (WebsiteDataItem) dataItem;
-            header = getResources().getString(R.string.header_website_entry);
+            header = context.getResources().getString(R.string.header_website_entry);
             subHeader = website.getUrl();
             entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
             try {
-                final WebAddress webAddress = new WebAddress(website.buildDataString(this, kind));
+                final WebAddress webAddress = new WebAddress(website.buildDataString(context,
+                        kind));
                 intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
             } catch (final ParseException e) {
-                Log.e(TAG, "Couldn't parse website: " + website.buildDataString(this, kind));
+                Log.e(TAG, "Couldn't parse website: " + website.buildDataString(context, kind));
             }
         } else if (dataItem instanceof EventDataItem) {
             final EventDataItem event = (EventDataItem) dataItem;
-            final String dataString = event.buildDataString(this, kind);
+            final String dataString = event.buildDataString(context, kind);
             final Calendar cal = DateUtils.parseDate(dataString, false);
             if (cal != null) {
                 final Date nextAnniversary =
@@ -1163,46 +1232,47 @@
                 ContentUris.appendId(builder, nextAnniversary.getTime());
                 intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
             }
-            header = getResources().getString(R.string.header_event_entry);
+            header = context.getResources().getString(R.string.header_event_entry);
             if (event.hasKindTypeColumn(kind)) {
-                subHeader = Event.getTypeLabel(getResources(), event.getKindTypeColumn(kind),
+                subHeader = Event.getTypeLabel(context.getResources(), event.getKindTypeColumn(kind),
                         event.getLabel()).toString();
             }
-            text = DateUtils.formatDate(this, dataString);
+            text = DateUtils.formatDate(context, dataString);
             entryContextMenuInfo = new EntryContextMenuInfo(text, header);
         } else if (dataItem instanceof RelationDataItem) {
             final RelationDataItem relation = (RelationDataItem) dataItem;
-            final String dataString = relation.buildDataString(this, kind);
+            final String dataString = relation.buildDataString(context, kind);
             if (!TextUtils.isEmpty(dataString)) {
                 intent = new Intent(Intent.ACTION_SEARCH);
                 intent.putExtra(SearchManager.QUERY, dataString);
                 intent.setType(Contacts.CONTENT_TYPE);
             }
-            header = getResources().getString(R.string.header_relation_entry);
+            header = context.getResources().getString(R.string.header_relation_entry);
             subHeader = relation.getName();
             entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header);
             if (relation.hasKindTypeColumn(kind)) {
-                text = Relation.getTypeLabel(getResources(), relation.getKindTypeColumn(kind),
+                text = Relation.getTypeLabel(context.getResources(),
+                        relation.getKindTypeColumn(kind),
                         relation.getLabel()).toString();
             }
         } else if (dataItem instanceof PhoneDataItem) {
             final PhoneDataItem phone = (PhoneDataItem) dataItem;
             if (!TextUtils.isEmpty(phone.getNumber())) {
-                header = phone.buildDataString(this, kind);
+                header = phone.buildDataString(context, kind);
                 entryContextMenuInfo = new EntryContextMenuInfo(header,
-                        getResources().getString(R.string.phoneLabelsGroup));
+                        context.getResources().getString(R.string.phoneLabelsGroup));
                 if (phone.hasKindTypeColumn(kind)) {
-                    text = Phone.getTypeLabel(getResources(), phone.getKindTypeColumn(kind),
+                    text = Phone.getTypeLabel(context.getResources(), phone.getKindTypeColumn(kind),
                             phone.getLabel()).toString();
                 }
-                icon = getResources().getDrawable(R.drawable.ic_phone_24dp);
-                if (PhoneCapabilityTester.isPhone(this)) {
+                icon = context.getResources().getDrawable(R.drawable.ic_phone_24dp);
+                if (PhoneCapabilityTester.isPhone(context)) {
                     intent = CallUtil.getCallIntent(phone.getNumber());
                 }
                 alternateIntent = new Intent(Intent.ACTION_SENDTO,
                         Uri.fromParts(CallUtil.SCHEME_SMSTO, phone.getNumber(), null));
-                alternateIcon = getResources().getDrawable(R.drawable.ic_message_24dp);
-                alternateContentDescription = getResources().getString(R.string.sms_other);
+                alternateIcon = context.getResources().getDrawable(R.drawable.ic_message_24dp);
+                alternateContentDescription = context.getResources().getString(R.string.sms_other);
             }
         } else if (dataItem instanceof EmailDataItem) {
             final EmailDataItem email = (EmailDataItem) dataItem;
@@ -1212,12 +1282,12 @@
                 intent = new Intent(Intent.ACTION_SENDTO, mailUri);
                 header = email.getAddress();
                 entryContextMenuInfo = new EntryContextMenuInfo(header,
-                        getResources().getString(R.string.emailLabelsGroup));
+                        context.getResources().getString(R.string.emailLabelsGroup));
                 if (email.hasKindTypeColumn(kind)) {
-                    text = Email.getTypeLabel(getResources(), email.getKindTypeColumn(kind),
+                    text = Email.getTypeLabel(context.getResources(), email.getKindTypeColumn(kind),
                             email.getLabel()).toString();
                 }
-                icon = getResources().getDrawable(R.drawable.ic_email_24dp);
+                icon = context.getResources().getDrawable(R.drawable.ic_email_24dp);
             }
         } else if (dataItem instanceof StructuredPostalDataItem) {
             StructuredPostalDataItem postal = (StructuredPostalDataItem) dataItem;
@@ -1226,18 +1296,18 @@
                 intent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
                 header = postal.getFormattedAddress();
                 entryContextMenuInfo = new EntryContextMenuInfo(header,
-                        getResources().getString(R.string.postalLabelsGroup));
+                        context.getResources().getString(R.string.postalLabelsGroup));
                 if (postal.hasKindTypeColumn(kind)) {
-                    text = StructuredPostal.getTypeLabel(getResources(),
+                    text = StructuredPostal.getTypeLabel(context.getResources(),
                             postal.getKindTypeColumn(kind), postal.getLabel()).toString();
                 }
                 alternateIntent =
                         StructuredPostalUtils.getViewPostalAddressDirectionsIntent(postalAddress);
-                alternateIcon = getResources().getDrawable(R.drawable.ic_directions_24dp);
-                icon = getResources().getDrawable(R.drawable.ic_place_24dp);
+                alternateIcon = context.getResources().getDrawable(R.drawable.ic_directions_24dp);
+                icon = context.getResources().getDrawable(R.drawable.ic_place_24dp);
             }
         } else if (dataItem instanceof SipAddressDataItem) {
-            if (PhoneCapabilityTester.isSipPhone(this)) {
+            if (PhoneCapabilityTester.isSipPhone(context)) {
                 final SipAddressDataItem sip = (SipAddressDataItem) dataItem;
                 final String address = sip.getSipAddress();
                 if (!TextUtils.isEmpty(address)) {
@@ -1245,35 +1315,25 @@
                     intent = CallUtil.getCallIntent(callUri);
                     header = address;
                     entryContextMenuInfo = new EntryContextMenuInfo(header,
-                            getResources().getString(R.string.phoneLabelsGroup));
+                            context.getResources().getString(R.string.phoneLabelsGroup));
                     if (sip.hasKindTypeColumn(kind)) {
-                        text = SipAddress.getTypeLabel(getResources(), sip.getKindTypeColumn(kind),
-                            sip.getLabel()).toString();
+                        text = SipAddress.getTypeLabel(context.getResources(),
+                                sip.getKindTypeColumn(kind), sip.getLabel()).toString();
                     }
-                    // Note that this item will get a SIP-specific variant
-                    // of the "call phone" icon, rather than the standard
-                    // app icon for the Phone app (which we show for
-                    // regular phone numbers.)  That's because the phone
-                    // app explicitly specifies an android:icon attribute
-                    // for the SIP-related intent-filters in its manifest.
-                    icon = ResolveCache.getInstance(this).getIcon(sip.getMimeType(), intent);
-                    // Call mutate to create a new Drawable.ConstantState for color filtering
-                    if (icon != null) {
-                        icon.mutate();
-                    }
+                    icon = context.getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
                 }
             }
         } else if (dataItem instanceof StructuredNameDataItem) {
             final String givenName = ((StructuredNameDataItem) dataItem).getGivenName();
             if (!TextUtils.isEmpty(givenName)) {
-                mAboutCard.setTitle(getResources().getString(R.string.about_card_title) +
-                        " " + givenName);
+                aboutCardName.value = context.getResources().getString(R.string.about_card_title) +
+                        " " + givenName;
             } else {
-                mAboutCard.setTitle(getResources().getString(R.string.about_card_title));
+                aboutCardName.value = context.getResources().getString(R.string.about_card_title);
             }
         } else {
             // Custom DataItem
-            header = dataItem.buildDataStringForDisplay(this, kind);
+            header = dataItem.buildDataStringForDisplay(context, kind);
             text = kind.typeColumn;
             intent = new Intent(Intent.ACTION_VIEW);
             final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, dataItem.getId());
@@ -1287,22 +1347,22 @@
                     case MIMETYPE_GPLUS_PROFILE:
                         if (INTENT_DATA_GPLUS_PROFILE_ADD_TO_CIRCLE.equals(
                                 intent.getDataString())) {
-                            icon = getResources().getDrawable(
+                            icon = context.getResources().getDrawable(
                                     R.drawable.ic_add_to_circles_black_24);
                         } else {
-                            icon = getResources().getDrawable(R.drawable.ic_google_plus_24dp);
+                            icon = context.getResources().getDrawable(R.drawable.ic_google_plus_24dp);
                         }
                         break;
                     case MIMETYPE_HANGOUTS:
                         if (INTENT_DATA_HANGOUTS_VIDEO.equals(intent.getDataString())) {
-                            icon = getResources().getDrawable(R.drawable.ic_hangout_video_24dp);
+                            icon = context.getResources().getDrawable(R.drawable.ic_hangout_video_24dp);
                         } else {
-                            icon = getResources().getDrawable(R.drawable.ic_hangout_24dp);
+                            icon = context.getResources().getDrawable(R.drawable.ic_hangout_24dp);
                         }
                         break;
                     default:
                         entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype);
-                        icon = ResolveCache.getInstance(this).getIcon(
+                        icon = ResolveCache.getInstance(context).getIcon(
                                 dataItem.getMimeType(), intent);
                         // Call mutate to create a new Drawable.ConstantState for color filtering
                         if (icon != null) {
@@ -1315,20 +1375,20 @@
 
         if (intent != null) {
             // Do not set the intent is there are no resolves
-            if (!PhoneCapabilityTester.isIntentRegistered(this, intent)) {
+            if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
                 intent = null;
             }
         }
 
         if (alternateIntent != null) {
             // Do not set the alternate intent is there are no resolves
-            if (!PhoneCapabilityTester.isIntentRegistered(this, alternateIntent)) {
+            if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
                 alternateIntent = null;
             }
 
             // Attempt to use package manager to find a suitable content description if needed
             if (TextUtils.isEmpty(alternateContentDescription)) {
-                alternateContentDescription = getIntentResolveLabel(alternateIntent);
+                alternateContentDescription = getIntentResolveLabel(alternateIntent, context);
             }
         }
 
@@ -1347,10 +1407,11 @@
                 isEditable, entryContextMenuInfo);
     }
 
-    private List<Entry> dataItemsToEntries(List<DataItem> dataItems) {
+    private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
+            MutableString aboutCardTitleOut) {
         final List<Entry> entries = new ArrayList<>();
         for (DataItem dataItem : dataItems) {
-            final Entry entry = dataItemToEntry(dataItem);
+            final Entry entry = dataItemToEntry(dataItem, this, mContactData, aboutCardTitleOut);
             if (entry != null) {
                 entries.add(entry);
             }
@@ -1358,8 +1419,8 @@
         return entries;
     }
 
-    private String getIntentResolveLabel(Intent intent) {
-        final List<ResolveInfo> matches = getPackageManager().queryIntentActivities(intent,
+    private static String getIntentResolveLabel(Intent intent, Context context) {
+        final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
                 PackageManager.MATCH_DEFAULT_ONLY);
 
         // Pick first match, otherwise best found
@@ -1368,14 +1429,14 @@
         if (size == 1) {
             bestResolve = matches.get(0);
         } else if (size > 1) {
-            bestResolve = ResolveCache.getInstance(this).getBestResolve(intent, matches);
+            bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
         }
 
         if (bestResolve == null) {
             return null;
         }
 
-        return String.valueOf(bestResolve.loadLabel(getPackageManager()));
+        return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
     }
 
     /**
@@ -1710,19 +1771,20 @@
     }
 
     private Intent getEditContactIntent() {
-        final Intent intent = new Intent(Intent.ACTION_EDIT, mLookupUri);
+        final Intent intent = new Intent(Intent.ACTION_EDIT, mContactData.getLookupUri());
         mContactLoader.cacheResult();
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
         return intent;
     }
 
     private void editContact() {
+        mHasIntentLaunched = true;
         startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
     }
 
     private void toggleStar(MenuItem starredMenuItem) {
         // Make sure there is a contact
-        if (mLookupUri != null) {
+        if (mContactData != null) {
             // Read the current starred value from the UI instead of using the last
             // loaded state. This allows rapid tapping without writing the same
             // value several times
@@ -1735,7 +1797,7 @@
 
             // Now perform the real save
             final Intent intent = ContactSaveService.createSetStarredIntent(
-                    QuickContactActivity.this, mLookupUri, !isStarred);
+                    QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
             startService(intent);
 
             final CharSequence accessibilityText = !isStarred
@@ -1785,6 +1847,7 @@
         final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
 
         try {
+            mHasIntentLaunched = true;
             this.startActivity(chooseIntent);
         } catch (final ActivityNotFoundException ex) {
             Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
@@ -1813,7 +1876,7 @@
                     }
 
                 });
-        builder.createContactShortcutIntent(mLookupUri);
+        builder.createContactShortcutIntent(mContactData.getLookupUri());
     }
 
     @Override
diff --git a/src/com/android/contacts/util/ImageViewDrawableSetter.java b/src/com/android/contacts/util/ImageViewDrawableSetter.java
index 226909e..e926e54 100644
--- a/src/com/android/contacts/util/ImageViewDrawableSetter.java
+++ b/src/com/android/contacts/util/ImageViewDrawableSetter.java
@@ -17,15 +17,13 @@
 package com.android.contacts.util;
 
 import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
-import android.provider.ContactsContract.DisplayNameSources;
+import android.media.ThumbnailUtils;
 import android.text.TextUtils;
-import android.util.Log;
 import android.widget.ImageView;
 
 import com.android.contacts.common.ContactPhotoManager;
@@ -161,8 +159,13 @@
     }
 
     private BitmapDrawable decodedBitmapDrawable(byte[] compressed) {
-        Resources rsrc = mTarget.getResources();
+        final Resources rsrc = mTarget.getResources();
         Bitmap bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
+        if (bitmap.getHeight() != bitmap.getWidth()) {
+            // Crop the bitmap into a square.
+            final int size = Math.min(bitmap.getWidth(), bitmap.getHeight());
+            bitmap = ThumbnailUtils.extractThumbnail(bitmap, size, size);
+        }
         return new BitmapDrawable(rsrc, bitmap);
     }
 }
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 82b3970..26b032b 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -54,6 +54,13 @@
  * MultiShrinkScroller's code is heavily influenced by ScrollView. Nonetheless, several ScrollView
  * features are missing. For example: handling of KEYCODES, OverScroll bounce and saving
  * scroll state in savedInstanceState bundles.
+ *
+ * Before copying this approach to nested scrolling, consider whether something simpler & less
+ * customized will work for you. For example, see the re-usable StickyHeaderListView used by
+ * WifiSetupActivity (very nice). Alternatively, check out Google+'s cover photo scrolling or
+ * Android L's built in nested scrolling support. I thought I needed a more custom ViewGroup in
+ * order to track velocity, modify EdgeEffect color & perform specific animations such as the ones
+ * inside snapToBottom(). As a result this ViewGroup has non-standard talkback and keyboard support.
  */
 public class MultiShrinkScroller extends FrameLayout {