Moving PhoneNumberInteraction from Contacts.

Bug: 6993891
Change-Id: Id4701c00455de609850a5afea74e68c6bd84cbb3
diff --git a/res/layout/phone_disambig_item.xml b/res/layout/phone_disambig_item.xml
new file mode 100755
index 0000000..a31d08d
--- /dev/null
+++ b/res/layout/phone_disambig_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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:orientation="vertical"
+    android:paddingLeft="30dip"
+    android:paddingRight="30dip"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical">
+
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textStyle="bold"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <TextView
+        android:id="@android:id/text2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-4dip"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout>
diff --git a/res/layout/set_primary_checkbox.xml b/res/layout/set_primary_checkbox.xml
new file mode 100644
index 0000000..8f28ec7
--- /dev/null
+++ b/res/layout/set_primary_checkbox.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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="14dip"
+    android:paddingRight="15dip"
+    android:orientation="vertical">
+
+    <CheckBox
+        android:id="@+id/setPrimary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:focusable="true"
+        android:clickable="true"
+        android:text="@string/make_primary"/>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1346d0d..86c36c4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -178,4 +178,12 @@
         <xliff:g id="date">%2$s</xliff:g>
     </string>
 
+    <!-- Title for the sms disambiguation dialog -->
+    <string name="sms_disambig_title">Choose number</string>
+
+    <!-- Title for the call disambiguation dialog -->
+    <string name="call_disambig_title">Choose number</string>
+
+    <!-- Message next to disamgiguation dialog check box -->
+    <string name="make_primary">Remember this choice</string>
 </resources>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index f2119e0..7154a12 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -58,7 +58,7 @@
 
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.activity.TransactionSafeActivity;
-import com.android.contacts.interactions.PhoneNumberInteraction;
+import com.android.dialer.interactions.PhoneNumberInteraction;
 import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
 import com.android.contacts.list.ContactListItemView;
diff --git a/src/com/android/dialer/interactions/PhoneNumberInteraction.java b/src/com/android/dialer/interactions/PhoneNumberInteraction.java
new file mode 100644
index 0000000..cb19a18
--- /dev/null
+++ b/src/com/android/dialer/interactions/PhoneNumberInteraction.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2010 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.dialer.interactions;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.contacts.common.CallUtil;
+import com.android.contacts.common.Collapser;
+import com.android.contacts.common.Collapser.Collapsible;
+import com.android.contacts.common.MoreContactUtils;
+import com.android.contacts.common.activity.TransactionSafeActivity;
+import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.dialer.R;
+import com.android.dialer.contact.ContactUpdateService;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Initiates phone calls or a text message. If there are multiple candidates, this class shows a
+ * dialog to pick one. Creating one of these interactions should be done through the static
+ * factory methods.
+ *
+ * Note that this class initiates not only usual *phone* calls but also *SIP* calls.
+ *
+ * TODO: clean up code and documents since it is quite confusing to use "phone numbers" or
+ *        "phone calls" here while they can be SIP addresses or SIP calls (See also issue 5039627).
+ */
+public class PhoneNumberInteraction implements OnLoadCompleteListener<Cursor> {
+    private static final String TAG = PhoneNumberInteraction.class.getSimpleName();
+
+    /**
+     * A model object for capturing a phone number for a given contact.
+     */
+    @VisibleForTesting
+    /* package */ static class PhoneItem implements Parcelable, Collapsible<PhoneItem> {
+        long id;
+        String phoneNumber;
+        String accountType;
+        String dataSet;
+        long type;
+        String label;
+        /** {@link Phone#CONTENT_ITEM_TYPE} or {@link SipAddress#CONTENT_ITEM_TYPE}. */
+        String mimeType;
+
+        public PhoneItem() {
+        }
+
+        private PhoneItem(Parcel in) {
+            this.id          = in.readLong();
+            this.phoneNumber = in.readString();
+            this.accountType = in.readString();
+            this.dataSet     = in.readString();
+            this.type        = in.readLong();
+            this.label       = in.readString();
+            this.mimeType    = in.readString();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(id);
+            dest.writeString(phoneNumber);
+            dest.writeString(accountType);
+            dest.writeString(dataSet);
+            dest.writeLong(type);
+            dest.writeString(label);
+            dest.writeString(mimeType);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public boolean collapseWith(PhoneItem phoneItem) {
+            if (!shouldCollapseWith(phoneItem)) {
+                return false;
+            }
+            // Just keep the number and id we already have.
+            return true;
+        }
+
+        @Override
+        public boolean shouldCollapseWith(PhoneItem phoneItem) {
+            return MoreContactUtils.shouldCollapse(Phone.CONTENT_ITEM_TYPE, phoneNumber,
+                    Phone.CONTENT_ITEM_TYPE, phoneItem.phoneNumber);
+        }
+
+        @Override
+        public String toString() {
+            return phoneNumber;
+        }
+
+        public static final Parcelable.Creator<PhoneItem> CREATOR
+                = new Parcelable.Creator<PhoneItem>() {
+            @Override
+            public PhoneItem createFromParcel(Parcel in) {
+                return new PhoneItem(in);
+            }
+
+            @Override
+            public PhoneItem[] newArray(int size) {
+                return new PhoneItem[size];
+            }
+        };
+    }
+
+    /**
+     * A list adapter that populates the list of contact's phone numbers.
+     */
+    private static class PhoneItemAdapter extends ArrayAdapter<PhoneItem> {
+        private final int mInteractionType;
+
+        public PhoneItemAdapter(Context context, List<PhoneItem> list,
+                int interactionType) {
+            super(context, R.layout.phone_disambig_item, android.R.id.text2, list);
+            mInteractionType = interactionType;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View view = super.getView(position, convertView, parent);
+
+            final PhoneItem item = getItem(position);
+            final TextView typeView = (TextView) view.findViewById(android.R.id.text1);
+            CharSequence value = ContactDisplayUtils.getLabelForCallOrSms((int) item.type,
+                    item.label, mInteractionType, getContext());
+
+            typeView.setText(value);
+            return view;
+        }
+    }
+
+    /**
+     * {@link DialogFragment} used for displaying a dialog with a list of phone numbers of which
+     * one will be chosen to make a call or initiate an sms message.
+     *
+     * It is recommended to use
+     * {@link PhoneNumberInteraction#startInteractionForPhoneCall(TransactionSafeActivity, Uri)} or
+     * {@link PhoneNumberInteraction#startInteractionForTextMessage(TransactionSafeActivity, Uri)}
+     * instead of directly using this class, as those methods handle one or multiple data cases
+     * appropriately.
+     */
+    /* Made public to let the system reach this class */
+    public static class PhoneDisambiguationDialogFragment extends DialogFragment
+            implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+        private static final String ARG_PHONE_LIST = "phoneList";
+        private static final String ARG_INTERACTION_TYPE = "interactionType";
+        private static final String ARG_CALL_ORIGIN = "callOrigin";
+
+        private int mInteractionType;
+        private ListAdapter mPhonesAdapter;
+        private List<PhoneItem> mPhoneList;
+        private String mCallOrigin;
+
+        public static void show(FragmentManager fragmentManager,
+                ArrayList<PhoneItem> phoneList, int interactionType,
+                String callOrigin) {
+            PhoneDisambiguationDialogFragment fragment = new PhoneDisambiguationDialogFragment();
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(ARG_PHONE_LIST, phoneList);
+            bundle.putSerializable(ARG_INTERACTION_TYPE, interactionType);
+            bundle.putString(ARG_CALL_ORIGIN, callOrigin);
+            fragment.setArguments(bundle);
+            fragment.show(fragmentManager, TAG);
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Activity activity = getActivity();
+            mPhoneList = getArguments().getParcelableArrayList(ARG_PHONE_LIST);
+            mInteractionType = getArguments().getInt(ARG_INTERACTION_TYPE);
+            mCallOrigin = getArguments().getString(ARG_CALL_ORIGIN);
+
+            mPhonesAdapter = new PhoneItemAdapter(activity, mPhoneList, mInteractionType);
+            final LayoutInflater inflater = activity.getLayoutInflater();
+            final View setPrimaryView = inflater.inflate(R.layout.set_primary_checkbox, null);
+            return new AlertDialog.Builder(activity)
+                    .setAdapter(mPhonesAdapter, this)
+                    .setTitle(mInteractionType == ContactDisplayUtils.INTERACTION_SMS
+                            ? R.string.sms_disambig_title : R.string.call_disambig_title)
+                    .setView(setPrimaryView)
+                    .create();
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            final Activity activity = getActivity();
+            if (activity == null) return;
+            final AlertDialog alertDialog = (AlertDialog)dialog;
+            if (mPhoneList.size() > which && which >= 0) {
+                final PhoneItem phoneItem = mPhoneList.get(which);
+                final CheckBox checkBox = (CheckBox)alertDialog.findViewById(R.id.setPrimary);
+                if (checkBox.isChecked()) {
+                    // Request to mark the data as primary in the background.
+                    final Intent serviceIntent = ContactUpdateService.createSetSuperPrimaryIntent(
+                            activity, phoneItem.id);
+                    activity.startService(serviceIntent);
+                }
+
+                PhoneNumberInteraction.performAction(activity, phoneItem.phoneNumber,
+                        mInteractionType, mCallOrigin);
+            } else {
+                dialog.dismiss();
+            }
+        }
+    }
+
+    private static final String[] PHONE_NUMBER_PROJECTION = new String[] {
+            Phone._ID,
+            Phone.NUMBER,
+            Phone.IS_SUPER_PRIMARY,
+            RawContacts.ACCOUNT_TYPE,
+            RawContacts.DATA_SET,
+            Phone.TYPE,
+            Phone.LABEL,
+            Phone.MIMETYPE
+    };
+
+    private static final String PHONE_NUMBER_SELECTION =
+            Data.MIMETYPE + " IN ('"
+                + Phone.CONTENT_ITEM_TYPE + "', "
+                + "'" + SipAddress.CONTENT_ITEM_TYPE + "') AND "
+                + Data.DATA1 + " NOT NULL";
+
+    private final Context mContext;
+    private final OnDismissListener mDismissListener;
+    private final int mInteractionType;
+
+    private final String mCallOrigin;
+
+    private CursorLoader mLoader;
+
+    /**
+     * Constructs a new {@link PhoneNumberInteraction}. The constructor takes in a {@link Context}
+     * instead of a {@link TransactionSafeActivity} for testing purposes to verify the functionality
+     * of this class. However, all factory methods for creating {@link PhoneNumberInteraction}s
+     * require a {@link TransactionSafeActivity} (i.e. see {@link #startInteractionForPhoneCall}).
+     */
+    @VisibleForTesting
+    /* package */ PhoneNumberInteraction(Context context, int interactionType,
+            DialogInterface.OnDismissListener dismissListener) {
+        this(context, interactionType, dismissListener, null);
+    }
+
+    private PhoneNumberInteraction(Context context, int interactionType,
+            DialogInterface.OnDismissListener dismissListener, String callOrigin) {
+        mContext = context;
+        mInteractionType = interactionType;
+        mDismissListener = dismissListener;
+        mCallOrigin = callOrigin;
+    }
+
+    private void performAction(String phoneNumber) {
+        PhoneNumberInteraction.performAction(mContext, phoneNumber, mInteractionType, mCallOrigin);
+    }
+
+    private static void performAction(
+            Context context, String phoneNumber, int interactionType,
+            String callOrigin) {
+        Intent intent;
+        switch (interactionType) {
+            case ContactDisplayUtils.INTERACTION_SMS:
+                intent = new Intent(
+                        Intent.ACTION_SENDTO, Uri.fromParts("sms", phoneNumber, null));
+                break;
+            default:
+                intent = CallUtil.getCallIntent(phoneNumber, callOrigin);
+                break;
+        }
+        context.startActivity(intent);
+    }
+
+    /**
+     * Initiates the interaction. This may result in a phone call or sms message started
+     * or a disambiguation dialog to determine which phone number should be used.
+     */
+    @VisibleForTesting
+    /* package */ void startInteraction(Uri uri) {
+        if (mLoader != null) {
+            mLoader.reset();
+        }
+
+        final Uri queryUri;
+        final String inputUriAsString = uri.toString();
+        if (inputUriAsString.startsWith(Contacts.CONTENT_URI.toString())) {
+            if (!inputUriAsString.endsWith(Contacts.Data.CONTENT_DIRECTORY)) {
+                queryUri = Uri.withAppendedPath(uri, Contacts.Data.CONTENT_DIRECTORY);
+            } else {
+                queryUri = uri;
+            }
+        } else if (inputUriAsString.startsWith(Data.CONTENT_URI.toString())) {
+            queryUri = uri;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Input Uri must be contact Uri or data Uri (input: \"" + uri + "\")");
+        }
+
+        mLoader = new CursorLoader(mContext,
+                queryUri,
+                PHONE_NUMBER_PROJECTION,
+                PHONE_NUMBER_SELECTION,
+                null,
+                null);
+        mLoader.registerListener(0, this);
+        mLoader.startLoading();
+    }
+
+    @Override
+    public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
+        if (cursor == null || !isSafeToCommitTransactions()) {
+            onDismiss();
+            return;
+        }
+
+        ArrayList<PhoneItem> phoneList = new ArrayList<PhoneItem>();
+        String primaryPhone = null;
+        try {
+            while (cursor.moveToNext()) {
+                if (cursor.getInt(cursor.getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0) {
+                    // Found super primary, call it.
+                    primaryPhone = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
+                    break;
+                }
+
+                PhoneItem item = new PhoneItem();
+                item.id = cursor.getLong(cursor.getColumnIndex(Data._ID));
+                item.phoneNumber = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
+                item.accountType =
+                        cursor.getString(cursor.getColumnIndex(RawContacts.ACCOUNT_TYPE));
+                item.dataSet = cursor.getString(cursor.getColumnIndex(RawContacts.DATA_SET));
+                item.type = cursor.getInt(cursor.getColumnIndex(Phone.TYPE));
+                item.label = cursor.getString(cursor.getColumnIndex(Phone.LABEL));
+                item.mimeType = cursor.getString(cursor.getColumnIndex(Phone.MIMETYPE));
+
+                phoneList.add(item);
+            }
+        } finally {
+            cursor.close();
+        }
+
+        if (primaryPhone != null) {
+            performAction(primaryPhone);
+            onDismiss();
+            return;
+        }
+
+        Collapser.collapseList(phoneList);
+
+        if (phoneList.size() == 0) {
+            onDismiss();
+        } else if (phoneList.size() == 1) {
+            PhoneItem item = phoneList.get(0);
+            onDismiss();
+            performAction(item.phoneNumber);
+        } else {
+            // There are multiple candidates. Let the user choose one.
+            showDisambiguationDialog(phoneList);
+        }
+    }
+
+    private boolean isSafeToCommitTransactions() {
+        return mContext instanceof TransactionSafeActivity ?
+                ((TransactionSafeActivity) mContext).isSafeToCommitTransactions() : true;
+    }
+
+    private void onDismiss() {
+        if (mDismissListener != null) {
+            mDismissListener.onDismiss(null);
+        }
+    }
+
+    /**
+     * Start call action using given contact Uri. If there are multiple candidates for the phone
+     * call, dialog is automatically shown and the user is asked to choose one.
+     *
+     * @param activity that is calling this interaction. This must be of type
+     * {@link TransactionSafeActivity} because we need to check on the activity state after the
+     * phone numbers have been queried for.
+     * @param uri contact Uri (built from {@link Contacts#CONTENT_URI}) or data Uri
+     * (built from {@link Data#CONTENT_URI}). Contact Uri may show the disambiguation dialog while
+     * data Uri won't.
+     */
+    public static void startInteractionForPhoneCall(TransactionSafeActivity activity, Uri uri) {
+        (new PhoneNumberInteraction(activity, ContactDisplayUtils.INTERACTION_CALL, null))
+                .startInteraction(uri);
+    }
+
+    /**
+     * @param activity that is calling this interaction. This must be of type
+     * {@link TransactionSafeActivity} because we need to check on the activity state after the
+     * phone numbers have been queried for.
+     * @param callOrigin If non null, {@link PhoneConstants#EXTRA_CALL_ORIGIN} will be
+     * appended to the Intent initiating phone call. See comments in Phone package (PhoneApp)
+     * for more detail.
+     */
+    public static void startInteractionForPhoneCall(TransactionSafeActivity activity, Uri uri,
+            String callOrigin) {
+        (new PhoneNumberInteraction(activity, ContactDisplayUtils.INTERACTION_CALL, null, callOrigin))
+                .startInteraction(uri);
+    }
+
+    /**
+     * Start text messaging (a.k.a SMS) action using given contact Uri. If there are multiple
+     * candidates for the phone call, dialog is automatically shown and the user is asked to choose
+     * one.
+     *
+     * @param activity that is calling this interaction. This must be of type
+     * {@link TransactionSafeActivity} because we need to check on the activity state after the
+     * phone numbers have been queried for.
+     * @param uri contact Uri (built from {@link Contacts#CONTENT_URI}) or data Uri
+     * (built from {@link Data#CONTENT_URI}). Contact Uri may show the disambiguation dialog while
+     * data Uri won't.
+     */
+    public static void startInteractionForTextMessage(TransactionSafeActivity activity, Uri uri) {
+        (new PhoneNumberInteraction(activity, ContactDisplayUtils.INTERACTION_SMS, null))
+                .startInteraction(uri);
+    }
+
+    @VisibleForTesting
+    /* package */ CursorLoader getLoader() {
+        return mLoader;
+    }
+
+    @VisibleForTesting
+    /* package */ void showDisambiguationDialog(ArrayList<PhoneItem> phoneList) {
+        PhoneDisambiguationDialogFragment.show(((Activity)mContext).getFragmentManager(),
+                phoneList, mInteractionType, mCallOrigin);
+    }
+}
diff --git a/tests/src/com/android/dialer/interactions/PhoneNumberInteractionTest.java b/tests/src/com/android/dialer/interactions/PhoneNumberInteractionTest.java
new file mode 100644
index 0000000..f86675e
--- /dev/null
+++ b/tests/src/com/android/dialer/interactions/PhoneNumberInteractionTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2010 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.dialer.interactions;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.common.test.mocks.ContactsMockContext;
+import com.android.contacts.common.test.mocks.MockContentProvider;
+import com.android.contacts.common.test.mocks.MockContentProvider.Query;
+import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.dialer.interactions.PhoneNumberInteraction.PhoneItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.contacts.common.interactions.PhoneNumberInteraction}.
+ *
+ * Running all tests:
+ *
+ *   runtest contacts
+ * or
+ *   adb shell am instrument \
+ *     -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class PhoneNumberInteractionTest extends InstrumentationTestCase {
+
+    static {
+        // AsyncTask class needs to be initialized on the main thread.
+        AsyncTask.init();
+    }
+
+    private final static class TestPhoneNumberInteraction extends PhoneNumberInteraction {
+        private ArrayList<PhoneItem> mPhoneList;
+
+        public TestPhoneNumberInteraction(Context context, int interactionType,
+                OnDismissListener dismissListener) {
+            super(context, interactionType, dismissListener);
+        }
+
+        @Override
+        void showDisambiguationDialog(ArrayList<PhoneItem> phoneList) {
+            this.mPhoneList = phoneList;
+        }
+    }
+
+    private ContactsMockContext mContext;
+    private MockContentProvider mContactsProvider;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+        mContactsProvider = mContext.getContactsProvider();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mContactsProvider.verify();
+        super.tearDown();
+    }
+
+    public void testSendSmsWhenOnlyOneNumberAvailable() {
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+        expectQuery(contactUri)
+                .returnRow(1, "123", 0, null, null, Phone.TYPE_HOME, null,
+                        Phone.CONTENT_ITEM_TYPE);
+
+        TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+                mContext, ContactDisplayUtils.INTERACTION_SMS, null);
+
+        interaction.startInteraction(contactUri);
+        interaction.getLoader().waitForLoader();
+
+        Intent intent = mContext.getIntentForStartActivity();
+        assertNotNull(intent);
+
+        assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+        assertEquals("sms:123", intent.getDataString());
+    }
+
+    public void testSendSmsWhenDataIdIsProvided() {
+        Uri dataUri = ContentUris.withAppendedId(Data.CONTENT_URI, 1);
+        expectQuery(dataUri, true /* isDataUri */ )
+                .returnRow(1, "987", 0, null, null, Phone.TYPE_HOME, null,
+                        Phone.CONTENT_ITEM_TYPE);
+
+        TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+                mContext, ContactDisplayUtils.INTERACTION_SMS, null);
+
+        interaction.startInteraction(dataUri);
+        interaction.getLoader().waitForLoader();
+
+        Intent intent = mContext.getIntentForStartActivity();
+        assertNotNull(intent);
+
+        assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+        assertEquals("sms:987", intent.getDataString());
+    }
+
+    public void testSendSmsWhenThereIsPrimaryNumber() {
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+        expectQuery(contactUri)
+                .returnRow(
+                        1, "123", 0, null, null, Phone.TYPE_HOME, null, Phone.CONTENT_ITEM_TYPE)
+                .returnRow(
+                        2, "456", 1, null, null, Phone.TYPE_HOME, null, Phone.CONTENT_ITEM_TYPE);
+
+        TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+                mContext, ContactDisplayUtils.INTERACTION_SMS, null);
+
+        interaction.startInteraction(contactUri);
+        interaction.getLoader().waitForLoader();
+
+        Intent intent = mContext.getIntentForStartActivity();
+        assertNotNull(intent);
+
+        assertEquals(Intent.ACTION_SENDTO, intent.getAction());
+        assertEquals("sms:456", intent.getDataString());
+    }
+
+    public void testShouldCollapseWith() {
+        PhoneNumberInteraction.PhoneItem phoneItem1 = new PhoneNumberInteraction.PhoneItem();
+        PhoneNumberInteraction.PhoneItem phoneItem2 = new PhoneNumberInteraction.PhoneItem();
+
+        phoneItem1.phoneNumber = "123";
+        phoneItem2.phoneNumber = "123";
+
+        assertTrue(phoneItem1.shouldCollapseWith(phoneItem2));
+
+        phoneItem1.phoneNumber = "123";
+        phoneItem2.phoneNumber = "456";
+
+        assertFalse(phoneItem1.shouldCollapseWith(phoneItem2));
+
+        phoneItem1.phoneNumber = "123#,123";
+        phoneItem2.phoneNumber = "123#,456";
+
+        assertFalse(phoneItem1.shouldCollapseWith(phoneItem2));
+    }
+
+    public void testCallNumberWhenThereAreDuplicates() {
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+        expectQuery(contactUri)
+                .returnRow(1, "123", 0, null, null, Phone.TYPE_HOME, null,
+                        Phone.CONTENT_ITEM_TYPE)
+                .returnRow(2, "123", 0, null, null, Phone.TYPE_WORK, null,
+                        Phone.CONTENT_ITEM_TYPE);
+
+        TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+                mContext, ContactDisplayUtils.INTERACTION_CALL, null);
+
+        interaction.startInteraction(contactUri);
+        interaction.getLoader().waitForLoader();
+
+        Intent intent = mContext.getIntentForStartActivity();
+        assertNotNull(intent);
+
+        assertEquals(Intent.ACTION_CALL_PRIVILEGED, intent.getAction());
+        assertEquals("tel:123", intent.getDataString());
+    }
+
+    public void testCallWithSip() {
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+        expectQuery(contactUri)
+                .returnRow(1, "example@example.com", 0, null, null, Phone.TYPE_HOME, null,
+                        SipAddress.CONTENT_ITEM_TYPE);
+        TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+                mContext, ContactDisplayUtils.INTERACTION_CALL, null);
+
+        interaction.startInteraction(contactUri);
+        interaction.getLoader().waitForLoader();
+
+        Intent intent = mContext.getIntentForStartActivity();
+        assertNotNull(intent);
+
+        assertEquals(Intent.ACTION_CALL_PRIVILEGED, intent.getAction());
+        assertEquals("sip:example%40example.com", intent.getDataString());
+    }
+
+    public void testShowDisambigDialogForCalling() {
+        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+        expectQuery(contactUri)
+                .returnRow(1, "123", 0, "account", null, Phone.TYPE_HOME, "label",
+                        Phone.CONTENT_ITEM_TYPE)
+                .returnRow(2, "456", 0, null, null, Phone.TYPE_WORK, null,
+                        Phone.CONTENT_ITEM_TYPE);
+
+        TestPhoneNumberInteraction interaction = new TestPhoneNumberInteraction(
+                mContext, ContactDisplayUtils.INTERACTION_CALL, null);
+
+        interaction.startInteraction(contactUri);
+        interaction.getLoader().waitForLoader();
+
+        List<PhoneItem> items = interaction.mPhoneList;
+        assertNotNull(items);
+        assertEquals(2, items.size());
+
+        PhoneItem item = items.get(0);
+        assertEquals(1, item.id);
+        assertEquals("123", item.phoneNumber);
+        assertEquals("account", item.accountType);
+        assertEquals(Phone.TYPE_HOME, item.type);
+        assertEquals("label", item.label);
+    }
+
+    private Query expectQuery(Uri contactUri) {
+        return expectQuery(contactUri, false);
+    }
+
+    private Query expectQuery(Uri uri, boolean isDataUri) {
+        final Uri dataUri;
+        if (isDataUri) {
+            dataUri = uri;
+        } else {
+            dataUri = Uri.withAppendedPath(uri, Contacts.Data.CONTENT_DIRECTORY);
+        }
+        return mContactsProvider
+                .expectQuery(dataUri)
+                .withProjection(
+                        Phone._ID,
+                        Phone.NUMBER,
+                        Phone.IS_SUPER_PRIMARY,
+                        RawContacts.ACCOUNT_TYPE,
+                        RawContacts.DATA_SET,
+                        Phone.TYPE,
+                        Phone.LABEL,
+                        Phone.MIMETYPE)
+                .withSelection("mimetype IN ('vnd.android.cursor.item/phone_v2',"
+                        + " 'vnd.android.cursor.item/sip_address') AND data1 NOT NULL");
+    }
+}