summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZachary Heidepriem <zachh@google.com>2017-10-11 16:03:06 -0700
committerZachary Heidepriem <zachh@google.com>2017-10-11 16:03:06 -0700
commita0df9f7f52b4d7f926581f30bd0a7774a6abac43 (patch)
treec83d8715c6c6ed61423c285bb71b8fe71e1bad5c
parent36a5f1a127ca18869cd25cef0315076591a0b518 (diff)
Added basic bottom sheet to new call log.
Also added ability to click on row to call. Required plumbing through the original phone number and phone account info through AnnotatedCallLog and CoalescedAnnotatedCallLog, so that clicking to dial doesn't require an additional lookup. Required some refactoring: -created autovalue for CoalescedRow. -created autovalue for ContactPrimaryActionInfo and use it in ContactActionBottomSheet -moved logic for building primary and secondary text into CallLogUtils so it can be shared between call log list and bottom sheets -moved clipboard logic to own package for copying numbers Bug: 34672501 Test: unit PiperOrigin-RevId: 171760252 Change-Id: I645d89974460b611c1d9668c3ca3e50a716c7f8f
-rw-r--r--Android.mk2
-rw-r--r--assets/quantum/res/drawable/quantum_ic_content_copy_vd_theme_24.xml10
-rw-r--r--assets/quantum/res/drawable/quantum_ic_info_outline_vd_theme_24.xml10
-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
33 files changed, 1133 insertions, 249 deletions
diff --git a/Android.mk b/Android.mk
index 0f138914f..bf44ffa13 100644
--- a/Android.mk
+++ b/Android.mk
@@ -102,7 +102,9 @@ LOCAL_AAPT_FLAGS := \
com.android.dialer.calldetails \
com.android.dialer.calllog.database \
com.android.dialer.calllog.ui \
+ com.android.dialer.calllog.ui.menu \
com.android.dialer.calllogutils \
+ com.android.dialer.clipboard \
com.android.dialer.common \
com.android.dialer.configprovider \
com.android.dialer.contactactions \
diff --git a/assets/quantum/res/drawable/quantum_ic_content_copy_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_content_copy_vd_theme_24.xml
new file mode 100644
index 000000000..29704d95c
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_content_copy_vd_theme_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
+</vector>
diff --git a/assets/quantum/res/drawable/quantum_ic_info_outline_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_info_outline_vd_theme_24.xml
new file mode 100644
index 000000000..9ac30d705
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_info_outline_vd_theme_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
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"