use of com.android.dialer.DialerPhoneNumber in project android_packages_apps_Dialer by LineageOS.
the class Cp2DefaultDirectoryPhoneLookup method buildMapForUpdatedOrAddedContacts.
/**
* 1. get all contact ids. if the id is unset, add the number to the list of contacts to look up.
* 2. reduce our list of contact ids to those that were updated after lastModified. 3. Now we have
* the smallest set of dialer phone numbers to query cp2 against. 4. build and return the map of
* dialerphonenumbers to their new Cp2ContactInfo
*
* @return Map of {@link DialerPhoneNumber} to {@link Cp2Info} with updated {@link
* Cp2ContactInfo}.
*/
private ListenableFuture<Map<DialerPhoneNumber, Set<Cp2ContactInfo>>> buildMapForUpdatedOrAddedContacts(Map<DialerPhoneNumber, Cp2Info> existingInfoMap, long lastModified, Set<DialerPhoneNumber> deletedPhoneNumbers) {
// Start by building a set of DialerPhoneNumbers that we want to update.
ListenableFuture<Set<DialerPhoneNumber>> updatedNumbersFuture = findNumbersToUpdate(existingInfoMap, lastModified, deletedPhoneNumbers);
return Futures.transformAsync(updatedNumbersFuture, updatedNumbers -> {
if (updatedNumbers.isEmpty()) {
return Futures.immediateFuture(new ArrayMap<>());
}
// Divide the numbers into those that are valid and those that are not. Issue a single
// batch query for the valid numbers against the PHONE table, and in parallel issue
// individual queries against PHONE_LOOKUP for each invalid number.
// TODO(zachh): These queries are inefficient without a lastModified column to filter on.
PartitionedNumbers partitionedNumbers = new PartitionedNumbers(ImmutableSet.copyOf(updatedNumbers));
ListenableFuture<Map<String, Set<Cp2ContactInfo>>> validNumbersFuture = batchQueryForValidNumbers(partitionedNumbers.validE164Numbers());
List<ListenableFuture<Set<Cp2ContactInfo>>> invalidNumbersFuturesList = new ArrayList<>();
for (String invalidNumber : partitionedNumbers.invalidNumbers()) {
invalidNumbersFuturesList.add(individualQueryForInvalidNumber(invalidNumber));
}
ListenableFuture<List<Set<Cp2ContactInfo>>> invalidNumbersFuture = Futures.allAsList(invalidNumbersFuturesList);
Callable<Map<DialerPhoneNumber, Set<Cp2ContactInfo>>> computeMap = () -> {
// These get() calls are safe because we are using whenAllSucceed below.
Map<String, Set<Cp2ContactInfo>> validNumbersResult = validNumbersFuture.get();
List<Set<Cp2ContactInfo>> invalidNumbersResult = invalidNumbersFuture.get();
Map<DialerPhoneNumber, Set<Cp2ContactInfo>> map = new ArrayMap<>();
// First update the map with the valid number results.
for (Entry<String, Set<Cp2ContactInfo>> entry : validNumbersResult.entrySet()) {
String validNumber = entry.getKey();
Set<Cp2ContactInfo> cp2ContactInfos = entry.getValue();
Set<DialerPhoneNumber> dialerPhoneNumbers = partitionedNumbers.dialerPhoneNumbersForValidE164(validNumber);
addInfo(map, dialerPhoneNumbers, cp2ContactInfos);
// We are going to remove the numbers that we've handled so that we later can
// detect numbers that weren't handled and therefore need to have their contact
// information removed.
updatedNumbers.removeAll(dialerPhoneNumbers);
}
// Next update the map with the invalid results.
int i = 0;
for (String invalidNumber : partitionedNumbers.invalidNumbers()) {
Set<Cp2ContactInfo> cp2Infos = invalidNumbersResult.get(i++);
Set<DialerPhoneNumber> dialerPhoneNumbers = partitionedNumbers.dialerPhoneNumbersForInvalid(invalidNumber);
addInfo(map, dialerPhoneNumbers, cp2Infos);
// We are going to remove the numbers that we've handled so that we later can
// detect numbers that weren't handled and therefore need to have their contact
// information removed.
updatedNumbers.removeAll(dialerPhoneNumbers);
}
// information for them.
for (DialerPhoneNumber dialerPhoneNumber : updatedNumbers) {
map.put(dialerPhoneNumber, ImmutableSet.of());
}
LogUtil.v("Cp2DefaultDirectoryPhoneLookup.buildMapForUpdatedOrAddedContacts", "found %d numbers that may need updating", updatedNumbers.size());
return map;
};
return Futures.whenAllSucceed(validNumbersFuture, invalidNumbersFuture).call(computeMap, lightweightExecutorService);
}, lightweightExecutorService);
}
use of com.android.dialer.DialerPhoneNumber in project android_packages_apps_Dialer by LineageOS.
the class Cp2DefaultDirectoryPhoneLookup method isDirty.
@Override
public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
LogUtil.w("Cp2DefaultDirectoryPhoneLookup.isDirty", "missing permissions");
Predicate<PhoneLookupInfo> phoneLookupInfoIsDirtyFn = phoneLookupInfo -> !phoneLookupInfo.getDefaultCp2Info().equals(Cp2Info.getDefaultInstance());
return missingPermissionsOperations.isDirtyForMissingPermissions(phoneNumbers, phoneLookupInfoIsDirtyFn);
}
PartitionedNumbers partitionedNumbers = new PartitionedNumbers(phoneNumbers);
if (partitionedNumbers.invalidNumbers().size() > getMaxSupportedInvalidNumbers()) {
// If there are N invalid numbers, we can't determine determine dirtiness without running N
// queries; since running this many queries is not feasible for the (lightweight) isDirty
// check, simply return true. The expectation is that this should rarely be the case as the
// vast majority of numbers in call logs should be valid.
LogUtil.v("Cp2DefaultDirectoryPhoneLookup.isDirty", "returning true because too many invalid numbers (%d)", partitionedNumbers.invalidNumbers().size());
return Futures.immediateFuture(true);
}
ListenableFuture<Long> lastModifiedFuture = backgroundExecutorService.submit(() -> sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L));
return Futures.transformAsync(lastModifiedFuture, lastModified -> {
// We are always going to need to do this check and it is pretty cheap so do it first.
ListenableFuture<Boolean> anyContactsDeletedFuture = anyContactsDeletedSince(lastModified);
return Futures.transformAsync(anyContactsDeletedFuture, anyContactsDeleted -> {
if (anyContactsDeleted) {
LogUtil.v("Cp2DefaultDirectoryPhoneLookup.isDirty", "returning true because contacts deleted");
return Futures.immediateFuture(true);
}
// Hopefully the most common case is there are no contacts updated; we can detect
// this cheaply.
ListenableFuture<Boolean> noContactsModifiedSinceFuture = noContactsModifiedSince(lastModified);
return Futures.transformAsync(noContactsModifiedSinceFuture, noContactsModifiedSince -> {
if (noContactsModifiedSince) {
LogUtil.v("Cp2DefaultDirectoryPhoneLookup.isDirty", "returning false because no contacts modified since last run");
return Futures.immediateFuture(false);
}
// This method is more expensive but is probably the most likely scenario; we
// are looking for changes to contacts which have been called.
ListenableFuture<Set<Long>> contactIdsFuture = queryPhoneTableForContactIds(phoneNumbers);
ListenableFuture<Boolean> contactsUpdatedFuture = Futures.transformAsync(contactIdsFuture, contactIds -> contactsUpdated(contactIds, lastModified), MoreExecutors.directExecutor());
return Futures.transformAsync(contactsUpdatedFuture, contactsUpdated -> {
if (contactsUpdated) {
LogUtil.v("Cp2DefaultDirectoryPhoneLookup.isDirty", "returning true because a previously called contact was updated");
return Futures.immediateFuture(true);
}
// This is the most expensive method so do it last; the scenario is that
// a contact which has been called got disassociated with a number and
// we need to clear their information.
ListenableFuture<Set<Long>> phoneLookupContactIdsFuture = queryPhoneLookupHistoryForContactIds();
return Futures.transformAsync(phoneLookupContactIdsFuture, phoneLookupContactIds -> contactsUpdated(phoneLookupContactIds, lastModified), MoreExecutors.directExecutor());
}, MoreExecutors.directExecutor());
}, MoreExecutors.directExecutor());
}, MoreExecutors.directExecutor());
}, MoreExecutors.directExecutor());
}
use of com.android.dialer.DialerPhoneNumber in project android_packages_apps_Dialer by LineageOS.
the class Cp2DefaultDirectoryPhoneLookup method findDialerPhoneNumbersContainingContactId.
private static Set<DialerPhoneNumber> findDialerPhoneNumbersContainingContactId(Map<DialerPhoneNumber, Cp2Info> existingInfoMap, long contactId) {
Set<DialerPhoneNumber> matches = new ArraySet<>();
for (Entry<DialerPhoneNumber, Cp2Info> entry : existingInfoMap.entrySet()) {
for (Cp2ContactInfo cp2ContactInfo : entry.getValue().getCp2ContactInfoList()) {
if (cp2ContactInfo.getContactId() == contactId) {
matches.add(entry.getKey());
}
}
}
Assert.checkArgument(matches.size() > 0, "Couldn't find DialerPhoneNumber for contact ID: " + contactId);
return matches;
}
use of com.android.dialer.DialerPhoneNumber in project android_packages_apps_Dialer by LineageOS.
the class Cp2DefaultDirectoryPhoneLookup method getMostRecentInfo.
@Override
public ListenableFuture<ImmutableMap<DialerPhoneNumber, Cp2Info>> getMostRecentInfo(ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) {
currentLastTimestampProcessed = null;
if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
LogUtil.w("Cp2DefaultDirectoryPhoneLookup.getMostRecentInfo", "missing permissions");
return missingPermissionsOperations.getMostRecentInfoForMissingPermissions(existingInfoMap);
}
ListenableFuture<Long> lastModifiedFuture = backgroundExecutorService.submit(() -> sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L));
return Futures.transformAsync(lastModifiedFuture, lastModified -> {
// Build a set of each DialerPhoneNumber that was associated with a contact, and is no
// longer associated with that same contact.
ListenableFuture<Set<DialerPhoneNumber>> deletedPhoneNumbersFuture = getDeletedPhoneNumbers(existingInfoMap, lastModified);
return Futures.transformAsync(deletedPhoneNumbersFuture, deletedPhoneNumbers -> {
// If there are too many invalid numbers, just defer the work to render time.
ArraySet<DialerPhoneNumber> unprocessableNumbers = findUnprocessableNumbers(existingInfoMap);
Map<DialerPhoneNumber, Cp2Info> existingInfoMapToProcess = existingInfoMap;
if (!unprocessableNumbers.isEmpty()) {
existingInfoMapToProcess = Maps.filterKeys(existingInfoMap, number -> !unprocessableNumbers.contains(number));
}
// 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.
ListenableFuture<Map<DialerPhoneNumber, Set<Cp2ContactInfo>>> updatedContactsFuture = buildMapForUpdatedOrAddedContacts(existingInfoMapToProcess, lastModified, deletedPhoneNumbers);
return Futures.transform(updatedContactsFuture, updatedContacts -> {
// Start build a new map of updated info. This will replace existing info.
ImmutableMap.Builder<DialerPhoneNumber, Cp2Info> newInfoMapBuilder = ImmutableMap.builder();
// For each DialerPhoneNumber in existing info...
for (Entry<DialerPhoneNumber, Cp2Info> entry : existingInfoMap.entrySet()) {
DialerPhoneNumber dialerPhoneNumber = entry.getKey();
Cp2Info existingInfo = entry.getValue();
// Build off the existing info
Cp2Info.Builder infoBuilder = Cp2Info.newBuilder(existingInfo);
// If the contact was updated, replace the Cp2ContactInfo list
if (updatedContacts.containsKey(dialerPhoneNumber)) {
infoBuilder.clear().addAllCp2ContactInfo(updatedContacts.get(dialerPhoneNumber));
// If it was deleted and not added to a new contact, clear all the CP2
// information.
} else if (deletedPhoneNumbers.contains(dialerPhoneNumber)) {
infoBuilder.clear();
} else if (unprocessableNumbers.contains(dialerPhoneNumber)) {
// does not have the ability to fetch information at render time).
if (!dialerPhoneNumber.getNormalizedNumber().isEmpty()) {
// Don't clear the existing info when the number is unprocessable. It's
// likely that the existing info is up-to-date so keep it in place so
// that the UI doesn't pop when the query is completed at display time.
infoBuilder.setIsIncomplete(true);
}
}
// If the DialerPhoneNumber didn't change, add the unchanged existing info.
newInfoMapBuilder.put(dialerPhoneNumber, infoBuilder.build());
}
return newInfoMapBuilder.build();
}, lightweightExecutorService);
}, lightweightExecutorService);
}, lightweightExecutorService);
}
use of com.android.dialer.DialerPhoneNumber in project android_packages_apps_Dialer by LineageOS.
the class SpamPhoneLookup method getMostRecentInfo.
@Override
public ListenableFuture<ImmutableMap<DialerPhoneNumber, SpamInfo>> getMostRecentInfo(ImmutableMap<DialerPhoneNumber, SpamInfo> existingInfoMap) {
currentLastTimestampProcessed = null;
ListenableFuture<ImmutableMap<DialerPhoneNumber, SpamStatus>> spamStatusMapFuture = spam.batchCheckSpamStatus(existingInfoMap.keySet());
return Futures.transform(spamStatusMapFuture, spamStatusMap -> {
ImmutableMap.Builder<DialerPhoneNumber, SpamInfo> mostRecentSpamInfo = new ImmutableMap.Builder<>();
for (Entry<DialerPhoneNumber, SpamStatus> dialerPhoneNumberAndSpamStatus : spamStatusMap.entrySet()) {
DialerPhoneNumber dialerPhoneNumber = dialerPhoneNumberAndSpamStatus.getKey();
SpamStatus spamStatus = dialerPhoneNumberAndSpamStatus.getValue();
mostRecentSpamInfo.put(dialerPhoneNumber, SpamInfo.newBuilder().setIsSpam(spamStatus.isSpam()).build());
Optional<Long> timestampMillis = spamStatus.getTimestampMillis();
if (timestampMillis.isPresent()) {
currentLastTimestampProcessed = currentLastTimestampProcessed == null ? timestampMillis.get() : Math.max(timestampMillis.get(), currentLastTimestampProcessed);
}
}
// triggering the bulk update flow repeatedly.
if (currentLastTimestampProcessed == null) {
currentLastTimestampProcessed = System.currentTimeMillis();
}
return mostRecentSpamInfo.build();
}, lightweightExecutorService);
}
Aggregations