diff options
Diffstat (limited to 'java/com/android/dialer/interactions')
7 files changed, 836 insertions, 0 deletions
diff --git a/java/com/android/dialer/interactions/AndroidManifest.xml b/java/com/android/dialer/interactions/AndroidManifest.xml new file mode 100644 index 000000000..4571a6965 --- /dev/null +++ b/java/com/android/dialer/interactions/AndroidManifest.xml @@ -0,0 +1,20 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dialer.interactions"> + + <application> + + <!-- Service to update a contact --> + <service + android:exported="false" + android:name="com.android.dialer.interactions.ContactUpdateService"/> + + <receiver android:name="com.android.dialer.interactions.UndemoteOutgoingCallReceiver"> + <intent-filter> + <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> + </intent-filter> + </receiver> + + </application> + +</manifest> + diff --git a/java/com/android/dialer/interactions/ContactUpdateService.java b/java/com/android/dialer/interactions/ContactUpdateService.java new file mode 100644 index 000000000..9b2d701d2 --- /dev/null +++ b/java/com/android/dialer/interactions/ContactUpdateService.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 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.IntentService; +import android.content.Context; +import android.content.Intent; +import com.android.contacts.common.database.ContactUpdateUtils; + +/** Service for updating primary number on a contact. */ +public class ContactUpdateService extends IntentService { + + public static final String EXTRA_PHONE_NUMBER_DATA_ID = "phone_number_data_id"; + + public ContactUpdateService() { + super(ContactUpdateService.class.getSimpleName()); + setIntentRedelivery(true); + } + + /** Creates an intent that sets the selected data item as super primary (default) */ + public static Intent createSetSuperPrimaryIntent(Context context, long dataId) { + Intent serviceIntent = new Intent(context, ContactUpdateService.class); + serviceIntent.putExtra(EXTRA_PHONE_NUMBER_DATA_ID, dataId); + return serviceIntent; + } + + @Override + protected void onHandleIntent(Intent intent) { + // Currently this service only handles one type of update. + long dataId = intent.getLongExtra(EXTRA_PHONE_NUMBER_DATA_ID, -1); + + ContactUpdateUtils.setSuperPrimary(this, dataId); + } +} diff --git a/java/com/android/dialer/interactions/PhoneNumberInteraction.java b/java/com/android/dialer/interactions/PhoneNumberInteraction.java new file mode 100644 index 000000000..f36e5319c --- /dev/null +++ b/java/com/android/dialer/interactions/PhoneNumberInteraction.java @@ -0,0 +1,557 @@ +/* + * 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.Manifest; +import android.annotation.SuppressLint; +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.Intent; +import android.content.Loader; +import android.content.Loader.OnLoadCompleteListener; +import android.content.pm.PackageManager; +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.support.annotation.IntDef; +import android.support.annotation.VisibleForTesting; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +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.Collapser; +import com.android.contacts.common.Collapser.Collapsible; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.callintent.CallIntentParser; +import com.android.dialer.callintent.nano.CallSpecificAppData; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.TransactionSafeActivity; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +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. + * + * <p>Note that this class initiates not only usual *phone* calls but also *SIP* calls. + * + * <p>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(); + /** The identifier for a permissions request if one is generated. */ + public static final int REQUEST_READ_CONTACTS = 1; + + 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, + Phone.CONTACT_ID, + }; + + private static final String PHONE_NUMBER_SELECTION = + Data.MIMETYPE + + " IN ('" + + Phone.CONTENT_ITEM_TYPE + + "', " + + "'" + + SipAddress.CONTENT_ITEM_TYPE + + "') AND " + + Data.DATA1 + + " NOT NULL"; + private static final int UNKNOWN_CONTACT_ID = -1; + private final Context mContext; + private final int mInteractionType; + private final CallSpecificAppData mCallSpecificAppData; + private long mContactId = UNKNOWN_CONTACT_ID; + private CursorLoader mLoader; + private boolean mIsVideoCall; + + /** Error codes for interactions. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + value = { + InteractionErrorCode.CONTACT_NOT_FOUND, + InteractionErrorCode.CONTACT_HAS_NO_NUMBER, + InteractionErrorCode.USER_LEAVING_ACTIVITY, + InteractionErrorCode.OTHER_ERROR + } + ) + public @interface InteractionErrorCode { + + int CONTACT_NOT_FOUND = 1; + int CONTACT_HAS_NO_NUMBER = 2; + int OTHER_ERROR = 3; + int USER_LEAVING_ACTIVITY = 4; + } + + /** + * Activities which use this class must implement this. They will be notified if there was an + * error performing the interaction. For example, this callback will be invoked on the activity if + * the contact URI provided points to a deleted contact, or to a contact without a phone number. + */ + public interface InteractionErrorListener { + + void interactionError(@InteractionErrorCode int interactionErrorCode); + } + + /** + * Activities which use this class must implement this. They will be notified if the phone number + * disambiguation dialog is dismissed. + */ + public interface DisambigDialogDismissedListener { + void onDisambigDialogDismissed(); + } + + private PhoneNumberInteraction( + Context context, + int interactionType, + boolean isVideoCall, + CallSpecificAppData callSpecificAppData) { + mContext = context; + mInteractionType = interactionType; + mCallSpecificAppData = callSpecificAppData; + mIsVideoCall = isVideoCall; + + Assert.checkArgument(context instanceof InteractionErrorListener); + Assert.checkArgument(context instanceof DisambigDialogDismissedListener); + Assert.checkArgument(context instanceof ActivityCompat.OnRequestPermissionsResultCallback); + } + + private static void performAction( + Context context, + String phoneNumber, + int interactionType, + boolean isVideoCall, + CallSpecificAppData callSpecificAppData) { + Intent intent; + switch (interactionType) { + case ContactDisplayUtils.INTERACTION_SMS: + intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("sms", phoneNumber, null)); + break; + default: + intent = + new CallIntentBuilder(phoneNumber, callSpecificAppData) + .setIsVideoCall(isVideoCall) + .build(); + break; + } + DialerUtils.startActivityWithErrorToast(context, intent); + } + + /** + * @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. The activity must implement {@link InteractionErrorListener} + * and {@link DisambigDialogDismissedListener}. + * @param isVideoCall {@code true} if the call is a video call, {@code false} otherwise. + */ + public static void startInteractionForPhoneCall( + TransactionSafeActivity activity, + Uri uri, + boolean isVideoCall, + CallSpecificAppData callSpecificAppData) { + new PhoneNumberInteraction( + activity, ContactDisplayUtils.INTERACTION_CALL, isVideoCall, callSpecificAppData) + .startInteraction(uri); + } + + private void performAction(String phoneNumber) { + PhoneNumberInteraction.performAction( + mContext, phoneNumber, mInteractionType, mIsVideoCall, mCallSpecificAppData); + } + + /** + * Initiates the interaction to result in either a phone call or sms message for a contact. + * + * @param uri Contact Uri + */ + private void startInteraction(Uri uri) { + // It's possible for a shortcut to have been created, and then Contacts permissions revoked. To + // avoid a crash when the user tries to use such a shortcut, check for this condition and ask + // the user for the permission. + if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_CONTACTS) + != PackageManager.PERMISSION_GRANTED) { + LogUtil.i("PhoneNumberInteraction.startInteraction", "No contact permissions"); + ActivityCompat.requestPermissions( + (Activity) mContext, + new String[] {Manifest.permission.READ_CONTACTS}, + REQUEST_READ_CONTACTS); + return; + } + + 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) { + LogUtil.i("PhoneNumberInteraction.onLoadComplete", "null cursor"); + interactionError(InteractionErrorCode.OTHER_ERROR); + return; + } + try { + ArrayList<PhoneItem> phoneList = new ArrayList<>(); + String primaryPhone = null; + if (!isSafeToCommitTransactions()) { + LogUtil.i("PhoneNumberInteraction.onLoadComplete", "not safe to commit transaction"); + interactionError(InteractionErrorCode.USER_LEAVING_ACTIVITY); + return; + } + if (cursor.moveToFirst()) { + int contactIdColumn = cursor.getColumnIndexOrThrow(Phone.CONTACT_ID); + int isSuperPrimaryColumn = cursor.getColumnIndexOrThrow(Phone.IS_SUPER_PRIMARY); + int phoneNumberColumn = cursor.getColumnIndexOrThrow(Phone.NUMBER); + int phoneIdColumn = cursor.getColumnIndexOrThrow(Phone._ID); + int accountTypeColumn = cursor.getColumnIndexOrThrow(RawContacts.ACCOUNT_TYPE); + int dataSetColumn = cursor.getColumnIndexOrThrow(RawContacts.DATA_SET); + int phoneTypeColumn = cursor.getColumnIndexOrThrow(Phone.TYPE); + int phoneLabelColumn = cursor.getColumnIndexOrThrow(Phone.LABEL); + int phoneMimeTpeColumn = cursor.getColumnIndexOrThrow(Phone.MIMETYPE); + do { + if (mContactId == UNKNOWN_CONTACT_ID) { + mContactId = cursor.getLong(contactIdColumn); + } + + if (cursor.getInt(isSuperPrimaryColumn) != 0) { + // Found super primary, call it. + primaryPhone = cursor.getString(phoneNumberColumn); + } + + PhoneItem item = new PhoneItem(); + item.id = cursor.getLong(phoneIdColumn); + item.phoneNumber = cursor.getString(phoneNumberColumn); + item.accountType = cursor.getString(accountTypeColumn); + item.dataSet = cursor.getString(dataSetColumn); + item.type = cursor.getInt(phoneTypeColumn); + item.label = cursor.getString(phoneLabelColumn); + item.mimeType = cursor.getString(phoneMimeTpeColumn); + + phoneList.add(item); + } while (cursor.moveToNext()); + } else { + interactionError(InteractionErrorCode.CONTACT_NOT_FOUND); + return; + } + + if (primaryPhone != null) { + performAction(primaryPhone); + return; + } + + Collapser.collapseList(phoneList, mContext); + if (phoneList.size() == 0) { + interactionError(InteractionErrorCode.CONTACT_HAS_NO_NUMBER); + } else if (phoneList.size() == 1) { + PhoneItem item = phoneList.get(0); + performAction(item.phoneNumber); + } else { + // There are multiple candidates. Let the user choose one. + showDisambiguationDialog(phoneList); + } + } finally { + cursor.close(); + } + } + + private void interactionError(@InteractionErrorCode int interactionErrorCode) { + // mContext is really the activity -- see ctor docs. + ((InteractionErrorListener) mContext).interactionError(interactionErrorCode); + } + + private boolean isSafeToCommitTransactions() { + return !(mContext instanceof TransactionSafeActivity) + || ((TransactionSafeActivity) mContext).isSafeToCommitTransactions(); + } + + @VisibleForTesting + /* package */ CursorLoader getLoader() { + return mLoader; + } + + private void showDisambiguationDialog(ArrayList<PhoneItem> phoneList) { + final Activity activity = (Activity) mContext; + if (activity.isDestroyed()) { + // Check whether the activity is still running + return; + } + try { + PhoneDisambiguationDialogFragment.show( + activity.getFragmentManager(), + phoneList, + mInteractionType, + mIsVideoCall, + mCallSpecificAppData); + } catch (IllegalStateException e) { + // ignore to be safe. Shouldn't happen because we checked the + // activity wasn't destroyed, but to be safe. + } + } + + /** A model object for capturing a phone number for a given contact. */ + @VisibleForTesting + /* package */ static class PhoneItem implements Parcelable, Collapsible<PhoneItem> { + + 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]; + } + }; + 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; + + private 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 void collapseWith(PhoneItem phoneItem) { + // Just keep the number and id we already have. + } + + @Override + public boolean shouldCollapseWith(PhoneItem phoneItem, Context context) { + return MoreContactUtils.shouldCollapse( + Phone.CONTENT_ITEM_TYPE, phoneNumber, Phone.CONTENT_ITEM_TYPE, phoneItem.phoneNumber); + } + + @Override + public String toString() { + return phoneNumber; + } + } + + /** A list adapter that populates the list of contact's phone numbers. */ + private static class PhoneItemAdapter extends ArrayAdapter<PhoneItem> { + + private final int mInteractionType; + + 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); + Assert.isNotNull(item, "Null item at position: %d", 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. + * + * <p>It is recommended to use {@link #startInteractionForPhoneCall(TransactionSafeActivity, Uri, + * boolean, int)} instead of directly using this class, as those methods handle one or multiple + * data cases appropriately. + * + * <p>This fragment may only be attached to activities which implement {@link + * DisambigDialogDismissedListener}. + */ + @SuppressWarnings("WeakerAccess") // 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_IS_VIDEO_CALL = "is_video_call"; + + private int mInteractionType; + private ListAdapter mPhonesAdapter; + private List<PhoneItem> mPhoneList; + private CallSpecificAppData mCallSpecificAppData; + private boolean mIsVideoCall; + + public PhoneDisambiguationDialogFragment() { + super(); + } + + public static void show( + FragmentManager fragmentManager, + ArrayList<PhoneItem> phoneList, + int interactionType, + boolean isVideoCall, + CallSpecificAppData callSpecificAppData) { + PhoneDisambiguationDialogFragment fragment = new PhoneDisambiguationDialogFragment(); + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(ARG_PHONE_LIST, phoneList); + bundle.putInt(ARG_INTERACTION_TYPE, interactionType); + bundle.putBoolean(ARG_IS_VIDEO_CALL, isVideoCall); + CallIntentParser.putCallSpecificAppData(bundle, callSpecificAppData); + fragment.setArguments(bundle); + fragment.show(fragmentManager, TAG); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + Assert.checkState(activity instanceof DisambigDialogDismissedListener); + + mPhoneList = getArguments().getParcelableArrayList(ARG_PHONE_LIST); + mInteractionType = getArguments().getInt(ARG_INTERACTION_TYPE); + mIsVideoCall = getArguments().getBoolean(ARG_IS_VIDEO_CALL); + mCallSpecificAppData = CallIntentParser.getCallSpecificAppData(getArguments()); + + mPhonesAdapter = new PhoneItemAdapter(activity, mPhoneList, mInteractionType); + final LayoutInflater inflater = activity.getLayoutInflater(); + @SuppressLint("InflateParams") // Allowed since dialog view is not available yet + 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, mIsVideoCall, mCallSpecificAppData); + } else { + dialog.dismiss(); + } + } + + @Override + public void onDismiss(DialogInterface dialogInterface) { + super.onDismiss(dialogInterface); + Activity activity = getActivity(); + if (activity != null) { + ((DisambigDialogDismissedListener) activity).onDisambigDialogDismissed(); + } + } + } +} diff --git a/java/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java b/java/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java new file mode 100644 index 000000000..68b011a04 --- /dev/null +++ b/java/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2013 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 static android.Manifest.permission.READ_CONTACTS; +import static android.Manifest.permission.WRITE_CONTACTS; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract.PhoneLookup; +import android.provider.ContactsContract.PinnedPositions; +import android.text.TextUtils; +import com.android.dialer.util.PermissionsUtil; + +/** + * This broadcast receiver is used to listen to outgoing calls and undemote formerly demoted + * contacts if a phone call is made to a phone number belonging to that contact. + * + * <p>NOTE This doesn't work for corp contacts. + */ +public class UndemoteOutgoingCallReceiver extends BroadcastReceiver { + + private static final long NO_CONTACT_FOUND = -1; + + @Override + public void onReceive(final Context context, Intent intent) { + if (!PermissionsUtil.hasPermission(context, READ_CONTACTS) + || !PermissionsUtil.hasPermission(context, WRITE_CONTACTS)) { + return; + } + if (intent != null && Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())) { + final String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); + if (TextUtils.isEmpty(number)) { + return; + } + new Thread() { + @Override + public void run() { + final long id = getContactIdFromPhoneNumber(context, number); + if (id != NO_CONTACT_FOUND) { + undemoteContactWithId(context, id); + } + } + }.start(); + } + } + + private void undemoteContactWithId(Context context, long id) { + // If the contact is not demoted, this will not do anything. Otherwise, it will + // restore it to an unpinned position. If it was a frequently called contact, it will + // show up once again show up on the favorites screen. + if (PermissionsUtil.hasPermission(context, WRITE_CONTACTS)) { + try { + PinnedPositions.undemote(context.getContentResolver(), id); + } catch (SecurityException e) { + // Just in case + } + } + } + + private long getContactIdFromPhoneNumber(Context context, String number) { + if (!PermissionsUtil.hasPermission(context, READ_CONTACTS)) { + return NO_CONTACT_FOUND; + } + final Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + final Cursor cursor; + try { + cursor = + context + .getContentResolver() + .query(contactUri, new String[] {PhoneLookup._ID}, null, null, null); + } catch (SecurityException e) { + // Just in case + return NO_CONTACT_FOUND; + } + if (cursor == null) { + return NO_CONTACT_FOUND; + } + try { + if (cursor.moveToFirst()) { + final long id = cursor.getLong(0); + return id; + } else { + return NO_CONTACT_FOUND; + } + } finally { + cursor.close(); + } + } +} diff --git a/java/com/android/dialer/interactions/res/layout/phone_disambig_item.xml b/java/com/android/dialer/interactions/res/layout/phone_disambig_item.xml new file mode 100644 index 000000000..879ea0e96 --- /dev/null +++ b/java/com/android/dialer/interactions/res/layout/phone_disambig_item.xml @@ -0,0 +1,43 @@ +<?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. +--> + +<view xmlns:android="http://schemas.android.com/apk/res/android" + class="com.android.contacts.common.widget.ActivityTouchLinearLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="30dip" + android:paddingEnd="30dip" + android:gravity="center_vertical" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="vertical"> + + <TextView + android:id="@android:id/text1" + android:textStyle="bold" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium"/> + + <!-- Phone number should be displayed ltr --> + <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" + android:textDirection="ltr"/> + +</view> diff --git a/java/com/android/dialer/interactions/res/layout/set_primary_checkbox.xml b/java/com/android/dialer/interactions/res/layout/set_primary_checkbox.xml new file mode 100644 index 000000000..62ef4b76f --- /dev/null +++ b/java/com/android/dialer/interactions/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:paddingStart="14dip" + android:paddingEnd="15dip" + android:orientation="vertical"> + + <CheckBox + android:id="@+id/setPrimary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clickable="true" + android:focusable="true" + android:text="@string/make_primary"/> +</LinearLayout> diff --git a/java/com/android/dialer/interactions/res/values/strings.xml b/java/com/android/dialer/interactions/res/values/strings.xml new file mode 100644 index 000000000..eea8795b5 --- /dev/null +++ b/java/com/android/dialer/interactions/res/values/strings.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2012 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 + --> + +<resources> + + <!-- 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 disambiguation dialog check box --> + <string name="make_primary">Remember this choice</string> + +</resources> |