Merge "Implement editor springboard activity" into ub-contactsdialer-h-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4e00b5f..6144e55 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -357,13 +357,6 @@
             android:windowSoftInputMode="stateHidden|adjustResize">
 
             <intent-filter>
-                <action android:name="android.intent.action.EDIT" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
-                <data android:mimeType="vnd.android.cursor.item/contact" />
-                <data android:mimeType="vnd.android.cursor.item/raw_contact" />
-            </intent-filter>
-            <intent-filter>
                 <action android:name="android.intent.action.INSERT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.cursor.dir/person" />
@@ -372,6 +365,22 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name=".activities.ContactEditorSpringBoardActivity"
+            android:noHistory="true"
+            android:theme="@style/TransparentThemeAppCompat">
+
+            <intent-filter>
+                <action android:name="android.intent.action.EDIT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.item/person" />
+                <data android:mimeType="vnd.android.cursor.item/contact" />
+                <data android:mimeType="vnd.android.cursor.item/raw_contact" />
+            </intent-filter>
+
+        </activity>
+
+
         <activity android:name=".common.test.FragmentTestActivity">
             <intent-filter>
                 <category android:name="android.intent.category.TEST" />
diff --git a/res/layout/raw_contact_list_item.xml b/res/layout/raw_contact_list_item.xml
new file mode 100644
index 0000000..ec5de0c
--- /dev/null
+++ b/res/layout/raw_contact_list_item.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="24dp"
+    android:paddingRight="24dp"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
+    android:background="?android:attr/selectableItemBackground"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/photo"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:layout_marginEnd="16dp"
+        android:scaleType="fitCenter"/>
+
+    <LinearLayout
+        android:id="@+id/text_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/display_name"
+            android:textSize="16sp"
+            android:textColor="@color/quantum_black_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"/>
+
+        <LinearLayout
+            android:id="@+id/account_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical">
+
+            <ImageView
+                android:id="@+id/account_icon"
+                android:layout_width="14dp"
+                android:layout_height="14dp"
+                android:layout_gravity="center_vertical"
+                android:layout_marginEnd="4dp"
+                android:scaleType="fitCenter"/>
+
+            <TextView
+                android:id="@+id/account_name"
+                android:textSize="13sp"
+                android:textColor="@color/quantum_black_secondary_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"/>
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index d62f0f8..98c522d 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <resources>
+    <!-- 87% black -->
+    <color name="quantum_black_text">#dd000000</color>
+
     <!-- 54% black -->
     <color name="quantum_black_secondary_text">#89000000</color>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 566bd75..454777e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -693,6 +693,12 @@
          at a pre-determined text size. [CHAR LIMIT=20] -->
     <string name="contact_editor_title_existing_contact">Edit contact</string>
 
+    <!-- Dialog title when the user is selecting a raw contact to edit.  [CHAR LIMIT=128] -->
+    <string name="contact_editor_pick_raw_contact_dialog_title">Choose linked contact</string>
+
+    <!-- Text next to a contact's name that lets the user know it is a read only contact.  [CHAR LIMIT=128] -->
+    <string name="contact_editor_pick_raw_contact_read_only"><xliff:g id="display_name">%s</xliff:g> (read only)</string>
+
     <!-- Button label to prompt the user to add an account (when there are 0 existing accounts on the device) [CHAR LIMIT=30] -->
     <string name="add_account">Add account</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index e7b6582..d51392a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -498,4 +498,14 @@
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowElevation">0dp</item>
     </style>
+
+    <!-- Transparent/blank activity -->
+    <style name="TransparentThemeAppCompat" parent="@style/PeopleActivityTheme">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
 </resources>
diff --git a/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java b/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
new file mode 100644
index 0000000..f32ee5b
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
@@ -0,0 +1,182 @@
+package com.android.contacts.activities;
+
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.app.LoaderManager;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.widget.Toast;
+
+import com.android.contacts.AppCompatContactsActivity;
+import com.android.contacts.R;
+import com.android.contacts.common.activity.RequestPermissionsActivity;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import com.android.contacts.editor.ContactEditorFragment;
+import com.android.contacts.editor.EditorIntents;
+import com.android.contacts.editor.PickRawContactDialogFragment;
+import com.android.contacts.editor.PickRawContactLoader;
+
+/**
+ * Transparent springboard activity that hosts a dialog to select a raw contact to edit.
+ * This activity has noHistory set to true, and all intents coming out from it have
+ * {@code FLAG_ACTIVITY_FORWARD_RESULT} set.
+ */
+public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity  {
+    private static final String TAG = "EditorSpringBoard";
+    private static final String TAG_RAW_CONTACTS_DIALOG = "rawContactsDialog";
+    private static final int LOADER_RAW_CONTACTS = 1;
+
+    private Uri mUri;
+    private Cursor mCursor;
+    private MaterialPalette mMaterialPalette;
+
+    /**
+     * The contact data loader listener.
+     */
+    protected final LoaderManager.LoaderCallbacks<Cursor> mRawContactLoaderListener =
+            new LoaderManager.LoaderCallbacks<Cursor>() {
+
+                @Override
+                public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+                    return new PickRawContactLoader(ContactEditorSpringBoardActivity.this, mUri);
+                }
+
+                @Override
+                public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+                    if (cursor == null) {
+                        Toast.makeText(ContactEditorSpringBoardActivity.this,
+                                R.string.editor_failed_to_load, Toast.LENGTH_SHORT).show();
+                        finish();
+                        return;
+                    }
+                    mCursor = cursor;
+                    if (mCursor.getCount() == 1) {
+                        loadEditor();
+                    } else {
+                        showDialog();
+                    }
+                }
+
+                @Override
+                public void onLoaderReset(Loader<Cursor> loader) {
+                    mCursor = null;
+                }
+            };
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (RequestPermissionsActivity.startPermissionActivity(this)) {
+            return;
+        }
+
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+
+        if (!Intent.ACTION_EDIT.equals(action)) {
+            finish();
+            return;
+        }
+        // Just for shorter variable names.
+        final String primary = ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR;
+        final String secondary =
+                ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR;
+        if (intent.hasExtra(primary) && intent.hasExtra(secondary)) {
+            mMaterialPalette = new MaterialPalette(intent.getIntExtra(primary, -1),
+                    intent.getIntExtra(secondary, -1));
+        }
+
+        mUri = intent.getData();
+        final String authority = mUri.getAuthority();
+        final String type = getContentResolver().getType(mUri);
+        // Go straight to editor if we're passed a raw contact Uri.
+        if (ContactsContract.AUTHORITY.equals(authority) &&
+                RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
+            final long rawContactId = ContentUris.parseId(mUri);
+            final Intent editorIntent = getIntentForRawContact(rawContactId);
+            ImplicitIntentsUtil.startActivityInApp(this, editorIntent);
+        } else {
+            getLoaderManager().initLoader(LOADER_RAW_CONTACTS, null, mRawContactLoaderListener);
+        }
+    }
+
+    /**
+     * Start the dialog to pick the raw contact to edit.
+     */
+    private void showDialog() {
+        final FragmentManager fm = getFragmentManager();
+        final PickRawContactDialogFragment oldFragment = (PickRawContactDialogFragment)
+                fm.findFragmentByTag(TAG_RAW_CONTACTS_DIALOG);
+        final FragmentTransaction ft = fm.beginTransaction();
+        if (oldFragment != null) {
+            ft.remove(oldFragment);
+        }
+        final PickRawContactDialogFragment newFragment =
+                PickRawContactDialogFragment.getInstance(mUri, mCursor, mMaterialPalette);
+        ft.add(newFragment, TAG_RAW_CONTACTS_DIALOG);
+        // commitAllowingStateLoss is safe in this activity because the fragment entirely depends
+        // on the result of the loader. Even if we lose the fragment because the activity was
+        // in the background, when it comes back onLoadFinished will be called again which will
+        // have all the state the picker needs. This situation should be very rare, since the load
+        // should be quick.
+        ft.commitAllowingStateLoss();
+    }
+
+    /**
+     * Starts the editor for the first (only) raw contact in the cursor.
+     */
+    private void loadEditor() {
+        final Intent intent;
+        if (isSingleWritableAccount()) {
+            mCursor.moveToFirst();
+            final long rawContactId = mCursor.getLong(PickRawContactLoader.RAW_CONTACT_ID);
+            intent = getIntentForRawContact(rawContactId);
+
+        } else {
+            // If it's a single read-only raw contact, we'll want to let the editor create
+            // the writable raw contact for it.
+            intent = EditorIntents.createEditContactIntent(this, mUri, mMaterialPalette, -1);
+            intent.setClass(this, ContactEditorActivity.class);
+        }
+        ImplicitIntentsUtil.startActivityInApp(this, intent);
+    }
+
+    /**
+     * @return true if there is only one raw contact in the contact and it is from a writable
+     * account.
+     */
+    private boolean isSingleWritableAccount() {
+        if (mCursor.getCount() != 1) {
+            return false;
+        }
+        mCursor.moveToFirst();
+        final String accountType = mCursor.getString(PickRawContactLoader.ACCOUNT_TYPE);
+        final String dataSet = mCursor.getString(PickRawContactLoader.DATA_SET);
+        final AccountType account = AccountTypeManager.getInstance(this)
+                .getAccountType(accountType, dataSet);
+        return account.areContactsWritable();
+    }
+
+    /**
+     * Returns an intent to load the editor for the given raw contact. Sets
+     * {@code FLAG_ACTIVITY_FORWARD_RESULT} in case the activity that started us expects a result.
+     * @param rawContactId Raw contact to edit
+     */
+    private Intent getIntentForRawContact(long rawContactId) {
+        final Intent intent = EditorIntents.createEditContactIntentForRawContact(
+                this, mUri, rawContactId, mMaterialPalette);
+        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+        return intent;
+    }
+}
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 80e1b82..0542436 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -203,6 +203,13 @@
     public static final String INTENT_EXTRA_PHOTO_ID = "photo_id";
 
     /**
+     * Intent key to pass the ID of the raw contact id that should be displayed in the full editor
+     * by itself.
+     */
+    public static final String INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE =
+            "raw_contact_id_to_display_alone";
+
+    /**
      * Intent extra to specify a {@link ContactEditor.SaveMode}.
      */
     public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
@@ -1478,6 +1485,8 @@
                         mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR),
                         mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR));
             }
+            mRawContactIdToDisplayAlone = mIntentExtras
+                    .getLong(INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE);
         }
     }
 
diff --git a/src/com/android/contacts/editor/EditorIntents.java b/src/com/android/contacts/editor/EditorIntents.java
index c4f48e6..c903b84 100644
--- a/src/com/android/contacts/editor/EditorIntents.java
+++ b/src/com/android/contacts/editor/EditorIntents.java
@@ -24,6 +24,7 @@
 import android.text.TextUtils;
 
 import com.android.contacts.activities.ContactEditorActivity;
+import com.android.contacts.activities.ContactEditorSpringBoardActivity;
 import com.android.contacts.common.model.RawContactDeltaList;
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 
@@ -38,19 +39,32 @@
     }
 
     /**
-     * Returns an Intent to start the {@link ContactEditorActivity} for an
+     * Returns an Intent to start the {@link ContactEditorSpringBoardActivity} for an
      * existing contact.
      */
-    public static Intent createEditContactIntent(Context context, Uri contactLookupUri,
+    public static Intent createEditContactIntent(Context context, Uri uri,
             MaterialPalette materialPalette, long photoId) {
-        final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri, context,
-                ContactEditorActivity.class);
+        final Intent intent = new Intent(Intent.ACTION_EDIT, uri, context,
+                ContactEditorSpringBoardActivity.class);
         putMaterialPalette(intent, materialPalette);
         putPhotoId(intent, photoId);
         return intent;
     }
 
     /**
+     * Returns an Intent to start the {@link ContactEditorActivity} for the given raw contact.
+     */
+    public static Intent createEditContactIntentForRawContact(Context context,
+            Uri uri, long rawContactId, MaterialPalette materialPalette) {
+        final Intent intent = new Intent(Intent.ACTION_EDIT, uri, context,
+                ContactEditorActivity.class);
+        intent.putExtra(ContactEditorFragment.INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE,
+                rawContactId);
+        putMaterialPalette(intent, materialPalette);
+        return intent;
+    }
+
+    /**
      * Returns an Intent to start the {@link ContactEditorActivity} for a new contact with
      * the field values specified by rawContactDeltaList pre-populate in the form.
      */
@@ -71,9 +85,9 @@
      * Returns an Intent to edit a different contact in the editor with whatever
      * values were already entered on the current editor.
      */
-    public static Intent createEditOtherContactIntent(Context context, Uri contactLookupUri,
+    public static Intent createEditOtherContactIntent(Context context, Uri uri,
             ArrayList<ContentValues> contentValues) {
-        final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri, context,
+        final Intent intent = new Intent(Intent.ACTION_EDIT, uri, context,
                 ContactEditorActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                 | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
diff --git a/src/com/android/contacts/editor/PickRawContactDialogFragment.java b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
new file mode 100644
index 0000000..20e8f35
--- /dev/null
+++ b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
@@ -0,0 +1,162 @@
+package com.android.contacts.editor;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+
+/**
+ * Dialog containing the raw contacts that make up a contact. On selection the editor is loaded
+ * for the chosen raw contact.
+ */
+public class PickRawContactDialogFragment extends DialogFragment {
+    /**
+     * Used to list the account info for the given raw contacts list.
+     */
+    private static final class RawContactAccountListAdapter extends CursorAdapter {
+        private final LayoutInflater mInflater;
+        private final Context mContext;
+
+        public RawContactAccountListAdapter(Context context, Cursor cursor) {
+            super(context, cursor, 0);
+            mContext = context;
+            mInflater = LayoutInflater.from(context);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            final long rawContactId = cursor.getLong(PickRawContactLoader.RAW_CONTACT_ID);
+            final String accountName = cursor.getString(PickRawContactLoader.ACCOUNT_NAME);
+            final String accountType = cursor.getString(PickRawContactLoader.ACCOUNT_TYPE);
+            final String dataSet = cursor.getString(PickRawContactLoader.DATA_SET);
+            final AccountType account = AccountTypeManager.getInstance(mContext)
+                    .getAccountType(accountType, dataSet);
+
+            final ContactsPreferences prefs = new ContactsPreferences(mContext);
+            final int displayNameColumn =
+                    prefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY
+                            ? PickRawContactLoader.DISPLAY_NAME_PRIMARY
+                            : PickRawContactLoader.DISPLAY_NAME_ALTERNATIVE;
+            String displayName = cursor.getString(displayNameColumn);
+
+            final TextView nameView = (TextView) view.findViewById(
+                    R.id.display_name);
+            final TextView accountTextView = (TextView) view.findViewById(
+                    R.id.account_name);
+            final ImageView accountIconView = (ImageView) view.findViewById(
+                    R.id.account_icon);
+
+            if (!account.areContactsWritable()) {
+                displayName = mContext
+                        .getString(R.string.contact_editor_pick_raw_contact_read_only, displayName);
+                view.setAlpha(.38f);
+            } else {
+                view.setAlpha(1f);
+            }
+
+            nameView.setText(displayName);
+            accountTextView.setText(accountName);
+            accountIconView.setImageDrawable(account.getDisplayIcon(mContext));
+
+            final ContactPhotoManager.DefaultImageRequest
+                    request = new ContactPhotoManager.DefaultImageRequest(
+                    displayName, String.valueOf(rawContactId), /* isCircular = */ true);
+            final ImageView photoView = (ImageView) view.findViewById(
+                    R.id.photo);
+            ContactPhotoManager.getInstance(mContext).loadDirectoryPhoto(photoView,
+                    ContactPhotoManager.getDefaultAvatarUriForContact(request),
+                    /* darkTheme = */ false,
+                    /* isCircular = */ true,
+                    request);
+        }
+
+        @Override
+        public View newView(Context context, Cursor cursor, ViewGroup parent) {
+            return mInflater.inflate(R.layout.raw_contact_list_item, parent, false);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            getCursor().moveToPosition(position);
+            return getCursor().getLong(PickRawContactLoader.RAW_CONTACT_ID);
+        }
+    }
+
+    // Cursor holding all raw contact rows for the given Contact.
+    private Cursor mCursor;
+    // Uri for the whole Contact.
+    private Uri mUri;
+    private MaterialPalette mMaterialPalette;
+
+    public static PickRawContactDialogFragment getInstance(Uri uri, Cursor cursor,
+            MaterialPalette materialPalette) {
+        final PickRawContactDialogFragment fragment = new PickRawContactDialogFragment();
+        fragment.setUri(uri);
+        fragment.setCursor(cursor);
+        fragment.setMaterialPalette(materialPalette);
+        return fragment;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        final CursorAdapter adapter = new RawContactAccountListAdapter(getContext(), mCursor);
+        builder.setTitle(R.string.contact_editor_pick_raw_contact_dialog_title);
+        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                final long rawContactId = adapter.getItemId(which);
+                final Intent intent = EditorIntents.createEditContactIntentForRawContact(
+                        getActivity(), mUri, rawContactId, mMaterialPalette);
+                intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+                ImplicitIntentsUtil.startActivityInApp(getActivity(), intent);
+            }
+        });
+        builder.setCancelable(true);
+        return builder.create();
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        mCursor = null;
+        finishActivity();
+    }
+
+    private void setUri(Uri uri) {
+        mUri = uri;
+    }
+
+    private void setCursor(Cursor cursor) {
+        mCursor = cursor;
+    }
+
+    private void setMaterialPalette(MaterialPalette materialPalette) {
+        mMaterialPalette = materialPalette;
+    }
+
+    private void finishActivity() {
+        if (getActivity() != null && !getActivity().isFinishing()) {
+            getActivity().finish();
+        }
+    }
+}
diff --git a/src/com/android/contacts/editor/PickRawContactLoader.java b/src/com/android/contacts/editor/PickRawContactLoader.java
new file mode 100644
index 0000000..62be517
--- /dev/null
+++ b/src/com/android/contacts/editor/PickRawContactLoader.java
@@ -0,0 +1,78 @@
+package com.android.contacts.editor;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+
+/**
+ * Loader for the pick a raw contact to edit activity. Loads all raw contact metadata for the
+ * given Contact {@link Uri}.
+ */
+public class PickRawContactLoader extends CursorLoader {
+    private Uri mContactUri;
+
+    public static final String[] COLUMNS = new String[] {
+            RawContacts.ACCOUNT_NAME,
+            RawContacts.ACCOUNT_TYPE,
+            RawContacts.DATA_SET,
+            RawContacts._ID,
+            RawContacts.DISPLAY_NAME_PRIMARY,
+            RawContacts.DISPLAY_NAME_ALTERNATIVE
+    };
+
+    public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
+
+    public static final int ACCOUNT_NAME = 0;
+    public static final int ACCOUNT_TYPE = 1;
+    public static final int DATA_SET = 2;
+    public static final int RAW_CONTACT_ID = 3;
+    public static final int DISPLAY_NAME_PRIMARY = 4;
+    public static final int DISPLAY_NAME_ALTERNATIVE = 5;
+
+    public PickRawContactLoader(Context context, Uri contactUri) {
+        super(context, ensureIsContactUri(contactUri), COLUMNS, SELECTION, null, RawContacts._ID);
+        mContactUri = contactUri;
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        // Get the id of the contact we're looking at.
+        final Cursor cursor = getContext().getContentResolver()
+                .query(mContactUri, new String[] { Contacts._ID }, null,
+                null, null);
+
+        if (cursor == null) {
+            return null;
+        }
+
+        if (cursor.getCount() < 1) {
+            cursor.close();
+            return null;
+        }
+
+        cursor.moveToFirst();
+        final long contactId = cursor.getLong(0);
+        cursor.close();
+        // Update selection arguments and uri.
+        setSelectionArgs(new String[]{ Long.toString(contactId) });
+        setUri(RawContacts.CONTENT_URI);
+        return super.loadInBackground();
+    }
+
+    /**
+     * Ensures that this is a valid contact URI. If invalid, then an exception is
+     * thrown. Otherwise, the original URI is returned.
+     */
+    private static Uri ensureIsContactUri(final Uri uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("Uri must not be null");
+        }
+        if (!uri.toString().startsWith(Contacts.CONTENT_URI.toString())) {
+            throw new IllegalArgumentException("Invalid contact Uri: " + uri);
+        }
+        return uri;
+    }
+}