diff options
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" |