diff options
author | Chiao Cheng <chiaocheng@google.com> | 2012-10-31 11:12:12 -0700 |
---|---|---|
committer | Chiao Cheng <chiaocheng@google.com> | 2012-10-31 15:10:47 -0700 |
commit | 9c5fe4420bc1a1fe4edcac99e1f73b13bca63fa2 (patch) | |
tree | 564e70adc0d48d154739c5b1d43859146a8a4c69 | |
parent | fe000c2346f425fb76f14b36e5cb8360929f8a1e (diff) |
Moving PhoneNumberInteraction from Contacts.
Bug: 6993891
Change-Id: Id4701c00455de609850a5afea74e68c6bd84cbb3
-rwxr-xr-x | res/layout/phone_disambig_item.xml | 40 | ||||
-rw-r--r-- | res/layout/set_primary_checkbox.xml | 32 | ||||
-rw-r--r-- | res/values/strings.xml | 8 | ||||
-rw-r--r-- | src/com/android/dialer/DialtactsActivity.java | 2 | ||||
-rw-r--r-- | src/com/android/dialer/interactions/PhoneNumberInteraction.java | 474 | ||||
-rw-r--r-- | tests/src/com/android/dialer/interactions/PhoneNumberInteractionTest.java | 259 |
6 files changed, 814 insertions, 1 deletions
diff --git a/res/layout/phone_disambig_item.xml b/res/layout/phone_disambig_item.xml new file mode 100755 index 000000000..a31d08d32 --- /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 000000000..8f28ec791 --- /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 1346d0d50..86c36c493 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 f2119e007..7154a12d3 100644 --- a/src/com/android/dialer/DialtactsActivity.java +++ b/src/com/android/dialer/DialtactsActivity.java @@ -58,7 +58,7 @@ import android.widget.SearchView.OnQueryTextListener; 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 000000000..cb19a1853 --- /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 000000000..f86675e57 --- /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"); + } +} |