summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/interactions
diff options
context:
space:
mode:
authorEric Erfanian <erfanian@google.com>2017-02-22 16:32:36 -0800
committerEric Erfanian <erfanian@google.com>2017-03-01 09:56:52 -0800
commitccca31529c07970e89419fb85a9e8153a5396838 (patch)
treea7034c0a01672b97728c13282a2672771cd28baa /java/com/android/dialer/interactions
parente7ae4624ba6f25cb8e648db74e0d64c0113a16ba (diff)
Update dialer sources.
Test: Built package and system image. This change clobbers the old source, and is an export from an internal Google repository. The internal repository was forked form Android in March, and this change includes modifications since then, to near the v8 release. Since the fork, we've moved code from monolithic to independent modules. In addition, we've switched to Blaze/Bazel as the build sysetm. This export, however, still uses make. New dependencies have been added: - Dagger - Auto-Value - Glide - Libshortcutbadger Going forward, development will still be in Google3, and the Gerrit release will become an automated export, with the next drop happening in ~ two weeks. Android.mk includes local modifications from ToT. Abridged changelog: Bug fixes ● Not able to mute, add a call when using Phone app in multiwindow mode ● Double tap on keypad triggering multiple key and tones ● Reported spam numbers not showing as spam in the call log ● Crash when user tries to block number while Phone app is not set as default ● Crash when user picks a number from search auto-complete list Visual Voicemail (VVM) improvements ● Share Voicemail audio via standard exporting mechanisms that support file attachment (email, MMS, etc.) ● Make phone number, email and web sites in VVM transcript clickable ● Set PIN before declining VVM Terms of Service {Carrier} ● Set client type for outbound visual voicemail SMS {Carrier} New incoming call and incall UI on older devices (Android M) ● Updated Phone app icon ● New incall UI (large buttons, button labels) ● New and animated Answer/Reject gestures Accessibility ● Add custom answer/decline call buttons on answer screen for touch exploration accessibility services ● Increase size of touch target ● Add verbal feedback when a Voicemail fails to load ● Fix pressing of Phone buttons while in a phone call using Switch Access ● Fix selecting and opening contacts in talkback mode ● Split focus for ‘Learn More’ link in caller id & spam to help distinguish similar text Other ● Backup & Restore for App Preferences ● Prompt user to enable Wi-Fi calling if the call ends due to out of service and Wi-Fi is connected ● Rename “Dialpad” to “Keypad” ● Show "Private number" for restricted calls ● Delete unused items (vcard, add contact, call history) from Phone menu Change-Id: I2a7e53532a24c21bf308bf0a6d178d7ddbca4958
Diffstat (limited to 'java/com/android/dialer/interactions')
-rw-r--r--java/com/android/dialer/interactions/AndroidManifest.xml20
-rw-r--r--java/com/android/dialer/interactions/ContactUpdateService.java48
-rw-r--r--java/com/android/dialer/interactions/PhoneNumberInteraction.java557
-rw-r--r--java/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java107
-rw-r--r--java/com/android/dialer/interactions/res/layout/phone_disambig_item.xml43
-rw-r--r--java/com/android/dialer/interactions/res/layout/set_primary_checkbox.xml32
-rw-r--r--java/com/android/dialer/interactions/res/values/strings.xml29
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>