diff options
Diffstat (limited to 'java')
14 files changed, 408 insertions, 257 deletions
diff --git a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java index f689571bd..513c8aa59 100644 --- a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java +++ b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java @@ -21,6 +21,7 @@ import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.text.format.Time; import com.android.contacts.common.util.DateUtils; @@ -193,7 +194,7 @@ public class CallLogGroupBuilder { if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) { return compareSipAddresses(number1, number2); } else { - return PhoneNumberHelper.compare(number1, number2); + return PhoneNumberUtils.compare(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 c9c053cea..7071ab5c1 100644 --- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java +++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java @@ -42,10 +42,9 @@ public class AnnotatedCallLogContract { String TIMESTAMP = "timestamp"; /** - * Copied from {@link android.provider.CallLog.Calls#CACHED_NAME}. - * - * <p>This is exactly how it should appear to the user. If the user's locale or name display - * preferences change, this column should be rewritten. + * The name (which may be a person's name or business name, but not a number) formatted exactly + * as it should appear to the user. If the user's locale or name display preferences change, + * this column should be rewritten. * * <p>Type: TEXT */ @@ -61,28 +60,29 @@ public class AnnotatedCallLogContract { String NUMBER = "number"; /** - * Copied from {@link android.provider.CallLog.Calls#CACHED_FORMATTED_NUMBER}. + * The number formatted as it should be displayed to the user. Note that it may not always be + * displayed, for example if the number has a corresponding person or business name. * * <p>Type: TEXT */ String FORMATTED_NUMBER = "formatted_number"; /** - * Copied from {@link android.provider.CallLog.Calls#CACHED_PHOTO_URI}. + * A photo URI for the contact to display in the call log list view. * * <p>TYPE: TEXT */ String PHOTO_URI = "photo_uri"; /** - * Copied from {@link android.provider.CallLog.Calls#CACHED_PHOTO_ID}. + * A photo ID (from the contacts provider) for the contact to display in the call log list view. * * <p>Type: INTEGER (long) */ String PHOTO_ID = "photo_id"; /** - * Copied from {@link android.provider.CallLog.Calls#CACHED_LOOKUP_URI}. + * The contacts provider lookup URI for the contact associated with the call. * * <p>TYPE: TEXT */ diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index fa7d3be16..93e841409 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -32,6 +32,7 @@ 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; +import com.android.dialer.calllog.datasources.util.RowCombiner; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; @@ -260,8 +261,13 @@ public final class PhoneLookupDataSource implements CallLogDataSource { @WorkerThread @Override public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { - // TODO(zachh): Implementation. - return new ContentValues(); + return new RowCombiner(individualRowsSortedByTimestampDesc) + .useMostRecentString(AnnotatedCallLog.NAME) + .useMostRecentString(AnnotatedCallLog.NUMBER_TYPE_LABEL) + .useMostRecentString(AnnotatedCallLog.PHOTO_URI) + .useMostRecentLong(AnnotatedCallLog.PHOTO_ID) + .useMostRecentString(AnnotatedCallLog.LOOKUP_URI) + .combine(); } @MainThread @@ -434,7 +440,7 @@ public final class PhoneLookupDataSource implements CallLogDataSource { PhoneLookupInfo phoneLookupInfo = existingInfo.get(id); // Existing info might be missing if data was cleared or for other reasons. if (phoneLookupInfo != null) { - contentValues.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo)); + updateContentValues(contentValues, phoneLookupInfo); } } } @@ -474,17 +480,17 @@ public final class PhoneLookupDataSource implements CallLogDataSource { * mutations from PhoneLookupHistory; in this case "John" would be copied during * populateInserts() and there wouldn't be further updates needed here. */ - contentValuesToInsert.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo)); + updateContentValues(contentValuesToInsert, phoneLookupInfo); continue; } ContentValues contentValuesToUpdate = mutations.getUpdates().get(id); if (contentValuesToUpdate != null) { - contentValuesToUpdate.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo)); + updateContentValues(contentValuesToUpdate, phoneLookupInfo); continue; } // Else this row is not already scheduled for insert or update and we need to schedule it. ContentValues contentValues = new ContentValues(); - contentValues.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo)); + updateContentValues(contentValues, phoneLookupInfo); mutations.getUpdates().put(id, contentValues); } } @@ -525,8 +531,17 @@ public final class PhoneLookupDataSource implements CallLogDataSource { return normalizedNumbersToDelete; } - private static String selectName(PhoneLookupInfo phoneLookupInfo) { - return PhoneLookupSelector.selectName(phoneLookupInfo); + private static void updateContentValues( + ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) { + contentValues.put(AnnotatedCallLog.NAME, PhoneLookupSelector.selectName(phoneLookupInfo)); + contentValues.put( + AnnotatedCallLog.PHOTO_URI, PhoneLookupSelector.selectPhotoUri(phoneLookupInfo)); + contentValues.put( + AnnotatedCallLog.PHOTO_ID, PhoneLookupSelector.selectPhotoId(phoneLookupInfo)); + contentValues.put( + AnnotatedCallLog.LOOKUP_URI, PhoneLookupSelector.selectLookupUri(phoneLookupInfo)); + contentValues.put( + AnnotatedCallLog.NUMBER_TYPE_LABEL, PhoneLookupSelector.selectNumberLabel(phoneLookupInfo)); } private static Uri numberUri(String number) { diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index 91db915ef..0ed185966 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -27,7 +27,6 @@ import android.os.Build; import android.os.Handler; import android.provider.CallLog; import android.provider.CallLog.Calls; -import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.ColorInt; import android.support.annotation.MainThread; import android.support.annotation.Nullable; @@ -35,6 +34,7 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.util.ArraySet; import com.android.dialer.DialerPhoneNumber; @@ -174,23 +174,16 @@ public class SystemCallLogDataSource implements CallLogDataSource { @Override public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { - // TODO(zachh): Complete implementation. - assertNoVoicemailsInRows(individualRowsSortedByTimestampDesc); return new RowCombiner(individualRowsSortedByTimestampDesc) .useMostRecentLong(AnnotatedCallLog.TIMESTAMP) .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) @@ -233,13 +226,6 @@ public class SystemCallLogDataSource implements CallLogDataSource { Calls.NUMBER, Calls.TYPE, Calls.COUNTRY_ISO, - Calls.CACHED_NAME, - Calls.CACHED_FORMATTED_NUMBER, - Calls.CACHED_PHOTO_URI, - Calls.CACHED_PHOTO_ID, - Calls.CACHED_LOOKUP_URI, - Calls.CACHED_NUMBER_TYPE, - Calls.CACHED_NUMBER_LABEL, Calls.DURATION, Calls.DATA_USAGE, Calls.TRANSCRIPTION, @@ -272,14 +258,6 @@ public class SystemCallLogDataSource implements CallLogDataSource { int numberColumn = cursor.getColumnIndexOrThrow(Calls.NUMBER); int typeColumn = cursor.getColumnIndexOrThrow(Calls.TYPE); int countryIsoColumn = cursor.getColumnIndexOrThrow(Calls.COUNTRY_ISO); - int cachedNameColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NAME); - int cachedFormattedNumberColumn = - cursor.getColumnIndexOrThrow(Calls.CACHED_FORMATTED_NUMBER); - int cachedPhotoUriColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_PHOTO_URI); - int cachedPhotoIdColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_PHOTO_ID); - int cachedLookupUriColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_LOOKUP_URI); - int cachedNumberTypeColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_TYPE); - int cachedNumberLabelColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_LABEL); int durationsColumn = cursor.getColumnIndexOrThrow(Calls.DURATION); int dataUsageColumn = cursor.getColumnIndexOrThrow(Calls.DATA_USAGE); int transcriptionColumn = cursor.getColumnIndexOrThrow(Calls.TRANSCRIPTION); @@ -301,13 +279,6 @@ public class SystemCallLogDataSource implements CallLogDataSource { String numberAsStr = cursor.getString(numberColumn); long type = cursor.getInt(typeColumn); String countryIso = cursor.getString(countryIsoColumn); - String cachedName = cursor.getString(cachedNameColumn); - String formattedNumber = cursor.getString(cachedFormattedNumberColumn); - String cachedPhotoUri = cursor.getString(cachedPhotoUriColumn); - long cachedPhotoId = cursor.getLong(cachedPhotoIdColumn); - String cachedLookupUri = cursor.getString(cachedLookupUriColumn); - int cachedNumberType = cursor.getInt(cachedNumberTypeColumn); - String cachedNumberLabel = cursor.getString(cachedNumberLabelColumn); int duration = cursor.getInt(durationsColumn); int dataUsage = cursor.getInt(dataUsageColumn); String transcription = cursor.getString(transcriptionColumn); @@ -323,31 +294,19 @@ public class SystemCallLogDataSource implements CallLogDataSource { contentValues.put(AnnotatedCallLog.TIMESTAMP, date); if (!TextUtils.isEmpty(numberAsStr)) { - byte[] numberAsProtoBytes = - dialerPhoneNumberUtil.parse(numberAsStr, countryIso).toByteArray(); + DialerPhoneNumber dialerPhoneNumber = + dialerPhoneNumberUtil.parse(numberAsStr, countryIso); + + contentValues.put(AnnotatedCallLog.NUMBER, dialerPhoneNumber.toByteArray()); + contentValues.put( + AnnotatedCallLog.FORMATTED_NUMBER, + PhoneNumberUtils.formatNumber(numberAsStr, countryIso)); // 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); - contentValues.put(AnnotatedCallLog.LOOKUP_URI, cachedLookupUri); - - // Phone.getTypeLabel returns "Custom" if given (0, null) which is not of any use. Just - // omit setting the label if there's no information for it. - if (cachedNumberType != 0 || cachedNumberLabel != null) { - contentValues.put( - AnnotatedCallLog.NUMBER_TYPE_LABEL, - Phone.getTypeLabel(appContext.getResources(), cachedNumberType, cachedNumberLabel) - .toString()); - } contentValues.put(AnnotatedCallLog.IS_READ, isRead); contentValues.put(AnnotatedCallLog.NEW, isNew); contentValues.put(AnnotatedCallLog.GEOCODED_LOCATION, geocodedLocation); diff --git a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java index a746ea44f..af8b849e5 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java +++ b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java @@ -16,6 +16,8 @@ package com.android.dialer.phonelookup; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo; /** * Prioritizes information from a {@link PhoneLookupInfo}. @@ -37,11 +39,80 @@ public final class PhoneLookupSelector { */ @NonNull public static String selectName(PhoneLookupInfo phoneLookupInfo) { - if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { - // Arbitrarily select the first contact's name. In the future, it may make sense to join the - // names such as "Mom, Dad" in the case that multiple contacts share the same number. - return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0).getName(); + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + String name = firstLocalContact.getName(); + if (!name.isEmpty()) { + return firstLocalContact.getName(); + } + } + return ""; + } + + /** Select the photo URI associated with this number. */ + @NonNull + public static String selectPhotoUri(PhoneLookupInfo phoneLookupInfo) { + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + String photoUri = firstLocalContact.getPhotoUri(); + if (!photoUri.isEmpty()) { + return photoUri; + } + } + return ""; + } + + /** Select the photo ID associated with this number, or 0 if there is none. */ + public static long selectPhotoId(PhoneLookupInfo phoneLookupInfo) { + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + long photoId = firstLocalContact.getPhotoId(); + if (photoId > 0) { + return photoId; + } + } + return 0; + } + + /** Select the lookup URI associated with this number. */ + @NonNull + public static String selectLookupUri(PhoneLookupInfo phoneLookupInfo) { + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + String lookupUri = firstLocalContact.getLookupUri(); + if (!lookupUri.isEmpty()) { + return lookupUri; + } } return ""; } + + /** + * A localized string representing the number type such as "Home" or "Mobile", or a custom value + * set by the user. + */ + @NonNull + public static String selectNumberLabel(PhoneLookupInfo phoneLookupInfo) { + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + String label = firstLocalContact.getLabel(); + if (!label.isEmpty()) { + return label; + } + } + return ""; + } + + /** + * Arbitrarily select the first contact. In the future, it may make sense to display contact + * information from all contacts with the same number (for example show the name as "Mom, Dad" or + * show a synthesized photo containing photos of both "Mom" and "Dad"). + */ + @Nullable + private static Cp2ContactInfo firstLocalContact(PhoneLookupInfo phoneLookupInfo) { + if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { + return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0); + } + return null; + } } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index 03e05b563..3829a8df1 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -58,17 +58,21 @@ public final class Cp2PhoneLookup implements PhoneLookup { Phone.DISPLAY_NAME_PRIMARY, // 0 Phone.PHOTO_THUMBNAIL_URI, // 1 Phone.PHOTO_ID, // 2 - Phone.LABEL, // 3 - Phone.NORMALIZED_NUMBER, // 4 - Phone.CONTACT_ID, // 5 + Phone.TYPE, // 3 + Phone.LABEL, // 4 + Phone.NORMALIZED_NUMBER, // 5 + Phone.CONTACT_ID, // 6 + Phone.LOOKUP_KEY // 7 }; private static final int CP2_INFO_NAME_INDEX = 0; private static final int CP2_INFO_PHOTO_URI_INDEX = 1; private static final int CP2_INFO_PHOTO_ID_INDEX = 2; - private static final int CP2_INFO_LABEL_INDEX = 3; - private static final int CP2_INFO_NUMBER_INDEX = 4; - private static final int CP2_INFO_CONTACT_ID_INDEX = 5; + private static final int CP2_INFO_TYPE_INDEX = 3; + private static final int CP2_INFO_LABEL_INDEX = 4; + private static final int CP2_INFO_NUMBER_INDEX = 5; + private static final int CP2_INFO_CONTACT_ID_INDEX = 6; + private static final int CP2_INFO_LOOKUP_KEY_INDEX = 7; private final Context appContext; private final SharedPreferences sharedPreferences; @@ -89,6 +93,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { @Override public ListenableFuture<PhoneLookupInfo> lookup(@NonNull Call call) { // TODO(zachh): Implementation. + // TODO(zachh): Note: Should write empty Cp2Info even when no contact found. return backgroundExecutorService.submit(PhoneLookupInfo::getDefaultInstance); } @@ -207,7 +212,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { // For each DialerPhoneNumber that was associated with a contact or added to a contact, // build a map of those DialerPhoneNumbers to a set Cp2ContactInfos, where each Cp2ContactInfo // represents a contact. - ImmutableMap<DialerPhoneNumber, Set<Cp2ContactInfo>> updatedContacts = + Map<DialerPhoneNumber, Set<Cp2ContactInfo>> updatedContacts = buildMapForUpdatedOrAddedContacts(existingInfoMap, lastModified, deletedPhoneNumbers); // Start build a new map of updated info. This will replace existing info. @@ -216,23 +221,26 @@ public final class Cp2PhoneLookup implements PhoneLookup { // For each DialerPhoneNumber in existing info... for (Entry<DialerPhoneNumber, PhoneLookupInfo> entry : existingInfoMap.entrySet()) { + DialerPhoneNumber dialerPhoneNumber = entry.getKey(); + PhoneLookupInfo existingInfo = entry.getValue(); + // Build off the existing info - PhoneLookupInfo.Builder infoBuilder = PhoneLookupInfo.newBuilder(entry.getValue()); + PhoneLookupInfo.Builder infoBuilder = PhoneLookupInfo.newBuilder(existingInfo); // If the contact was updated, replace the Cp2ContactInfo list - if (updatedContacts.containsKey(entry.getKey())) { + if (updatedContacts.containsKey(dialerPhoneNumber)) { infoBuilder.setCp2Info( - Cp2Info.newBuilder().addAllCp2ContactInfo(updatedContacts.get(entry.getKey()))); + Cp2Info.newBuilder().addAllCp2ContactInfo(updatedContacts.get(dialerPhoneNumber))); // If it was deleted and not added to a new contact, replace the Cp2ContactInfo list with // the default instance of Cp2ContactInfo - } else if (deletedPhoneNumbers.contains(entry.getKey())) { + } else if (deletedPhoneNumbers.contains(dialerPhoneNumber)) { infoBuilder.setCp2Info( Cp2Info.newBuilder().addCp2ContactInfo(Cp2ContactInfo.getDefaultInstance())); } // If the DialerPhoneNumber didn't change, add the unchanged existing info. - newInfoMapBuilder.put(entry.getKey(), infoBuilder.build()); + newInfoMapBuilder.put(dialerPhoneNumber, infoBuilder.build()); } return newInfoMapBuilder.build(); } @@ -260,7 +268,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { * @return Map of {@link DialerPhoneNumber} to {@link PhoneLookupInfo} with updated {@link * Cp2ContactInfo}. */ - private ImmutableMap<DialerPhoneNumber, Set<Cp2ContactInfo>> buildMapForUpdatedOrAddedContacts( + private Map<DialerPhoneNumber, Set<Cp2ContactInfo>> buildMapForUpdatedOrAddedContacts( ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap, long lastModified, Set<DialerPhoneNumber> deletedPhoneNumbers) { @@ -270,16 +278,24 @@ public final class Cp2PhoneLookup implements PhoneLookup { Set<Long> contactIds = new ArraySet<>(); for (Entry<DialerPhoneNumber, PhoneLookupInfo> entry : existingInfoMap.entrySet()) { + DialerPhoneNumber dialerPhoneNumber = entry.getKey(); + PhoneLookupInfo existingInfo = entry.getValue(); + // If the number was deleted, we need to check if it was added to a new contact. - if (deletedPhoneNumbers.contains(entry.getKey())) { - updatedNumbers.add(entry.getKey()); + if (deletedPhoneNumbers.contains(dialerPhoneNumber)) { + updatedNumbers.add(dialerPhoneNumber); continue; } + // Note: Methods in this class must always set at least one Cp2Info, setting it to + // getDefaultInstance() if there is no information for the contact. + Assert.checkState( + existingInfo.getCp2Info().getCp2ContactInfoCount() > 0, "existing info has no cp2 infos"); + // For each Cp2ContactInfo for each existing DialerPhoneNumber... // Store the contact id if it exist, else automatically add the DialerPhoneNumber to our // set of DialerPhoneNumbers we want to update. - for (Cp2ContactInfo cp2ContactInfo : entry.getValue().getCp2Info().getCp2ContactInfoList()) { + for (Cp2ContactInfo cp2ContactInfo : existingInfo.getCp2Info().getCp2ContactInfoList()) { if (Objects.equals(cp2ContactInfo, Cp2ContactInfo.getDefaultInstance())) { // If the number doesn't have any Cp2ContactInfo set to it, for various reasons, we need // to look up the number to check if any exists. @@ -287,7 +303,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { // - An existing contact that wasn't in the call log is now in the call log. // - A number was in the call log before but has now been added to a contact. // - A number is in the call log, but isn't associated with any contact. - updatedNumbers.add(entry.getKey()); + updatedNumbers.add(dialerPhoneNumber); } else { contactIds.add(cp2ContactInfo.getContactId()); } @@ -322,7 +338,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { // Map each dialer phone number to it's new cp2 info Set<DialerPhoneNumber> phoneNumbers = getDialerPhoneNumbers(updatedNumbers, cursor.getString(CP2_INFO_NUMBER_INDEX)); - Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(cursor); + Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(appContext, cursor); for (DialerPhoneNumber phoneNumber : phoneNumbers) { if (map.containsKey(phoneNumber)) { map.get(phoneNumber).add(info); @@ -334,7 +350,7 @@ public final class Cp2PhoneLookup implements PhoneLookup { } } } - return ImmutableMap.copyOf(map); + return map; } /** @@ -358,10 +374,15 @@ public final class Cp2PhoneLookup implements PhoneLookup { * @param cursor with projection {@link #CP2_INFO_PROJECTION}. * @return new {@link Cp2ContactInfo} based on current row of {@code cursor}. */ - private static Cp2ContactInfo buildCp2ContactInfoFromUpdatedContactsCursor(Cursor cursor) { + private static Cp2ContactInfo buildCp2ContactInfoFromUpdatedContactsCursor( + Context appContext, Cursor cursor) { String displayName = cursor.getString(CP2_INFO_NAME_INDEX); String photoUri = cursor.getString(CP2_INFO_PHOTO_URI_INDEX); + int photoId = cursor.getInt(CP2_INFO_PHOTO_ID_INDEX); + int type = cursor.getInt(CP2_INFO_TYPE_INDEX); String label = cursor.getString(CP2_INFO_LABEL_INDEX); + int contactId = cursor.getInt(CP2_INFO_CONTACT_ID_INDEX); + String lookupKey = cursor.getString(CP2_INFO_LOOKUP_KEY_INDEX); Cp2ContactInfo.Builder infoBuilder = Cp2ContactInfo.newBuilder(); if (!TextUtils.isEmpty(displayName)) { @@ -370,11 +391,19 @@ public final class Cp2PhoneLookup implements PhoneLookup { if (!TextUtils.isEmpty(photoUri)) { infoBuilder.setPhotoUri(photoUri); } - if (!TextUtils.isEmpty(label)) { - infoBuilder.setLabel(label); + if (photoId > 0) { + infoBuilder.setPhotoId(photoId); + } + + // Phone.getTypeLabel returns "Custom" if given (0, null) which is not of any use. Just + // omit setting the label if there's no information for it. + if (type != 0 || !TextUtils.isEmpty(label)) { + infoBuilder.setLabel(Phone.getTypeLabel(appContext.getResources(), type, label).toString()); + } + infoBuilder.setContactId(contactId); + if (!TextUtils.isEmpty(lookupKey)) { + infoBuilder.setLookupUri(Contacts.getLookupUri(contactId, lookupKey).toString()); } - infoBuilder.setPhotoId(cursor.getLong(CP2_INFO_PHOTO_ID_INDEX)); - infoBuilder.setContactId(cursor.getLong(CP2_INFO_CONTACT_ID_INDEX)); return infoBuilder.build(); } diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto index 93dd01e86..36596c1a2 100644 --- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto +++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto @@ -34,9 +34,13 @@ message PhoneLookupInfo { // android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID optional fixed64 contact_id = 5; + + // android.provider.ContactsContract.CONTENT_LOOKUP_URI + optional string lookup_uri = 6; } // Repeated because one phone number can be associated with multiple CP2 - // contacts. + // contacts. If no contact is found for a number, this will contain exactly + // one element which is the default Cp2ContactInfo instance. repeated Cp2ContactInfo cp2_contact_info = 1; } optional Cp2Info cp2_info = 1; diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java index cdc06dead..e32ace59e 100644 --- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java +++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java @@ -29,7 +29,6 @@ import android.telephony.TelephonyManager; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; import android.text.TextUtils; -import android.util.SparseIntArray; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; @@ -38,7 +37,6 @@ import com.android.dialer.phonenumbergeoutil.PhoneNumberGeoUtilComponent; import com.android.dialer.telecom.TelecomUtil; import java.util.Arrays; import java.util.HashSet; -import java.util.Objects; import java.util.Set; public class PhoneNumberHelper { @@ -47,71 +45,6 @@ public class PhoneNumberHelper { private static final Set<String> LEGACY_UNKNOWN_NUMBERS = new HashSet<>(Arrays.asList("-1", "-2", "-3")); - /** The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) */ - private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); - - static { - KEYPAD_MAP.put('a', '2'); - KEYPAD_MAP.put('b', '2'); - KEYPAD_MAP.put('c', '2'); - KEYPAD_MAP.put('A', '2'); - KEYPAD_MAP.put('B', '2'); - KEYPAD_MAP.put('C', '2'); - - KEYPAD_MAP.put('d', '3'); - KEYPAD_MAP.put('e', '3'); - KEYPAD_MAP.put('f', '3'); - KEYPAD_MAP.put('D', '3'); - KEYPAD_MAP.put('E', '3'); - KEYPAD_MAP.put('F', '3'); - - KEYPAD_MAP.put('g', '4'); - KEYPAD_MAP.put('h', '4'); - KEYPAD_MAP.put('i', '4'); - KEYPAD_MAP.put('G', '4'); - KEYPAD_MAP.put('H', '4'); - KEYPAD_MAP.put('I', '4'); - - KEYPAD_MAP.put('j', '5'); - KEYPAD_MAP.put('k', '5'); - KEYPAD_MAP.put('l', '5'); - KEYPAD_MAP.put('J', '5'); - KEYPAD_MAP.put('K', '5'); - KEYPAD_MAP.put('L', '5'); - - KEYPAD_MAP.put('m', '6'); - KEYPAD_MAP.put('n', '6'); - KEYPAD_MAP.put('o', '6'); - KEYPAD_MAP.put('M', '6'); - KEYPAD_MAP.put('N', '6'); - KEYPAD_MAP.put('O', '6'); - - KEYPAD_MAP.put('p', '7'); - KEYPAD_MAP.put('q', '7'); - KEYPAD_MAP.put('r', '7'); - KEYPAD_MAP.put('s', '7'); - KEYPAD_MAP.put('P', '7'); - KEYPAD_MAP.put('Q', '7'); - KEYPAD_MAP.put('R', '7'); - KEYPAD_MAP.put('S', '7'); - - KEYPAD_MAP.put('t', '8'); - KEYPAD_MAP.put('u', '8'); - KEYPAD_MAP.put('v', '8'); - KEYPAD_MAP.put('T', '8'); - KEYPAD_MAP.put('U', '8'); - KEYPAD_MAP.put('V', '8'); - - KEYPAD_MAP.put('w', '9'); - KEYPAD_MAP.put('x', '9'); - KEYPAD_MAP.put('y', '9'); - KEYPAD_MAP.put('z', '9'); - KEYPAD_MAP.put('W', '9'); - KEYPAD_MAP.put('X', '9'); - KEYPAD_MAP.put('Y', '9'); - KEYPAD_MAP.put('Z', '9'); - } - /** Returns true if it is possible to place a call to the given number. */ public static boolean canPlaceCallsTo(CharSequence number, int presentation) { return presentation == CallLog.Calls.PRESENTATION_ALLOWED @@ -120,76 +53,7 @@ public class PhoneNumberHelper { } /** - * Compare two phone numbers, return true if they're identical enough for caller ID purposes. This - * is an enhanced version of {@link PhoneNumberUtils#compare(String, String)}. - * - * <p>The two phone numbers are considered "identical enough" if - * - * <ul> - * <li>their corresponding raw numbers are both global phone numbers (i.e., they can be accepted - * by {@link PhoneNumberUtils#isGlobalPhoneNumber(String)}) and {@link - * PhoneNumberUtils#compare(String, String)} deems them as "identical enough"; OR - * <li>neither of the raw numbers is a global phone number and they are identical. - * </ul> - * - * See {@link #convertAndStrip(String)} for how a raw number is obtained. - */ - public static boolean compare(@Nullable String number1, @Nullable String number2) { - if (number1 == null || number2 == null) { - return Objects.equals(number1, number2); - } - - String rawNumber1 = convertAndStrip(number1); - String rawNumber2 = convertAndStrip(number2); - - boolean isGlobalPhoneNumber1 = PhoneNumberUtils.isGlobalPhoneNumber(rawNumber1); - boolean isGlobalPhoneNumber2 = PhoneNumberUtils.isGlobalPhoneNumber(rawNumber2); - - if (isGlobalPhoneNumber1 && isGlobalPhoneNumber2) { - return PhoneNumberUtils.compare(rawNumber1, rawNumber2); - } - if (!isGlobalPhoneNumber1 && !isGlobalPhoneNumber2) { - return rawNumber1.equals(rawNumber2); - } - return false; - } - - /** - * Translating any alphabetic letters ([A-Za-z]) in the given phone number into the equivalent - * numeric digits and then removing all separators. The caller should ensure the number passed to - * this method is not null. - */ - private static String convertAndStrip(@NonNull String number) { - int len = number.length(); - if (len == 0) { - return number; - } - - StringBuilder ret = new StringBuilder(len); - for (int i = 0; i < len; i++) { - char c = number.charAt(i); - - // If the char isn't in KEYPAD_MAP, leave it alone for now. - c = (char) KEYPAD_MAP.get(c, c); - - // Append the char to the result if it's a digit or non-separator. - int digit = Character.digit(c, 10); - if (digit != -1) { - ret.append(digit); - } else if (PhoneNumberUtils.isNonSeparator(c)) { - ret.append(c); - } - } - - return ret.toString(); - } - - /** - * Find the cursor pointing to the row in which a number is identical enough to the number in a - * contact lookup URI. - * - * <p>See the description of {@link PhoneNumberHelper#compare(String, String)} for the definition - * of "identical enough". + * Find the cursor pointing to a number that matches the number in a contact lookup URI. * * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the * Contacts Provider uses {@link PhoneNumberUtils#compare(String, String)}, which ignores special @@ -197,17 +61,25 @@ public class PhoneNumberHelper { * by the Contacts Provider to have multiple rows even when the URI asks for a specific number. * * <p>For example, suppose the user has two contacts whose numbers are "#123" and "123", - * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, - * {@link PhoneNumberHelper#compare(String, String)}, which is an enhanced version of {@link - * PhoneNumberUtils#compare(String, String)}, is employed to find a match. + * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, the + * following strategy is employed to find a match. + * + * <p>If the cursor points to a global phone number (i.e., a number that can be accepted by {@link + * PhoneNumberUtils#isGlobalPhoneNumber(String)}) and the lookup number in the URI is a PARTIAL + * match, return the cursor. + * + * <p>If the cursor points to a number that is not a global phone number, return the cursor iff + * the lookup number in the URI is an EXACT match. + * + * <p>Return null in all other circumstances. * * @param cursor A cursor returned by the Contacts Provider. * @param columnIndexForNumber The index of the column where phone numbers are stored. It is the * caller's responsibility to pass the correct column index. * @param contactLookupUri A URI used to retrieve a contact via the Contacts Provider. It is the * caller's responsibility to ensure the URI is one that asks for a specific phone number. - * @return The cursor pointing to the row in which the number is considered a match by the - * description above or null if no such cursor can be found. + * @return The cursor considered as a match by the description above or null if no such cursor can + * be found. */ public static Cursor getCursorMatchForContactLookupUri( Cursor cursor, int columnIndexForNumber, Uri contactLookupUri) { @@ -227,10 +99,22 @@ public class PhoneNumberHelper { return null; } + boolean isMatchFound; do { - String existingContactNumber = cursor.getString(columnIndexForNumber); - - boolean isMatchFound = compare(existingContactNumber, lookupNumber); + // All undialable characters should be converted/removed before comparing the lookup number + // and the existing contact number. + String rawExistingContactNumber = + PhoneNumberUtils.stripSeparators( + PhoneNumberUtils.convertKeypadLettersToDigits( + cursor.getString(columnIndexForNumber))); + String rawQueryNumber = + PhoneNumberUtils.stripSeparators( + PhoneNumberUtils.convertKeypadLettersToDigits(lookupNumber)); + + isMatchFound = + PhoneNumberUtils.isGlobalPhoneNumber(rawExistingContactNumber) + ? rawExistingContactNumber.contains(rawQueryNumber) + : rawExistingContactNumber.equals(rawQueryNumber); if (isMatchFound) { return cursor; diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java index 955c7daee..671a39a67 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java @@ -56,7 +56,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> int VOICEMAIL_ENTRY = 2; } - private final Cursor cursor; + private Cursor cursor; private final Clock clock; /** {@link Integer#MAX_VALUE} when the "Today" header should not be displayed. */ @@ -129,6 +129,11 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> mediaPlayer.setOnErrorListener(onErrorListener); } + public void updateCursor(Cursor updatedCursor) { + this.cursor = updatedCursor; + notifyDataSetChanged(); + } + @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) { LogUtil.enterBlock("NewVoicemailAdapter.onCreateViewHolder"); @@ -714,7 +719,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> // returned when currentlyExpandedViewHolderId = -1 (viewholder was collapsed) LogUtil.i( "NewVoicemailAdapter.getCurrentlyExpandedViewHolder", - "no view holder found in newVoicemailViewHolderArrayMap size:%d for %d", + "no view holder found in hashmap size:%d for %d", newVoicemailViewHolderArrayMap.size(), currentlyExpandedViewHolderId); // TODO(uabdullah): a bug Remove logging, temporarily here for debugging. @@ -749,4 +754,34 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder> } return RowType.VOICEMAIL_ENTRY; } + + /** + * This will be called once the voicemail that was attempted to be played (and was not locally + * available) was downloaded from the server. However it is possible that by the time the download + * was completed, the view holder was collapsed. In that case we shouldn't play the voicemail. + */ + public void checkAndPlayVoicemail() { + LogUtil.i( + "NewVoicemailAdapter.checkAndPlayVoicemail", + "expandedViewHolder:%d, inViewHolderSet:%b, MPRequestToDownload:%s", + currentlyExpandedViewHolderId, + isCurrentlyExpandedViewHolderInViewHolderSet(), + String.valueOf(mediaPlayer.getVoicemailRequestedToDownload())); + + NewVoicemailViewHolder currentlyExpandedViewHolder = getCurrentlyExpandedViewHolder(); + if (currentlyExpandedViewHolderId != -1 + && isCurrentlyExpandedViewHolderInViewHolderSet() + && currentlyExpandedViewHolder != null + // Used to differentiate underlying table changes from voicemail downloads and other changes + // (e.g delete) + && mediaPlayer.getVoicemailRequestedToDownload() != null + && (mediaPlayer + .getVoicemailRequestedToDownload() + .equals(currentlyExpandedViewHolder.getViewHolderVoicemailUri()))) { + currentlyExpandedViewHolder.clickPlayButtonOfViewHoldersMediaPlayerView( + currentlyExpandedViewHolder); + } else { + LogUtil.i("NewVoicemailAdapter.checkAndPlayVoicemail", "not playing downloaded voicemail"); + } + } } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java index 9a89dbe3e..82e704d39 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -38,6 +38,7 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba @Override public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + LogUtil.enterBlock("NewVoicemailFragment.onCreateView"); View view = inflater.inflate(R.layout.new_voicemail_call_log_fragment, container, false); recyclerView = view.findViewById(R.id.new_voicemail_call_log_recycler_view); getLoaderManager().restartLoader(0, null, this); @@ -52,11 +53,24 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount()); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - recyclerView.setAdapter( - new NewVoicemailAdapter( - data, System::currentTimeMillis, getActivity().getFragmentManager())); + LogUtil.i("NewVoicemailFragment.onLoadFinished", "cursor size is %d", data.getCount()); + if (recyclerView.getAdapter() == null) { + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setAdapter( + new NewVoicemailAdapter( + data, System::currentTimeMillis, getActivity().getFragmentManager())); + } else { + // This would only be called in cases such as when voicemail has been fetched from the server + // or a changed occurred in the annotated table changed (e.g deletes). To check if the change + // was due to a voicemail download, + // NewVoicemailAdapter.mediaPlayer.getVoicemailRequestedToDownload() is called. + LogUtil.i( + "NewVoicemailFragment.onLoadFinished", + "adapter: %s was not null, checking and playing the voicemail if conditions met", + recyclerView.getAdapter()); + ((NewVoicemailAdapter) recyclerView.getAdapter()).updateCursor(data); + ((NewVoicemailAdapter) recyclerView.getAdapter()).checkAndPlayVoicemail(); + } } @Override diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java index 2d59b241b..48062a87d 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java @@ -23,6 +23,7 @@ import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import java.io.IOException; @@ -38,6 +39,7 @@ public class NewVoicemailMediaPlayer { private OnPreparedListener newVoicemailMediaPlayerOnPreparedListener; private OnCompletionListener newVoicemailMediaPlayerOnCompletionListener; private Uri pausedUri; + @Nullable private Uri voicemailRequestedToDownload; public NewVoicemailMediaPlayer(@NonNull MediaPlayer player) { mediaPlayer = Assert.isNotNull(player); @@ -94,6 +96,7 @@ public class NewVoicemailMediaPlayer { mediaPlayer.start(); voicemailLastPlayedOrPlayingUri = startPlayingVoicemailUri; pausedUri = null; + voicemailRequestedToDownload = null; } public void reset() { @@ -102,6 +105,7 @@ public class NewVoicemailMediaPlayer { voicemailLastPlayedOrPlayingUri = null; voicemailUriLastPreparedOrPreparingToPlay = null; pausedUri = null; + voicemailRequestedToDownload = null; } public void pauseMediaPlayer(Uri voicemailUri) { @@ -134,6 +138,11 @@ public class NewVoicemailMediaPlayer { newVoicemailMediaPlayerOnCompletionListener = onCompletionListener; } + public void setVoicemailRequestedToDownload(@NonNull Uri uri) { + Assert.isNotNull(uri, "cannot download a null voicemail"); + voicemailRequestedToDownload = uri; + } + /** * Note: In some cases it's possible mediaPlayer.isPlaying() can return true, but * mediaPlayer.getCurrentPosition() can be greater than mediaPlayer.getDuration(), after which @@ -182,6 +191,17 @@ public class NewVoicemailMediaPlayer { return mediaPlayer.getDuration(); } + /** + * A null v/s non-value is important for the {@link NewVoicemailAdapter} to differentiate between + * a underlying table change due to a voicemail being downloaded or something else (e.g delete). + * + * @return if there was a Uri that was requested to be downloaded from the server, null otherwise. + */ + @Nullable + public Uri getVoicemailRequestedToDownload() { + return voicemailRequestedToDownload; + } + public boolean isPaused() { return pausedUri != null; } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java index 77dd9cc4b..3f2de7d00 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java @@ -17,12 +17,15 @@ package com.android.dialer.voicemail.listui; import android.app.FragmentManager; +import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.VoicemailContract; +import android.provider.VoicemailContract.Voicemails; import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; +import android.support.annotation.Nullable; import android.support.v4.util.Pair; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -45,7 +48,7 @@ import java.util.Locale; /** * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded. */ -public class NewVoicemailMediaPlayerView extends LinearLayout { +public final class NewVoicemailMediaPlayerView extends LinearLayout { private ImageButton playButton; private ImageButton pauseButton; @@ -55,6 +58,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { private TextView currentSeekBarPosition; private SeekBar seekBarView; private TextView totalDurationView; + private TextView voicemailLoadingStatusView; private Uri voicemailUri; private FragmentManager fragmentManager; private NewVoicemailViewHolder newVoicemailViewHolder; @@ -86,6 +90,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { phoneButton = findViewById(R.id.phoneButton); deleteButton = findViewById(R.id.deleteButton); totalDurationView = findViewById(R.id.playback_seek_total_duration); + voicemailLoadingStatusView = findViewById(R.id.playback_state_text); } private void setupListenersForMediaPlayerButtons() { @@ -100,6 +105,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { public void reset() { LogUtil.i("NewVoicemailMediaPlayer.reset", "the uri for this is " + voicemailUri); voicemailUri = null; + voicemailLoadingStatusView.setVisibility(GONE); } /** @@ -261,6 +267,16 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { } }; + /** + * Attempts to imitate clicking the play button. This is useful for when we the user attempted to + * play a voicemail, but the media player didn't start playing till the voicemail was downloaded + * from the server. However once we have the voicemail downloaded, we want to start playing, so as + * to make it seem like that this is a continuation of the users initial play button click. + */ + public final void clickPlayButton() { + playButtonListener.onClick(null); + } + private final View.OnClickListener playButtonListener = new View.OnClickListener() { @Override @@ -268,7 +284,8 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { LogUtil.i( "NewVoicemailMediaPlayer.playButtonListener", "play button for voicemailUri: %s", - voicemailUri.toString()); + String.valueOf(voicemailUri)); + if (mediaPlayer.getLastPausedVoicemailUri() != null && mediaPlayer .getLastPausedVoicemailUri() @@ -350,10 +367,83 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { + getContext()); } } else { - // TODO(a bug): Add logic for downloading voicemail content from the server. LogUtil.i( "NewVoicemailMediaPlayer.prepareVoicemailForMediaPlayer", "need to download content"); + // Important to set since it allows the adapter to differentiate when to start playing the + // voicemail, after it's downloaded. + mediaPlayer.setVoicemailRequestedToDownload(uri); + voicemailLoadingStatusView.setVisibility(VISIBLE); + sendIntentToDownloadVoicemail(uri); + } + } + + private void sendIntentToDownloadVoicemail(Uri uri) { + LogUtil.i("NewVoicemailMediaPlayer.sendIntentToDownloadVoicemail", "uri:%s", uri.toString()); + // Send voicemail fetch request. + Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri); + + Worker<Pair<Context, Uri>, Pair<String, Uri>> getVoicemailSourcePackage = + this::queryVoicemailSourcePackage; + SuccessListener<Pair<String, Uri>> checkVoicemailHasSourcePackageCallBack = this::sendIntent; + + DialerExecutorComponent.get(getContext()) + .dialerExecutorFactory() + .createUiTaskBuilder(fragmentManager, "lookup_voicemail_pkg", getVoicemailSourcePackage) + .onSuccess(checkVoicemailHasSourcePackageCallBack) + .build() + .executeSerial(new Pair<>(getContext(), voicemailUri)); + } + + private void sendIntent(Pair<String, Uri> booleanUriPair) { + String sourcePackage = booleanUriPair.first; + Uri uri = booleanUriPair.second; + LogUtil.i( + "NewVoicemailMediaPlayer.sendIntent", + "srcPkg:%s, uri:%%s", + sourcePackage, + String.valueOf(uri)); + Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri); + intent.setPackage(sourcePackage); + voicemailLoadingStatusView.setVisibility(VISIBLE); + getContext().sendBroadcast(intent); + } + + @Nullable + private Pair<String, Uri> queryVoicemailSourcePackage(Pair<Context, Uri> contextUriPair) { + LogUtil.enterBlock("NewVoicemailMediaPlayer.queryVoicemailSourcePackage"); + Context context = contextUriPair.first; + Uri uri = contextUriPair.second; + String sourcePackage; + try (Cursor cursor = + context + .getContentResolver() + .query(uri, new String[] {Voicemails.SOURCE_PACKAGE}, null, null, null)) { + + if (!hasContent(cursor)) { + LogUtil.e( + "NewVoicemailMediaPlayer.queryVoicemailSourcePackage", + "uri: %s does not return a SOURCE_PACKAGE", + uri.toString()); + sourcePackage = null; + } else { + sourcePackage = cursor.getString(0); + LogUtil.i( + "NewVoicemailMediaPlayer.queryVoicemailSourcePackage", + "uri: %s has a SOURCE_PACKAGE: %s", + uri.toString(), + sourcePackage); + } + LogUtil.i( + "NewVoicemailMediaPlayer.queryVoicemailSourcePackage", + "uri: %s has a SOURCE_PACKAGE: %s", + uri.toString(), + sourcePackage); } + return new Pair<>(sourcePackage, uri); + } + + private boolean hasContent(Cursor cursor) { + return cursor != null && cursor.moveToFirst(); } private final View.OnClickListener speakerButtonListener = @@ -386,6 +476,21 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { "NewVoicemailMediaPlayer.deleteButtonListener", "delete voicemailUri %s", voicemailUri.toString()); + // TODO(uabdullah): This will be removed in cl/177404259. It only sets the has_content to + // 0, to allow the annotated call log to change, and refresh the fragment. This is used to + // demo and test the downloading of voicemails from the server. + ContentValues contentValues = new ContentValues(); + contentValues.put("has_content", 0); + + try { + getContext().getContentResolver().update(voicemailUri, contentValues, "type = 4", null); + } catch (Exception e) { + LogUtil.i( + "NewVoicemailMediaPlayer.deleteButtonListener", + "update has content of voicemailUri %s caused an error: %s", + voicemailUri.toString(), + e.toString()); + } } }; @@ -401,6 +506,7 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { playButton.setVisibility(GONE); pauseButton.setVisibility(VISIBLE); + voicemailLoadingStatusView.setVisibility(GONE); Assert.checkArgument( mp.equals(mediaPlayer), "there should only be one instance of a media player"); @@ -510,9 +616,4 @@ public class NewVoicemailMediaPlayerView extends LinearLayout { } return String.format(Locale.US, "%02d:%02d", minutes, seconds); } - - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - void setFragmentManager(FragmentManager fragmentManager) { - this.fragmentManager = fragmentManager; - } } diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java index d5b17a19d..072546552 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java @@ -187,6 +187,8 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On String.valueOf(viewHolderVoicemailUri)); transcriptionTextView.setMaxLines(1); isViewHolderExpanded = false; + + mediaPlayerView.reset(); mediaPlayerView.setVisibility(GONE); } @@ -333,6 +335,23 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On return viewHolderVoicemailUri; } + public void clickPlayButtonOfViewHoldersMediaPlayerView( + NewVoicemailViewHolder expandedViewHolder) { + LogUtil.i( + "NewVoicemailViewHolder.clickPlayButtonOfViewHoldersMediaPlayerView", + "expandedViewHolderID:%d", + expandedViewHolder.getViewHolderId()); + + Assert.checkArgument( + mediaPlayerView.getVoicemailUri().equals(expandedViewHolder.getViewHolderVoicemailUri())); + Assert.checkArgument( + expandedViewHolder.getViewHolderVoicemailUri().equals(getViewHolderVoicemailUri())); + Assert.checkArgument( + mediaPlayerView.getVisibility() == View.VISIBLE, + "the media player must be visible for viewholder id:%d, before we attempt to play"); + mediaPlayerView.clickPlayButton(); + } + interface NewVoicemailViewHolderListener { void expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders( NewVoicemailViewHolder expandedViewHolder, diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml index 32726a9e5..3efcea543 100644 --- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml +++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml @@ -22,7 +22,6 @@ android:paddingTop="@dimen/voicemail_media_player_padding_top" android:orientation="vertical"> - <!-- TODO(uabdullah): Make visibility gone (once implement fetching from vm server) --> <TextView android:id="@+id/playback_state_text" android:layout_width="match_parent" |