Merge "Replace $(COMMON_JAVA_PACKAGE_SUFFIX) with .jar"
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 48796db..cc3f81b 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -109,6 +109,7 @@
import com.android.dialer.dialpadview.DialpadFragment;
import com.android.dialer.dialpadview.DialpadFragment.DialpadListener;
import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback;
+import com.android.dialer.duo.DuoComponent;
import com.android.dialer.interactions.PhoneNumberInteraction;
import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode;
import com.android.dialer.logging.DialerImpression;
@@ -836,6 +837,10 @@
.setActionTextColor(getResources().getColor(R.color.dialer_snackbar_action_text_color))
.show();
}
+ } else if (requestCode == ActivityRequestCodes.DIALTACTS_DUO) {
+ // We just returned from starting Duo for a task. Reload our reachability data since it
+ // may have changed after a user finished activating Duo.
+ DuoComponent.get(this).getDuo().reloadReachability(this);
}
super.onActivityResult(requestCode, resultCode, data);
}
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index a5a0cff..a6489cd 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -683,7 +683,11 @@
inviteVideoButtonView.setTag(IntentProvider.getDuoInviteIntentProvider(number));
inviteVideoButtonView.setVisibility(View.VISIBLE);
} else if (duo.isEnabled(mContext)) {
- setUpVideoButtonView.setTag(IntentProvider.getSetUpDuoIntentProvider());
+ if (!duo.isInstalled(mContext)) {
+ setUpVideoButtonView.setTag(IntentProvider.getInstallDuoIntentProvider());
+ } else {
+ setUpVideoButtonView.setTag(IntentProvider.getSetUpDuoIntentProvider());
+ }
setUpVideoButtonView.setVisibility(View.VISIBLE);
}
break;
diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java
index 996bca0..c86a260 100644
--- a/java/com/android/dialer/app/calllog/IntentProvider.java
+++ b/java/com/android/dialer/app/calllog/IntentProvider.java
@@ -104,6 +104,26 @@
};
}
+ public static IntentProvider getInstallDuoIntentProvider() {
+ return new IntentProvider() {
+ @Override
+ public Intent getIntent(Context context) {
+ return new Intent(
+ Intent.ACTION_VIEW,
+ new Uri.Builder()
+ .scheme("https")
+ .authority("play.google.com")
+ .appendEncodedPath("store/apps/details")
+ .appendQueryParameter("id", "com.google.android.apps.tachyon")
+ .appendQueryParameter(
+ "referrer",
+ "utm_source=dialer&utm_medium=text&utm_campaign=product") // This string is from
+ // the Duo team
+ .build());
+ }
+ };
+ }
+
public static IntentProvider getSetUpDuoIntentProvider() {
return new IntentProvider() {
@Override
diff --git a/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java b/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
index 2787320..a714b6d 100644
--- a/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
+++ b/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
@@ -187,7 +187,7 @@
return true;
}
- if (isVoicemailTranscriptionEnabled() && !isLegacyVoicemailUser()) {
+ if (isVoicemailTranscriptionAvailable() && !isLegacyVoicemailUser()) {
LogUtil.i(
"VoicemailTosMessageCreator.shouldShowTos", "showing TOS for Google transcription users");
return true;
@@ -203,7 +203,7 @@
return false;
}
- if (isVoicemailTranscriptionEnabled()) {
+ if (isVoicemailTranscriptionAvailable()) {
LogUtil.i(
"VoicemailTosMessageCreator.shouldShowPromo",
"showing promo for Google transcription users");
@@ -227,9 +227,10 @@
}
}
- private boolean isVoicemailTranscriptionEnabled() {
+ private boolean isVoicemailTranscriptionAvailable() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- && ConfigProviderBindings.get(context).getBoolean("voicemail_transcription_enabled", false);
+ && ConfigProviderBindings.get(context)
+ .getBoolean("voicemail_transcription_available", false);
}
private void showDeclineTosDialog(final PhoneAccountHandle handle) {
@@ -407,7 +408,7 @@
}
private CharSequence getNewUserDialerTos() {
- if (!isVoicemailTranscriptionEnabled()) {
+ if (!isVoicemailTranscriptionAvailable()) {
return "";
}
@@ -416,7 +417,7 @@
}
private CharSequence getExistingUserDialerTos() {
- if (!isVoicemailTranscriptionEnabled()) {
+ if (!isVoicemailTranscriptionAvailable()) {
return "";
}
diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java
index c29f9e9..b314e26 100644
--- a/java/com/android/dialer/calldetails/CallDetailsActivity.java
+++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java
@@ -19,9 +19,12 @@
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.CallLog;
@@ -35,11 +38,13 @@
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;
+import com.android.dialer.CoalescedIds;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.assisteddialing.ui.AssistedDialingSettingActivity;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.AsyncTaskExecutors;
@@ -62,6 +67,7 @@
import com.android.dialer.postcall.PostCall;
import com.android.dialer.precall.PreCall;
import com.android.dialer.protos.ProtoParsers;
+import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import java.lang.ref.WeakReference;
@@ -71,10 +77,12 @@
/** Displays the details of a specific call log entry. */
public class CallDetailsActivity extends AppCompatActivity {
+ private static final int CALL_DETAILS_LOADER_ID = 0;
public static final String EXTRA_PHONE_NUMBER = "phone_number";
public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data";
public static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries";
+ public static final String EXTRA_COALESCED_CALL_LOG_IDS = "coalesced_call_log_ids";
public static final String EXTRA_CONTACT = "contact";
public static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id";
private static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing";
@@ -93,23 +101,47 @@
private DialerContact contact;
private CallDetailsAdapter adapter;
+ // This will be present only when the activity is launched from the new call log UI, i.e., a list
+ // of coalesced annotated call log IDs is included in the intent.
+ private Optional<CoalescedIds> coalescedCallLogIds = Optional.absent();
+
public static boolean isLaunchIntent(Intent intent) {
return intent.getComponent() != null
&& CallDetailsActivity.class.getName().equals(intent.getComponent().getClassName());
}
+ /**
+ * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the old call log
+ * UI.
+ */
public static Intent newInstance(
Context context,
- @NonNull CallDetailsEntries details,
- @NonNull DialerContact contact,
+ CallDetailsEntries details,
+ DialerContact contact,
boolean canReportCallerId,
boolean canSupportAssistedDialing) {
- Assert.isNotNull(details);
- Assert.isNotNull(contact);
-
Intent intent = new Intent(context, CallDetailsActivity.class);
- ProtoParsers.put(intent, EXTRA_CONTACT, contact);
- ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, details);
+ ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact));
+ ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, Assert.isNotNull(details));
+ intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
+ intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
+ return intent;
+ }
+
+ /**
+ * Returns an {@link Intent} for launching the {@link CallDetailsActivity} from the new call log
+ * UI.
+ */
+ public static Intent newInstance(
+ Context context,
+ CoalescedIds coalescedAnnotatedCallLogIds,
+ DialerContact contact,
+ boolean canReportCallerId,
+ boolean canSupportAssistedDialing) {
+ Intent intent = new Intent(context, CallDetailsActivity.class);
+ ProtoParsers.put(intent, EXTRA_CONTACT, Assert.isNotNull(contact));
+ ProtoParsers.put(
+ intent, EXTRA_COALESCED_CALL_LOG_IDS, Assert.isNotNull(coalescedAnnotatedCallLogIds));
intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
return intent;
@@ -166,10 +198,30 @@
}
private void onHandleIntent(Intent intent) {
+ boolean hasCallDetailsEntries = intent.hasExtra(EXTRA_CALL_DETAILS_ENTRIES);
+ boolean hasCoalescedCallLogIds = intent.hasExtra(EXTRA_COALESCED_CALL_LOG_IDS);
+ Assert.checkArgument(
+ (hasCallDetailsEntries && !hasCoalescedCallLogIds)
+ || (!hasCallDetailsEntries && hasCoalescedCallLogIds),
+ "One and only one of EXTRA_CALL_DETAILS_ENTRIES and EXTRA_COALESCED_CALL_LOG_IDS "
+ + "can be included in the intent.");
+
contact = ProtoParsers.getTrusted(intent, EXTRA_CONTACT, DialerContact.getDefaultInstance());
- entries =
- ProtoParsers.getTrusted(
- intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance());
+ if (hasCallDetailsEntries) {
+ entries =
+ ProtoParsers.getTrusted(
+ intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance());
+ } else {
+ entries = CallDetailsEntries.getDefaultInstance();
+ coalescedCallLogIds =
+ Optional.of(
+ ProtoParsers.getTrusted(
+ intent, EXTRA_COALESCED_CALL_LOG_IDS, CoalescedIds.getDefaultInstance()));
+ getLoaderManager()
+ .initLoader(
+ CALL_DETAILS_LOADER_ID, /* args = */ null, new CallDetailsLoaderCallbacks(this));
+ }
+
adapter =
new CallDetailsAdapter(
this /* context */,
@@ -191,6 +243,43 @@
super.onBackPressed();
}
+ /**
+ * {@link LoaderCallbacks} for {@link CallDetailsCursorLoader}, which loads call detail entries
+ * from {@link AnnotatedCallLog}.
+ */
+ private static final class CallDetailsLoaderCallbacks implements LoaderCallbacks<Cursor> {
+ private final CallDetailsActivity activity;
+
+ CallDetailsLoaderCallbacks(CallDetailsActivity callDetailsActivity) {
+ this.activity = callDetailsActivity;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ Assert.checkState(activity.coalescedCallLogIds.isPresent());
+
+ return new CallDetailsCursorLoader(activity, activity.coalescedCallLogIds.get());
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(data));
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ updateCallDetailsEntries(CallDetailsEntries.getDefaultInstance());
+ }
+
+ private void updateCallDetailsEntries(CallDetailsEntries newEntries) {
+ activity.entries = newEntries;
+ activity.adapter.updateCallDetailsEntries(newEntries.getEntriesList());
+ EnrichedCallComponent.get(activity)
+ .getEnrichedCallManager()
+ .requestAllHistoricalData(activity.contact.getNumber(), newEntries);
+ }
+ }
+
/** Delete specified calls from the call log. */
private static class DeleteCallsTask extends AsyncTask<Void, Void, Void> {
// Use a weak reference to hold the Activity so that there is no memory leak.
diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java
index 9095b86..030366e 100644
--- a/java/com/android/dialer/calldetails/CallDetailsAdapter.java
+++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java
@@ -115,7 +115,9 @@
@Override
public int getItemCount() {
- return callDetailsEntries.size() + 2; // Header + footer
+ return callDetailsEntries.isEmpty()
+ ? 0
+ : callDetailsEntries.size() + 2; // plus header and footer
}
void updateCallDetailsEntries(List<CallDetailsEntry> entries) {
diff --git a/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java
new file mode 100644
index 0000000..8385253
--- /dev/null
+++ b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 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.calldetails;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import com.android.dialer.CoalescedIds;
+import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
+import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
+import com.android.dialer.common.Assert;
+import com.android.dialer.duo.DuoConstants;
+
+/**
+ * A {@link CursorLoader} that loads call detail entries from {@link AnnotatedCallLog} for {@link
+ * CallDetailsActivity}.
+ */
+public final class CallDetailsCursorLoader extends CursorLoader {
+
+ // Columns in AnnotatedCallLog that are needed to build a CallDetailsEntry proto.
+ // Be sure to update (1) constants that store indexes of the elements and (2) method
+ // toCallDetailsEntry(Cursor) when updating this array.
+ public static final String[] COLUMNS_FOR_CALL_DETAILS =
+ new String[] {
+ AnnotatedCallLog._ID,
+ AnnotatedCallLog.CALL_TYPE,
+ AnnotatedCallLog.FEATURES,
+ AnnotatedCallLog.TIMESTAMP,
+ AnnotatedCallLog.DURATION,
+ AnnotatedCallLog.DATA_USAGE,
+ AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME
+ };
+
+ // Indexes for COLUMNS_FOR_CALL_DETAILS
+ private static final int ID = 0;
+ private static final int CALL_TYPE = 1;
+ private static final int FEATURES = 2;
+ private static final int TIMESTAMP = 3;
+ private static final int DURATION = 4;
+ private static final int DATA_USAGE = 5;
+ private static final int PHONE_ACCOUNT_COMPONENT_NAME = 6;
+
+ CallDetailsCursorLoader(Context context, CoalescedIds coalescedIds) {
+ super(
+ context,
+ AnnotatedCallLog.CONTENT_URI,
+ COLUMNS_FOR_CALL_DETAILS,
+ annotatedCallLogIdsSelection(coalescedIds),
+ annotatedCallLogIdsSelectionArgs(coalescedIds),
+ AnnotatedCallLog.TIMESTAMP + " DESC");
+ }
+
+ /**
+ * Build a string of the form "COLUMN_NAME IN (?, ?, ..., ?)", where COLUMN_NAME is the name of
+ * the ID column in {@link AnnotatedCallLog}.
+ *
+ * <p>This string will be used as the {@code selection} parameter to initialize the loader.
+ */
+ private static String annotatedCallLogIdsSelection(CoalescedIds coalescedIds) {
+ // First, build a string of question marks ('?') separated by commas (',').
+ StringBuilder questionMarks = new StringBuilder();
+ for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) {
+ if (i != 0) {
+ questionMarks.append(", ");
+ }
+ questionMarks.append("?");
+ }
+
+ return AnnotatedCallLog._ID + " IN (" + questionMarks + ")";
+ }
+
+ /**
+ * Returns a string that will be used as the {@code selectionArgs} parameter to initialize the
+ * loader.
+ */
+ private static String[] annotatedCallLogIdsSelectionArgs(CoalescedIds coalescedIds) {
+ String[] args = new String[coalescedIds.getCoalescedIdCount()];
+
+ for (int i = 0; i < coalescedIds.getCoalescedIdCount(); i++) {
+ args[i] = String.valueOf(coalescedIds.getCoalescedId(i));
+ }
+
+ return args;
+ }
+
+ /**
+ * Creates a new {@link CallDetailsEntries} from the entire data set loaded by this loader.
+ *
+ * @param cursor A cursor pointing to the data set loaded by this loader. The caller must ensure
+ * the cursor is not null and the data set it points to is not empty.
+ * @return A {@link CallDetailsEntries} proto.
+ */
+ static CallDetailsEntries toCallDetailsEntries(Cursor cursor) {
+ Assert.isNotNull(cursor);
+ Assert.checkArgument(cursor.moveToFirst());
+
+ CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder();
+
+ do {
+ entries.addEntries(toCallDetailsEntry(cursor));
+ } while (cursor.moveToNext());
+
+ return entries.build();
+ }
+
+ /** Creates a new {@link CallDetailsEntry} from the provided cursor using its current position. */
+ private static CallDetailsEntry toCallDetailsEntry(Cursor cursor) {
+ CallDetailsEntry.Builder entry = CallDetailsEntry.newBuilder();
+ entry
+ .setCallId(cursor.getLong(ID))
+ .setCallType(cursor.getInt(CALL_TYPE))
+ .setFeatures(cursor.getInt(FEATURES))
+ .setDate(cursor.getLong(TIMESTAMP))
+ .setDuration(cursor.getLong(DURATION))
+ .setDataUsage(cursor.getLong(DATA_USAGE));
+
+ String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME);
+ entry.setIsDuoCall(
+ DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
+ .flattenToString()
+ .equals(phoneAccountComponentName));
+
+ return entry.build();
+ }
+}
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index 93e8414..fbb5831 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -197,7 +197,7 @@
}
// Also save the updated information so that it can be written to PhoneLookupHistory
// in onSuccessfulFill.
- String normalizedNumber = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber);
+ String normalizedNumber = dialerPhoneNumberUtil.normalizeNumber(dialerPhoneNumber);
phoneLookupHistoryRowsToUpdate.put(normalizedNumber, upToDateInfo);
}
}
@@ -369,7 +369,7 @@
DialerPhoneNumberUtil dialerPhoneNumberUtil =
new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance());
Map<DialerPhoneNumber, String> dialerPhoneNumberToNormalizedNumbers =
- Maps.asMap(uniqueDialerPhoneNumbers, dialerPhoneNumberUtil::formatToE164);
+ Maps.asMap(uniqueDialerPhoneNumbers, dialerPhoneNumberUtil::normalizeNumber);
// Convert values to a set to remove any duplicates that are the result of two
// DialerPhoneNumbers mapping to the same normalized number.
@@ -508,7 +508,7 @@
for (Entry<DialerPhoneNumber, Set<Long>> entry : annotatedCallLogIdsByNumber.entrySet()) {
DialerPhoneNumber dialerPhoneNumber = entry.getKey();
Set<Long> idsForDialerPhoneNumber = entry.getValue();
- String normalizedNumber = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber);
+ String normalizedNumber = dialerPhoneNumberUtil.normalizeNumber(dialerPhoneNumber);
Set<Long> idsForNormalizedNumber = idsByNormalizedNumber.get(normalizedNumber);
if (idsForNormalizedNumber == null) {
idsForNormalizedNumber = new ArraySet<>();
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index 550e284..cccaa73 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -24,7 +24,6 @@
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import com.android.dialer.calldetails.CallDetailsActivity;
-import com.android.dialer.calldetails.CallDetailsEntries;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.calllog.model.CoalescedRow;
import com.android.dialer.calllogutils.PhoneAccountUtils;
@@ -65,7 +64,7 @@
// TODO(zachh): Revisit if DialerContact is the best thing to pass to CallDetails; could
// it use a ContactPrimaryActionInfo instead?
- addModuleForAccessingCallDetails(context, createDialerContactFromRow(row), modules);
+ addModuleForAccessingCallDetails(context, row, modules);
return modules;
}
@@ -182,10 +181,9 @@
}
private static void addModuleForAccessingCallDetails(
- Context context, DialerContact dialerContact, List<ContactActionModule> modules) {
- // TODO(zachh): Load CallDetailsEntries and canReportInaccurateNumber in
- // CallDetailsActivity (see also isPeopleApiSource(sourceType)).
- CallDetailsEntries callDetailsEntries = CallDetailsEntries.getDefaultInstance();
+ Context context, CoalescedRow row, List<ContactActionModule> modules) {
+ // TODO(zachh): Load canReportInaccurateNumber in CallDetailsActivity
+ // (see also isPeopleApiSource(sourceType)).
boolean canReportInaccurateNumber = false;
boolean canSupportAssistedDialing = false; // TODO(zachh): Properly set value.
@@ -194,8 +192,8 @@
context,
CallDetailsActivity.newInstance(
context,
- callDetailsEntries,
- dialerContact,
+ row.coalescedIds(),
+ createDialerContactFromRow(row),
canReportInaccurateNumber,
canSupportAssistedDialing),
R.string.call_details_menu_label,
diff --git a/java/com/android/dialer/database/DialerDatabaseHelper.java b/java/com/android/dialer/database/DialerDatabaseHelper.java
index 9a25812..113e863 100644
--- a/java/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/java/com/android/dialer/database/DialerDatabaseHelper.java
@@ -26,19 +26,21 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
-import android.os.AsyncTask;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import com.android.contacts.common.R;
import com.android.contacts.common.util.StopWatch;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
import com.android.dialer.smartdial.SmartDialNameMatcher;
import com.android.dialer.smartdial.SmartDialPrefix;
@@ -332,7 +334,11 @@
/** Starts the database upgrade process in the background. */
public void startSmartDialUpdateThread() {
if (PermissionsUtil.hasContactsReadPermissions(mContext)) {
- new SmartDialUpdateAsyncTask().execute();
+ DialerExecutorComponent.get(mContext)
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new UpdateSmartDialWorker())
+ .build()
+ .executeParallel(null);
}
}
@@ -1228,10 +1234,11 @@
}
}
- private class SmartDialUpdateAsyncTask extends AsyncTask<Object, Object, Object> {
+ private class UpdateSmartDialWorker implements Worker<Void, Void> {
+ @Nullable
@Override
- protected Object doInBackground(Object... objects) {
+ public Void doInBackground(@Nullable Void input) throws Throwable {
updateSmartDialDatabase();
return null;
}
diff --git a/java/com/android/dialer/duo/Duo.java b/java/com/android/dialer/duo/Duo.java
index 9012dee..d914784 100644
--- a/java/com/android/dialer/duo/Duo.java
+++ b/java/com/android/dialer/duo/Duo.java
@@ -30,40 +30,65 @@
/** Interface for Duo video call integration. */
public interface Duo {
+ /** @return true if the Duo integration is enabled on this device. */
boolean isEnabled(@NonNull Context context);
+ /** @return true if Duo is installed on this device. */
+ boolean isInstalled(@NonNull Context context);
+
/**
* @return true if Duo is installed and the user has gone through the set-up flow confirming their
* phone number.
*/
boolean isActivated(@NonNull Context context);
+ /** @return true if the parameter number is reachable on Duo. */
@MainThread
boolean isReachable(@NonNull Context context, @Nullable String number);
- /** @return {@code null} if result is unknown. */
+ /**
+ * @return true if the number supports upgrading a voice call to a Duo video call. Returns {@code
+ * null} if result is unknown.
+ */
@MainThread
Optional<Boolean> supportsUpgrade(@NonNull Context context, @Nullable String number);
+ /** Starts a task to update the reachability of the parameter numbers asynchronously. */
@MainThread
void updateReachability(@NonNull Context context, @NonNull List<String> numbers);
+ /**
+ * Clears the current reachability data and starts a task to load the latest reachability data
+ * asynchronously.
+ */
+ @MainThread
+ void reloadReachability(@NonNull Context context);
+
+ /**
+ * @return an Intent to start a Duo video call with the parameter number. Must be started using
+ * startActivityForResult.
+ */
@MainThread
Intent getIntent(@NonNull Context context, @NonNull String number);
+ /** Requests upgrading the parameter ongoing call to a Duo video call. */
@MainThread
void requestUpgrade(@NonNull Context context, Call call);
+ /** Registers a listener for reachability data changes. */
@MainThread
void registerListener(@NonNull DuoListener listener);
+ /** Unregisters a listener for reachability data changes. */
@MainThread
void unregisterListener(@NonNull DuoListener listener);
+ /** The string resource to use for outgoing Duo call entries in call details. */
@StringRes
@MainThread
int getOutgoingCallTypeText();
+ /** The string resource to use for incoming Duo call entries in call details. */
@StringRes
@MainThread
int getIncomingCallTypeText();
diff --git a/java/com/android/dialer/duo/stub/DuoStub.java b/java/com/android/dialer/duo/stub/DuoStub.java
index 628d6dc..cfa400a 100644
--- a/java/com/android/dialer/duo/stub/DuoStub.java
+++ b/java/com/android/dialer/duo/stub/DuoStub.java
@@ -41,6 +41,11 @@
}
@Override
+ public boolean isInstalled(@NonNull Context context) {
+ return false;
+ }
+
+ @Override
public boolean isActivated(@NonNull Context context) {
return false;
}
@@ -68,6 +73,9 @@
Assert.isNotNull(numbers);
}
+ @Override
+ public void reloadReachability(@NonNull Context context) {}
+
@MainThread
@Override
public Intent getIntent(@NonNull Context context, @NonNull String number) {
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
index 3829a8d..501b088 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
@@ -30,17 +30,21 @@
import android.text.TextUtils;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.inject.ApplicationContext;
import com.android.dialer.phonelookup.PhoneLookup;
import com.android.dialer.phonelookup.PhoneLookupInfo;
import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo;
+import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
import com.android.dialer.storage.Unencrypted;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -61,8 +65,9 @@
Phone.TYPE, // 3
Phone.LABEL, // 4
Phone.NORMALIZED_NUMBER, // 5
- Phone.CONTACT_ID, // 6
- Phone.LOOKUP_KEY // 7
+ Phone.NUMBER, // 6
+ Phone.CONTACT_ID, // 7
+ Phone.LOOKUP_KEY // 8
};
private static final int CP2_INFO_NAME_INDEX = 0;
@@ -70,9 +75,10 @@
private static final int CP2_INFO_PHOTO_ID_INDEX = 2;
private static final int CP2_INFO_TYPE_INDEX = 3;
private static final int CP2_INFO_LABEL_INDEX = 4;
- private static final int CP2_INFO_NUMBER_INDEX = 5;
- private static final int CP2_INFO_CONTACT_ID_INDEX = 6;
- private static final int CP2_INFO_LOOKUP_KEY_INDEX = 7;
+ private static final int CP2_INFO_NORMALIZED_NUMBER_INDEX = 5;
+ private static final int CP2_INFO_NUMBER_INDEX = 6;
+ private static final int CP2_INFO_CONTACT_ID_INDEX = 7;
+ private static final int CP2_INFO_LOOKUP_KEY_INDEX = 8;
private final Context appContext;
private final SharedPreferences sharedPreferences;
@@ -108,19 +114,37 @@
|| contactsDeleted(lastModified);
}
- /** Returns set of contact ids that correspond to {@code phoneNumbers} if the contact exists. */
- private Set<Long> queryPhoneTableForContactIds(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
+ /**
+ * Returns set of contact ids that correspond to {@code dialerPhoneNumbers} if the contact exists.
+ */
+ private Set<Long> queryPhoneTableForContactIds(
+ ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers) {
Set<Long> contactIds = new ArraySet<>();
+
+ PartitionedNumbers partitionedNumbers = new PartitionedNumbers(dialerPhoneNumbers);
+
+ // First use the E164 numbers to query the NORMALIZED_NUMBER column.
+ contactIds.addAll(
+ queryPhoneTableForContactIdsBasedOnE164(partitionedNumbers.validE164Numbers()));
+
+ // Then run a separate query using the NUMBER column to handle numbers that can't be formatted.
+ contactIds.addAll(
+ queryPhoneTableForContactIdsBasedOnRawNumber(partitionedNumbers.unformattableNumbers()));
+
+ return contactIds;
+ }
+
+ private Set<Long> queryPhoneTableForContactIdsBasedOnE164(Set<String> validE164Numbers) {
+ Set<Long> contactIds = new ArraySet<>();
+ if (validE164Numbers.isEmpty()) {
+ return contactIds;
+ }
try (Cursor cursor =
- appContext
- .getContentResolver()
- .query(
- Phone.CONTENT_URI,
- new String[] {Phone.CONTACT_ID},
- Phone.NORMALIZED_NUMBER + " IN (" + questionMarks(phoneNumbers.size()) + ")",
- contactIdsSelectionArgs(phoneNumbers),
- null)) {
- cursor.moveToPosition(-1);
+ queryPhoneTableBasedOnE164(new String[] {Phone.CONTACT_ID}, validE164Numbers)) {
+ if (cursor == null) {
+ LogUtil.w("Cp2PhoneLookup.queryPhoneTableForContactIdsBasedOnE164", "null cursor");
+ return contactIds;
+ }
while (cursor.moveToNext()) {
contactIds.add(cursor.getLong(0 /* columnIndex */));
}
@@ -128,18 +152,22 @@
return contactIds;
}
- private static String[] contactIdsSelectionArgs(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
- String[] args = new String[phoneNumbers.size()];
- int i = 0;
- for (DialerPhoneNumber phoneNumber : phoneNumbers) {
- args[i++] = getNormalizedNumber(phoneNumber);
+ private Set<Long> queryPhoneTableForContactIdsBasedOnRawNumber(Set<String> unformattableNumbers) {
+ Set<Long> contactIds = new ArraySet<>();
+ if (unformattableNumbers.isEmpty()) {
+ return contactIds;
}
- return args;
- }
-
- private static String getNormalizedNumber(DialerPhoneNumber phoneNumber) {
- // TODO(calderwoodra): implement normalization logic that matches contacts.
- return phoneNumber.getRawInput().getNumber();
+ try (Cursor cursor =
+ queryPhoneTableBasedOnRawNumber(new String[] {Phone.CONTACT_ID}, unformattableNumbers)) {
+ if (cursor == null) {
+ LogUtil.w("Cp2PhoneLookup.queryPhoneTableForContactIdsBasedOnE164", "null cursor");
+ return contactIds;
+ }
+ while (cursor.moveToNext()) {
+ contactIds.add(cursor.getLong(0 /* columnIndex */));
+ }
+ }
+ return contactIds;
}
/** Returns true if any contacts were modified after {@code lastModified}. */
@@ -188,6 +216,10 @@
DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?",
new String[] {Long.toString(lastModified)},
null)) {
+ if (cursor == null) {
+ LogUtil.w("Cp2PhoneLookup.contactsDeleted", "null cursor");
+ return false;
+ }
return cursor.getCount() > 0;
}
}
@@ -312,40 +344,65 @@
// Query the contacts table and get those that whose Contacts.CONTACT_LAST_UPDATED_TIMESTAMP is
// after lastModified, such that Contacts._ID is in our set of contact IDs we build above.
- try (Cursor cursor = queryContactsTableForContacts(contactIds, lastModified)) {
- int contactIdIndex = cursor.getColumnIndex(Contacts._ID);
- int lastUpdatedIndex = cursor.getColumnIndex(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- // Find the DialerPhoneNumber for each contact id and add it to our updated numbers set.
- // These, along with our number not associated with any Cp2ContactInfo need to be updated.
- long contactId = cursor.getLong(contactIdIndex);
- updatedNumbers.addAll(getDialerPhoneNumber(existingInfoMap, contactId));
- long lastUpdatedTimestamp = cursor.getLong(lastUpdatedIndex);
- if (currentLastTimestampProcessed == null
- || currentLastTimestampProcessed < lastUpdatedTimestamp) {
- currentLastTimestampProcessed = lastUpdatedTimestamp;
+ if (!contactIds.isEmpty()) {
+ try (Cursor cursor = queryContactsTableForContacts(contactIds, lastModified)) {
+ int contactIdIndex = cursor.getColumnIndex(Contacts._ID);
+ int lastUpdatedIndex = cursor.getColumnIndex(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP);
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ // Find the DialerPhoneNumber for each contact id and add it to our updated numbers set.
+ // These, along with our number not associated with any Cp2ContactInfo need to be updated.
+ long contactId = cursor.getLong(contactIdIndex);
+ updatedNumbers.addAll(
+ findDialerPhoneNumbersContainingContactId(existingInfoMap, contactId));
+ long lastUpdatedTimestamp = cursor.getLong(lastUpdatedIndex);
+ if (currentLastTimestampProcessed == null
+ || currentLastTimestampProcessed < lastUpdatedTimestamp) {
+ currentLastTimestampProcessed = lastUpdatedTimestamp;
+ }
}
}
}
- // Query the Phone table and build Cp2ContactInfo for each DialerPhoneNumber in our
- // updatedNumbers set.
+ if (updatedNumbers.isEmpty()) {
+ return new ArrayMap<>();
+ }
+
Map<DialerPhoneNumber, Set<Cp2ContactInfo>> map = new ArrayMap<>();
- try (Cursor cursor = getAllCp2Rows(updatedNumbers)) {
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- // Map each dialer phone number to it's new cp2 info
- Set<DialerPhoneNumber> phoneNumbers =
- getDialerPhoneNumbers(updatedNumbers, cursor.getString(CP2_INFO_NUMBER_INDEX));
- Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(appContext, cursor);
- for (DialerPhoneNumber phoneNumber : phoneNumbers) {
- if (map.containsKey(phoneNumber)) {
- map.get(phoneNumber).add(info);
- } else {
- Set<Cp2ContactInfo> cp2ContactInfos = new ArraySet<>();
- cp2ContactInfos.add(info);
- map.put(phoneNumber, cp2ContactInfos);
+
+ // Divide the numbers into those we can format to E164 and those we can't. Then run separate
+ // queries against the contacts table using the NORMALIZED_NUMBER and NUMBER columns.
+ // TODO(zachh): These queries are inefficient without a lastModified column to filter on.
+ PartitionedNumbers partitionedNumbers = new PartitionedNumbers(updatedNumbers);
+ if (!partitionedNumbers.validE164Numbers().isEmpty()) {
+ try (Cursor cursor =
+ queryPhoneTableBasedOnE164(CP2_INFO_PROJECTION, partitionedNumbers.validE164Numbers())) {
+ if (cursor == null) {
+ LogUtil.w("Cp2PhoneLookup.buildMapForUpdatedOrAddedContacts", "null cursor");
+ } else {
+ while (cursor.moveToNext()) {
+ String e164Number = cursor.getString(CP2_INFO_NORMALIZED_NUMBER_INDEX);
+ Set<DialerPhoneNumber> dialerPhoneNumbers =
+ partitionedNumbers.dialerPhoneNumbersForE164(e164Number);
+ Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(appContext, cursor);
+ addInfo(map, dialerPhoneNumbers, info);
+ }
+ }
+ }
+ }
+ if (!partitionedNumbers.unformattableNumbers().isEmpty()) {
+ try (Cursor cursor =
+ queryPhoneTableBasedOnRawNumber(
+ CP2_INFO_PROJECTION, partitionedNumbers.unformattableNumbers())) {
+ if (cursor == null) {
+ LogUtil.w("Cp2PhoneLookup.buildMapForUpdatedOrAddedContacts", "null cursor");
+ } else {
+ while (cursor.moveToNext()) {
+ String unformattableNumber = cursor.getString(CP2_INFO_NUMBER_INDEX);
+ Set<DialerPhoneNumber> dialerPhoneNumbers =
+ partitionedNumbers.dialerPhoneNumbersForUnformattable(unformattableNumber);
+ Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(appContext, cursor);
+ addInfo(map, dialerPhoneNumbers, info);
}
}
}
@@ -354,20 +411,45 @@
}
/**
- * Returns cursor with projection {@link #CP2_INFO_PROJECTION} and only phone numbers that are in
- * {@code updateNumbers}.
+ * Adds the {@code cp2ContactInfo} to the entries for all specified {@code dialerPhoneNumbers} in
+ * the {@code map}.
*/
- private Cursor getAllCp2Rows(Set<DialerPhoneNumber> updatedNumbers) {
- String where = Phone.NORMALIZED_NUMBER + " IN (" + questionMarks(updatedNumbers.size()) + ")";
- String[] selectionArgs = new String[updatedNumbers.size()];
- int i = 0;
- for (DialerPhoneNumber phoneNumber : updatedNumbers) {
- selectionArgs[i++] = getNormalizedNumber(phoneNumber);
+ private static void addInfo(
+ Map<DialerPhoneNumber, Set<Cp2ContactInfo>> map,
+ Set<DialerPhoneNumber> dialerPhoneNumbers,
+ Cp2ContactInfo cp2ContactInfo) {
+ for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) {
+ if (map.containsKey(dialerPhoneNumber)) {
+ map.get(dialerPhoneNumber).add(cp2ContactInfo);
+ } else {
+ Set<Cp2ContactInfo> cp2ContactInfos = new ArraySet<>();
+ cp2ContactInfos.add(cp2ContactInfo);
+ map.put(dialerPhoneNumber, cp2ContactInfos);
+ }
}
+ }
+ private Cursor queryPhoneTableBasedOnE164(String[] projection, Set<String> validE164Numbers) {
return appContext
.getContentResolver()
- .query(Phone.CONTENT_URI, CP2_INFO_PROJECTION, where, selectionArgs, null);
+ .query(
+ Phone.CONTENT_URI,
+ projection,
+ Phone.NORMALIZED_NUMBER + " IN (" + questionMarks(validE164Numbers.size()) + ")",
+ validE164Numbers.toArray(new String[validE164Numbers.size()]),
+ null);
+ }
+
+ private Cursor queryPhoneTableBasedOnRawNumber(
+ String[] projection, Set<String> unformattableNumbers) {
+ return appContext
+ .getContentResolver()
+ .query(
+ Phone.CONTENT_URI,
+ projection,
+ Phone.NUMBER + " IN (" + questionMarks(unformattableNumbers.size()) + ")",
+ unformattableNumbers.toArray(new String[unformattableNumbers.size()]),
+ null);
}
/**
@@ -466,7 +548,8 @@
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
long contactId = cursor.getLong(contactIdIndex);
- deletedPhoneNumbers.addAll(getDialerPhoneNumber(existingInfoMap, contactId));
+ deletedPhoneNumbers.addAll(
+ findDialerPhoneNumbersContainingContactId(existingInfoMap, contactId));
long deletedTime = cursor.getLong(deletedTimeIndex);
if (currentLastTimestampProcessed == null || currentLastTimestampProcessed < deletedTime) {
// TODO(zachh): There's a problem here if a contact for a new row is deleted?
@@ -476,20 +559,7 @@
return deletedPhoneNumbers;
}
- private static Set<DialerPhoneNumber> getDialerPhoneNumbers(
- Set<DialerPhoneNumber> phoneNumbers, String number) {
- Set<DialerPhoneNumber> matches = new ArraySet<>();
- for (DialerPhoneNumber phoneNumber : phoneNumbers) {
- if (getNormalizedNumber(phoneNumber).equals(number)) {
- matches.add(phoneNumber);
- }
- }
- Assert.checkArgument(
- matches.size() > 0, "Couldn't find DialerPhoneNumber for number: " + number);
- return matches;
- }
-
- private static Set<DialerPhoneNumber> getDialerPhoneNumber(
+ private static Set<DialerPhoneNumber> findDialerPhoneNumbersContainingContactId(
ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap, long contactId) {
Set<DialerPhoneNumber> matches = new ArraySet<>();
for (Entry<DialerPhoneNumber, PhoneLookupInfo> entry : existingInfoMap.entrySet()) {
@@ -514,4 +584,56 @@
}
return where.toString();
}
+
+ /**
+ * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} by those that can be formatted to
+ * E164 and those that cannot.
+ */
+ private static class PartitionedNumbers {
+ private Map<String, Set<DialerPhoneNumber>> e164NumbersToDialerPhoneNumbers = new ArrayMap<>();
+ private Map<String, Set<DialerPhoneNumber>> unformattableNumbersToDialerPhoneNumbers =
+ new ArrayMap<>();
+
+ PartitionedNumbers(Set<DialerPhoneNumber> dialerPhoneNumbers) {
+ DialerPhoneNumberUtil dialerPhoneNumberUtil =
+ new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance());
+ for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) {
+ Optional<String> e164 = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber);
+ if (e164.isPresent()) {
+ String validE164 = e164.get();
+ Set<DialerPhoneNumber> currentNumbers = e164NumbersToDialerPhoneNumbers.get(validE164);
+ if (currentNumbers == null) {
+ currentNumbers = new ArraySet<>();
+ e164NumbersToDialerPhoneNumbers.put(validE164, currentNumbers);
+ }
+ currentNumbers.add(dialerPhoneNumber);
+ } else {
+ String unformattableNumber = dialerPhoneNumber.getRawInput().getNumber();
+ Set<DialerPhoneNumber> currentNumbers =
+ unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber);
+ if (currentNumbers == null) {
+ currentNumbers = new ArraySet<>();
+ unformattableNumbersToDialerPhoneNumbers.put(unformattableNumber, currentNumbers);
+ }
+ currentNumbers.add(dialerPhoneNumber);
+ }
+ }
+ }
+
+ Set<String> unformattableNumbers() {
+ return unformattableNumbersToDialerPhoneNumbers.keySet();
+ }
+
+ Set<String> validE164Numbers() {
+ return e164NumbersToDialerPhoneNumbers.keySet();
+ }
+
+ Set<DialerPhoneNumber> dialerPhoneNumbersForE164(String e164) {
+ return e164NumbersToDialerPhoneNumbers.get(e164);
+ }
+
+ Set<DialerPhoneNumber> dialerPhoneNumbersForUnformattable(String unformattableNumber) {
+ return unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber);
+ }
+ }
}
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
index 8e08fb2..39e3866 100644
--- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
+++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
@@ -340,7 +340,12 @@
try (Cursor phoneLookupCursor =
mContext
.getContentResolver()
- .query(uri, PhoneQuery.getPhoneLookupProjection(uri), null, null, null)) {
+ .query(
+ uri,
+ PhoneQuery.getPhoneLookupProjection(uri),
+ null /* selection */,
+ null /* selectionArgs */,
+ null /* sortOrder */)) {
if (phoneLookupCursor == null) {
LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null");
return null;
@@ -350,15 +355,8 @@
return ContactInfo.EMPTY;
}
- Cursor matchedCursor =
- PhoneNumberHelper.getCursorMatchForContactLookupUri(
- phoneLookupCursor, PhoneQuery.MATCHED_NUMBER, uri);
- if (matchedCursor == null) {
- return ContactInfo.EMPTY;
- }
-
- String lookupKey = matchedCursor.getString(PhoneQuery.LOOKUP_KEY);
- ContactInfo contactInfo = createPhoneLookupContactInfo(matchedCursor, lookupKey);
+ String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY);
+ ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey);
fillAdditionalContactInfo(mContext, contactInfo);
return contactInfo;
}
diff --git a/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java b/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java
index ac011d4..d23b5a1 100644
--- a/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java
+++ b/java/com/android/dialer/phonenumberproto/DialerPhoneNumberUtil.java
@@ -25,6 +25,7 @@
import com.android.dialer.DialerPhoneNumber.RawInput;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.google.common.base.Optional;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.i18n.phonenumbers.NumberParseException;
@@ -127,17 +128,30 @@
}
/**
- * Formats the provided number to e164 format. May return raw number if number is unparseable.
+ * Formats the provided number to e164 format or return raw number if number is unparseable.
*
* @see PhoneNumberUtil#format(PhoneNumber, PhoneNumberFormat)
*/
@WorkerThread
- public String formatToE164(@NonNull DialerPhoneNumber number) {
+ public String normalizeNumber(DialerPhoneNumber number) {
+ Assert.isWorkerThread();
+ return formatToE164(number).or(number.getRawInput().getNumber());
+ }
+
+ /**
+ * Formats the provided number to e164 format if possible.
+ *
+ * @see PhoneNumberUtil#format(PhoneNumber, PhoneNumberFormat)
+ */
+ @WorkerThread
+ public Optional<String> formatToE164(DialerPhoneNumber number) {
Assert.isWorkerThread();
if (number.hasDialerInternalPhoneNumber()) {
- return phoneNumberUtil.format(
- Converter.protoToPojo(number.getDialerInternalPhoneNumber()), PhoneNumberFormat.E164);
+ return Optional.of(
+ phoneNumberUtil.format(
+ Converter.protoToPojo(number.getDialerInternalPhoneNumber()),
+ PhoneNumberFormat.E164));
}
- return number.getRawInput().getNumber();
+ return Optional.absent();
}
}
diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
index e32ace5..be1b062 100644
--- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
+++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
@@ -17,8 +17,6 @@
package com.android.dialer.phonenumberutil;
import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
import android.os.Trace;
import android.provider.CallLog;
import android.support.annotation.NonNull;
@@ -29,7 +27,6 @@
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
-import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.compat.CompatUtils;
import com.android.dialer.compat.telephony.TelephonyManagerCompat;
@@ -53,78 +50,6 @@
}
/**
- * Find the cursor pointing to a number that matches the number in a contact lookup URI.
- *
- * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the
- * Contacts Provider uses {@link PhoneNumberUtils#compare(String, String)}, which ignores special
- * dialable characters such as '#', '*', '+', etc. This makes it possible for the cursor returned
- * by the Contacts Provider to have multiple rows even when the URI asks for a specific number.
- *
- * <p>For example, suppose the user has two contacts whose numbers are "#123" and "123",
- * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, the
- * following strategy is employed to find a match.
- *
- * <p>If the cursor points to a global phone number (i.e., a number that can be accepted by {@link
- * PhoneNumberUtils#isGlobalPhoneNumber(String)}) and the lookup number in the URI is a PARTIAL
- * match, return the cursor.
- *
- * <p>If the cursor points to a number that is not a global phone number, return the cursor iff
- * the lookup number in the URI is an EXACT match.
- *
- * <p>Return null in all other circumstances.
- *
- * @param cursor A cursor returned by the Contacts Provider.
- * @param columnIndexForNumber The index of the column where phone numbers are stored. It is the
- * caller's responsibility to pass the correct column index.
- * @param contactLookupUri A URI used to retrieve a contact via the Contacts Provider. It is the
- * caller's responsibility to ensure the URI is one that asks for a specific phone number.
- * @return The cursor considered as a match by the description above or null if no such cursor can
- * be found.
- */
- public static Cursor getCursorMatchForContactLookupUri(
- Cursor cursor, int columnIndexForNumber, Uri contactLookupUri) {
- if (cursor == null || contactLookupUri == null) {
- return null;
- }
-
- if (!cursor.moveToFirst()) {
- return null;
- }
-
- Assert.checkArgument(
- 0 <= columnIndexForNumber && columnIndexForNumber < cursor.getColumnCount());
-
- String lookupNumber = contactLookupUri.getLastPathSegment();
- if (lookupNumber == null) {
- return null;
- }
-
- boolean isMatchFound;
- do {
- // All undialable characters should be converted/removed before comparing the lookup number
- // and the existing contact number.
- String rawExistingContactNumber =
- PhoneNumberUtils.stripSeparators(
- PhoneNumberUtils.convertKeypadLettersToDigits(
- cursor.getString(columnIndexForNumber)));
- String rawQueryNumber =
- PhoneNumberUtils.stripSeparators(
- PhoneNumberUtils.convertKeypadLettersToDigits(lookupNumber));
-
- isMatchFound =
- PhoneNumberUtils.isGlobalPhoneNumber(rawExistingContactNumber)
- ? rawExistingContactNumber.contains(rawQueryNumber)
- : rawExistingContactNumber.equals(rawQueryNumber);
-
- if (isMatchFound) {
- return cursor;
- }
- } while (cursor.moveToNext());
-
- return null;
- }
-
- /**
* Returns true if the given number is the number of the configured voicemail. To be able to
* mock-out this, it is not a static method.
*/
diff --git a/java/com/android/dialer/speeddial/DisambigDialog.java b/java/com/android/dialer/speeddial/DisambigDialog.java
new file mode 100644
index 0000000..ca02f41
--- /dev/null
+++ b/java/com/android/dialer/speeddial/DisambigDialog.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2017 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.speeddial;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.duo.DuoComponent;
+import com.android.dialer.precall.PreCall;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
+
+/** Disambiguation dialog for favorite contacts in {@link SpeedDialFragment}. */
+public class DisambigDialog extends DialogFragment {
+
+ @VisibleForTesting public static final String DISAMBIG_DIALOG_TAG = "disambig_dialog";
+ private static final String DISAMBIG_DIALOG_WORKER_TAG = "disambig_dialog_worker";
+
+ private final Set<String> phoneNumbers = new ArraySet<>();
+ private LinearLayout container;
+ private String lookupKey;
+
+ /** Show a disambiguation dialog for a starred contact without a favorite communication avenue. */
+ public static DisambigDialog show(String lookupKey, FragmentManager manager) {
+ DisambigDialog dialog = new DisambigDialog();
+ dialog.lookupKey = lookupKey;
+ dialog.show(manager, DISAMBIG_DIALOG_TAG);
+ return dialog;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ LayoutInflater inflater = getActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.disambig_dialog_layout, null, false);
+ container = view.findViewById(R.id.communication_avenue_container);
+ return new AlertDialog.Builder(getActivity()).setView(view).create();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ lookupContactInfo();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // TODO(calderwoodra): for simplicity, just dismiss the dialog on configuration change and
+ // consider changing this later.
+ dismiss();
+ }
+
+ private void lookupContactInfo() {
+ DialerExecutorComponent.get(getContext())
+ .dialerExecutorFactory()
+ .createUiTaskBuilder(
+ getFragmentManager(),
+ DISAMBIG_DIALOG_WORKER_TAG,
+ new LookupContactInfoWorker(getContext().getContentResolver()))
+ .onSuccess(this::insertOptions)
+ .onFailure(this::onLookupFailed)
+ .build()
+ .executeParallel(lookupKey);
+ }
+
+ /**
+ * Inflates and inserts the following in the dialog:
+ *
+ * <ul>
+ * <li>Header for each unique phone number
+ * <li>Clickable video option if the phone number is video reachable (ViLTE, Duo)
+ * <li>Clickable voice option
+ * </ul>
+ */
+ private void insertOptions(Cursor cursor) {
+ if (!cursorIsValid(cursor)) {
+ dismiss();
+ return;
+ }
+
+ do {
+ String number = cursor.getString(LookupContactInfoWorker.NUMBER_INDEX);
+ // TODO(calderwoodra): improve this to include fuzzy matching
+ if (phoneNumbers.add(number)) {
+ insertOption(
+ number,
+ getLabel(getContext().getResources(), cursor),
+ isVideoReachable(cursor, number));
+ }
+ } while (cursor.moveToNext());
+ cursor.close();
+ // TODO(calderwoodra): set max height of the scrollview. Might need to override onMeasure.
+ }
+
+ /** Returns true if the given number is ViLTE reachable or Duo reachable. */
+ private boolean isVideoReachable(Cursor cursor, String number) {
+ boolean isVideoReachable = cursor.getInt(LookupContactInfoWorker.PHONE_PRESENCE_INDEX) == 1;
+ if (!isVideoReachable) {
+ isVideoReachable = DuoComponent.get(getContext()).getDuo().isReachable(getContext(), number);
+ }
+ return isVideoReachable;
+ }
+
+ /** Inserts a group of options for a specific phone number. */
+ private void insertOption(String number, String phoneType, boolean isVideoReachable) {
+ View view =
+ getActivity()
+ .getLayoutInflater()
+ .inflate(R.layout.disambig_option_layout, container, false);
+ ((TextView) view.findViewById(R.id.phone_type)).setText(phoneType);
+ ((TextView) view.findViewById(R.id.phone_number)).setText(number);
+
+ if (isVideoReachable) {
+ View videoOption = view.findViewById(R.id.video_call_container);
+ videoOption.setOnClickListener(v -> onVideoOptionClicked(number));
+ videoOption.setVisibility(View.VISIBLE);
+ }
+ View voiceOption = view.findViewById(R.id.voice_call_container);
+ voiceOption.setOnClickListener(v -> onVoiceOptionClicked(number));
+ container.addView(view);
+ }
+
+ private void onVideoOptionClicked(String number) {
+ // TODO(calderwoodra): save this option if remember is checked
+ // TODO(calderwoodra): place a duo call if possible
+ PreCall.start(
+ getContext(),
+ new CallIntentBuilder(number, CallInitiationType.Type.SPEED_DIAL).setIsVideoCall(true));
+ }
+
+ private void onVoiceOptionClicked(String number) {
+ // TODO(calderwoodra): save this option if remember is checked
+ PreCall.start(getContext(), new CallIntentBuilder(number, CallInitiationType.Type.SPEED_DIAL));
+ }
+
+ // TODO(calderwoodra): handle CNAP and cequint types.
+ // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType
+ private static String getLabel(Resources resources, Cursor cursor) {
+ int numberType = cursor.getInt(LookupContactInfoWorker.PHONE_TYPE_INDEX);
+ String numberLabel = cursor.getString(LookupContactInfoWorker.PHONE_LABEL_INDEX);
+
+ // Returns empty label instead of "custom" if the custom label is empty.
+ if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) {
+ return "";
+ }
+ return (String) Phone.getTypeLabel(resources, numberType, numberLabel);
+ }
+
+ // Checks if the cursor is valid and logs an error if there are any issues.
+ private static boolean cursorIsValid(Cursor cursor) {
+ if (cursor == null) {
+ LogUtil.e("DisambigDialog.insertOptions", "cursor null.");
+ return false;
+ } else if (cursor.isClosed()) {
+ LogUtil.e("DisambigDialog.insertOptions", "cursor closed.");
+ cursor.close();
+ return false;
+ } else if (!cursor.moveToFirst()) {
+ LogUtil.e("DisambigDialog.insertOptions", "cursor empty.");
+ cursor.close();
+ return false;
+ }
+ return true;
+ }
+
+ private void onLookupFailed(Throwable throwable) {
+ LogUtil.e("DisambigDialog.onLookupFailed", null, throwable);
+ insertOptions(null);
+ }
+
+ private static class LookupContactInfoWorker implements Worker<String, Cursor> {
+
+ static final int NUMBER_INDEX = 0;
+ static final int PHONE_TYPE_INDEX = 1;
+ static final int PHONE_LABEL_INDEX = 2;
+ static final int PHONE_PRESENCE_INDEX = 3;
+
+ private static final String[] projection =
+ new String[] {Phone.NUMBER, Phone.TYPE, Phone.LABEL, Phone.CARRIER_PRESENCE};
+ private final ContentResolver resolver;
+
+ LookupContactInfoWorker(ContentResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ @Nullable
+ @Override
+ public Cursor doInBackground(@Nullable String lookupKey) throws Throwable {
+ if (TextUtils.isEmpty(lookupKey)) {
+ LogUtil.e("LookupConctactInfoWorker.doInBackground", "contact id unsest.");
+ return null;
+ }
+ return resolver.query(
+ Phone.CONTENT_URI, projection, Phone.LOOKUP_KEY + " = ?", new String[] {lookupKey}, null);
+ }
+ }
+
+ @VisibleForTesting
+ public static String[] getProjectionForTesting() {
+ ArrayList<String> projection =
+ new ArrayList<>(Arrays.asList(LookupContactInfoWorker.projection));
+ projection.add(Phone.LOOKUP_KEY);
+ return projection.toArray(new String[projection.size()]);
+ }
+
+ @VisibleForTesting
+ public LinearLayout getContainer() {
+ return container;
+ }
+}
diff --git a/java/com/android/dialer/speeddial/FavoritesViewHolder.java b/java/com/android/dialer/speeddial/FavoritesViewHolder.java
index 0cde716..c25b05e 100644
--- a/java/com/android/dialer/speeddial/FavoritesViewHolder.java
+++ b/java/com/android/dialer/speeddial/FavoritesViewHolder.java
@@ -45,8 +45,10 @@
private final TextView phoneType;
private final FrameLayout videoCallIcon;
+ private boolean hasDefaultNumber;
private boolean isVideoCall;
private String number;
+ private String lookupKey;
public FavoritesViewHolder(View view, FavoriteContactsListener listener) {
super(view);
@@ -67,7 +69,7 @@
String name = cursor.getString(StrequentContactsCursorLoader.PHONE_DISPLAY_NAME);
long contactId = cursor.getLong(StrequentContactsCursorLoader.PHONE_ID);
- String lookupKey = cursor.getString(StrequentContactsCursorLoader.PHONE_LOOKUP_KEY);
+ lookupKey = cursor.getString(StrequentContactsCursorLoader.PHONE_LOOKUP_KEY);
Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
String photoUri = cursor.getString(StrequentContactsCursorLoader.PHONE_PHOTO_URI);
@@ -82,6 +84,9 @@
nameView.setText(name);
phoneType.setText(getLabel(context.getResources(), cursor));
videoCallIcon.setVisibility(isVideoCall ? View.VISIBLE : View.GONE);
+
+ // TODO(calderwoodra): Update this to include communication avenues also
+ hasDefaultNumber = cursor.getInt(StrequentContactsCursorLoader.PHONE_IS_SUPER_PRIMARY) != 0;
}
// TODO(calderwoodra): handle CNAP and cequint types.
@@ -99,7 +104,11 @@
@Override
public void onClick(View v) {
- listener.onClick(number, isVideoCall);
+ if (hasDefaultNumber) {
+ listener.onClick(number, isVideoCall);
+ } else {
+ listener.onAmbiguousContactClicked(lookupKey);
+ }
}
@Override
@@ -112,6 +121,9 @@
/** Listener/callback for {@link FavoritesViewHolder} actions. */
public interface FavoriteContactsListener {
+ /** Called when the user clicks on a favorite contact that doesn't have a default number. */
+ void onAmbiguousContactClicked(String contactId);
+
/** Called when the user clicks on a favorite contact. */
void onClick(String number, boolean isVideoCall);
diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java
index 08861da..979c894 100644
--- a/java/com/android/dialer/speeddial/SpeedDialFragment.java
+++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java
@@ -98,6 +98,11 @@
private class SpeedDialFavoritesListener implements FavoriteContactsListener {
@Override
+ public void onAmbiguousContactClicked(String lookupKey) {
+ DisambigDialog.show(lookupKey, getFragmentManager());
+ }
+
+ @Override
public void onClick(String number, boolean isVideoCall) {
// TODO(calderwoodra): add logic for duo video calls
PreCall.start(
diff --git a/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java b/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java
index f5f0045..e9e3e32 100644
--- a/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java
+++ b/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.CursorLoader;
-import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
@@ -58,8 +57,6 @@
Phone.CONTACT_ID, // 11
};
- private final ContentObserver contentObserver = new ForceLoadContentObserver();
-
StrequentContactsCursorLoader(Context context) {
super(
context,
@@ -97,8 +94,4 @@
public Cursor loadInBackground() {
return SpeedDialCursor.newInstance(super.loadInBackground());
}
-
- ContentObserver getContentObserver() {
- return contentObserver;
- }
}
diff --git a/java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml b/java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml
new file mode 100644
index 0000000..3562058
--- /dev/null
+++ b/java/com/android/dialer/speeddial/res/layout/disambig_dialog_layout.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/disambig_dialog_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="64dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:elevation="1dp"
+ android:text="@string/speed_dial_disambig_dialog_title"
+ android:textSize="20sp"
+ android:textColor="@color/dialer_primary_text_color"
+ android:fontFamily="sans-serif-medium"
+ android:background="@android:color/white"/>
+
+ <ScrollView
+ android:id="@+id/disambig_scrollview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/communication_avenue_container"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </ScrollView>
+
+ <FrameLayout
+ android:id="@+id/remember_this_choice_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:minHeight="56dp"
+ android:padding="12dp"
+ android:elevation="4dp"
+ android:background="@android:color/white">
+
+ <CheckBox
+ android:id="@+id/remember_this_choice_checkbox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/speed_dial_remember_this_choice"/>
+ </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml b/java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml
new file mode 100644
index 0000000..097ac40
--- /dev/null
+++ b/java/com/android/dialer/speeddial/res/layout/disambig_option_layout.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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:id="@+id/disambig_option_container"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="56dp"
+ android:layout_marginBottom="8dp">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="56dp"
+ android:gravity="center_vertical"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp">
+
+ <TextView
+ android:id="@+id/phone_type"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/PrimaryText"/>
+
+ <TextView
+ android:id="@+id/phone_number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SecondaryText"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/video_call_container"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:minHeight="56dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone"
+ android:contentDescription="@string/disambig_option_video_call">
+
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:tint="@color/dialer_secondary_text_color"
+ android:src="@drawable/quantum_ic_videocam_vd_theme_24"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_gravity="center_vertical"
+ android:text="@string/disambig_option_video_call"
+ style="@style/PrimaryText"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/voice_call_container"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:minHeight="56dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:contentDescription="@string/disambig_option_voice_call">
+
+ <ImageView
+ android:id="@+id/disambig_option_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:tint="@color/dialer_secondary_text_color"
+ android:src="@drawable/quantum_ic_phone_vd_theme_24"/>
+
+ <TextView
+ android:id="@+id/disambig_option_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_gravity="center_vertical"
+ android:text="@string/disambig_option_voice_call"
+ style="@style/PrimaryText"/>
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/values/dimens.xml b/java/com/android/dialer/speeddial/res/values/dimens.xml
index 5929df8..74b509b 100644
--- a/java/com/android/dialer/speeddial/res/values/dimens.xml
+++ b/java/com/android/dialer/speeddial/res/values/dimens.xml
@@ -15,4 +15,5 @@
~ limitations under the License
-->
<resources>
+ <dimen name="scrollview_max_height">280dp</dimen>
</resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/values/strings.xml b/java/com/android/dialer/speeddial/res/values/strings.xml
index d64d035..677f772 100644
--- a/java/com/android/dialer/speeddial/res/values/strings.xml
+++ b/java/com/android/dialer/speeddial/res/values/strings.xml
@@ -24,6 +24,20 @@
<!-- text for a button that prompts the user to add a contact to their favorites. [CHAR LIMIT=12] -->
<string name="speed_dial_add_button_text">Add</string>
+ <!-- text for a checkbox in a dialog that prompts the user to select a phone number from a list.
+ If the user checks this box, we will remember their selection and never ask for it again. [CHAR LIMIT=NONE]-->
+ <string name="speed_dial_remember_this_choice">Remember this choice</string>
+
+ <!-- Title of a dialog asking the user to choose their favorite mode of communication for a
+ specific contact where communication modes are video calling and voice calling. [CHAR LIMIT=NONE]-->
+ <string name="speed_dial_disambig_dialog_title">Choose a Favorite mode</string>
+
+ <!-- Text for a button that places a video call [CHAR LIMIT=15]-->
+ <string name="disambig_option_video_call">Video call</string>
+
+ <!-- Text for a button that places a phone/voice call [CHAR LIMIT=15]-->
+ <string name="disambig_option_voice_call">Call</string>
+
<!-- Title for screen prompting the user to select a contact to mark as a favorite. [CHAR LIMIT=NONE] -->
<string name="add_favorite_activity_title">Add Favorite</string>
</resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/telecom/TelecomCallUtil.java b/java/com/android/dialer/telecom/TelecomCallUtil.java
new file mode 100644
index 0000000..acec498
--- /dev/null
+++ b/java/com/android/dialer/telecom/TelecomCallUtil.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.telecom;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.telecom.Call;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.google.common.base.Optional;
+import java.util.Locale;
+
+/**
+ * Class to provide a standard interface for obtaining information from the underlying
+ * android.telecom.Call. Much of this should be obtained through the incall.Call, but on occasion we
+ * need to interact with the telecom.Call directly (eg. call blocking, before the incall.Call has
+ * been created).
+ */
+public class TelecomCallUtil {
+
+ /** Returns Whether the call handle is an emergency number. */
+ public static boolean isEmergencyCall(@NonNull Call call) {
+ Assert.isNotNull(call);
+ Uri handle = call.getDetails().getHandle();
+ return PhoneNumberUtils.isEmergencyNumber(handle == null ? "" : handle.getSchemeSpecificPart());
+ }
+
+ /**
+ * Returns The phone number which the {@code Call} is currently connected, or {@code null} if the
+ * number is not available.
+ */
+ @Nullable
+ public static String getNumber(@Nullable Call call) {
+ if (call == null) {
+ return null;
+ }
+ if (call.getDetails().getGatewayInfo() != null) {
+ return call.getDetails().getGatewayInfo().getOriginalAddress().getSchemeSpecificPart();
+ }
+ Uri handle = getHandle(call);
+ return handle == null ? null : handle.getSchemeSpecificPart();
+ }
+
+ /**
+ * Returns The handle (e.g., phone number) to which the {@code Call} is currently connected, or
+ * {@code null} if the number is not available.
+ */
+ @Nullable
+ public static Uri getHandle(@Nullable Call call) {
+ return call == null ? null : call.getDetails().getHandle();
+ }
+
+ /**
+ * Normalizes the number of the {@code call} to E.164. If the country code is missing in the
+ * number the SIM's country will be used. Only removes non-dialable digits if the country code is
+ * missing.
+ */
+ @WorkerThread
+ public static Optional<String> getNormalizedNumber(Context appContext, Call call) {
+ Assert.isWorkerThread();
+ PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle();
+ Optional<SubscriptionInfo> subscriptionInfo =
+ TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle);
+ String rawNumber = getNumber(call);
+ if (TextUtils.isEmpty(rawNumber)) {
+ return Optional.absent();
+ }
+ String normalizedNumber = PhoneNumberUtils.normalizeNumber(rawNumber);
+ if (TextUtils.isEmpty(normalizedNumber)) {
+ return Optional.absent();
+ }
+ String countryCode =
+ subscriptionInfo.isPresent() ? subscriptionInfo.get().getCountryIso() : null;
+ if (countryCode == null) {
+ LogUtil.w(
+ "PhoneLookupHistoryRecorder.getNormalizedNumber",
+ "couldn't find a country code for call");
+ return Optional.of(normalizedNumber);
+ }
+
+ String e164Number =
+ PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.toUpperCase(Locale.US));
+ return e164Number == null ? Optional.of(normalizedNumber) : Optional.of(e164Number);
+ }
+}
diff --git a/java/com/android/incallui/CallerInfo.java b/java/com/android/incallui/CallerInfo.java
index 4a9cf21..5c43b4f 100644
--- a/java/com/android/incallui/CallerInfo.java
+++ b/java/com/android/incallui/CallerInfo.java
@@ -192,7 +192,7 @@
*
* @param context the context used to retrieve string constants
* @param contactRef the URI to attach to this CallerInfo object
- * @param cursor the first matching object in the cursor is used to build the CallerInfo object.
+ * @param cursor the first object in the cursor is used to build the CallerInfo object.
* @return the CallerInfo which contains the caller id for the given number. The returned
* CallerInfo is null if no number is supplied.
*/
@@ -223,24 +223,18 @@
long contactId = 0L;
int columnIndex;
- // If the cursor has the phone number column, find the one that matches the lookup number in the
- // URI.
- columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
- if (columnIndex != -1 && contactRef != null) {
- cursor = PhoneNumberHelper.getCursorMatchForContactLookupUri(cursor, columnIndex, contactRef);
- if (cursor != null) {
- info.phoneNumber = cursor.getString(columnIndex);
- } else {
- return info;
- }
- }
-
// Look for the name
columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
if (columnIndex != -1) {
info.name = normalize(cursor.getString(columnIndex));
}
+ // Look for the number
+ columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
+ if (columnIndex != -1) {
+ info.phoneNumber = cursor.getString(columnIndex);
+ }
+
// Look for the normalized number
columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
if (columnIndex != -1) {
@@ -510,7 +504,6 @@
Log.e(TAG, "Cannot access VoiceMail.", se);
}
// TODO: There is no voicemail picture?
-
// photoResource = android.R.drawable.badge_voicemail;
return this;
}
diff --git a/java/com/android/incallui/DialpadFragment.java b/java/com/android/incallui/DialpadFragment.java
index 2f3a68c..b2aacf7 100644
--- a/java/com/android/incallui/DialpadFragment.java
+++ b/java/com/android/incallui/DialpadFragment.java
@@ -202,15 +202,6 @@
mDtmfDialerField.setText(PhoneNumberUtilsCompat.createTtsSpannable(text));
}
- @Override
- public void setVisible(boolean on) {
- if (on) {
- getView().setVisibility(View.VISIBLE);
- } else {
- getView().setVisibility(View.INVISIBLE);
- }
- }
-
/** Starts the slide up animation for the Dialpad keys when the Dialpad is revealed. */
public void animateShowDialpad() {
final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
diff --git a/java/com/android/incallui/DialpadPresenter.java b/java/com/android/incallui/DialpadPresenter.java
index 7a784c2..002fefc 100644
--- a/java/com/android/incallui/DialpadPresenter.java
+++ b/java/com/android/incallui/DialpadPresenter.java
@@ -84,8 +84,6 @@
public interface DialpadUi extends Ui {
- void setVisible(boolean on);
-
void appendDigitsToField(char digit);
}
}
diff --git a/java/com/android/incallui/ExternalCallNotifier.java b/java/com/android/incallui/ExternalCallNotifier.java
index 9e78052..7915b85 100644
--- a/java/com/android/incallui/ExternalCallNotifier.java
+++ b/java/com/android/incallui/ExternalCallNotifier.java
@@ -44,11 +44,11 @@
import com.android.dialer.contactphoto.BitmapUtil;
import com.android.dialer.notification.DialerNotificationManager;
import com.android.dialer.notification.NotificationChannelId;
+import com.android.dialer.telecom.TelecomCallUtil;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.DialerCallDelegate;
import com.android.incallui.call.ExternalCallList;
import com.android.incallui.latencyreport.LatencyReport;
-import com.android.incallui.util.TelecomCallUtil;
import java.util.Map;
/**
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 28ff7da..47b5986 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -35,7 +35,6 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
-import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.res.ResourcesCompat;
@@ -787,6 +786,7 @@
transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT);
} else {
transaction.show(dialpadFragment);
+ dialpadFragment.setUserVisibleHint(true);
}
transaction.commitAllowingStateLoss();
dialpadFragmentManager.executePendingTransactions();
@@ -801,19 +801,20 @@
return;
}
- Fragment dialpadFragment = dialpadFragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
+ DialpadFragment dialpadFragment = getDialpadFragment();
if (dialpadFragment != null) {
FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
transaction.hide(dialpadFragment);
transaction.commitAllowingStateLoss();
dialpadFragmentManager.executePendingTransactions();
+ dialpadFragment.setUserVisibleHint(false);
}
updateNavigationBar(false /* isDialpadVisible */);
}
public boolean isDialpadVisible() {
DialpadFragment dialpadFragment = getDialpadFragment();
- return dialpadFragment != null && dialpadFragment.isVisible();
+ return dialpadFragment != null && dialpadFragment.getUserVisibleHint();
}
/** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index f8605ae..3debd70 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -52,6 +52,7 @@
import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
import com.android.dialer.postcall.PostCall;
+import com.android.dialer.telecom.TelecomCallUtil;
import com.android.dialer.telecom.TelecomUtil;
import com.android.dialer.util.TouchPointManager;
import com.android.incallui.InCallOrientationEventListener.ScreenOrientation;
@@ -66,7 +67,6 @@
import com.android.incallui.latencyreport.LatencyReport;
import com.android.incallui.legacyblocking.BlockedNumberContentObserver;
import com.android.incallui.spam.SpamCallListListener;
-import com.android.incallui.util.TelecomCallUtil;
import com.android.incallui.videosurface.bindings.VideoSurfaceBindings;
import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
import com.android.incallui.videotech.utils.VideoUtils;
@@ -213,7 +213,7 @@
}
}
};
-
+
/** Whether or not InCallService is bound to Telecom. */
private boolean mServiceBound = false;
diff --git a/java/com/android/incallui/PhoneLookupHistoryRecorder.java b/java/com/android/incallui/PhoneLookupHistoryRecorder.java
index 2632e65..667c0d1 100644
--- a/java/com/android/incallui/PhoneLookupHistoryRecorder.java
+++ b/java/com/android/incallui/PhoneLookupHistoryRecorder.java
@@ -19,25 +19,18 @@
import android.content.Context;
import android.support.annotation.Nullable;
import android.telecom.Call;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SubscriptionInfo;
-import android.text.TextUtils;
import com.android.dialer.buildtype.BuildType;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutors;
-import com.android.dialer.location.CountryDetector;
import com.android.dialer.phonelookup.PhoneLookupComponent;
import com.android.dialer.phonelookup.PhoneLookupInfo;
import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory;
-import com.android.dialer.telecom.TelecomUtil;
-import com.android.incallui.util.TelecomCallUtil;
+import com.android.dialer.telecom.TelecomCallUtil;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.Locale;
/**
* Fetches the current {@link PhoneLookupInfo} for the provided call and writes it to the
@@ -61,7 +54,8 @@
@Override
public void onSuccess(@Nullable PhoneLookupInfo result) {
Assert.checkArgument(result != null);
- Optional<String> normalizedNumber = getNormalizedNumber(appContext, call);
+ Optional<String> normalizedNumber =
+ TelecomCallUtil.getNormalizedNumber(appContext, call);
if (!normalizedNumber.isPresent()) {
LogUtil.w("PhoneLookupHistoryRecorder.onSuccess", "couldn't get a number");
return;
@@ -90,27 +84,4 @@
},
DialerExecutors.getLowPriorityThreadPool(appContext));
}
-
- private static Optional<String> getNormalizedNumber(Context appContext, Call call) {
- PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle();
- Optional<SubscriptionInfo> subscriptionInfo =
- TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle);
- String countryCode =
- subscriptionInfo.isPresent()
- ? subscriptionInfo.get().getCountryIso()
- : CountryDetector.getInstance(appContext).getCurrentCountryIso();
- if (countryCode == null) {
- LogUtil.w(
- "PhoneLookupHistoryRecorder.getNormalizedNumber",
- "couldn't find a country code for call");
- countryCode = "US";
- }
- String rawNumber = TelecomCallUtil.getNumber(call);
- if (TextUtils.isEmpty(rawNumber)) {
- return Optional.absent();
- }
- String normalizedNumber =
- PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.toUpperCase(Locale.US));
- return normalizedNumber == null ? Optional.of(rawNumber) : Optional.of(normalizedNumber);
- }
}
diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java
index d2ac483..150b20e 100644
--- a/java/com/android/incallui/call/CallList.java
+++ b/java/com/android/incallui/call/CallList.java
@@ -40,9 +40,9 @@
import com.android.dialer.shortcuts.ShortcutUsageReporter;
import com.android.dialer.spam.Spam;
import com.android.dialer.spam.SpamComponent;
+import com.android.dialer.telecom.TelecomCallUtil;
import com.android.incallui.call.DialerCall.State;
import com.android.incallui.latencyreport.LatencyReport;
-import com.android.incallui.util.TelecomCallUtil;
import com.android.incallui.videotech.utils.SessionModificationState;
import java.util.Collection;
import java.util.Collections;
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index e8523d6..8120249 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -64,12 +64,12 @@
import com.android.dialer.logging.ContactLookupResult.Type;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
+import com.android.dialer.telecom.TelecomCallUtil;
import com.android.dialer.telecom.TelecomUtil;
import com.android.dialer.theme.R;
import com.android.dialer.util.PermissionsUtil;
import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.latencyreport.LatencyReport;
-import com.android.incallui.util.TelecomCallUtil;
import com.android.incallui.videotech.VideoTech;
import com.android.incallui.videotech.VideoTech.VideoTechListener;
import com.android.incallui.videotech.duo.DuoVideoTech;
diff --git a/java/com/android/incallui/util/TelecomCallUtil.java b/java/com/android/incallui/util/TelecomCallUtil.java
deleted file mode 100644
index 8855543..0000000
--- a/java/com/android/incallui/util/TelecomCallUtil.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 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.incallui.util;
-
-import android.net.Uri;
-import android.telecom.Call;
-import android.telephony.PhoneNumberUtils;
-
-/**
- * Class to provide a standard interface for obtaining information from the underlying
- * android.telecom.Call. Much of this should be obtained through the incall.Call, but on occasion we
- * need to interact with the telecom.Call directly (eg. call blocking, before the incall.Call has
- * been created).
- */
-public class TelecomCallUtil {
-
- // Whether the call handle is an emergency number.
- public static boolean isEmergencyCall(Call call) {
- Uri handle = call.getDetails().getHandle();
- return PhoneNumberUtils.isEmergencyNumber(handle == null ? "" : handle.getSchemeSpecificPart());
- }
-
- public static String getNumber(Call call) {
- if (call == null) {
- return null;
- }
- if (call.getDetails().getGatewayInfo() != null) {
- return call.getDetails().getGatewayInfo().getOriginalAddress().getSchemeSpecificPart();
- }
- Uri handle = getHandle(call);
- return handle == null ? null : handle.getSchemeSpecificPart();
- }
-
- public static Uri getHandle(Call call) {
- return call == null ? null : call.getDetails().getHandle();
- }
-}
diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java
index 23c4411..34a9585 100644
--- a/java/com/android/newbubble/NewBubble.java
+++ b/java/com/android/newbubble/NewBubble.java
@@ -47,11 +47,14 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.ImageView;
@@ -228,6 +231,8 @@
if (isUserAction) {
logBasicOrCallImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND);
}
+ setPrimaryButtonAccessibilityAction(
+ context.getString(R.string.a11y_bubble_primary_button_collapse_action));
viewHolder.setDrawerVisibility(View.INVISIBLE);
View expandedView = viewHolder.getExpandedView();
expandedView
@@ -310,6 +315,8 @@
if (isUserAction && collapseEndAction == CollapseEnd.NOTHING) {
logBasicOrCallImpression(DialerImpression.Type.BUBBLE_COLLAPSE_BY_USER);
}
+ setPrimaryButtonAccessibilityAction(
+ context.getString(R.string.a11y_bubble_primary_button_expand_action));
// Animate expanded view to move from its position to above primary button and hide
collapseAnimation =
expandedView
@@ -448,6 +455,9 @@
viewHolder.setChildClickable(true);
visibility = Visibility.ENTERING;
+ setPrimaryButtonAccessibilityAction(
+ context.getString(R.string.a11y_bubble_primary_button_expand_action));
+
// Show bubble animation: scale the whole bubble to 1, and change avatar+icon's alpha to 1
ObjectAnimator scaleXAnimator =
ObjectAnimator.ofFloat(viewHolder.getPrimaryButton(), "scaleX", 1);
@@ -726,6 +736,11 @@
exitAnimatorSet.addListener(
new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ viewHolder.getPrimaryButton().setAccessibilityDelegate(null);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
afterHiding.run();
}
@@ -793,6 +808,7 @@
button.setChecked(action.isChecked());
button.setEnabled(action.isEnabled());
button.setText(action.getName());
+ button.setContentDescription(action.getName());
button.setOnClickListener(v -> doAction(action));
}
@@ -822,6 +838,8 @@
viewHolder
.getPrimaryIcon()
.setTranslationX(isDrawingFromRight() ? -primaryIconMoveDistance : 0);
+ setPrimaryButtonAccessibilityAction(
+ context.getString(R.string.a11y_bubble_primary_button_expand_action));
update();
@@ -883,6 +901,22 @@
}
}
+ private void setPrimaryButtonAccessibilityAction(String description) {
+ viewHolder
+ .getPrimaryButton()
+ .setAccessibilityDelegate(
+ new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(v, info);
+
+ AccessibilityAction clickAction =
+ new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, description);
+ info.addAction(clickAction);
+ }
+ });
+ }
+
@VisibleForTesting
class ViewHolder {
diff --git a/java/com/android/newbubble/res/layout/new_bubble_base.xml b/java/com/android/newbubble/res/layout/new_bubble_base.xml
index 8d47716..216dce0 100644
--- a/java/com/android/newbubble/res/layout/new_bubble_base.xml
+++ b/java/com/android/newbubble/res/layout/new_bubble_base.xml
@@ -38,6 +38,7 @@
android:layout_marginEnd="@dimen/bubble_shadow_padding_size_horizontal"
android:layout_marginTop="@dimen/bubble_shadow_padding_size_vertical"
android:layout_marginBottom="@dimen/bubble_shadow_padding_size_vertical"
+ android:contentDescription="@string/a11y_bubble_description"
android:background="@drawable/bubble_shape_circle"
android:measureAllChildren="false"
android:elevation="@dimen/bubble_elevation"
diff --git a/java/com/android/newbubble/res/values/strings.xml b/java/com/android/newbubble/res/values/strings.xml
new file mode 100644
index 0000000..5b82b18
--- /dev/null
+++ b/java/com/android/newbubble/res/values/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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>
+ <!-- A string for Talkback to read when accessibility user touch bubble. -->
+ <string name="a11y_bubble_description">Dialer bubble</string>
+ <!-- A string to describe available action for accessibility user. It will be read as "Actions:
+ double tap to expand call action menu". -->
+ <string name="a11y_bubble_primary_button_expand_action">Expand call action menu</string>
+ <!-- A string to describe available action for accessibility user. It will be read as "Actions:
+ double tap to collapse call action menu". -->
+ <string name="a11y_bubble_primary_button_collapse_action">Collapse call action menu</string>
+</resources>
\ No newline at end of file
diff --git a/java/com/android/voicemail/impl/VoicemailClientImpl.java b/java/com/android/voicemail/impl/VoicemailClientImpl.java
index 60fc806..75d6dfc 100644
--- a/java/com/android/voicemail/impl/VoicemailClientImpl.java
+++ b/java/com/android/voicemail/impl/VoicemailClientImpl.java
@@ -130,7 +130,7 @@
}
TranscriptionConfigProvider provider = new TranscriptionConfigProvider(context);
- if (!provider.isVoicemailTranscriptionEnabled()) {
+ if (!provider.isVoicemailTranscriptionAvailable()) {
LogUtil.i(
"VoicemailClientImpl.isVoicemailTranscriptionAvailable", "feature disabled by config");
return false;
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java
index 3d1755b..54a1ae4 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java
@@ -28,9 +28,10 @@
this.context = context;
}
- public boolean isVoicemailTranscriptionEnabled() {
+ public boolean isVoicemailTranscriptionAvailable() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
- && ConfigProviderBindings.get(context).getBoolean("voicemail_transcription_enabled", false);
+ && ConfigProviderBindings.get(context)
+ .getBoolean("voicemail_transcription_available", false);
}
public String getServerAddress() {
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
index a19ab62..0f53003 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
@@ -142,8 +142,8 @@
public boolean onStartJob(JobParameters params) {
Assert.isMainThread();
LogUtil.enterBlock("TranscriptionService.onStartJob");
- if (!getConfigProvider().isVoicemailTranscriptionEnabled()) {
- LogUtil.i("TranscriptionService.onStartJob", "transcription not enabled, exiting.");
+ if (!getConfigProvider().isVoicemailTranscriptionAvailable()) {
+ LogUtil.i("TranscriptionService.onStartJob", "transcription not available, exiting.");
return false;
} else if (TextUtils.isEmpty(getConfigProvider().getServerAddress())) {
LogUtil.i("TranscriptionService.onStartJob", "transcription server not configured, exiting.");