Small modifications to speedup ViewContactActivity
Bug:2546767
 - Use query parameter instead of sql string concatenation
 - Try to use the ContactID that is passed in the Intent; fallback to lookup key if it is wrong (saves up to 2 queries)
 - Initialize the newEntities list directly with the correct length

Change-Id: Iab271f9b30a10fb23a50cd9feba831c72cfd994f
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index ec365dc..2824ab9 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -15,7 +15,6 @@
  */
 
 package com.android.contacts;
-
 import com.android.contacts.Collapser.Collapsible;
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
@@ -92,6 +91,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Displays the details of a specific contact.
@@ -333,7 +333,7 @@
         (new AsyncTask<Void, Void, ArrayList<Entity>>() {
             @Override
             protected ArrayList<Entity> doInBackground(Void... params) {
-                ArrayList<Entity> newEntities = Lists.newArrayList();
+                ArrayList<Entity> newEntities = new ArrayList<Entity>(cursor.getCount());
                 EntityIterator iterator = RawContacts.newEntityIterator(cursor);
                 try {
                     while (iterator.hasNext()) {
@@ -394,19 +394,65 @@
         mHasStatuses = true;
     }
 
+    private static Cursor setupContactCursor(ContentResolver resolver, Uri lookupUri) {
+        if (lookupUri == null) {
+            return null;
+        }
+        final List<String> segments = lookupUri.getPathSegments();
+        if (segments.size() != 4) {
+            return null;
+        }
+
+        // Contains an Id.
+        final long uriContactId = Long.parseLong(segments.get(3));
+        final String uriLookupKey = segments.get(2);
+        final Uri dataUri = Uri.withAppendedPath(
+                ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId),
+                Contacts.Data.CONTENT_DIRECTORY);
+
+        // This cursor has several purposes:
+        // - Fetch NAME_RAW_CONTACT_ID and DISPLAY_NAME_SOURCE
+        // - Fetch the lookup-key to ensure we are looking at the right record
+        // - Watcher for change events
+        Cursor cursor = resolver.query(dataUri,
+                new String[] {
+                    Contacts.NAME_RAW_CONTACT_ID,
+                    Contacts.DISPLAY_NAME_SOURCE,
+                    Contacts.LOOKUP_KEY
+                }, null, null, null);
+
+        if (cursor.moveToFirst()) {
+            String lookupKey =
+                    cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
+            if (!lookupKey.equals(uriLookupKey)) {
+                // ID and lookup key do not match
+                cursor.close();
+                return null;
+            }
+            return cursor;
+        } else {
+            cursor.close();
+            return null;
+        }
+    }
+
     private synchronized void startEntityQuery() {
         closeCursor();
 
-        Uri uri = null;
-        if (mLookupUri != null) {
+        // Interprete mLookupUri
+        mCursor = setupContactCursor(mResolver, mLookupUri);
+
+        // If mCursor is null now we did not succeed in using the Uri's Id (or it didn't contain
+        // a Uri). Instead we now have to use the lookup key to find the record
+        if (mCursor == null) {
             mLookupUri = Contacts.getLookupUri(getContentResolver(), mLookupUri);
-            if (mLookupUri != null) {
-                uri = Contacts.lookupContact(getContentResolver(), mLookupUri);
-            }
+            mCursor = setupContactCursor(mResolver, mLookupUri);
         }
 
-        if (uri == null) {
-
+        // If mCursor is still null, we were unsuccessful in finding the record
+        if (mCursor == null) {
+            mNameRawContactId = -1;
+            mDisplayNameSource = DisplayNameSources.UNDEFINED;
             // TODO either figure out a way to prevent a flash of black background or
             // use some other UI than a toast
             Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_SHORT).show();
@@ -415,34 +461,26 @@
             return;
         }
 
-        final Uri dataUri = Uri.withAppendedPath(uri, Contacts.Data.CONTENT_DIRECTORY);
+        final long contactId = ContentUris.parseId(mLookupUri);
 
-        // This cursor has two purposes:
-        // - Fetch NAME_RAW_CONTACT_ID and DISPLAY_NAME_SOURCE
-        // - Watcher for change events
-        mCursor = mResolver.query(dataUri,
-                new String[] { Contacts.NAME_RAW_CONTACT_ID, Contacts.DISPLAY_NAME_SOURCE },
-                null, null, null);
-
-        if (mCursor.moveToFirst()) {
-            mNameRawContactId =
+        mNameRawContactId =
                 mCursor.getLong(mCursor.getColumnIndex(Contacts.NAME_RAW_CONTACT_ID));
-            mDisplayNameSource =
+        mDisplayNameSource =
                 mCursor.getInt(mCursor.getColumnIndex(Contacts.DISPLAY_NAME_SOURCE));
-        } else {
-            mNameRawContactId = -1;
-            mDisplayNameSource = DisplayNameSources.UNDEFINED;
-        }
 
         mCursor.registerContentObserver(mObserver);
-        final long contactId = ContentUris.parseId(uri);
 
         // Clear flags and start queries to data and status
         mHasEntities = false;
         mHasStatuses = false;
 
         mHandler.startQuery(TOKEN_ENTITIES, null, RawContactsEntity.CONTENT_URI, null,
-                RawContacts.CONTACT_ID + "=" + contactId, null, null);
+                RawContacts.CONTACT_ID + "=?", new String[] {
+                    String.valueOf(contactId)
+                }, null);
+        final Uri dataUri = Uri.withAppendedPath(
+                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
+                Contacts.Data.CONTENT_DIRECTORY);
         mHandler.startQuery(TOKEN_STATUSES, null, dataUri, StatusQuery.PROJECTION,
                         StatusUpdates.PRESENCE + " IS NOT NULL OR " + StatusUpdates.STATUS
                                 + " IS NOT NULL", null, null);