Merge "Remove "joined contact" message from the contact card"
diff --git a/res/layout/directory_header.xml b/res/layout/directory_header.xml
index 6043c7b..a1516ef 100644
--- a/res/layout/directory_header.xml
+++ b/res/layout/directory_header.xml
@@ -24,25 +24,12 @@
     android:paddingLeft="?attr/list_item_padding_left"
     android:paddingRight="?attr/list_item_padding_right">
     <TextView
-        android:id="@+id/display_name"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@+id/label"
-        android:layout_toLeftOf="@+id/count"
-        android:layout_centerVertical="true"
-        android:layout_marginLeft="8dip"
-        android:layout_marginRight="8dip"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorSecondary"
-        android:singleLine="true"
-        android:textStyle="bold"
-        android:textAllCaps="true" />
-    <TextView
         android:id="@+id/label"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
-        android:layout_alignBaseline="@id/display_name"
+        android:layout_centerVertical="true"
         android:layout_marginLeft="8dip"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textColor="?android:attr/textColorSecondary"
@@ -54,10 +41,24 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentRight="true"
-        android:layout_alignBaseline="@id/display_name"
+        android:layout_alignBaseline="@id/label"
         android:singleLine="true"
         android:textSize="12sp"
         android:textColor="@color/contact_count_text_color" />
+    <TextView
+        android:id="@+id/display_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toRightOf="@id/label"
+        android:layout_toLeftOf="@id/count"
+        android:layout_alignBaseline="@id/label"
+        android:layout_marginLeft="8dip"
+        android:layout_marginRight="8dip"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"
+        android:singleLine="true"
+        android:textStyle="bold"
+        android:textAllCaps="true" />
     <View
         android:id="@+id/contact_filter_header_bottom_divider"
         style="@style/SectionDivider"
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index 2c83f6b..8a2fd91 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -16,6 +16,7 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:id="@+id/search_on_action_bar"
+        android:title="@string/menu_search"
         android:icon="@android:drawable/ic_menu_search"
         android:showAsAction="always" />
 
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 4941121..d7881a2 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -139,6 +139,7 @@
         PhoneLookup.NUMBER,
         PhoneLookup.NORMALIZED_NUMBER,
         PhoneLookup.PHOTO_URI,
+        PhoneLookup.LOOKUP_KEY,
     };
     static final int COLUMN_INDEX_ID = 0;
     static final int COLUMN_INDEX_NAME = 1;
@@ -147,6 +148,7 @@
     static final int COLUMN_INDEX_NUMBER = 4;
     static final int COLUMN_INDEX_NORMALIZED_NUMBER = 5;
     static final int COLUMN_INDEX_PHOTO_URI = 6;
+    static final int COLUMN_INDEX_LOOKUP_KEY = 7;
 
     private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
         @Override
@@ -328,7 +330,7 @@
                 // first.
                 PhoneCallDetails firstDetails = details[0];
                 mNumber = firstDetails.number.toString();
-                final long personId = firstDetails.personId;
+                final Uri contactUri = firstDetails.contactUri;
                 final Uri photoUri = firstDetails.photoUri;
 
                 // Set the details header, based on the first phone call.
@@ -353,9 +355,8 @@
                     nameOrNumber = firstDetails.number;
                 }
 
-                if (firstDetails.personId != -1) {
-                    Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, personId);
-                    mainActionIntent = new Intent(Intent.ACTION_VIEW, personUri);
+                if (contactUri != null) {
+                    mainActionIntent = new Intent(Intent.ACTION_VIEW, contactUri);
                     mainActionIcon = R.drawable.ic_contacts_holo_dark;
                     mainActionDescription =
                             getString(R.string.description_view_contact, nameOrNumber);
@@ -498,8 +499,8 @@
             CharSequence nameText = "";
             int numberType = 0;
             CharSequence numberLabel = "";
-            long personId = -1L;
             Uri photoUri = null;
+            Uri contactUri = null;
             // If this is not a regular number, there is no point in looking it up in the contacts.
             if (!mPhoneNumberHelper.canPlaceCallsTo(number)) {
                 numberText = mPhoneNumberHelper.getDisplayNumber(number, null);
@@ -511,7 +512,6 @@
                 String candidateNumberText = number;
                 try {
                     if (phonesCursor != null && phonesCursor.moveToFirst()) {
-                        personId = phonesCursor.getLong(COLUMN_INDEX_ID);
                         nameText = phonesCursor.getString(COLUMN_INDEX_NAME);
                         String photoUriString = phonesCursor.getString(COLUMN_INDEX_PHOTO_URI);
                         photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
@@ -521,6 +521,11 @@
                                 countryIso);
                         numberType = phonesCursor.getInt(COLUMN_INDEX_TYPE);
                         numberLabel = phonesCursor.getString(COLUMN_INDEX_LABEL);
+                        long personId = phonesCursor.getLong(COLUMN_INDEX_ID);
+                        if (personId > 0) {
+                            contactUri = Contacts.getLookupUri(personId,
+                                    phonesCursor.getString(COLUMN_INDEX_LOOKUP_KEY));
+                        }
                     } else {
                         // We could not find this contact in the contacts, just format the phone
                         // number as best as we can. All the other fields will have their default
@@ -535,7 +540,7 @@
             }
             return new PhoneCallDetails(number, numberText, countryIso, geocode,
                     new int[]{ callType }, date, duration,
-                    nameText, numberType, numberLabel, personId, photoUri);
+                    nameText, numberType, numberLabel, contactUri, photoUri);
         } finally {
             if (callCursor != null) {
                 callCursor.close();
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 1107530..a28b1db 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -93,14 +93,7 @@
         /**
          * Singleton instance that represents "No Contact Found"
          */
-        public static final Result NOT_FOUND = new Result();
-
-        /**
-         * Singleton instance that represents an error, e.g. because of an invalid Uri
-         * TODO: We should come up with something nicer here. Maybe use an Either type so
-         * that we can capture the Exception?
-         */
-        public static final Result ERROR = new Result();
+        public static final Result NOT_FOUND = new Result((Exception) null);
 
         private final Uri mLookupUri;
         private final Uri mUri;
@@ -135,11 +128,12 @@
         private final String mCustomRingtone;
         private final boolean mIsUserProfile;
 
+        private final Exception mException;
+
         /**
-         * Constructor for case "no contact found". This must only be used for the
-         * final {@link Result#NOT_FOUND} singleton
+         * Constructor for special results, namely "no contact found" and "error".
          */
-        private Result() {
+        private Result(Exception exception) {
             mLookupUri = null;
             mUri = null;
             mDirectoryId = -1;
@@ -161,7 +155,11 @@
             mSendToVoicemail = false;
             mCustomRingtone = null;
             mIsUserProfile = false;
+            mException = exception;
+        }
 
+        private static Result forError(Exception exception) {
+            return new Result(exception);
         }
 
         /**
@@ -172,6 +170,7 @@
                 String displayName, String altDisplayName, String phoneticName, boolean starred,
                 Integer presence, boolean sendToVoicemail, String customRingtone,
                 boolean isUserProfile) {
+            mException = null;
             mLookupUri = lookupUri;
             mUri = uri;
             mDirectoryId = directoryId;
@@ -196,6 +195,7 @@
         }
 
         private Result(Result from) {
+            mException = from.mException;
             mLookupUri = from.mLookupUri;
             mUri = from.mUri;
             mDirectoryId = from.mDirectoryId;
@@ -267,6 +267,18 @@
             return mId;
         }
 
+        /**
+         * @return true when an exception happened during loading, in which case
+         *     {@link #getException} returns the actual exception object.
+         */
+        public boolean isError() {
+            return mException != null;
+        }
+
+        public Exception getException() {
+            return mException;
+        }
+
         public long getNameRawContactId() {
             return mNameRawContactId;
         }
@@ -631,7 +643,7 @@
                 return result;
             } catch (Exception e) {
                 Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
-                return Result.ERROR;
+                return Result.forError(e);
             }
         }
 
@@ -1087,7 +1099,7 @@
 
             mContact = result;
 
-            if (result != Result.ERROR && result != Result.NOT_FOUND) {
+            if (!result.isError() && result != Result.NOT_FOUND) {
                 mLookupUri = result.getLookupUri();
 
                 if (!result.isDirectoryEntry()) {
diff --git a/src/com/android/contacts/PhoneCallDetails.java b/src/com/android/contacts/PhoneCallDetails.java
index 5718091..78ac9b3 100644
--- a/src/com/android/contacts/PhoneCallDetails.java
+++ b/src/com/android/contacts/PhoneCallDetails.java
@@ -48,10 +48,10 @@
     public final int numberType;
     /** The custom label associated with the phone number in the contact, or the empty string. */
     public final CharSequence numberLabel;
-    /** The id of the contact associated with this phone call. */
-    public final long personId;
+    /** The URI of the contact associated with this phone call. */
+    public final Uri contactUri;
     /**
-     * The photo uri of the picture of the contact that is associated with this phone call or
+     * The photo URI of the picture of the contact that is associated with this phone call or
      * null if there is none.
      */
     public final Uri photoUri;
@@ -60,13 +60,13 @@
     public PhoneCallDetails(CharSequence number, CharSequence formattedNumber,
             String countryIso, String geocode, int[] callTypes, long date, long duration) {
         this(number, formattedNumber, countryIso, geocode, callTypes, date, duration, "", 0, "",
-                -1L, null);
+                null, null);
     }
 
     /** Create the details for a call with a number associated with a contact. */
     public PhoneCallDetails(CharSequence number, CharSequence formattedNumber,
             String countryIso, String geocode, int[] callTypes, long date, long duration,
-            CharSequence name, int numberType, CharSequence numberLabel, long personId,
+            CharSequence name, int numberType, CharSequence numberLabel, Uri contactUri,
             Uri photoUri) {
         this.number = number;
         this.formattedNumber = formattedNumber;
@@ -78,7 +78,7 @@
         this.name = name;
         this.numberType = numberType;
         this.numberLabel = numberLabel;
-        this.personId = personId;
+        this.contactUri = contactUri;
         this.photoUri = photoUri;
     }
 }
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 28e2e90..bed721a 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -313,8 +313,11 @@
                 // Note the Data.CONTACT_ID column here is
                 // equivalent to the PERSON_ID_COLUMN_INDEX column
                 // we use with "phonesCursor" below.
-                info.personId = dataTableCursor.getLong(
+                long contactId = dataTableCursor.getLong(
                         dataTableCursor.getColumnIndex(Data.CONTACT_ID));
+                String lookupKey = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
+                info.contactUri = Contacts.getLookupUri(contactId, lookupKey);
                 info.name = dataTableCursor.getString(
                         dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
                 // "type" and "label" are currently unused for SIP addresses
@@ -331,8 +334,6 @@
                 info.thumbnailUri = thumbnailUriString == null
                         ? null
                         : Uri.parse(thumbnailUriString);
-                info.lookupKey = dataTableCursor.getString(
-                        dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
             } else {
                 info = ContactInfo.EMPTY;
             }
@@ -366,7 +367,9 @@
         if (phonesCursor != null) {
             if (phonesCursor.moveToFirst()) {
                 info = new ContactInfo();
-                info.personId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+                long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+                String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
+                info.contactUri = Contacts.getLookupUri(contactId, lookupKey);
                 info.name = phonesCursor.getString(PhoneQuery.NAME);
                 info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
                 info.label = phonesCursor.getString(PhoneQuery.LABEL);
@@ -379,7 +382,6 @@
                 info.thumbnailUri = thumbnailUriString == null
                         ? null
                         : Uri.parse(thumbnailUriString);
-                info.lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
             } else {
                 info = ContactInfo.EMPTY;
             }
@@ -629,12 +631,11 @@
             info = cachedContactInfo;
         }
 
-        final long personId = info.personId;
+        final Uri contactUri = info.contactUri;
         final String name = info.name;
         final int ntype = info.type;
         final String label = info.label;
         final Uri thumbnailUri = info.thumbnailUri;
-        final String lookupKey = info.lookupKey;
         final int[] callTypes = getCallTypes(c, count);
         final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
         final PhoneCallDetails details;
@@ -643,14 +644,14 @@
                     callTypes, date, duration);
         } else {
             details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
-                    callTypes, date, duration, name, ntype, label, personId, thumbnailUri);
+                    callTypes, date, duration, name, ntype, label, contactUri , thumbnailUri);
         }
 
         final boolean isNew = CallLogQuery.isNewSection(c);
         // New items also use the highlighted version of the text.
         final boolean isHighlighted = isNew;
         mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
-        setPhoto(views, thumbnailUri, personId, lookupKey);
+        setPhoto(views, thumbnailUri, contactUri);
 
         // Listen for the first draw
         if (mPreDrawListener == null) {
@@ -709,7 +710,7 @@
     /** Returns the contact information as stored in the call log. */
     private ContactInfo getContactInfoFromCallLog(Cursor c) {
         ContactInfo info = new ContactInfo();
-        info.personId = -1;
+        info.contactUri = null;
         info.name = c.getString(CallLogQuery.CACHED_NAME);
         info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
         info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
@@ -718,7 +719,6 @@
         info.formattedNumber = info.number;
         info.normalizedNumber = info.number;
         info.thumbnailUri = null;
-        info.lookupKey = null;
         return info;
     }
 
@@ -740,10 +740,8 @@
         return callTypes;
     }
 
-    private void setPhoto(CallLogListItemViews views, Uri thumbnailUri, long contactId,
-            String lookupKey) {
-        views.quickContactView.assignContactUri(contactId == -1 ? null :
-                Contacts.getLookupUri(contactId, lookupKey));
+    private void setPhoto(CallLogListItemViews views, Uri thumbnailUri, Uri contactUri) {
+        views.quickContactView.assignContactUri(contactUri);
         mContactPhotoManager.loadPhoto(views.quickContactView, thumbnailUri);
     }
 
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 215fd7b..83f44d0 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -18,7 +18,6 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.activities.DialtactsActivity;
 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
 import com.android.contacts.test.NeededForTesting;
 import com.android.contacts.voicemail.VoicemailStatusHelper;
diff --git a/src/com/android/contacts/calllog/ContactInfo.java b/src/com/android/contacts/calllog/ContactInfo.java
index 1f106e4..58c5f6a 100644
--- a/src/com/android/contacts/calllog/ContactInfo.java
+++ b/src/com/android/contacts/calllog/ContactInfo.java
@@ -16,6 +16,8 @@
 
 package com.android.contacts.calllog;
 
+import com.android.contacts.util.UriUtils;
+
 import android.net.Uri;
 import android.text.TextUtils;
 
@@ -23,7 +25,7 @@
  * Information for a contact as needed by the Call Log.
  */
 public final class ContactInfo {
-    public long personId = -1;
+    public Uri contactUri;
     public String name;
     public int type;
     public String label;
@@ -31,18 +33,17 @@
     public String formattedNumber;
     public String normalizedNumber;
     public Uri thumbnailUri;
-    public String lookupKey;
 
     public static ContactInfo EMPTY = new ContactInfo();
 
     @Override
     public int hashCode() {
-        // Uses only name and personId to determine hashcode.
+        // Uses only name and contactUri to determine hashcode.
         // This should be sufficient to have a reasonable distribution of hash codes.
-        // Moreover, there should be no two people with the same personId.
+        // Moreover, there should be no two people with the same contactUri.
         final int prime = 31;
         int result = 1;
-        result = prime * result + (int) (personId ^ (personId >>> 32));
+        result = prime * result + ((contactUri == null) ? 0 : contactUri.hashCode());
         result = prime * result + ((name == null) ? 0 : name.hashCode());
         return result;
     }
@@ -53,21 +54,14 @@
         if (obj == null) return false;
         if (getClass() != obj.getClass()) return false;
         ContactInfo other = (ContactInfo) obj;
-        if (personId != other.personId) return false;
+        if (!UriUtils.areEqual(contactUri, other.contactUri)) return false;
         if (!TextUtils.equals(name, other.name)) return false;
         if (type != other.type) return false;
         if (!TextUtils.equals(label, other.label)) return false;
         if (!TextUtils.equals(number, other.number)) return false;
         // Ignore formatted number.
         if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
-        if (!uriEquals(thumbnailUri, other.thumbnailUri)) return false;
-        if (!TextUtils.equals(lookupKey, other.lookupKey)) return false;
+        if (!UriUtils.areEqual(thumbnailUri, other.thumbnailUri)) return false;
         return true;
     }
-
-    private static boolean uriEquals(Uri thumbnailUri1, Uri thumbnailUri2) {
-        if (thumbnailUri1 == thumbnailUri2) return true;
-        if (thumbnailUri1 == null) return false;
-        return thumbnailUri1.equals(thumbnailUri2);
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index c82d735..f3c6158 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -187,13 +187,10 @@
                 return;
             }
 
-            if (data == ContactLoader.Result.ERROR) {
+            if (data.isError()) {
                 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
                 // should log the actual exception.
-                // TODO: Make the {@link ContactLoader.Result} pass the exception so we can include
-                // the original stack trace when this error is thrown.
-                throw new IllegalStateException("The result of the ContactLoader is "
-                        + "ContactLoader.Result.ERROR");
+                throw new IllegalStateException("Failed to load contact", data.getException());
             } else if (data == ContactLoader.Result.NOT_FOUND) {
                 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
                 mContactData = null;
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index eef5a61..e3e8875 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -96,6 +96,10 @@
         public void onSearchButtonPressed();
     }
 
+    /**
+     * View (usually FrameLayout) containing mDigits field. This can be null, in which mDigits
+     * isn't enclosed by the container.
+     */
     private View mDigitsContainer;
     private EditText mDigits;
 
@@ -931,7 +935,12 @@
 
         if (enabled) {
             // Log.i(TAG, "Showing dialpad chooser!");
-            mDigitsContainer.setVisibility(View.GONE);
+            if (mDigitsContainer != null) {
+                mDigitsContainer.setVisibility(View.GONE);
+            } else {
+                // mDigits is not enclosed by the container. Make the digits field itself gone.
+                mDigits.setVisibility(View.GONE);
+            }
             if (mDialpad != null) mDialpad.setVisibility(View.GONE);
             mAdditionalButtonsRow.setVisibility(View.GONE);
             mDialpadChooser.setVisibility(View.VISIBLE);
@@ -944,7 +953,11 @@
             mDialpadChooser.setAdapter(mDialpadChooserAdapter);
         } else {
             // Log.i(TAG, "Displaying normal Dialer UI.");
-            mDigitsContainer.setVisibility(View.VISIBLE);
+            if (mDigitsContainer != null) {
+                mDigitsContainer.setVisibility(View.VISIBLE);
+            } else {
+                mDigits.setVisibility(View.VISIBLE);
+            }
             if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
             mAdditionalButtonsRow.setVisibility(View.VISIBLE);
             mDialpadChooser.setVisibility(View.GONE);
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 5a96e7f..046b1ed 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -1610,7 +1610,7 @@
         public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
             final long loaderCurrentTime = SystemClock.elapsedRealtime();
             Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
-            if (data == ContactLoader.Result.NOT_FOUND || data == ContactLoader.Result.ERROR) {
+            if (data == ContactLoader.Result.NOT_FOUND || data.isError()) {
                 // Item has been deleted
                 Log.i(TAG, "No contact found. Closing activity");
                 if (mListener != null) mListener.onContactNotFound();
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 49835ef..2b9c594 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -101,6 +101,15 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mSelectedGroupUri = savedInstanceState.getParcelable(EXTRA_KEY_GROUP_URI);
+            if (mSelectedGroupUri != null) {
+                // The selection may be out of screen, if rotated from portrait to landscape,
+                // so ensure it's visible.
+                mSelectionToScreenRequested = true;
+            }
+        }
+
         mRootView = inflater.inflate(R.layout.group_browse_list_fragment, null);
         mEmptyView = (TextView)mRootView.findViewById(R.id.empty);
 
@@ -137,12 +146,6 @@
         });
         setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
 
-        if (savedInstanceState != null) {
-            String groupUriString = savedInstanceState.getString(EXTRA_KEY_GROUP_URI);
-            if (groupUriString != null) {
-                mSelectedGroupUri = Uri.parse(groupUriString);
-            }
-        }
         return mRootView;
     }
 
@@ -300,12 +303,7 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        if (mSelectedGroupUri != null) {
-            String uriString = mSelectedGroupUri.toString();
-            if (!TextUtils.isEmpty(uriString)) {
-                outState.putString(EXTRA_KEY_GROUP_URI, uriString);
-            }
-        }
+        outState.putParcelable(EXTRA_KEY_GROUP_URI, mSelectedGroupUri);
     }
 
     public void setAddAccountsVisibility(boolean visible) {
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index b29a9cd..cd6a494 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -133,8 +133,7 @@
         final RemoteViews views = new RemoteViews(context.getPackageName(),
                 R.layout.social_widget);
 
-        if (contactData == ContactLoader.Result.ERROR ||
-                contactData == ContactLoader.Result.NOT_FOUND) {
+        if (contactData.isError() || contactData == ContactLoader.Result.NOT_FOUND) {
             setDisplayNameAndSnippet(context, views,
                     context.getString(R.string.invalidContactMessage), null, null, null);
             setPhoto(views, ContactBadgeUtil.loadPlaceholderPhoto(context));
diff --git a/src/com/android/contacts/util/UriUtils.java b/src/com/android/contacts/util/UriUtils.java
new file mode 100644
index 0000000..28874f2
--- /dev/null
+++ b/src/com/android/contacts/util/UriUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.util;
+
+import android.net.Uri;
+
+/**
+ * Utility methods for dealing with URIs.
+ */
+public class UriUtils {
+    /** Static helper, not instantiable. */
+    private UriUtils() {}
+
+    /** Checks whether two URI are equal, taking care of the case where either is null. */
+    public static boolean areEqual(Uri uri1, Uri uri2) {
+        if (uri1 == null && uri2 == null) {
+            return true;
+        }
+        if (uri1 == null || uri2 == null) {
+            return false;
+        }
+        return uri1.equals(uri2);
+    }
+}
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/ContactLoaderTest.java
index 3560ce1..5d44cf1 100644
--- a/tests/src/com/android/contacts/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactLoaderTest.java
@@ -76,17 +76,17 @@
 
     public void testNullUri() {
         ContactLoader.Result result = assertLoadContact(null);
-        assertEquals(ContactLoader.Result.ERROR, result);
+        assertTrue(result.isError());
     }
 
     public void testEmptyUri() {
         ContactLoader.Result result = assertLoadContact(Uri.EMPTY);
-        assertEquals(ContactLoader.Result.ERROR, result);
+        assertTrue(result.isError());
     }
 
     public void testInvalidUri() {
         ContactLoader.Result result = assertLoadContact(Uri.parse("content://wtf"));
-        assertEquals(ContactLoader.Result.ERROR, result);
+        assertTrue(result.isError());
     }
 
     public void testLoadContactWithContactIdUri() {
diff --git a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
index bb93b98..7830a01 100644
--- a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
+++ b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
@@ -298,6 +298,6 @@
         mHelper.setPhoneCallName(mNameView,
                 new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
                         TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION,
-                        name, 0, "", 1, null));
+                        name, 0, "", null, null));
     }
 }
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 5926fd9..0f43313 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -66,12 +66,10 @@
     private static final int RAND_DURATION = -1;
     private static final long NOW = -1L;
 
-    /** A test value for the person id of a contact. */
-    private static final long TEST_PERSON_ID = 1;
+    /** A test value for the URI of a contact. */
+    private static final Uri TEST_CONTACT_URI = Uri.parse("content://contacts/2");
     /** A test value for the photo uri of a contact. */
     private static final Uri TEST_THUMBNAIL_URI = Uri.parse("something://picture/2");
-    /** A test value for the lookup key for contacts. */
-    private static final String TEST_LOOKUP_KEY = "contact_id";
     /** A test value for the country ISO of the phone number in the call log. */
     private static final String TEST_COUNTRY_ISO = "US";
     /** A phone number to be used in tests. */
@@ -454,7 +452,7 @@
             String cachedName, int cachedNumberType, String cachedNumberLabel) {
         insert(number, date, duration, type);
         ContactInfo contactInfo = new ContactInfo();
-        contactInfo.personId = TEST_PERSON_ID;
+        contactInfo.contactUri = TEST_CONTACT_URI;
         contactInfo.name = cachedName;
         contactInfo.type = cachedNumberType;
         contactInfo.label = cachedNumberLabel;
@@ -465,7 +463,6 @@
         contactInfo.formattedNumber = formattedNumber;
         contactInfo.normalizedNumber = number;
         contactInfo.thumbnailUri = TEST_THUMBNAIL_URI;
-        contactInfo.lookupKey = TEST_LOOKUP_KEY;
         mAdapter.injectContactInfoForTest(number, contactInfo);
     }