Bag of QuickContact improvements

* QuickContacts open full screen when inside the Contact app
* Status bar color (aka system bar)
* Added and animated shim
* Moving code off UI thread (without these, I was seeing ~20
 frames dropped from the shim animation)
* QuickContacts now acts reasonably when re-entering QuickContacts
 from the edit view

Change-Id: Idafc20b62b1d04f0a9c437b3708555ec6dddedf5
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a87949a..e2aad85 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -255,14 +255,11 @@
             android:theme="@style/EditorActivityTheme"
             android:windowSoftInputMode="adjustResize" />
 
-        <!-- Used to show QuickContact window over a translucent activity, which is a
-             temporary hack until we add better framework support. -->
         <activity
             android:name=".quickcontact.QuickContactActivity"
             android:theme="@style/Theme.QuickContact"
             android:launchMode="singleTop"
             android:excludeFromRecents="true"
-            android:noHistory="true"
             android:taskAffinity=""
             android:windowSoftInputMode="stateUnchanged">
 
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 8b75a7e..50f9879 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -70,6 +70,7 @@
 import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
 import com.android.contacts.common.util.AccountFilterUtil;
 import com.android.contacts.common.util.ViewUtil;
+import com.android.contacts.quickcontact.QuickContactActivity;
 import com.android.contacts.util.AccountPromptUtils;
 import com.android.contacts.common.util.Constants;
 import com.android.contacts.util.DialogManager;
@@ -842,8 +843,8 @@
         @Override
         public void onViewContactAction(Uri contactLookupUri) {
             Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this,
-                    getCurrentFocus().getRootView(), contactLookupUri, QuickContact.MODE_LARGE,
-                    null);
+                    getCurrentFocus().getRootView(), contactLookupUri,
+                    QuickContactActivity.MODE_FULLY_EXPANDED, null);
             startActivity(intent);
         }
 
@@ -912,7 +913,7 @@
         @Override
         public void onContactSelected(Uri contactUri, Rect targetRect) {
             Intent intent = QuickContact.composeQuickContactsIntent(PeopleActivity.this,
-                    targetRect, contactUri, QuickContact.MODE_LARGE, null);
+                    targetRect, contactUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
             startActivity(intent);
         }
 
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 1585c66..1b950d2 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -35,6 +35,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Rect;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Bundle;
@@ -48,6 +49,7 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.Log;
@@ -86,6 +88,7 @@
 import com.android.contacts.common.model.RawContactDelta;
 import com.android.contacts.common.model.RawContactDeltaList;
 import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.quickcontact.QuickContactActivity;
 import com.android.contacts.util.ContactPhotoUtils;
 import com.android.contacts.util.HelpUtils;
 import com.android.contacts.util.PhoneCapabilityTester;
@@ -1309,9 +1312,7 @@
                             mLookupUri == null ? null : mLookupUri.getAuthority();
 
                     final String legacyAuthority = "contacts";
-
-                    resultIntent = new Intent();
-                    resultIntent.setAction(Intent.ACTION_VIEW);
+                    final Uri lookupUri;
                     if (legacyAuthority.equals(requestAuthority)) {
                         // Build legacy Uri when requested by caller
                         final long contactId = ContentUris.parseId(Contacts.lookupContact(
@@ -1319,12 +1320,15 @@
                         final Uri legacyContentUri = Uri.parse("content://contacts/people");
                         final Uri legacyUri = ContentUris.withAppendedId(
                                 legacyContentUri, contactId);
-                        resultIntent.setData(legacyUri);
+                        lookupUri = legacyUri;
                     } else {
                         // Otherwise pass back a lookup-style Uri
-                        resultIntent.setData(contactLookupUri);
+                        lookupUri = contactLookupUri;
                     }
-
+                    resultIntent = QuickContact.composeQuickContactsIntent(getActivity(),
+                            (Rect) null, lookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
+                    // Make sure not to show QuickContacts on top of another QuickContacts.
+                    resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 } else {
                     resultIntent = null;
                 }
diff --git a/src/com/android/contacts/interactions/ContactDeletionInteraction.java b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
index 2880f77..458556e 100644
--- a/src/com/android/contacts/interactions/ContactDeletionInteraction.java
+++ b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
@@ -54,6 +54,7 @@
     private static final String KEY_CONTACT_URI = "contactUri";
     private static final String KEY_FINISH_WHEN_DONE = "finishWhenDone";
     public static final String ARG_CONTACT_URI = "contactUri";
+    public static final int RESULT_CODE_DELETED = 3;
 
     private static final String[] ENTITY_PROJECTION = new String[] {
         Entity.RAW_CONTACT_ID, //0
@@ -317,6 +318,7 @@
     protected void doDeleteContact(Uri contactUri) {
         mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri));
         if (isAdded() && mFinishActivityWhenDone) {
+            getActivity().setResult(RESULT_CODE_DELETED);
             getActivity().finish();
         }
     }
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 8f495a9..36d1f6e 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -76,6 +76,9 @@
 
     public DefaultContactBrowseListFragment() {
         setPhotoLoaderEnabled(true);
+        // Don't use a QuickContactBadge. Just use a regular ImageView. Using a QuickContactBadge
+        // inside the ListView prevents us from using MODE_FULLY_EXPANDED and messes up ripples.
+        setQuickContactEnabled(false);
         setSectionHeaderDisplayEnabled(true);
         setVisibleScrollbarEnabled(true);
     }
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index dc6b5f2..8341c7f 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.quickcontact;
 
+import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.app.LoaderManager.LoaderCallbacks;
@@ -25,12 +26,14 @@
 import android.content.Loader;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Trace;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
@@ -66,15 +69,13 @@
 import com.android.contacts.common.model.dataitem.EmailDataItem;
 import com.android.contacts.common.model.dataitem.ImDataItem;
 import com.android.contacts.common.model.dataitem.PhoneDataItem;
-import com.android.contacts.common.util.Constants;
 import com.android.contacts.common.util.DataStatus;
 import com.android.contacts.common.util.UriUtils;
+import com.android.contacts.interactions.ContactDeletionInteraction;
 import com.android.contacts.interactions.ContactInteraction;
-import com.android.contacts.interactions.ContactInteractionUtil;
 import com.android.contacts.interactions.SmsInteractionsLoader;
 import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
 import com.android.contacts.util.ImageViewDrawableSetter;
-import com.android.contacts.common.util.StopWatch;
 import com.android.contacts.util.SchedulingUtils;
 import com.android.contacts.widget.MultiShrinkScroller;
 import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
@@ -83,7 +84,6 @@
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -98,12 +98,21 @@
  * {@link Intent#getSourceBounds()}.
  */
 public class QuickContactActivity extends Activity {
+
+    /**
+     * QuickContacts immediately takes up the full screen. All possible information is shown.
+     * This value for {@link android.provider.ContactsContract.QuickContact#EXTRA_MODE}
+     * should only be used by the Contacts app.
+     */
+    public static final int MODE_FULLY_EXPANDED = 4;
+
     private static final String TAG = "QuickContact";
 
-    private static final boolean TRACE_LAUNCH = false;
-    private static final String TRACE_TAG = "quickcontact";
-    private static final int ANIMATION_DURATION = 250;
-    private static final boolean ENABLE_STOPWATCH = false;
+    private static final int ANIMATION_SLIDE_OPEN_DURATION = 250;
+    private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 75;
+    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);
 
 
     @SuppressWarnings("deprecation")
@@ -111,7 +120,9 @@
 
     private Uri mLookupUri;
     private String[] mExcludeMimes;
-    private List<String> mSortedActionMimeTypes = Lists.newArrayList();
+    private int mExtraMode;
+    private int mStatusBarColor;
+    private boolean mHasAlreadyBeenOpened;
 
     private View mPhotoContainer;
 
@@ -121,6 +132,7 @@
     private ExpandingEntryCardView mCommunicationCard;
     private ExpandingEntryCardView mRecentCard;
     private MultiShrinkScroller mScroller;
+    private AsyncTask<Void, Void, Void> mEntriesAndActionsTask;
 
     private static final int MIN_NUM_COMMUNICATION_ENTRIES_SHOWN = 3;
     private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
@@ -172,17 +184,13 @@
     private static final int[] mRecentLoaderIds = new int[LOADER_SMS_ID];
     private Map<Integer, List<ContactInteraction>> mRecentLoaderResults;
 
-
-    private StopWatch mStopWatch = ENABLE_STOPWATCH
-            ? StopWatch.start("QuickContact") : StopWatch.getNullStopWatch();
-
     final OnClickListener mEditContactClickHandler = new OnClickListener() {
         @Override
         public void onClick(View v) {
             final Intent intent = new Intent(Intent.ACTION_EDIT, mLookupUri);
             mContactLoader.cacheResult();
             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
-            startActivity(intent);
+            startActivityForResult(intent, REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
         }
     };
 
@@ -224,16 +232,27 @@
         public void onScrolledOffBottom() {
             onBackPressed();
         }
+
+        @Override
+        public void onEnterFullscreen() {
+            updateStatusBarColor();
+        }
+
+        @Override
+        public void onExitFullscreen() {
+            updateStatusBarColor();
+        }
     };
 
     @Override
-    protected void onCreate(Bundle icicle) {
-        mStopWatch.lap("c"); // create start
-        super.onCreate(icicle);
+    protected void onCreate(Bundle savedInstanceState) {
+        Trace.beginSection("onCreate()");
+        super.onCreate(savedInstanceState);
 
-        mStopWatch.lap("sc"); // super.onCreate
-
-        if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG);
+        getWindow().setStatusBarColor(Color.TRANSPARENT);
+        // Since we can't disable Window animations from the Launcher, we can minimize the
+        // silliness of the animation by setting the navigation bar transparent.
+        getWindow().setNavigationBarColor(Color.TRANSPARENT);
 
         // Parse intent
         final Intent intent = getIntent();
@@ -247,35 +266,28 @@
                     ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
         }
 
+        mExtraMode = getIntent().getIntExtra(QuickContact.EXTRA_MODE,
+                QuickContact.MODE_LARGE);
+
         mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
 
         mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
 
-        mStopWatch.lap("i"); // intent parsed
-
         mContactLoader = (ContactLoader) getLoaderManager().initLoader(
                 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
 
-        mStopWatch.lap("ld"); // loader started
-
         // Show QuickContact in front of soft input
         getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
 
         setContentView(R.layout.quickcontact_activity);
 
-        mStopWatch.lap("l"); // layout inflated
-
         mEditOrAddContactImage = (ImageView) findViewById(R.id.contact_edit_image);
         mStarImage = (ImageView) findViewById(R.id.quickcontact_star_button);
         mCommunicationCard = (ExpandingEntryCardView) findViewById(R.id.communication_card);
         mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
         mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
 
-        if (mScroller != null) {
-            mScroller.initialize(mMultiShrinkScrollerListener);
-        }
-
         mEditOrAddContactImage.setOnClickListener(mEditContactClickHandler);
 
         mCommunicationCard.setOnClickListener(mEntryClickHandler);
@@ -292,20 +304,53 @@
         mPhotoView = (ImageView) mPhotoContainer.findViewById(R.id.photo);
         mPhotoView.setOnClickListener(mEditContactClickHandler);
 
-        mStopWatch.lap("v"); // view initialized
+        mHasAlreadyBeenOpened = savedInstanceState != null;
 
-        if (mScroller != null) {
-            mScroller.setVisibility(View.GONE);
+        final ColorDrawable windowShim = new ColorDrawable(SHIM_COLOR);
+        getWindow().setBackgroundDrawable(windowShim);
+        if (!mHasAlreadyBeenOpened) {
+            final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
+            ObjectAnimator.ofInt(windowShim, "alpha", 0, 0xFF).setDuration(duration).start();
         }
 
-        mStopWatch.lap("cf"); // onCreate finished
+        if (mScroller != null) {
+            mScroller.initialize(mMultiShrinkScrollerListener);
+            if (mHasAlreadyBeenOpened) {
+                mScroller.setVisibility(View.VISIBLE);
+                mScroller.setScroll(mScroller.getScrollNeededToBeFullScreen());
+            } else {
+                mScroller.setVisibility(View.GONE);
+            }
+        }
+
+        Trace.endSection();
+    }
+
+    protected void onActivityResult(int requestCode, int resultCode,
+            Intent data) {
+        if (requestCode == REQUEST_CODE_CONTACT_EDITOR_ACTIVITY &&
+                resultCode == ContactDeletionInteraction.RESULT_CODE_DELETED) {
+            // The contact that we were showing has been deleted.
+            finish();
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        mHasAlreadyBeenOpened = true;
     }
 
     private void runEntranceAnimation() {
+        if (mHasAlreadyBeenOpened) {
+            return;
+        }
+        mHasAlreadyBeenOpened = true;
         final int bottomScroll = mScroller.getScrollUntilOffBottom() - 1;
         final ObjectAnimator scrollAnimation
-                = ObjectAnimator.ofInt(mScroller, "scroll", -bottomScroll, 0);
-        scrollAnimation.setDuration(ANIMATION_DURATION);
+                = ObjectAnimator.ofInt(mScroller, "scroll", -bottomScroll,
+                mExtraMode != MODE_FULLY_EXPANDED ? 0 : mScroller.getScrollNeededToBeFullScreen());
+        scrollAnimation.setDuration(ANIMATION_SLIDE_OPEN_DURATION);
         scrollAnimation.start();
     }
 
@@ -341,9 +386,9 @@
     /**
      * Handle the result from the ContactLoader
      */
-    private void bindContactData(Contact data) {
+    private void bindContactData(final Contact data) {
+        Trace.beginSection("bindContactData");
         mContactData = data;
-        final ResolveCache cache = ResolveCache.getInstance(this);
         final Context context = this;
 
         mEditOrAddContactImage.setVisibility(isMimeExcluded(Contacts.CONTENT_ITEM_TYPE) ?
@@ -399,15 +444,90 @@
 
         mDefaultsMap.clear();
 
-        mStopWatch.lap("sph"); // Start photo setting
+        Trace.endSection();
+        Trace.beginSection("Set display photo & name");
 
         mPhotoSetter.setupContactPhoto(data, mPhotoView);
         extractAndApplyTintFromPhotoViewAsynchronously();
+        setHeaderNameText(R.id.name, data.getDisplayName());
 
-        mStopWatch.lap("ph"); // Photo set
+        Trace.endSection();
 
+        final List<String> sortedActionMimeTypes = Lists.newArrayList();
         // Maintain a list of phone numbers to pass into SmsInteractionsLoader
-        List<String> phoneNumbers = new ArrayList<>();
+        final List<String> phoneNumbers = Lists.newArrayList();
+        // List of Entry that makes up the ExpandingEntryCardView
+        final List<Entry> entries = Lists.newArrayList();
+
+        mEntriesAndActionsTask = new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                computeEntriesAndActions(data, phoneNumbers, sortedActionMimeTypes, entries);
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void aVoid) {
+                super.onPostExecute(aVoid);
+                // 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()) {
+                    bindEntriesAndActions(entries, phoneNumbers, sortedActionMimeTypes);
+                    showActivity();
+                }
+            }
+        };
+        mEntriesAndActionsTask.execute();
+    }
+
+    private void bindEntriesAndActions(List<Entry> entries,
+            List<String> phoneNumbers,
+            List<String> sortedActionMimeTypes) {
+        Trace.beginSection("start sms loader");
+
+        Bundle smsExtraBundle = new Bundle();
+        smsExtraBundle.putStringArray(KEY_LOADER_EXTRA_SMS_PHONES,
+                phoneNumbers.toArray(new String[phoneNumbers.size()]));
+        getLoaderManager().initLoader(
+                LOADER_SMS_ID,
+                smsExtraBundle,
+                mLoaderInteractionsCallbacks);
+
+        Trace.endSection();
+        Trace.beginSection("bind communicate card");
+
+        if (entries.size() > 0) {
+            mCommunicationCard.initialize(entries,
+                    /* numInitialVisibleEntries = */ MIN_NUM_COMMUNICATION_ENTRIES_SHOWN,
+                    /* isExpanded = */ false,
+                    /* themeColor = */ 0);
+        }
+
+        final boolean hasData = !sortedActionMimeTypes.isEmpty();
+        mCommunicationCard.setVisibility(hasData ? View.VISIBLE: View.GONE);
+
+        Trace.endSection();
+    }
+
+    private void showActivity() {
+        if (mScroller != null) {
+            mScroller.setVisibility(View.VISIBLE);
+            SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            runEntranceAnimation();
+                        }
+                    });
+        }
+    }
+
+    private void computeEntriesAndActions(Contact data, List<String> phoneNumbers,
+            List<String> sortedActionMimeTypes, List<Entry> entries) {
+        Trace.beginSection("inflate entries and actions");
+
+        final ResolveCache cache = ResolveCache.getInstance(this);
         for (RawContact rawContact : data.getRawContacts()) {
             for (DataItem dataItem : rawContact.getDataItems()) {
                 final String mimeType = dataItem.getMimeType();
@@ -430,7 +550,8 @@
                     // Build an action for this data entry, find a mapping to a UI
                     // element, build its summary from the cursor, and collect it
                     // along with all others of this MIME-type.
-                    final Action action = new DataAction(context, dataItem, dataKind);
+                    final Action action = new DataAction(getApplicationContext(),
+                            dataItem, dataKind);
                     final boolean wasAdded = considerAdd(action, cache, isSuperPrimary);
                     if (wasAdded) {
                         // Remember the default
@@ -446,7 +567,8 @@
                     final EmailDataItem email = (EmailDataItem) dataItem;
                     final ImDataItem im = ImDataItem.createFromEmail(email);
                     if (dataKind != null) {
-                        final DataAction action = new DataAction(context, im, dataKind);
+                        final DataAction action = new DataAction(getApplicationContext(),
+                                im, dataKind);
                         action.setPresence(status.getPresence());
                         considerAdd(action, cache, isSuperPrimary);
                     }
@@ -454,26 +576,23 @@
             }
         }
 
-        mStopWatch.lap("e"); // Entities inflated
+        Trace.endSection();
+        Trace.beginSection("collapsing action list");
 
         // Collapse Action Lists (remove e.g. duplicate e-mail addresses from different sources)
         for (List<Action> actionChildren : mActions.values()) {
             Collapser.collapseList(actionChildren);
         }
 
-        mStopWatch.lap("c"); // List collapsed
+        Trace.endSection();
+        Trace.beginSection("sort mimetypes");
 
-        setHeaderNameText(R.id.name, data.getDisplayName());
-
-        // List of Entry that makes up the ExpandingEntryCardView
-        final List<Entry> entries = new ArrayList<>();
         // All the mime-types to add.
         final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
-        mSortedActionMimeTypes.clear();
         // First, add LEADING_MIMETYPES, which are most common.
         for (String mimeType : LEADING_MIMETYPES) {
             if (containedTypes.contains(mimeType)) {
-                mSortedActionMimeTypes.add(mimeType);
+                sortedActionMimeTypes.add(mimeType);
                 containedTypes.remove(mimeType);
                 entries.addAll(actionsToEntries(mActions.get(mimeType)));
             }
@@ -482,7 +601,7 @@
         // Add all the remaining ones that are not TRAILING
         for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
             if (!TRAILING_MIMETYPES.contains(mimeType)) {
-                mSortedActionMimeTypes.add(mimeType);
+                sortedActionMimeTypes.add(mimeType);
                 containedTypes.remove(mimeType);
                 entries.addAll(actionsToEntries(mActions.get(mimeType)));
             }
@@ -492,29 +611,12 @@
         for (String mimeType : TRAILING_MIMETYPES) {
             if (containedTypes.contains(mimeType)) {
                 containedTypes.remove(mimeType);
-                mSortedActionMimeTypes.add(mimeType);
+                sortedActionMimeTypes.add(mimeType);
                 entries.addAll(actionsToEntries(mActions.get(mimeType)));
             }
         }
 
-        Bundle smsExtraBundle = new Bundle();
-        smsExtraBundle.putStringArray(KEY_LOADER_EXTRA_SMS_PHONES,
-                phoneNumbers.toArray(new String[phoneNumbers.size()]));
-        getLoaderManager().initLoader(
-                LOADER_SMS_ID,
-                smsExtraBundle,
-                mLoaderInteractionsCallbacks);
-
-        if (entries.size() > 0) {
-            mCommunicationCard.initialize(entries,
-                    /* numInitialVisibleEntries = */ MIN_NUM_COMMUNICATION_ENTRIES_SHOWN,
-                    /* isExpanded = */ false,
-                    /* themeColor = */ 0);
-            mCommunicationCard.setVisibility(View.VISIBLE);
-        }
-
-        final boolean hasData = !mSortedActionMimeTypes.isEmpty();
-        mCommunicationCard.setVisibility(hasData ? View.VISIBLE: View.GONE);
+        Trace.endSection();
     }
 
     /**
@@ -530,10 +632,11 @@
         new AsyncTask<Void, Void, Integer>() {
             @Override
             protected Integer doInBackground(Void... params) {
-                final Bitmap bitmap;
                 if (imageViewDrawable instanceof BitmapDrawable) {
-                    bitmap = ((BitmapDrawable) imageViewDrawable).getBitmap();
-                } else if (imageViewDrawable instanceof LetterTileDrawable) {
+                    final Bitmap bitmap = ((BitmapDrawable) imageViewDrawable).getBitmap();
+                    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
@@ -541,21 +644,19 @@
                     // consistency.
                     final LetterTileDrawable tileDrawable = (LetterTileDrawable) imageViewDrawable;
                     final int PALETTE_BITMAP_SIZE = 1;
-                    bitmap = Bitmap.createBitmap(PALETTE_BITMAP_SIZE,
+                    final Bitmap bitmap = Bitmap.createBitmap(PALETTE_BITMAP_SIZE,
                             PALETTE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
-                    final Canvas canvas = new Canvas(bitmap);
-                    canvas.drawColor(tileDrawable.getColor());
-                } else {
-                    return 0;
+                    // 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;
+                    }
                 }
-                // Author of Palette recommends using 24 colors when analyzing profile photos.
-                final int NUMBER_OF_PALETTE_COLORS = 24;
-                final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
-                if (palette != null && palette.getVibrantColor() != null) {
-                    return palette.getVibrantColor().getRgb();
-                } else {
-                    return 0;
-                }
+                return 0;
             }
 
             @Override
@@ -564,11 +665,52 @@
                 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();
                 }
             }
         }.execute();
     }
 
+    private void updateStatusBarColor() {
+        if (mScroller == null) {
+            return;
+        }
+        final int desiredStatusBarColor;
+        // Only use a custom status bar color if QuickContacts touches the top of the viewport.
+        if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
+            desiredStatusBarColor = mStatusBarColor;
+        } else {
+            desiredStatusBarColor = Color.TRANSPARENT;
+        }
+        // Animate to the new color.
+        if (desiredStatusBarColor != getWindow().getStatusBarColor()) {
+            final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
+                    getWindow().getStatusBarColor(), desiredStatusBarColor);
+            animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
+            animation.setEvaluator(new ArgbEvaluator());
+            animation.start();
+        }
+    }
+
+    private int colorFromBitmap(Bitmap bitmap) {
+        // Author of Palette recommends using 24 colors when analyzing profile photos.
+        final int NUMBER_OF_PALETTE_COLORS = 24;
+        final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
+        if (palette != null && palette.getVibrantColor() != null) {
+            return palette.getVibrantColor().getRgb();
+        }
+        return 0;
+    }
+
     /**
      * Consider adding the given {@link Action}, which will only happen if
      * {@link PackageManager} finds an application to handle
@@ -613,8 +755,10 @@
         List<Entry> entries = new ArrayList<>();
         for (Action action :  actions) {
             entries.add(new Entry(ResolveCache.getInstance(this).getIcon(action),
-                    action.getMimeType(), action.getSubtitle().toString(),
-                    action.getBody().toString(), action.getIntent(), /* isEditable= */ false));
+                    action.getMimeType(),
+                    action.getSubtitle() == null ? null : action.getSubtitle().toString(),
+                    action.getBody() == null ? null : action.getBody().toString(),
+                    action.getIntent(), /* isEditable= */ false));
         }
         return entries;
     }
@@ -642,7 +786,8 @@
 
         @Override
         public void onLoadFinished(Loader<Contact> loader, Contact data) {
-            mStopWatch.lap("lf"); // onLoadFinished
+            Trace.beginSection("onLoadFinished()");
+
             if (isFinishing()) {
                 return;
             }
@@ -652,35 +797,19 @@
                 throw new IllegalStateException("Failed to load contact", data.getException());
             }
             if (data.isNotFound()) {
-                Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
-                Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
-                        Toast.LENGTH_LONG).show();
+                if (mHasAlreadyBeenOpened) {
+                    finish();
+                } else {
+                    Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
+                    Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
+                            Toast.LENGTH_LONG).show();
+                }
                 return;
             }
 
             bindContactData(data);
 
-            mStopWatch.lap("bd"); // bindData finished
-
-            if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing();
-            if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
-                Log.d(Constants.PERFORMANCE_TAG, "QuickContact shown");
-            }
-
-            if (mScroller != null) {
-                // Data bound and ready, pull curtain to show. Put this on the Handler to ensure
-                // that the layout passes are completed
-                mScroller.setVisibility(View.VISIBLE);
-                SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
-                        new Runnable() {
-                    @Override
-                    public void run() {
-                        runEntranceAnimation();
-                    }
-                });
-            }
-            mStopWatch.stopAndLog(TAG, 0);
-            mStopWatch = StopWatch.getNullStopWatch();
+            Trace.endSection();
         }
 
         @Override
@@ -704,6 +833,14 @@
         }
     }
 
+    @Override
+    public void finish() {
+        super.finish();
+
+        // override transitions to skip the standard window animations
+        overridePendingTransition(0, 0);
+    }
+
     private LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
             new LoaderCallbacks<List<ContactInteraction>>() {
 
@@ -769,4 +906,17 @@
             mRecentCard.setVisibility(View.VISIBLE);
         }
     }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        if (mEntriesAndActionsTask != null) {
+            // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
+            // results on the UI thread. In some circumstances Activities are killed without
+            // onStop() being called. This is not a problem, because in these circumstances
+            // the entire process will be killed.
+            mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
+        }
+    }
 }
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 9df3c61..00dc7b4 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -68,6 +68,10 @@
 
     public interface MultiShrinkScrollerListener {
         void onScrolledOffBottom();
+
+        void onEnterFullscreen();
+
+        void onExitFullscreen();
     }
 
     // Interpolator from android.support.v4.view.ViewPager
@@ -292,13 +296,22 @@
 
     @Override
     public void scrollTo(int x, int y) {
-        int delta = y - getScroll();
+        final int delta = y - getScroll();
+        boolean wasFullscreen = getScrollNeededToBeFullScreen() <= 0;
         if (delta > 0) {
             scrollUp(delta);
         } else {
             scrollDown(delta);
         }
         updatePhotoTint();
+        final boolean isFullscreen = getScrollNeededToBeFullScreen() <= 0;
+        if (mListener != null) {
+            if (wasFullscreen && !isFullscreen) {
+                 mListener.onExitFullscreen();
+            } else if (!wasFullscreen && isFullscreen) {
+                mListener.onEnterFullscreen();
+            }
+        }
     }
 
     @NeededForReflection
@@ -318,6 +331,15 @@
     }
 
     /**
+     * Amount of transparent space above the header/toolbar.
+     */
+    public int getScrollNeededToBeFullScreen() {
+        final LinearLayout.LayoutParams toolbarLayoutParams
+                = (LayoutParams) mToolbar.getLayoutParams();
+        return toolbarLayoutParams.topMargin;
+    }
+
+    /**
      * Return amount of scrolling needed in order for all the visible subviews to scroll off the
      * bottom.
      */