summaryrefslogtreecommitdiff
path: root/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'java/com')
-rw-r--r--java/com/android/contacts/common/res/values/strings.xml3
-rw-r--r--java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java2
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsActivity.java6
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java2
-rw-r--r--java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java5
-rw-r--r--java/com/android/dialer/calllog/database/Coalescer.java23
-rw-r--r--java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java38
-rw-r--r--java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java14
-rw-r--r--java/com/android/dialer/calllog/datasources/util/RowCombiner.java6
-rw-r--r--java/com/android/dialer/calllog/model/CoalescedRow.java147
-rw-r--r--java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java160
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogAdapter.java11
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java104
-rw-r--r--java/com/android/dialer/calllog/ui/menu/AndroidManifest.xml16
-rw-r--r--java/com/android/dialer/calllog/ui/menu/Modules.java234
-rw-r--r--java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java33
-rw-r--r--java/com/android/dialer/calllog/ui/menu/PrimaryAction.java48
-rw-r--r--java/com/android/dialer/calllog/ui/menu/res/values/strings.xml35
-rw-r--r--java/com/android/dialer/calllog/ui/res/values/strings.xml6
-rw-r--r--java/com/android/dialer/calllogutils/CallLogEntryText.java148
-rw-r--r--java/com/android/dialer/calllogutils/CallLogIntents.java55
-rw-r--r--java/com/android/dialer/calllogutils/PhoneAccountUtils.java2
-rw-r--r--java/com/android/dialer/calllogutils/res/values/strings.xml6
-rw-r--r--java/com/android/dialer/clipboard/AndroidManifest.xml16
-rw-r--r--java/com/android/dialer/clipboard/ClipboardUtils.java (renamed from java/com/android/contacts/common/ClipboardUtils.java)5
-rw-r--r--java/com/android/dialer/clipboard/res/values/strings.xml23
-rw-r--r--java/com/android/dialer/contactactions/ContactActionBottomSheet.java66
-rw-r--r--java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java118
-rw-r--r--java/com/android/dialer/contactactions/IntentModule.java24
-rw-r--r--java/com/android/dialer/contactactions/res/layout/contact_layout.xml4
30 files changed, 1111 insertions, 249 deletions
diff --git a/java/com/android/contacts/common/res/values/strings.xml b/java/com/android/contacts/common/res/values/strings.xml
index df8d70fc3..dbe97ea39 100644
--- a/java/com/android/contacts/common/res/values/strings.xml
+++ b/java/com/android/contacts/common/res/values/strings.xml
@@ -16,9 +16,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Toast shown when text is copied to the clipboard [CHAR LIMIT=64] -->
- <string name="toast_text_copied">Text copied</string>
-
<!-- Action string for calling a custom phone number -->
<string name="call_custom">Call
<xliff:g id="custom">%s</xliff:g>
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 6067c4239..cf86ef6aa 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -50,7 +50,6 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
-import com.android.contacts.common.ClipboardUtils;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
import com.android.contacts.common.dialog.CallSubjectDialog;
import com.android.dialer.app.DialtactsActivity;
@@ -67,6 +66,7 @@ import com.android.dialer.calldetails.CallDetailsActivity;
import com.android.dialer.calldetails.CallDetailsEntries;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction;
+import com.android.dialer.clipboard.ClipboardUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.compat.CompatUtils;
diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java
index d7f414bb2..d871fce12 100644
--- a/java/com/android/dialer/calldetails/CallDetailsActivity.java
+++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java
@@ -67,9 +67,9 @@ public class CallDetailsActivity extends AppCompatActivity
public static final String EXTRA_PHONE_NUMBER = "phone_number";
public static final String EXTRA_HAS_ENRICHED_CALL_DATA = "has_enriched_call_data";
- private static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries";
- private static final String EXTRA_CONTACT = "contact";
- private static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id";
+ public static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries";
+ 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";
private static final String TASK_DELETE = "task_delete";
diff --git a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java
index 9d3f4bcbc..6a5188e56 100644
--- a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java
@@ -22,7 +22,7 @@ import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
-import com.android.contacts.common.ClipboardUtils;
+import com.android.dialer.clipboard.ClipboardUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index 40d922f41..a5f1425f8 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -39,6 +39,7 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper {
.append(AnnotatedCallLog._ID + " integer primary key, ")
.append(AnnotatedCallLog.TIMESTAMP + " integer, ")
.append(AnnotatedCallLog.NAME + " string, ")
+ .append(AnnotatedCallLog.NUMBER + " blob, ")
.append(AnnotatedCallLog.FORMATTED_NUMBER + " string, ")
.append(AnnotatedCallLog.PHOTO_URI + " string, ")
.append(AnnotatedCallLog.PHOTO_ID + " integer, ")
@@ -47,13 +48,13 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper {
.append(AnnotatedCallLog.IS_READ + " integer, ")
.append(AnnotatedCallLog.NEW + " integer, ")
.append(AnnotatedCallLog.GEOCODED_LOCATION + " string, ")
+ .append(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME + " string, ")
+ .append(AnnotatedCallLog.PHONE_ACCOUNT_ID + " string, ")
.append(AnnotatedCallLog.PHONE_ACCOUNT_LABEL + " string, ")
.append(AnnotatedCallLog.PHONE_ACCOUNT_COLOR + " integer, ")
.append(AnnotatedCallLog.FEATURES + " integer, ")
.append(AnnotatedCallLog.IS_BUSINESS + " integer, ")
.append(AnnotatedCallLog.IS_VOICEMAIL + " integer, ")
- // Columns only in AnnotatedCallLog
- .append(AnnotatedCallLog.NUMBER + " blob, ")
.append(AnnotatedCallLog.CALL_TYPE + " integer")
.append(");")
.toString();
diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java
index 63fa9f828..a8a8f2f1d 100644
--- a/java/com/android/dialer/calllog/database/Coalescer.java
+++ b/java/com/android/dialer/calllog/database/Coalescer.java
@@ -20,11 +20,13 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccountHandle;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog;
import com.android.dialer.calllog.datasources.CallLogDataSource;
import com.android.dialer.calllog.datasources.DataSources;
+import com.android.dialer.calllogutils.PhoneAccountUtils;
import com.android.dialer.common.Assert;
import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
@@ -131,11 +133,19 @@ public class Coalescer {
private static boolean rowsShouldBeCombined(
DialerPhoneNumberUtil dialerPhoneNumberUtil, ContentValues row1, ContentValues row2) {
// Don't combine rows which don't use the same phone account.
- if (!Objects.equals(
- row1.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_LABEL),
- row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_LABEL))) {
+ PhoneAccountHandle phoneAccount1 =
+ PhoneAccountUtils.getAccount(
+ row1.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME),
+ row1.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_ID));
+ PhoneAccountHandle phoneAccount2 =
+ PhoneAccountUtils.getAccount(
+ row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME),
+ row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_ID));
+
+ if (!Objects.equals(phoneAccount1, phoneAccount2)) {
return false;
}
+
DialerPhoneNumber number1;
DialerPhoneNumber number2;
try {
@@ -153,13 +163,8 @@ public class Coalescer {
throw Assert.createAssertionFailException("error parsing DialerPhoneNumber proto", e);
}
- if (!number1.hasDialerInternalPhoneNumber() && !number2.hasDialerInternalPhoneNumber()) {
- // Empty numbers should not be combined.
- return false;
- }
-
if (!number1.hasDialerInternalPhoneNumber() || !number2.hasDialerInternalPhoneNumber()) {
- // An empty number should not be combined with a non-empty number.
+ // An empty number should not be combined with any other number.
return false;
}
return dialerPhoneNumberUtil.isExactMatch(number1, number2);
diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
index d466da9ae..e79ffd090 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -52,6 +52,15 @@ public class AnnotatedCallLogContract {
String NAME = "name";
/**
+ * The phone number called or number the call came from, encoded as a {@link
+ * com.android.dialer.DialerPhoneNumber} proto. The number may be empty if it was an incoming
+ * call and the number was unknown.
+ *
+ * <p>Type: BLOB
+ */
+ String NUMBER = "number";
+
+ /**
* Copied from {@link android.provider.CallLog.Calls#CACHED_FORMATTED_NUMBER}.
*
* <p>Type: TEXT
@@ -112,6 +121,20 @@ public class AnnotatedCallLogContract {
String GEOCODED_LOCATION = "geocoded_location";
/**
+ * See {@link android.provider.CallLog.Calls#PHONE_ACCOUNT_COMPONENT_NAME}.
+ *
+ * <p>TYPE: TEXT
+ */
+ String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
+
+ /**
+ * See {@link android.provider.CallLog.Calls#PHONE_ACCOUNT_ID}.
+ *
+ * <p>TYPE: TEXT
+ */
+ String PHONE_ACCOUNT_ID = "phone_account_id";
+
+ /**
* String suitable for display which indicates the phone account used to make the call.
*
* <p>TYPE: TEXT
@@ -160,6 +183,7 @@ public class AnnotatedCallLogContract {
_ID,
TIMESTAMP,
NAME,
+ NUMBER,
FORMATTED_NUMBER,
PHOTO_URI,
PHOTO_ID,
@@ -168,6 +192,8 @@ public class AnnotatedCallLogContract {
IS_READ,
NEW,
GEOCODED_LOCATION,
+ PHONE_ACCOUNT_COMPONENT_NAME,
+ PHONE_ACCOUNT_ID,
PHONE_ACCOUNT_LABEL,
PHONE_ACCOUNT_COLOR,
FEATURES,
@@ -192,18 +218,6 @@ public class AnnotatedCallLogContract {
/** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/annotated_call_log";
-
- /**
- * The phone number called or number the call came from, encoded as a {@link
- * com.android.dialer.DialerPhoneNumber} proto. The number may be empty if it was an incoming
- * call and the number was unknown.
- *
- * <p>This column is only present in the annotated call log, and not the coalesced annotated
- * call log. The coalesced version uses a formatted number string rather than proto bytes.
- *
- * <p>Type: BLOB
- */
- public static final String NUMBER = "number";
}
/**
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index d6ad618b3..0a965f63e 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -38,6 +38,7 @@ import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import android.util.ArraySet;
+import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.calllog.datasources.CallLogDataSource;
import com.android.dialer.calllog.datasources.CallLogMutations;
@@ -156,11 +157,17 @@ public class SystemCallLogDataSource implements CallLogDataSource {
.useMostRecentLong(AnnotatedCallLog.NEW)
.useMostRecentString(AnnotatedCallLog.NUMBER_TYPE_LABEL)
.useMostRecentString(AnnotatedCallLog.NAME)
+ // Two different DialerPhoneNumbers could be combined if they are different but considered
+ // to be an "exact match" by libphonenumber; in this case we arbitrarily select the most
+ // recent one.
+ .useMostRecentBlob(AnnotatedCallLog.NUMBER)
.useMostRecentString(AnnotatedCallLog.FORMATTED_NUMBER)
.useMostRecentString(AnnotatedCallLog.PHOTO_URI)
.useMostRecentLong(AnnotatedCallLog.PHOTO_ID)
.useMostRecentString(AnnotatedCallLog.LOOKUP_URI)
.useMostRecentString(AnnotatedCallLog.GEOCODED_LOCATION)
+ .useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME)
+ .useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_ID)
.useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_LABEL)
.useSingleValueLong(AnnotatedCallLog.PHONE_ACCOUNT_COLOR)
.useMostRecentLong(AnnotatedCallLog.CALL_TYPE)
@@ -272,10 +279,14 @@ public class SystemCallLogDataSource implements CallLogDataSource {
dialerPhoneNumberUtil.parse(numberAsStr, countryIso).toByteArray();
// TODO(zachh): Need to handle post-dial digits; different on N and M.
contentValues.put(AnnotatedCallLog.NUMBER, numberAsProtoBytes);
+ } else {
+ contentValues.put(
+ AnnotatedCallLog.NUMBER, DialerPhoneNumber.getDefaultInstance().toByteArray());
}
contentValues.put(AnnotatedCallLog.CALL_TYPE, type);
contentValues.put(AnnotatedCallLog.NAME, cachedName);
+ // TODO(zachh): Format the number using DialerPhoneNumberUtil here.
contentValues.put(AnnotatedCallLog.FORMATTED_NUMBER, formattedNumber);
contentValues.put(AnnotatedCallLog.PHOTO_URI, cachedPhotoUri);
contentValues.put(AnnotatedCallLog.PHOTO_ID, cachedPhotoId);
@@ -292,6 +303,9 @@ public class SystemCallLogDataSource implements CallLogDataSource {
contentValues.put(AnnotatedCallLog.IS_READ, isRead);
contentValues.put(AnnotatedCallLog.NEW, isNew);
contentValues.put(AnnotatedCallLog.GEOCODED_LOCATION, geocodedLocation);
+ contentValues.put(
+ AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME, phoneAccountComponentName);
+ contentValues.put(AnnotatedCallLog.PHONE_ACCOUNT_ID, phoneAccountId);
populatePhoneAccountLabelAndColor(
appContext, contentValues, phoneAccountComponentName, phoneAccountId);
contentValues.put(AnnotatedCallLog.FEATURES, features);
diff --git a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java b/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
index adb7a0742..8e9e9c659 100644
--- a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
+++ b/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
@@ -43,6 +43,12 @@ public class RowCombiner {
return this;
}
+ public RowCombiner useMostRecentBlob(String columnName) {
+ combinedRow.put(
+ columnName, individualRowsSortedByTimestampDesc.get(0).getAsByteArray(columnName));
+ return this;
+ }
+
/** Asserts that all column values for the given column name are the same, and uses it. */
public RowCombiner useSingleValueString(String columnName) {
Iterator<ContentValues> iterator = individualRowsSortedByTimestampDesc.iterator();
diff --git a/java/com/android/dialer/calllog/model/CoalescedRow.java b/java/com/android/dialer/calllog/model/CoalescedRow.java
new file mode 100644
index 000000000..091467430
--- /dev/null
+++ b/java/com/android/dialer/calllog/model/CoalescedRow.java
@@ -0,0 +1,147 @@
+/*
+ * 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.calllog.model;
+
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.dialer.DialerPhoneNumber;
+import com.google.auto.value.AutoValue;
+
+/** Data class containing the contents of a row from the CoalescedAnnotatedCallLog. */
+@AutoValue
+public abstract class CoalescedRow {
+
+ public static Builder builder() {
+ return new AutoValue_CoalescedRow.Builder()
+ .setId(0)
+ .setTimestamp(0)
+ .setNumber(DialerPhoneNumber.getDefaultInstance())
+ .setPhotoId(0)
+ .setIsRead(false)
+ .setIsNew(false)
+ .setPhoneAccountColor(0)
+ .setFeatures(0)
+ .setIsBusiness(false)
+ .setIsVoicemail(false)
+ .setNumberCalls(0)
+ .setCallType(0);
+ }
+
+ public abstract int id();
+
+ public abstract long timestamp();
+
+ @NonNull
+ public abstract DialerPhoneNumber number();
+
+ @Nullable
+ public abstract String name();
+
+ @Nullable
+ public abstract String formattedNumber();
+
+ @Nullable
+ public abstract String photoUri();
+
+ public abstract long photoId();
+
+ @Nullable
+ public abstract String lookupUri();
+
+ @Nullable
+ public abstract String numberTypeLabel();
+
+ public abstract boolean isRead();
+
+ public abstract boolean isNew();
+
+ @Nullable
+ public abstract String geocodedLocation();
+
+ @Nullable
+ public abstract String phoneAccountComponentName();
+
+ @Nullable
+ public abstract String phoneAccountId();
+
+ @Nullable
+ public abstract String phoneAccountLabel();
+
+ @ColorInt
+ public abstract int phoneAccountColor();
+
+ public abstract int features();
+
+ public abstract boolean isBusiness();
+
+ public abstract boolean isVoicemail();
+
+ public abstract int callType();
+
+ public abstract int numberCalls();
+
+ /** Builder for {@link CoalescedRow}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setId(int id);
+
+ public abstract Builder setTimestamp(long timestamp);
+
+ public abstract Builder setNumber(@NonNull DialerPhoneNumber number);
+
+ public abstract Builder setName(@Nullable String name);
+
+ public abstract Builder setFormattedNumber(@Nullable String formattedNumber);
+
+ public abstract Builder setPhotoUri(@Nullable String photoUri);
+
+ public abstract Builder setPhotoId(long photoId);
+
+ public abstract Builder setLookupUri(@Nullable String lookupUri);
+
+ public abstract Builder setNumberTypeLabel(@Nullable String numberTypeLabel);
+
+ public abstract Builder setIsRead(boolean isRead);
+
+ public abstract Builder setIsNew(boolean isNew);
+
+ public abstract Builder setGeocodedLocation(@Nullable String geocodedLocation);
+
+ public abstract Builder setPhoneAccountComponentName(
+ @Nullable String phoneAccountComponentName);
+
+ public abstract Builder setPhoneAccountId(@Nullable String phoneAccountId);
+
+ public abstract Builder setPhoneAccountLabel(@Nullable String phoneAccountLabel);
+
+ public abstract Builder setPhoneAccountColor(@ColorInt int phoneAccountColor);
+
+ public abstract Builder setFeatures(int features);
+
+ public abstract Builder setIsBusiness(boolean isBusiness);
+
+ public abstract Builder setIsVoicemail(boolean isVoicemail);
+
+ public abstract Builder setCallType(int callType);
+
+ public abstract Builder setNumberCalls(int numberCalls);
+
+ public abstract CoalescedRow build();
+ }
+}
diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
index 13a801ac8..9f635439a 100644
--- a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
+++ b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
@@ -18,115 +18,37 @@ package com.android.dialer.calllog.ui;
import android.content.Context;
import android.database.Cursor;
-import android.support.annotation.ColorInt;
import android.support.v4.content.CursorLoader;
+import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog;
+import com.android.dialer.calllog.model.CoalescedRow;
+import com.google.protobuf.InvalidProtocolBufferException;
/** CursorLoader for the coalesced annotated call log. */
final class CoalescedAnnotatedCallLogCursorLoader extends CursorLoader {
- /** Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS */
+ // Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS
private static final int ID = 0;
-
private static final int TIMESTAMP = 1;
private static final int NAME = 2;
- private static final int FORMATTED_NUMBER = 3;
- private static final int PHOTO_URI = 4;
- private static final int PHOTO_ID = 5;
- private static final int LOOKUP_URI = 6;
- private static final int NUMBER_TYPE_LABEL = 7;
- private static final int IS_READ = 8;
- private static final int NEW = 9;
- private static final int GEOCODED_LOCATION = 10;
- private static final int PHONE_ACCOUNT_LABEL = 11;
- private static final int PHONE_ACCOUNT_COLOR = 12;
- private static final int FEATURES = 13;
- private static final int IS_BUSINESS = 14;
- private static final int IS_VOICEMAIL = 15;
- private static final int TYPE = 16;
- private static final int NUMBER_CALLS = 17;
-
- /** Convenience class for accessing values using an abbreviated syntax. */
- static final class Row {
- private final Cursor cursor;
-
- Row(Cursor cursor) {
- this.cursor = cursor;
- }
-
- long id() {
- return cursor.getInt(ID);
- }
-
- long timestamp() {
- return cursor.getLong(TIMESTAMP);
- }
-
- String name() {
- return cursor.getString(NAME);
- }
-
- String formattedNumber() {
- return cursor.getString(FORMATTED_NUMBER);
- }
-
- String photoUri() {
- return cursor.getString(PHOTO_URI);
- }
-
- long photoId() {
- return cursor.getLong(PHOTO_ID);
- }
-
- String lookupUri() {
- return cursor.getString(LOOKUP_URI);
- }
-
- String numberTypeLabel() {
- return cursor.getString(NUMBER_TYPE_LABEL);
- }
-
- boolean isRead() {
- return cursor.getInt(IS_READ) == 1;
- }
-
- boolean isNew() {
- return cursor.getInt(NEW) == 1;
- }
-
- String geocodedLocation() {
- return cursor.getString(GEOCODED_LOCATION);
- }
-
- String phoneAccountLabel() {
- return cursor.getString(PHONE_ACCOUNT_LABEL);
- }
-
- @ColorInt
- int phoneAccountColor() {
- return cursor.getInt(PHONE_ACCOUNT_COLOR);
- }
-
- int features() {
- return cursor.getInt(FEATURES);
- }
-
- boolean isBusiness() {
- return cursor.getInt(IS_BUSINESS) == 1;
- }
-
- boolean isVoicemail() {
- return cursor.getInt(IS_VOICEMAIL) == 1;
- }
-
- int numberCalls() {
- return cursor.getInt(NUMBER_CALLS);
- }
-
- int callType() {
- return cursor.getInt(TYPE);
- }
- }
+ private static final int NUMBER = 3;
+ private static final int FORMATTED_NUMBER = 4;
+ private static final int PHOTO_URI = 5;
+ private static final int PHOTO_ID = 6;
+ private static final int LOOKUP_URI = 7;
+ private static final int NUMBER_TYPE_LABEL = 8;
+ private static final int IS_READ = 9;
+ private static final int NEW = 10;
+ private static final int GEOCODED_LOCATION = 11;
+ private static final int PHONE_ACCOUNT_COMPONENT_NAME = 12;
+ private static final int PHONE_ACCOUNT_ID = 13;
+ private static final int PHONE_ACCOUNT_LABEL = 14;
+ private static final int PHONE_ACCOUNT_COLOR = 15;
+ private static final int FEATURES = 16;
+ private static final int IS_BUSINESS = 17;
+ private static final int IS_VOICEMAIL = 18;
+ private static final int CALL_TYPE = 19;
+ private static final int NUMBER_CALLS = 20;
CoalescedAnnotatedCallLogCursorLoader(Context context) {
// CoalescedAnnotatedCallLog requires that PROJECTION be ALL_COLUMNS and the following params be
@@ -139,4 +61,42 @@ final class CoalescedAnnotatedCallLogCursorLoader extends CursorLoader {
null,
null);
}
+
+ /** Creates a new {@link CoalescedRow} from the provided cursor using the current position. */
+ static CoalescedRow toRow(Cursor cursor) {
+ DialerPhoneNumber number;
+ try {
+ number = DialerPhoneNumber.parseFrom(cursor.getBlob(NUMBER));
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes");
+ }
+
+ return CoalescedRow.builder()
+ .setId(cursor.getInt(ID))
+ .setTimestamp(cursor.getLong(TIMESTAMP))
+ .setName(cursor.getString(NAME))
+ .setNumber(number)
+ .setFormattedNumber(cursor.getString(FORMATTED_NUMBER))
+ .setPhotoUri(cursor.getString(PHOTO_URI))
+ .setPhotoId(cursor.getLong(PHOTO_ID))
+ .setLookupUri(cursor.getString(LOOKUP_URI))
+ .setNumberTypeLabel(cursor.getString(NUMBER_TYPE_LABEL))
+ .setIsRead(cursor.getInt(IS_READ) == 1)
+ .setIsNew(cursor.getInt(NEW) == 1)
+ .setGeocodedLocation(cursor.getString(GEOCODED_LOCATION))
+ .setPhoneAccountComponentName(cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME))
+ .setPhoneAccountId(cursor.getString(PHONE_ACCOUNT_ID))
+ .setPhoneAccountLabel(cursor.getString(PHONE_ACCOUNT_LABEL))
+ .setPhoneAccountColor(cursor.getInt(PHONE_ACCOUNT_COLOR))
+ .setFeatures(cursor.getInt(FEATURES))
+ .setIsBusiness(cursor.getInt(IS_BUSINESS) == 1)
+ .setIsVoicemail(cursor.getInt(IS_VOICEMAIL) == 1)
+ .setCallType(cursor.getInt(CALL_TYPE))
+ .setNumberCalls(cursor.getInt(NUMBER_CALLS))
+ .build();
+ }
+
+ static long getTimestamp(Cursor cursor) {
+ return cursor.getLong(TIMESTAMP);
+ }
}
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
index b922a6e3b..d5cfb7e24 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
@@ -58,15 +58,14 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
// Calculate header adapter positions by reading cursor.
long currentTimeMillis = clock.currentTimeMillis();
if (cursor.moveToNext()) {
- CoalescedAnnotatedCallLogCursorLoader.Row firstRow =
- new CoalescedAnnotatedCallLogCursorLoader.Row(cursor);
- if (CallLogDates.isSameDay(currentTimeMillis, firstRow.timestamp())) {
+ long firstTimestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor);
+ if (CallLogDates.isSameDay(currentTimeMillis, firstTimestamp)) {
this.todayHeaderPosition = 0;
int adapterPosition = 2; // Accounted for "Today" header and first row.
while (cursor.moveToNext()) {
- CoalescedAnnotatedCallLogCursorLoader.Row row =
- new CoalescedAnnotatedCallLogCursorLoader.Row(cursor);
- if (CallLogDates.isSameDay(currentTimeMillis, row.timestamp())) {
+ long timestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor);
+
+ if (CallLogDates.isSameDay(currentTimeMillis, timestamp)) {
adapterPosition++;
} else {
this.olderHeaderPosition = adapterPosition;
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index 8ac419e56..4e59301ce 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -16,16 +16,19 @@
package com.android.dialer.calllog.ui;
import android.content.Context;
+import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
import android.view.View;
+import android.widget.ImageView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
-import com.android.dialer.calllog.ui.CoalescedAnnotatedCallLogCursorLoader.Row;
-import com.android.dialer.calllogutils.CallLogDates;
+import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.calllog.ui.menu.NewCallLogMenu;
+import com.android.dialer.calllogutils.CallLogEntryText;
+import com.android.dialer.calllogutils.CallLogIntents;
import com.android.dialer.calllogutils.CallTypeIconsView;
import com.android.dialer.contactphoto.ContactPhotoManager;
import com.android.dialer.lettertile.LetterTileDrawable;
@@ -43,6 +46,8 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
private final CallTypeIconsView primaryCallTypeIconsView; // Used for Wifi, HD icons
private final CallTypeIconsView secondaryCallTypeIconsView; // Used for call types
private final TextView phoneAccountView;
+ private final ImageView menuButton;
+
private final Clock clock;
NewCallLogViewHolder(View view, Clock clock) {
@@ -54,17 +59,18 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
primaryCallTypeIconsView = view.findViewById(R.id.primary_call_type_icons);
secondaryCallTypeIconsView = view.findViewById(R.id.secondary_call_type_icons);
phoneAccountView = view.findViewById(R.id.phone_account);
+ menuButton = view.findViewById(R.id.menu_button);
+
this.clock = clock;
}
/** @param cursor a cursor from {@link CoalescedAnnotatedCallLogCursorLoader}. */
void bind(Cursor cursor) {
- CoalescedAnnotatedCallLogCursorLoader.Row row =
- new CoalescedAnnotatedCallLogCursorLoader.Row(cursor);
+ CoalescedRow row = CoalescedAnnotatedCallLogCursorLoader.toRow(cursor);
// TODO(zachh): Handle RTL properly.
- primaryTextView.setText(buildPrimaryText(row));
- secondaryTextView.setText(buildSecondaryText(row));
+ primaryTextView.setText(CallLogEntryText.buildPrimaryText(context, row));
+ secondaryTextView.setText(CallLogEntryText.buildSecondaryTextForEntries(context, clock, row));
if (isNewMissedCall(row)) {
primaryTextView.setTextAppearance(R.style.primary_textview_new_call);
@@ -72,77 +78,29 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
secondaryTextView.setTextAppearance(R.style.secondary_textview_new_call);
}
+ setNumberCalls(row);
setPhoto(row);
setPrimaryCallTypes(row);
setSecondaryCallTypes(row);
setPhoneAccounts(row);
+ setOnClickListenerForRow(row);
+ setOnClickListenerForMenuButon(row);
}
- private String buildPrimaryText(CoalescedAnnotatedCallLogCursorLoader.Row row) {
- StringBuilder primaryText = new StringBuilder();
- if (!TextUtils.isEmpty(row.name())) {
- primaryText.append(row.name());
- } else if (!TextUtils.isEmpty(row.formattedNumber())) {
- primaryText.append(row.formattedNumber());
- } else {
- // TODO(zachh): Handle CallLog.Calls.PRESENTATION_*, including Verizon restricted numbers.
- primaryText.append(context.getText(R.string.new_call_log_unknown));
- }
+ private void setNumberCalls(CoalescedRow row) {
+ // TODO(zachh): Number of calls shouldn't be text, but a circle with a number inside.
if (row.numberCalls() > 1) {
- primaryText.append(String.format(Locale.getDefault(), " (%d)", row.numberCalls()));
+ primaryTextView.append(String.format(Locale.getDefault(), " (%d)", row.numberCalls()));
}
- return primaryText.toString();
}
- private boolean isNewMissedCall(CoalescedAnnotatedCallLogCursorLoader.Row row) {
+ private boolean isNewMissedCall(CoalescedRow row) {
// Show missed call styling if the most recent call in the group was missed and it is still
// marked as NEW. It is not clear what IS_READ should be used for and it is currently not used.
return row.callType() == Calls.MISSED_TYPE && row.isNew();
}
- private String buildSecondaryText(CoalescedAnnotatedCallLogCursorLoader.Row row) {
- /*
- * Rules: (Duo video, )?$Label|$Location • Date
- *
- * Examples:
- * Duo Video, Mobile • Now
- * Duo Video • 11:45pm
- * Mobile • 11:45pm
- * Mobile • Sunday
- * Brooklyn, NJ • Jan 15
- *
- * Date rules:
- * if < 1 minute ago: "Now"; else if today: HH:MM(am|pm); else if < 3 days: day; else: MON D
- */
- StringBuilder secondaryText = new StringBuilder();
- if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
- // TODO(zachh): Add "Duo" prefix?
- secondaryText.append(context.getText(R.string.new_call_log_video));
- }
- String numberTypeLabel = row.numberTypeLabel();
- if (!TextUtils.isEmpty(numberTypeLabel)) {
- if (secondaryText.length() > 0) {
- secondaryText.append(", ");
- }
- secondaryText.append(numberTypeLabel);
- } else { // If there's a number type label, don't show the location.
- String location = row.geocodedLocation();
- if (!TextUtils.isEmpty(location)) {
- if (secondaryText.length() > 0) {
- secondaryText.append(", ");
- }
- secondaryText.append(location);
- }
- }
- if (secondaryText.length() > 0) {
- secondaryText.append(" • ");
- }
- secondaryText.append(
- CallLogDates.newCallLogTimestampLabel(context, clock.currentTimeMillis(), row.timestamp()));
- return secondaryText.toString();
- }
-
- private void setPhoto(Row row) {
+ private void setPhoto(CoalescedRow row) {
// TODO(zachh): Set the contact type.
ContactPhotoManager.getInstance(context)
.loadDialerThumbnailOrPhoto(
@@ -154,7 +112,7 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
LetterTileDrawable.TYPE_DEFAULT);
}
- private void setPrimaryCallTypes(Row row) {
+ private void setPrimaryCallTypes(CoalescedRow row) {
// Only HD and Wifi icons are shown following the primary text.
primaryCallTypeIconsView.setShowHd(
MotorolaUtils.shouldShowHdIconInCallLog(context, row.features()));
@@ -162,18 +120,32 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
MotorolaUtils.shouldShowWifiIconInCallLog(context, row.features()));
}
- private void setSecondaryCallTypes(Row row) {
+ private void setSecondaryCallTypes(CoalescedRow row) {
// Only call type icon is shown before the secondary text.
secondaryCallTypeIconsView.add(row.callType());
// TODO(zachh): Per new mocks, may need to add method to CallTypeIconsView to disable coloring.
}
- private void setPhoneAccounts(Row row) {
+ private void setPhoneAccounts(CoalescedRow row) {
if (row.phoneAccountLabel() != null) {
phoneAccountView.setText(row.phoneAccountLabel());
phoneAccountView.setTextColor(row.phoneAccountColor());
phoneAccountView.setVisibility(View.VISIBLE);
}
}
+
+ private void setOnClickListenerForRow(CoalescedRow row) {
+ itemView.setOnClickListener(
+ (view) -> {
+ Intent callbackIntent = CallLogIntents.getCallBackIntent(row);
+ if (callbackIntent != null) {
+ context.startActivity(callbackIntent);
+ }
+ });
+ }
+
+ private void setOnClickListenerForMenuButon(CoalescedRow row) {
+ menuButton.setOnClickListener(NewCallLogMenu.createOnClickListener(context, row));
+ }
}
diff --git a/java/com/android/dialer/calllog/ui/menu/AndroidManifest.xml b/java/com/android/dialer/calllog/ui/menu/AndroidManifest.xml
new file mode 100644
index 000000000..0d8274dff
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/menu/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<!--
+ ~ 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
+ -->
+<manifest package="com.android.dialer.calllog.ui.menu"/>
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
new file mode 100644
index 000000000..8de63518c
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -0,0 +1,234 @@
+/*
+ * 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.calllog.ui.menu;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+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;
+import com.android.dialer.clipboard.ClipboardUtils;
+import com.android.dialer.contactactions.ContactActionModule;
+import com.android.dialer.contactactions.DividerModule;
+import com.android.dialer.contactactions.IntentModule;
+import com.android.dialer.dialercontact.DialerContact;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.util.IntentUtil;
+import com.android.dialer.util.UriUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Configures the modules for the bottom sheet; these are the rows below the top row (primary
+ * action) in the bottom sheet.
+ */
+final class Modules {
+
+ static List<ContactActionModule> fromRow(Context context, CoalescedRow row) {
+ // Conditionally add each module, which are items in the bottom sheet's menu.
+ List<ContactActionModule> modules = new ArrayList<>();
+
+ maybeAddModuleForVideoOrAudioCall(context, row, modules);
+ maybeAddModuleForAddingToContacts(context, row, modules);
+
+ String originalNumber = row.number().getRawInput().getNumber();
+ maybeAddModuleForSendingTextMessage(context, originalNumber, modules);
+
+ if (!modules.isEmpty()) {
+ modules.add(new DividerModule());
+ }
+
+ // TODO(zachh): Module for blocking/unblocking spam.
+ // TODO(zachh): Module for CallComposer.
+ maybeAddModuleForCopyingNumber(context, originalNumber, modules);
+
+ // TODO(zachh): Revisit if DialerContact is the best thing to pass to CallDetails; could
+ // it use a ContactPrimaryActionInfo instead?
+ addModuleForAccessingCallDetails(context, createDialerContactFromRow(row), modules);
+
+ return modules;
+ }
+
+ private static void maybeAddModuleForVideoOrAudioCall(
+ Context context, CoalescedRow row, List<ContactActionModule> modules) {
+ String originalNumber = row.number().getRawInput().getNumber();
+ if (TextUtils.isEmpty(originalNumber)) {
+ // Skip adding the menu item if the phone number is unknown.
+ return;
+ }
+
+ PhoneAccountHandle phoneAccountHandle =
+ PhoneAccountUtils.getAccount(row.phoneAccountComponentName(), row.phoneAccountId());
+
+ if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
+ // Add an audio call item for video calls. Clicking the top entry on the bottom sheet will
+ // trigger a video call.
+ modules.add(
+ IntentModule.newCallModule(
+ context, originalNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG));
+ } else {
+ // Add a video call item for audio calls. Click the top entry on the bottom sheet will
+ // trigger an audio call.
+ // TODO(zachh): Only show video option if video capabilities present?
+ modules.add(
+ IntentModule.newVideoCallModule(
+ context, originalNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG));
+ }
+ }
+
+ private static void maybeAddModuleForAddingToContacts(
+ Context context, CoalescedRow row, List<ContactActionModule> modules) {
+ // TODO(zachh): Only show this for non-spam/blocked numbers.
+
+ // Skip showing the menu item for existing contacts.
+ if (isExistingContact(row)) {
+ return;
+ }
+
+ // Skip showing the menu item if there is no number.
+ String originalNumber = row.number().getRawInput().getNumber();
+ if (TextUtils.isEmpty(originalNumber)) {
+ return;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
+ intent.putExtra(ContactsContract.Intents.Insert.PHONE, originalNumber);
+
+ if (!TextUtils.isEmpty(row.name())) {
+ intent.putExtra(ContactsContract.Intents.Insert.NAME, row.name());
+ }
+ modules.add(
+ new IntentModule(
+ context,
+ intent,
+ R.string.add_to_contacts,
+ R.drawable.quantum_ic_person_add_vd_theme_24));
+ }
+
+ /**
+ * Lookup URIs are currently fetched from the cached column of the system call log. This URI
+ * contains encoded information for non-contacts for the purposes of populating contact cards.
+ *
+ * <p>We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or
+ * not.
+ *
+ * <p>TODO(zachh): We should revisit this once the contact URI is no longer being read from the
+ * cached column in the system database, in case we decide not to overload the column.
+ */
+ private static boolean isExistingContact(CoalescedRow row) {
+ return !TextUtils.isEmpty(row.lookupUri())
+ && !UriUtils.isEncodedContactUri(Uri.parse(row.lookupUri()));
+ }
+
+ private static void maybeAddModuleForSendingTextMessage(
+ Context context, String originalNumber, List<ContactActionModule> modules) {
+ // TODO(zachh): There are some conditions where this module should not be shown; consider
+ // voicemail, business numbers, blocked numbers, spam numbers, etc.
+ if (!TextUtils.isEmpty(originalNumber)) {
+ modules.add(
+ new IntentModule(
+ context,
+ IntentUtil.getSendSmsIntent(originalNumber),
+ R.string.send_a_message,
+ R.drawable.quantum_ic_message_vd_theme_24));
+ }
+ }
+
+ private static void maybeAddModuleForCopyingNumber(
+ Context context, String originalNumber, List<ContactActionModule> modules) {
+ if (TextUtils.isEmpty(originalNumber)) {
+ return;
+ }
+ modules.add(
+ new ContactActionModule() {
+ @Override
+ public int getStringId() {
+ return R.string.copy_number;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.quantum_ic_content_copy_vd_theme_24;
+ }
+
+ @Override
+ public boolean onClick() {
+ ClipboardUtils.copyText(context, null, originalNumber, true);
+ return false;
+ }
+ });
+ }
+
+ 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();
+ boolean canReportInaccurateNumber = false;
+ boolean canSupportAssistedDialing = false; // TODO(zachh): Properly set value.
+
+ modules.add(
+ new IntentModule(
+ context,
+ CallDetailsActivity.newInstance(
+ context,
+ callDetailsEntries,
+ dialerContact,
+ canReportInaccurateNumber,
+ canSupportAssistedDialing),
+ R.string.call_details,
+ R.drawable.quantum_ic_info_outline_vd_theme_24));
+ }
+
+ private static DialerContact createDialerContactFromRow(CoalescedRow row) {
+ // TODO(zachh): Do something with parsed values to make more dialable?
+ String originalNumber = row.number().getRawInput().getNumber();
+
+ DialerContact.Builder dialerContactBuilder =
+ DialerContact.newBuilder()
+ .setNumber(originalNumber)
+ .setContactType(LetterTileDrawable.TYPE_DEFAULT) // TODO(zachh): Use proper type.
+ .setPhotoId(row.photoId());
+
+ if (!TextUtils.isEmpty(row.name())) {
+ dialerContactBuilder.setNameOrNumber(row.name());
+ } else if (!TextUtils.isEmpty(originalNumber)) {
+ dialerContactBuilder.setNameOrNumber(originalNumber);
+ }
+ if (row.numberTypeLabel() != null) {
+ dialerContactBuilder.setNumberLabel(row.numberTypeLabel());
+ }
+ if (row.photoUri() != null) {
+ dialerContactBuilder.setPhotoUri(row.photoUri());
+ }
+ if (row.lookupUri() != null) {
+ dialerContactBuilder.setContactUri(row.lookupUri());
+ }
+ if (row.formattedNumber() != null) {
+ dialerContactBuilder.setDisplayNumber(row.formattedNumber());
+ }
+ return dialerContactBuilder.build();
+ }
+}
diff --git a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
new file mode 100644
index 000000000..2ae823e7f
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
@@ -0,0 +1,33 @@
+/*
+ * 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.calllog.ui.menu;
+
+import android.content.Context;
+import android.view.View;
+import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.contactactions.ContactActionBottomSheet;
+
+/** Handles configuration of the bottom sheet menus for call log entries. */
+public final class NewCallLogMenu {
+
+ /** Creates and returns the OnClickListener which opens the menu for the provided row. */
+ public static View.OnClickListener createOnClickListener(Context context, CoalescedRow row) {
+ return (view) ->
+ ContactActionBottomSheet.show(
+ context, PrimaryAction.fromRow(context, row), Modules.fromRow(context, row));
+ }
+}
diff --git a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
new file mode 100644
index 000000000..7077d0231
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.calllog.ui.menu;
+
+import android.content.Context;
+import android.provider.CallLog.Calls;
+import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.calllogutils.CallLogEntryText;
+import com.android.dialer.calllogutils.CallLogIntents;
+import com.android.dialer.contactactions.ContactPrimaryActionInfo;
+import com.android.dialer.contactactions.ContactPrimaryActionInfo.PhotoInfo;
+import com.android.dialer.lettertile.LetterTileDrawable;
+
+/** Configures the primary action row (top row) for the bottom sheet. */
+final class PrimaryAction {
+
+ static ContactPrimaryActionInfo fromRow(Context context, CoalescedRow row) {
+ return ContactPrimaryActionInfo.builder()
+ .setNumber(row.number())
+ .setPhotoInfo(
+ PhotoInfo.builder()
+ .setPhotoId(row.photoId())
+ .setPhotoUri(row.photoUri())
+ .setLookupUri(row.lookupUri())
+ .setIsVideo((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)
+ .setContactType(LetterTileDrawable.TYPE_DEFAULT) // TODO(zachh): Use proper type.
+ .setDisplayName(row.name())
+ .build())
+ .setPrimaryText(CallLogEntryText.buildPrimaryText(context, row))
+ .setSecondaryText(CallLogEntryText.buildSecondaryTextForBottomSheet(context, row))
+ .setIntent(CallLogIntents.getCallBackIntent(row))
+ .build();
+ }
+}
diff --git a/java/com/android/dialer/calllog/ui/menu/res/values/strings.xml b/java/com/android/dialer/calllog/ui/menu/res/values/strings.xml
new file mode 100644
index 000000000..aaa7da04a
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/menu/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?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>
+
+ <!-- Option shown in call log menu to add the phone number from an entry to an existing contact
+ (also provides option to create a new contact from the number). [CHAR LIMIT=30] -->
+ <string name="add_to_contacts">Add to contacts</string>
+
+ <!-- Option displayed in call log menu to copy phone number. [CHAR LIMIT=30] -->
+ <string name="copy_number">Copy number</string>
+
+ <!-- Options shown in call log menu to send a SMS to the number represented by the call log entry.
+ [CHAR LIMIT=30] -->
+ <string name="send_a_message">Send a message</string>
+
+ <!-- Option shown in call log menu to navigate the user to the call details screen where the user
+ can view details for the call log entry. [CHAR LIMIT=30] -->
+ <string name="call_details">Call details</string>
+
+</resources> \ No newline at end of file
diff --git a/java/com/android/dialer/calllog/ui/res/values/strings.xml b/java/com/android/dialer/calllog/ui/res/values/strings.xml
index 9b044ca08..0ef0eaf26 100644
--- a/java/com/android/dialer/calllog/ui/res/values/strings.xml
+++ b/java/com/android/dialer/calllog/ui/res/values/strings.xml
@@ -17,12 +17,6 @@
<resources>
- <!-- Text to show in call log for a video call. [CHAR LIMIT=16] -->
- <string name="new_call_log_video">Video</string>
-
- <!-- String used to display calls from unknown numbers in the call log. [CHAR LIMIT=30] -->
- <string name="new_call_log_unknown">Unknown</string>
-
<!-- Header in call log to group calls from the current day. [CHAR LIMIT=30] -->
<string name="new_call_log_header_today">Today</string>
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
new file mode 100644
index 000000000..873f9ebd0
--- /dev/null
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -0,0 +1,148 @@
+/*
+ * 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.calllogutils;
+
+import android.content.Context;
+import android.provider.CallLog.Calls;
+import android.text.TextUtils;
+import com.android.dialer.calllog.model.CoalescedRow;
+import com.android.dialer.time.Clock;
+
+/**
+ * Computes the primary text and secondary text for call log entries.
+ *
+ * <p>These text values are shown in the main call log list or in the top item of the bottom sheet
+ * menu.
+ */
+public final class CallLogEntryText {
+
+ /**
+ * The primary text for bottom sheets is the same as shown in the entry list.
+ *
+ * <p>(In the entry list, the number of calls and additional icons are displayed as images
+ * following the primary text.)
+ */
+ public static CharSequence buildPrimaryText(Context context, CoalescedRow row) {
+ StringBuilder primaryText = new StringBuilder();
+ if (!TextUtils.isEmpty(row.name())) {
+ primaryText.append(row.name());
+ } else if (!TextUtils.isEmpty(row.formattedNumber())) {
+ primaryText.append(row.formattedNumber());
+ } else {
+ // TODO(zachh): Handle CallLog.Calls.PRESENTATION_*, including Verizon restricted numbers.
+ primaryText.append(context.getText(R.string.new_call_log_unknown));
+ }
+ return primaryText.toString();
+ }
+
+ /** The secondary text to show in the main call log entry list. */
+ public static CharSequence buildSecondaryTextForEntries(
+ Context context, Clock clock, CoalescedRow row) {
+ /*
+ * Rules: (Duo video, )?$Label|$Location • Date
+ *
+ * Examples:
+ * Duo Video, Mobile • Now
+ * Duo Video • 11:45pm
+ * Mobile • 11:45pm
+ * Mobile • Sunday
+ * Brooklyn, NJ • Jan 15
+ *
+ * Date rules:
+ * if < 1 minute ago: "Now"; else if today: HH:MM(am|pm); else if < 3 days: day; else: MON D
+ */
+ StringBuilder secondaryText = secondaryTextPrefix(context, row);
+
+ if (secondaryText.length() > 0) {
+ secondaryText.append(" • ");
+ }
+ secondaryText.append(
+ CallLogDates.newCallLogTimestampLabel(context, clock.currentTimeMillis(), row.timestamp()));
+ return secondaryText.toString();
+ }
+
+ /**
+ * The secondary text to show in the top item of the bottom sheet.
+ *
+ * <p>This is basically the same as {@link #buildSecondaryTextForEntries(Context, Clock,
+ * CoalescedRow)} except that instead of suffixing with the time of the call, we suffix with the
+ * formatted number.
+ */
+ public static String buildSecondaryTextForBottomSheet(Context context, CoalescedRow row) {
+ /*
+ * Rules: (Duo video, )?$Label|$Location [• NumberIfNoName]?
+ *
+ * The number is shown at the end if there is no name for the entry. (It is shown in primary
+ * text otherwise.)
+ *
+ * Examples:
+ * Duo Video, Mobile • 555-1234
+ * Duo Video • 555-1234
+ * Mobile • 555-1234
+ * Mobile • 555-1234
+ * Brooklyn, NJ
+ */
+ StringBuilder secondaryText = secondaryTextPrefix(context, row);
+
+ if (TextUtils.isEmpty(row.name())) {
+ // If the name is empty the number is shown as the primary text and there's nothing to add.
+ return secondaryText.toString();
+ }
+ if (TextUtils.isEmpty(row.formattedNumber())) {
+ // If there's no number, don't append anything.
+ return secondaryText.toString();
+ }
+ // Otherwise append the number.
+ if (secondaryText.length() > 0) {
+ secondaryText.append(" • ");
+ }
+ secondaryText.append(row.formattedNumber());
+ return secondaryText.toString();
+ }
+
+ /**
+ * Returns a value such as "Duo Video, Mobile" without the time of the call or formatted number
+ * appended.
+ *
+ * <p>When the secondary text is shown in call log entry list, this prefix is suffixed with the
+ * time of the call, and when it is shown in a bottom sheet, it is suffixed with the formatted
+ * number.
+ */
+ private static StringBuilder secondaryTextPrefix(Context context, CoalescedRow row) {
+ StringBuilder secondaryText = new StringBuilder();
+ if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
+ // TODO(zachh): Add "Duo" prefix?
+ secondaryText.append(context.getText(R.string.new_call_log_video));
+ }
+ String numberTypeLabel = row.numberTypeLabel();
+ if (!TextUtils.isEmpty(numberTypeLabel)) {
+ if (secondaryText.length() > 0) {
+ secondaryText.append(", ");
+ }
+ secondaryText.append(numberTypeLabel);
+ } else { // If there's a number type label, don't show the location.
+ String location = row.geocodedLocation();
+ if (!TextUtils.isEmpty(location)) {
+ if (secondaryText.length() > 0) {
+ secondaryText.append(", ");
+ }
+ secondaryText.append(location);
+ }
+ }
+ return secondaryText;
+ }
+}
diff --git a/java/com/android/dialer/calllogutils/CallLogIntents.java b/java/com/android/dialer/calllogutils/CallLogIntents.java
new file mode 100644
index 000000000..11308e607
--- /dev/null
+++ b/java/com/android/dialer/calllogutils/CallLogIntents.java
@@ -0,0 +1,55 @@
+/*
+ * 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.calllogutils;
+
+import android.content.Intent;
+import android.provider.CallLog.Calls;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.calllog.model.CoalescedRow;
+
+/** Provides intents related to call log entries. */
+public final class CallLogIntents {
+
+ /**
+ * Returns an intent which can be used to call back for the provided row.
+ *
+ * <p>If the call was a video call, a video call will be placed, and if the call was an audio
+ * call, an audio call will be placed.
+ *
+ * @return null if the provided {@code row} doesn't have a number
+ */
+ @Nullable
+ public static Intent getCallBackIntent(CoalescedRow row) {
+ // TODO(zachh): Do something with parsed values to make more dialable?
+ String originalNumber = row.number().getRawInput().getNumber();
+
+ // TODO(zachh): Make this more sophisticated, e.g. return null for non-dialable numbers?
+ if (TextUtils.isEmpty(originalNumber)) {
+ return null;
+ }
+
+ // TODO(zachh): More granular logging?
+ // TODO(zachh): Support assisted dialing.
+ return new CallIntentBuilder(originalNumber, CallInitiationType.Type.CALL_LOG)
+ .setPhoneAccountHandle(
+ PhoneAccountUtils.getAccount(row.phoneAccountComponentName(), row.phoneAccountId()))
+ .setIsVideoCall((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)
+ .build();
+ }
+}
diff --git a/java/com/android/dialer/calllogutils/PhoneAccountUtils.java b/java/com/android/dialer/calllogutils/PhoneAccountUtils.java
index c639893ef..153f29185 100644
--- a/java/com/android/dialer/calllogutils/PhoneAccountUtils.java
+++ b/java/com/android/dialer/calllogutils/PhoneAccountUtils.java
@@ -31,7 +31,7 @@ public class PhoneAccountUtils {
/** Return a list of phone accounts that are subscription/SIM accounts. */
public static List<PhoneAccountHandle> getSubscriptionPhoneAccounts(Context context) {
- List<PhoneAccountHandle> subscriptionAccountHandles = new ArrayList<PhoneAccountHandle>();
+ List<PhoneAccountHandle> subscriptionAccountHandles = new ArrayList<>();
final List<PhoneAccountHandle> accountHandles =
TelecomUtil.getCallCapablePhoneAccounts(context);
for (PhoneAccountHandle accountHandle : accountHandles) {
diff --git a/java/com/android/dialer/calllogutils/res/values/strings.xml b/java/com/android/dialer/calllogutils/res/values/strings.xml
index 56cd94a9e..b8ba5b1f3 100644
--- a/java/com/android/dialer/calllogutils/res/values/strings.xml
+++ b/java/com/android/dialer/calllogutils/res/values/strings.xml
@@ -130,4 +130,10 @@
<!-- String to be displayed to indicate in the call log that a call just now occurred. -->
<string name="now">Now</string>
+
+ <!-- Text to show in call log for a video call. [CHAR LIMIT=16] -->
+ <string name="new_call_log_video">Video</string>
+
+ <!-- String used to display calls from unknown numbers in the call log. [CHAR LIMIT=30] -->
+ <string name="new_call_log_unknown">Unknown</string>
</resources> \ No newline at end of file
diff --git a/java/com/android/dialer/clipboard/AndroidManifest.xml b/java/com/android/dialer/clipboard/AndroidManifest.xml
new file mode 100644
index 000000000..d6da6efeb
--- /dev/null
+++ b/java/com/android/dialer/clipboard/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<!--
+ ~ 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
+ -->
+<manifest package="com.android.dialer.clipboard"/>
diff --git a/java/com/android/contacts/common/ClipboardUtils.java b/java/com/android/dialer/clipboard/ClipboardUtils.java
index 3d7683941..933ac755b 100644
--- a/java/com/android/contacts/common/ClipboardUtils.java
+++ b/java/com/android/dialer/clipboard/ClipboardUtils.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.contacts.common;
+package com.android.dialer.clipboard;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -22,7 +22,8 @@ import android.content.Context;
import android.text.TextUtils;
import android.widget.Toast;
-public class ClipboardUtils {
+/** Copies provided label and text to the clipboard and optionally shows a "text copied" toast. */
+public final class ClipboardUtils {
private ClipboardUtils() {}
diff --git a/java/com/android/dialer/clipboard/res/values/strings.xml b/java/com/android/dialer/clipboard/res/values/strings.xml
new file mode 100644
index 000000000..2edd293f4
--- /dev/null
+++ b/java/com/android/dialer/clipboard/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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>
+
+ <!-- Toast shown when text is copied to the clipboard [CHAR LIMIT=64] -->
+ <string name="toast_text_copied">Text copied</string>
+
+</resources> \ No newline at end of file
diff --git a/java/com/android/dialer/contactactions/ContactActionBottomSheet.java b/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
index 9bf7ca095..f2f1d189b 100644
--- a/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
+++ b/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
@@ -29,33 +29,38 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.dialer.common.Assert;
+import com.android.dialer.contactactions.ContactPrimaryActionInfo.PhotoInfo;
import com.android.dialer.contactphoto.ContactPhotoManager;
-import com.android.dialer.dialercontact.DialerContact;
import java.util.List;
/**
* {@link BottomSheetDialog} used for building a list of contact actions in a bottom sheet menu.
*
- * <p>{@link #show(Context, DialerContact, List)} should be used to create and display the menu.
- * Modules are built using {@link ContactActionModule} and some defaults are provided by {@link
- * IntentModule} and {@link DividerModule}.
+ * <p>{@link #show(Context, ContactPrimaryActionInfo, List)} should be used to create and display
+ * the menu. Modules are built using {@link ContactActionModule} and some defaults are provided by
+ * {@link IntentModule} and {@link DividerModule}.
*/
public class ContactActionBottomSheet extends BottomSheetDialog implements OnClickListener {
private final List<ContactActionModule> modules;
- private final DialerContact contact;
+ private final ContactPrimaryActionInfo contactPrimaryActionInfo;
private ContactActionBottomSheet(
- Context context, DialerContact contact, List<ContactActionModule> modules) {
+ Context context,
+ ContactPrimaryActionInfo contactPrimaryActionInfo,
+ List<ContactActionModule> modules) {
super(context);
this.modules = modules;
- this.contact = contact;
+ this.contactPrimaryActionInfo = contactPrimaryActionInfo;
setContentView(LayoutInflater.from(context).inflate(R.layout.sheet_layout, null));
}
public static ContactActionBottomSheet show(
- Context context, DialerContact contact, List<ContactActionModule> modules) {
- ContactActionBottomSheet sheet = new ContactActionBottomSheet(context, contact, modules);
+ Context context,
+ ContactPrimaryActionInfo contactPrimaryActionInfo,
+ List<ContactActionModule> modules) {
+ ContactActionBottomSheet sheet =
+ new ContactActionBottomSheet(context, contactPrimaryActionInfo, modules);
sheet.show();
return sheet;
}
@@ -75,38 +80,37 @@ public class ContactActionBottomSheet extends BottomSheetDialog implements OnCli
}
}
- // TODO(calderwoodra): add on click action to contact.
private View getContactView(ViewGroup container) {
LayoutInflater inflater = LayoutInflater.from(getContext());
View contactView = inflater.inflate(R.layout.contact_layout, container, false);
+ // TODO(zachh): The contact image should be badged with a video icon if it is for a video call.
+ PhotoInfo photoInfo = contactPrimaryActionInfo.photoInfo();
ContactPhotoManager.getInstance(getContext())
.loadDialerThumbnailOrPhoto(
contactView.findViewById(R.id.quick_contact_photo),
- contact.hasContactUri() ? Uri.parse(contact.getContactUri()) : null,
- contact.getPhotoId(),
- contact.hasPhotoUri() ? Uri.parse(contact.getPhotoUri()) : null,
- contact.getNameOrNumber(),
- contact.getContactType());
+ photoInfo.lookupUri() != null ? Uri.parse(photoInfo.lookupUri()) : null,
+ photoInfo.photoId(),
+ photoInfo.photoUri() != null ? Uri.parse(photoInfo.photoUri()) : null,
+ photoInfo.displayName(),
+ photoInfo.contactType());
- TextView nameView = contactView.findViewById(R.id.contact_name);
- TextView numberView = contactView.findViewById(R.id.phone_number);
+ TextView primaryTextView = contactView.findViewById(R.id.primary_text);
+ TextView secondaryTextView = contactView.findViewById(R.id.secondary_text);
- nameView.setText(contact.getNameOrNumber());
- if (!TextUtils.isEmpty(contact.getDisplayNumber())) {
- numberView.setVisibility(View.VISIBLE);
- String secondaryInfo =
- TextUtils.isEmpty(contact.getNumberLabel())
- ? contact.getDisplayNumber()
- : getContext()
- .getString(
- com.android.contacts.common.R.string.call_subject_type_and_number,
- contact.getNumberLabel(),
- contact.getDisplayNumber());
- numberView.setText(secondaryInfo);
+ primaryTextView.setText(contactPrimaryActionInfo.primaryText());
+ if (!TextUtils.isEmpty(contactPrimaryActionInfo.secondaryText())) {
+ secondaryTextView.setText(contactPrimaryActionInfo.secondaryText());
} else {
- numberView.setVisibility(View.GONE);
- numberView.setText(null);
+ secondaryTextView.setVisibility(View.GONE);
+ secondaryTextView.setText(null);
+ }
+ if (contactPrimaryActionInfo.intent() != null) {
+ contactView.setOnClickListener(
+ (view) -> {
+ getContext().startActivity(contactPrimaryActionInfo.intent());
+ dismiss();
+ });
}
return contactView;
}
diff --git a/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java b/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
new file mode 100644
index 000000000..2535f853d
--- /dev/null
+++ b/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
@@ -0,0 +1,118 @@
+/*
+ * 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.contactactions;
+
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.google.auto.value.AutoValue;
+
+/**
+ * Contains information necessary to construct the primary action for a contact bottom sheet.
+ *
+ * <p>This may include information about the call, for instance when the bottom sheet is shown from
+ * the call log.
+ */
+@AutoValue
+public abstract class ContactPrimaryActionInfo {
+
+ @Nullable
+ public abstract DialerPhoneNumber number();
+
+ /** Information used to construct the photo for the contact. */
+ @AutoValue
+ public abstract static class PhotoInfo {
+ public abstract long photoId();
+
+ @Nullable
+ public abstract String photoUri();
+
+ @Nullable
+ public abstract String lookupUri();
+
+ /** Badges the photo with a video icon if true. */
+ public abstract boolean isVideo();
+
+ @LetterTileDrawable.ContactType
+ public abstract int contactType();
+
+ /** Used to generate letter tile if there is no photo. */
+ @Nullable
+ public abstract String displayName();
+
+ /** Builder for {@link PhotoInfo}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setPhotoId(long photoId);
+
+ public abstract Builder setPhotoUri(@Nullable String photoUri);
+
+ public abstract Builder setLookupUri(@Nullable String lookupUri);
+
+ public abstract Builder setIsVideo(boolean isVideo);
+
+ public abstract Builder setContactType(@LetterTileDrawable.ContactType int contactType);
+
+ public abstract Builder setDisplayName(@Nullable String displayName);
+
+ public abstract PhotoInfo build();
+ }
+
+ public static Builder builder() {
+ return new AutoValue_ContactPrimaryActionInfo_PhotoInfo.Builder();
+ }
+ }
+
+ @NonNull
+ public abstract PhotoInfo photoInfo();
+
+ @Nullable
+ public abstract CharSequence primaryText();
+
+ @Nullable
+ public abstract CharSequence secondaryText();
+
+ /**
+ * The intent to fire when the user clicks the top row of the bottom sheet. Null if no action
+ * should occur (e.g. if the number is unknown).
+ */
+ @Nullable
+ public abstract Intent intent();
+
+ // TODO(zachh): Add SIM info here if should be shown in bottom sheet.
+
+ /** Builder for {@link ContactPrimaryActionInfo}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setNumber(@Nullable DialerPhoneNumber dialerPhoneNumber);
+
+ public abstract Builder setPhotoInfo(@NonNull PhotoInfo photoInfo);
+
+ public abstract Builder setPrimaryText(@Nullable CharSequence primaryText);
+
+ public abstract Builder setSecondaryText(@Nullable CharSequence secondaryText);
+
+ public abstract Builder setIntent(@Nullable Intent intent);
+
+ public abstract ContactPrimaryActionInfo build();
+ }
+
+ public static Builder builder() {
+ return new AutoValue_ContactPrimaryActionInfo.Builder();
+ }
+}
diff --git a/java/com/android/dialer/contactactions/IntentModule.java b/java/com/android/dialer/contactactions/IntentModule.java
index 201f52192..5a4870cbe 100644
--- a/java/com/android/dialer/contactactions/IntentModule.java
+++ b/java/com/android/dialer/contactactions/IntentModule.java
@@ -19,7 +19,9 @@ package com.android.dialer.contactactions;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
+import android.telecom.PhoneAccountHandle;
import com.android.dialer.callintent.CallInitiationType.Type;
import com.android.dialer.callintent.CallIntentBuilder;
@@ -56,19 +58,33 @@ public class IntentModule implements ContactActionModule {
return true;
}
- public static IntentModule newCallModule(Context context, String number, Type initiationType) {
+ public static IntentModule newCallModule(
+ Context context,
+ String number,
+ @Nullable PhoneAccountHandle phoneAccountHandle,
+ Type initiationType) {
+ // TODO(zachh): Support post-dial digits; consider using DialerPhoneNumber.
return new IntentModule(
context,
- new CallIntentBuilder(number, initiationType).build(),
+ new CallIntentBuilder(number, initiationType)
+ .setPhoneAccountHandle(phoneAccountHandle)
+ .build(),
R.string.call,
R.drawable.quantum_ic_call_white_24);
}
public static IntentModule newVideoCallModule(
- Context context, String number, Type initiationType) {
+ Context context,
+ String number,
+ @Nullable PhoneAccountHandle phoneAccountHandle,
+ Type initiationType) {
+ // TODO(zachh): Support post-dial digits; consider using DialerPhoneNumber.
return new IntentModule(
context,
- new CallIntentBuilder(number, initiationType).setIsVideoCall(true).build(),
+ new CallIntentBuilder(number, initiationType)
+ .setPhoneAccountHandle(phoneAccountHandle)
+ .setIsVideoCall(true)
+ .build(),
R.string.video_call,
R.drawable.quantum_ic_videocam_white_24);
}
diff --git a/java/com/android/dialer/contactactions/res/layout/contact_layout.xml b/java/com/android/dialer/contactactions/res/layout/contact_layout.xml
index bf3297153..8ea05d4d6 100644
--- a/java/com/android/dialer/contactactions/res/layout/contact_layout.xml
+++ b/java/com/android/dialer/contactactions/res/layout/contact_layout.xml
@@ -38,13 +38,13 @@
android:gravity="center_vertical">
<TextView
- android:id="@+id/contact_name"
+ android:id="@+id/primary_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/PrimaryText"/>
<TextView
- android:id="@+id/phone_number"
+ android:id="@+id/secondary_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"