Search in sources :

Example 1 with PartitionedNumbers

use of com.android.dialer.phonenumberproto.PartitionedNumbers in project android_packages_apps_Dialer by LineageOS.

the class Cp2DefaultDirectoryPhoneLookup method lookupInternal.

private Cp2Info lookupInternal(DialerPhoneNumber dialerPhoneNumber) {
    String number = dialerPhoneNumber.getNormalizedNumber();
    if (TextUtils.isEmpty(number)) {
        return Cp2Info.getDefaultInstance();
    }
    Set<Cp2ContactInfo> cp2ContactInfos = new ArraySet<>();
    // Even though this is only a single number, use PartitionedNumbers to mimic the logic used
    // during getMostRecentInfo.
    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(ImmutableSet.of(dialerPhoneNumber));
    Cursor cursor = null;
    try {
        // to ensure consistency when the batch methods are used to update data.
        if (!partitionedNumbers.validE164Numbers().isEmpty()) {
            cursor = queryPhoneTableBasedOnE164(Cp2Projections.getProjectionForPhoneTable(), partitionedNumbers.validE164Numbers());
        } else {
            cursor = queryPhoneLookup(Cp2Projections.getProjectionForPhoneLookupTable(), Iterables.getOnlyElement(partitionedNumbers.invalidNumbers()));
        }
        if (cursor == null) {
            LogUtil.w("Cp2DefaultDirectoryPhoneLookup.lookupInternal", "null cursor");
            return Cp2Info.getDefaultInstance();
        }
        while (cursor.moveToNext()) {
            cp2ContactInfos.add(Cp2Projections.buildCp2ContactInfoFromCursor(appContext, cursor, Directory.DEFAULT));
        }
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return Cp2Info.newBuilder().addAllCp2ContactInfo(cp2ContactInfos).build();
}
Also used : PartitionedNumbers(com.android.dialer.phonenumberproto.PartitionedNumbers) ArraySet(android.support.v4.util.ArraySet) Cp2ContactInfo(com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo) Cursor(android.database.Cursor)

Example 2 with PartitionedNumbers

use of com.android.dialer.phonenumberproto.PartitionedNumbers 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);
}
Also used : ImmutableSet(com.google.common.collect.ImmutableSet) Set(java.util.Set) ArraySet(android.support.v4.util.ArraySet) ArrayList(java.util.ArrayList) Cp2ContactInfo(com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo) PartitionedNumbers(com.android.dialer.phonenumberproto.PartitionedNumbers) Entry(java.util.Map.Entry) DialerPhoneNumber(com.android.dialer.DialerPhoneNumber) ListenableFuture(com.google.common.util.concurrent.ListenableFuture) ArrayList(java.util.ArrayList) List(java.util.List) ArrayMap(android.support.v4.util.ArrayMap) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap)

Example 3 with PartitionedNumbers

use of com.android.dialer.phonenumberproto.PartitionedNumbers 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());
}
Also used : Assert(com.android.dialer.common.Assert) Context(android.content.Context) LogUtil(com.android.dialer.common.LogUtil) Iterables(com.google.common.collect.Iterables) MoreExecutors(com.google.common.util.concurrent.MoreExecutors) ListenableFuture(com.google.common.util.concurrent.ListenableFuture) Uri(android.net.Uri) DeletedContacts(android.provider.ContactsContract.DeletedContacts) Callable(java.util.concurrent.Callable) ConfigProvider(com.android.dialer.configprovider.ConfigProvider) Cp2Info(com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info) ArrayList(java.util.ArrayList) Inject(javax.inject.Inject) BackgroundExecutor(com.android.dialer.common.concurrent.Annotations.BackgroundExecutor) ContactsContract(android.provider.ContactsContract) PhoneLookup(com.android.dialer.phonelookup.PhoneLookup) ArrayMap(android.support.v4.util.ArrayMap) PartitionedNumbers(com.android.dialer.phonenumberproto.PartitionedNumbers) Unencrypted(com.android.dialer.storage.Unencrypted) Map(java.util.Map) PermissionsUtil(com.android.dialer.util.PermissionsUtil) Logger(com.android.dialer.logging.Logger) PhoneLookupHistory(com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory) ApplicationContext(com.android.dialer.inject.ApplicationContext) Cursor(android.database.Cursor) Contacts(android.provider.ContactsContract.Contacts) InvalidProtocolBufferException(com.google.protobuf.InvalidProtocolBufferException) LightweightExecutor(com.android.dialer.common.concurrent.Annotations.LightweightExecutor) ImmutableSet(com.google.common.collect.ImmutableSet) PhoneLookupInfo(com.android.dialer.phonelookup.PhoneLookupInfo) ImmutableMap(com.google.common.collect.ImmutableMap) Predicate(java.util.function.Predicate) Set(java.util.Set) TextUtils(android.text.TextUtils) ArraySet(android.support.v4.util.ArraySet) Cp2ContactInfo(com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo) Phone(android.provider.ContactsContract.CommonDataKinds.Phone) Maps(com.google.common.collect.Maps) DialerPhoneNumber(com.android.dialer.DialerPhoneNumber) Futures(com.google.common.util.concurrent.Futures) List(java.util.List) SharedPreferences(android.content.SharedPreferences) Entry(java.util.Map.Entry) Directory(android.provider.ContactsContract.Directory) Nullable(android.support.annotation.Nullable) ListeningExecutorService(com.google.common.util.concurrent.ListeningExecutorService) PartitionedNumbers(com.android.dialer.phonenumberproto.PartitionedNumbers) PhoneLookupInfo(com.android.dialer.phonelookup.PhoneLookupInfo) ImmutableSet(com.google.common.collect.ImmutableSet) Set(java.util.Set) ArraySet(android.support.v4.util.ArraySet)

Example 4 with PartitionedNumbers

use of com.android.dialer.phonenumberproto.PartitionedNumbers in project android_packages_apps_Dialer by LineageOS.

the class SystemBlockedNumberPhoneLookup method queryNumbers.

@WorkerThread
private ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> queryNumbers(ImmutableSet<DialerPhoneNumber> numbers) {
    Assert.isWorkerThread();
    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers);
    Set<DialerPhoneNumber> blockedNumbers = new ArraySet<>();
    Selection normalizedSelection = Selection.column(BlockedNumbers.COLUMN_E164_NUMBER).in(partitionedNumbers.validE164Numbers());
    try (Cursor cursor = appContext.getContentResolver().query(BlockedNumbers.CONTENT_URI, new String[] { BlockedNumbers.COLUMN_E164_NUMBER }, normalizedSelection.getSelection(), normalizedSelection.getSelectionArgs(), null)) {
        while (cursor != null && cursor.moveToNext()) {
            blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForValidE164(cursor.getString(0)));
        }
    }
    Selection rawSelection = Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER).in(partitionedNumbers.invalidNumbers());
    try (Cursor cursor = appContext.getContentResolver().query(BlockedNumbers.CONTENT_URI, new String[] { BlockedNumbers.COLUMN_ORIGINAL_NUMBER }, rawSelection.getSelection(), rawSelection.getSelectionArgs(), null)) {
        while (cursor != null && cursor.moveToNext()) {
            blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForInvalid(cursor.getString(0)));
        }
    }
    ImmutableMap.Builder<DialerPhoneNumber, SystemBlockedNumberInfo> result = ImmutableMap.builder();
    for (DialerPhoneNumber number : numbers) {
        result.put(number, SystemBlockedNumberInfo.newBuilder().setBlockedState(blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED).build());
    }
    return result.build();
}
Also used : SystemBlockedNumberInfo(com.android.dialer.phonelookup.PhoneLookupInfo.SystemBlockedNumberInfo) PartitionedNumbers(com.android.dialer.phonenumberproto.PartitionedNumbers) ArraySet(android.util.ArraySet) Selection(com.android.dialer.common.database.Selection) DialerPhoneNumber(com.android.dialer.DialerPhoneNumber) Cursor(android.database.Cursor) ImmutableMap(com.google.common.collect.ImmutableMap) WorkerThread(android.support.annotation.WorkerThread)

Example 5 with PartitionedNumbers

use of com.android.dialer.phonenumberproto.PartitionedNumbers in project android_packages_apps_Dialer by LineageOS.

the class Cp2DefaultDirectoryPhoneLookup method queryPhoneTableForContactIds.

/**
 * Returns set of contact ids that correspond to {@code dialerPhoneNumbers} if the contact exists.
 */
private ListenableFuture<Set<Long>> queryPhoneTableForContactIds(ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers) {
    PartitionedNumbers partitionedNumbers = new PartitionedNumbers(dialerPhoneNumbers);
    List<ListenableFuture<Set<Long>>> queryFutures = new ArrayList<>();
    // First use the valid E164 numbers to query the NORMALIZED_NUMBER column.
    queryFutures.add(queryPhoneTableForContactIdsBasedOnE164(partitionedNumbers.validE164Numbers()));
    // Then run a separate query for each invalid number. Separate queries are done to accomplish
    // loose matching which couldn't be accomplished with a batch query.
    Assert.checkState(partitionedNumbers.invalidNumbers().size() <= getMaxSupportedInvalidNumbers());
    for (String invalidNumber : partitionedNumbers.invalidNumbers()) {
        queryFutures.add(queryPhoneLookupTableForContactIdsBasedOnRawNumber(invalidNumber));
    }
    return Futures.transform(Futures.allAsList(queryFutures), listOfSets -> {
        Set<Long> contactIds = new ArraySet<>();
        for (Set<Long> ids : listOfSets) {
            contactIds.addAll(ids);
        }
        return contactIds;
    }, lightweightExecutorService);
}
Also used : PartitionedNumbers(com.android.dialer.phonenumberproto.PartitionedNumbers) ArraySet(android.support.v4.util.ArraySet) ArrayList(java.util.ArrayList) ListenableFuture(com.google.common.util.concurrent.ListenableFuture)

Aggregations

PartitionedNumbers (com.android.dialer.phonenumberproto.PartitionedNumbers)6 ArraySet (android.support.v4.util.ArraySet)5 DialerPhoneNumber (com.android.dialer.DialerPhoneNumber)4 Cursor (android.database.Cursor)3 Cp2ContactInfo (com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo)3 ImmutableMap (com.google.common.collect.ImmutableMap)3 ListenableFuture (com.google.common.util.concurrent.ListenableFuture)3 ArrayList (java.util.ArrayList)3 ArrayMap (android.support.v4.util.ArrayMap)2 ImmutableSet (com.google.common.collect.ImmutableSet)2 List (java.util.List)2 Map (java.util.Map)2 Entry (java.util.Map.Entry)2 Set (java.util.Set)2 Context (android.content.Context)1 SharedPreferences (android.content.SharedPreferences)1 Uri (android.net.Uri)1 ContactsContract (android.provider.ContactsContract)1 Phone (android.provider.ContactsContract.CommonDataKinds.Phone)1 Contacts (android.provider.ContactsContract.Contacts)1