From ff88f7bbe07fb3e267af49427adf1a91e3fc21e9 Mon Sep 17 00:00:00 2001 From: zachh Date: Tue, 9 Jan 2018 17:29:06 -0800 Subject: Added RealtimeRowProcessor. This is for performing work inside of the call log's RecyclerView, when the view holder is bound. Most of the time, this should be a no-op but there are possible edge cases where the call log data cannot be updated efficiently through the standard batch mechanism. One example of this is when there are too many invalid numbers in the call log; the CP2 information for invalid numbers cannot be efficiently batch updated so we fetch this information at display time. (Note that we do handle up to 5 invalid numbers in the batch update mechanism, but if there are more than that we fallback to this realtime processing.) Test: unit, manual PiperOrigin-RevId: 181400016 Change-Id: Iea6b380742e757b48d19f319fe46dc5fae837604 --- .../basecomponent/BaseDialerRootComponent.java | 2 + .../database/AnnotatedCallLogDatabaseHelper.java | 3 +- .../contract/AnnotatedCallLogContract.java | 10 +- .../phonelookup/PhoneLookupDataSource.java | 3 + .../android/dialer/calllog/model/CoalescedRow.java | 7 ++ .../dialer/calllog/ui/CallLogUiComponent.java | 37 +++++++ .../ui/CoalescedAnnotatedCallLogCursorLoader.java | 4 +- .../dialer/calllog/ui/NewCallLogAdapter.java | 8 +- .../dialer/calllog/ui/NewCallLogFragment.java | 3 +- .../dialer/calllog/ui/NewCallLogViewHolder.java | 55 ++++++++++- .../dialer/calllog/ui/RealtimeRowProcessor.java | 107 +++++++++++++++++++++ .../dialer/phonelookup/cp2/Cp2PhoneLookup.java | 30 ++++++ 12 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 java/com/android/dialer/calllog/ui/CallLogUiComponent.java create mode 100644 java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java (limited to 'java') diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java index 8973a329f..20152af03 100644 --- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java +++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java @@ -18,6 +18,7 @@ package com.android.dialer.binary.basecomponent; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.database.CallLogDatabaseComponent; +import com.android.dialer.calllog.ui.CallLogUiComponent; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.configprovider.ConfigProviderComponent; import com.android.dialer.duo.DuoComponent; @@ -44,6 +45,7 @@ public interface BaseDialerRootComponent extends CallLocationComponent.HasComponent, CallLogComponent.HasComponent, CallLogDatabaseComponent.HasComponent, + CallLogUiComponent.HasComponent, ConfigProviderComponent.HasComponent, DialerExecutorComponent.HasComponent, DuoComponent.HasComponent, diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java index 68d4b95df..f90d657b8 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java @@ -60,7 +60,8 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { + (AnnotatedCallLog.TRANSCRIPTION + " integer, ") + (AnnotatedCallLog.VOICEMAIL_URI + " text, ") + (AnnotatedCallLog.CALL_TYPE + " integer, ") - + (AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER + " integer") + + (AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER + " integer, ") + + (AnnotatedCallLog.CP2_INFO_INCOMPLETE + " integer") + ");"; /** Deletes all but the first maxRows rows (by timestamp) to keep the table a manageable size. */ diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java index 1b3e09095..9161d6087 100644 --- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java +++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java @@ -185,6 +185,13 @@ public class AnnotatedCallLogContract { */ String CAN_REPORT_AS_INVALID_NUMBER = "can_report_as_invalid_number"; + /** + * True if the CP2 information is incomplete and needs to be queried at display time. + * + *

TYPE: INTEGER (boolean) + */ + String CP2_INFO_INCOMPLETE = "cp2_info_incomplete"; + String[] ALL_COMMON_COLUMNS = new String[] { _ID, @@ -207,7 +214,8 @@ public class AnnotatedCallLogContract { IS_BUSINESS, IS_VOICEMAIL, CALL_TYPE, - CAN_REPORT_AS_INVALID_NUMBER + CAN_REPORT_AS_INVALID_NUMBER, + CP2_INFO_INCOMPLETE }; } diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 6ec11ad13..935ea7406 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -276,6 +276,7 @@ public final class PhoneLookupDataSource .useMostRecentLong(AnnotatedCallLog.PHOTO_ID) .useMostRecentString(AnnotatedCallLog.LOOKUP_URI) .useMostRecentInt(AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER) + .useMostRecentInt(AnnotatedCallLog.CP2_INFO_INCOMPLETE) .combine(); } @@ -582,6 +583,8 @@ public final class PhoneLookupDataSource contentValues.put( AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER, PhoneLookupSelector.canReportAsInvalidNumber(phoneLookupInfo)); + contentValues.put( + AnnotatedCallLog.CP2_INFO_INCOMPLETE, phoneLookupInfo.getCp2Info().getIsIncomplete()); } private static Uri numberUri(String number) { diff --git a/java/com/android/dialer/calllog/model/CoalescedRow.java b/java/com/android/dialer/calllog/model/CoalescedRow.java index 1824ba146..2520d996a 100644 --- a/java/com/android/dialer/calllog/model/CoalescedRow.java +++ b/java/com/android/dialer/calllog/model/CoalescedRow.java @@ -40,9 +40,12 @@ public abstract class CoalescedRow { .setIsVoicemail(false) .setCallType(0) .setCanReportAsInvalidNumber(false) + .setCp2InfoIncomplete(false) .setCoalescedIds(CoalescedIds.getDefaultInstance()); } + public abstract Builder toBuilder(); + public abstract int id(); public abstract long timestamp(); @@ -95,6 +98,8 @@ public abstract class CoalescedRow { public abstract boolean canReportAsInvalidNumber(); + public abstract boolean cp2InfoIncomplete(); + public abstract CoalescedIds coalescedIds(); /** Builder for {@link CoalescedRow}. */ @@ -144,6 +149,8 @@ public abstract class CoalescedRow { public abstract Builder setCanReportAsInvalidNumber(boolean canReportAsInvalidNumber); + public abstract Builder setCp2InfoIncomplete(boolean cp2InfoIncomplete); + public abstract Builder setCoalescedIds(CoalescedIds coalescedIds); public abstract CoalescedRow build(); diff --git a/java/com/android/dialer/calllog/ui/CallLogUiComponent.java b/java/com/android/dialer/calllog/ui/CallLogUiComponent.java new file mode 100644 index 000000000..a8e3b225b --- /dev/null +++ b/java/com/android/dialer/calllog/ui/CallLogUiComponent.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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; + +import android.content.Context; +import com.android.dialer.inject.HasRootComponent; +import dagger.Subcomponent; + +/** Dagger component for the call log UI package. */ +@Subcomponent +public abstract class CallLogUiComponent { + + public abstract RealtimeRowProcessor realtimeRowProcessor(); + + public static CallLogUiComponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .callLogUiComponent(); + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + CallLogUiComponent callLogUiComponent(); + } +} diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java index 6d60bdda4..5c0ce2816 100644 --- a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java +++ b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java @@ -50,7 +50,8 @@ final class CoalescedAnnotatedCallLogCursorLoader extends CursorLoader { private static final int IS_VOICEMAIL = 18; private static final int CALL_TYPE = 19; private static final int CAN_REPORT_AS_INVALID_NUMBER = 20; - private static final int COALESCED_IDS = 21; + private static final int CP2_INFO_INCOMPLETE = 21; + private static final int COALESCED_IDS = 22; CoalescedAnnotatedCallLogCursorLoader(Context context) { // CoalescedAnnotatedCallLog requires that PROJECTION be ALL_COLUMNS and the following params be @@ -102,6 +103,7 @@ final class CoalescedAnnotatedCallLogCursorLoader extends CursorLoader { .setIsVoicemail(cursor.getInt(IS_VOICEMAIL) == 1) .setCallType(cursor.getInt(CALL_TYPE)) .setCanReportAsInvalidNumber(cursor.getInt(CAN_REPORT_AS_INVALID_NUMBER) == 1) + .setCp2InfoIncomplete(cursor.getInt(CP2_INFO_INCOMPLETE) == 1) .setCoalescedIds(coalescedIds) .build(); } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java index d5cfb7e24..6dd742be5 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java @@ -15,6 +15,7 @@ */ package com.android.dialer.calllog.ui; +import android.content.Context; import android.database.Cursor; import android.support.annotation.IntDef; import android.support.annotation.Nullable; @@ -45,15 +46,17 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { private final Cursor cursor; private final Clock clock; + private final RealtimeRowProcessor realtimeRowProcessor; /** Null when the "Today" header should not be displayed. */ @Nullable private final Integer todayHeaderPosition; /** Null when the "Older" header should not be displayed. */ @Nullable private final Integer olderHeaderPosition; - NewCallLogAdapter(Cursor cursor, Clock clock) { + NewCallLogAdapter(Context context, Cursor cursor, Clock clock) { this.cursor = cursor; this.clock = clock; + this.realtimeRowProcessor = CallLogUiComponent.get(context).realtimeRowProcessor(); // Calculate header adapter positions by reading cursor. long currentTimeMillis = clock.currentTimeMillis(); @@ -95,7 +98,8 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { return new NewCallLogViewHolder( LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.new_call_log_entry, viewGroup, false), - clock); + clock, + realtimeRowProcessor); default: throw Assert.createUnsupportedOperationFailException("Unsupported view type: " + viewType); } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 719878cec..e422b5f83 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -171,7 +171,8 @@ public final class NewCallLogFragment extends Fragment } // TODO(zachh): Handle empty cursor by showing empty view. recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - recyclerView.setAdapter(new NewCallLogAdapter(newCursor, System::currentTimeMillis)); + recyclerView.setAdapter( + new NewCallLogAdapter(getContext(), newCursor, System::currentTimeMillis)); } @Override diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java index e45257f7b..5cceac989 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java @@ -30,12 +30,18 @@ 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.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.compat.telephony.TelephonyManagerCompat; import com.android.dialer.contactphoto.ContactPhotoManager; import com.android.dialer.lettertile.LetterTileDrawable; import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.time.Clock; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import java.util.Locale; +import java.util.concurrent.ExecutorService; /** {@link RecyclerView.ViewHolder} for the new call log. */ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { @@ -50,8 +56,12 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { private final ImageView menuButton; private final Clock clock; + private final RealtimeRowProcessor realtimeRowProcessor; + private final ExecutorService uiExecutorService; - NewCallLogViewHolder(View view, Clock clock) { + private int currentRowId; + + NewCallLogViewHolder(View view, Clock clock, RealtimeRowProcessor realtimeRowProcessor) { super(view); this.context = view.getContext(); primaryTextView = view.findViewById(R.id.primary_text); @@ -63,12 +73,29 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { menuButton = view.findViewById(R.id.menu_button); this.clock = clock; + this.realtimeRowProcessor = realtimeRowProcessor; + uiExecutorService = DialerExecutorComponent.get(context).uiExecutorService(); } /** @param cursor a cursor from {@link CoalescedAnnotatedCallLogCursorLoader}. */ void bind(Cursor cursor) { CoalescedRow row = CoalescedAnnotatedCallLogCursorLoader.toRow(cursor); + currentRowId = row.id(); // Used to make sure async updates are applied to the correct views + + // Even if there is additional real time processing necessary, we still want to immediately show + // what information we have, rather than an empty card. For example, if CP2 information needs to + // be queried on the fly, we can still show the phone number until the contact name loads. + handleRow(row); + + // Note: This leaks the view holder via the callback (which is an inner class), but this is OK + // because we only create ~10 of them (and they'll be collected assuming all jobs finish). + Futures.addCallback( + realtimeRowProcessor.applyRealtimeProcessing(row), + new RealtimeRowFutureCallback(row.id()), + uiExecutorService); + } + private void handleRow(CoalescedRow row) { // TODO(zachh): Handle RTL properly. primaryTextView.setText(CallLogEntryText.buildPrimaryText(context, row)); secondaryTextView.setText(CallLogEntryText.buildSecondaryTextForEntries(context, clock, row)); @@ -152,4 +179,30 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { private void setOnClickListenerForMenuButon(CoalescedRow row) { menuButton.setOnClickListener(NewCallLogMenu.createOnClickListener(context, row)); } + + private class RealtimeRowFutureCallback implements FutureCallback> { + private final int id; + + RealtimeRowFutureCallback(int id) { + this.id = id; + } + + /** + * @param updatedRow the updated row if an update is required, or absent if no updates are + * required + */ + @Override + public void onSuccess(Optional updatedRow) { + // If the user scrolled then this ViewHolder may not correspond to the completed task and + // there's nothing to do. + if (updatedRow.isPresent() && id == currentRowId) { + handleRow(updatedRow.get()); + } + } + + @Override + public void onFailure(Throwable throwable) { + LogUtil.e("RealtimeRowFutureCallback.onFailure", "realtime processing failed", throwable); + } + } } diff --git a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java new file mode 100644 index 000000000..814efc779 --- /dev/null +++ b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 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; + +import android.support.annotation.MainThread; +import android.util.ArrayMap; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.calllog.model.CoalescedRow; +import com.android.dialer.common.concurrent.Annotations.Ui; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info; +import com.android.dialer.phonelookup.cp2.Cp2PhoneLookup; +import com.android.dialer.phonelookup.selector.PhoneLookupSelector; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import java.util.Map; +import javax.inject.Inject; + +/** + * Does work necessary to update a {@link CoalescedRow} when it is requested to be displayed. + * + *

In most cases this is a no-op as most AnnotatedCallLog rows can be displayed immediately + * as-is. However, there are certain times that a row from the AnnotatedCallLog cannot be displayed + * without further work being performed. + * + *

For example, when there are many invalid numbers in the call log, we cannot efficiently update + * the CP2 information for all of them at once, and so information for those rows must be retrieved + * at display time. + */ +public final class RealtimeRowProcessor { + + private final ListeningExecutorService uiExecutor; + private final Cp2PhoneLookup cp2PhoneLookup; + private final PhoneLookupSelector phoneLookupSelector; + + private final Map cache = new ArrayMap<>(); + + @Inject + RealtimeRowProcessor( + @Ui ListeningExecutorService uiExecutor, + Cp2PhoneLookup cp2PhoneLookup, + PhoneLookupSelector phoneLookupSelector) { + this.uiExecutor = uiExecutor; + this.cp2PhoneLookup = cp2PhoneLookup; + this.phoneLookupSelector = phoneLookupSelector; + } + + /** + * Converts a {@link CoalescedRow} to a future which is the result of performing additional work + * on the row. Returns {@link Optional#absent()} if no modifications were necessary. + */ + @MainThread + ListenableFuture> applyRealtimeProcessing(final CoalescedRow row) { + // Cp2PhoneLookup can not always efficiently process all rows. + if (!row.cp2InfoIncomplete()) { + return Futures.immediateFuture(Optional.absent()); + } + + Cp2Info cachedCp2Info = cache.get(row.number()); + if (cachedCp2Info != null) { + if (cachedCp2Info.equals(Cp2Info.getDefaultInstance())) { + return Futures.immediateFuture(Optional.absent()); + } + return Futures.immediateFuture(Optional.of(applyCp2InfoToRow(cachedCp2Info, row))); + } + + ListenableFuture cp2InfoFuture = cp2PhoneLookup.lookupByNumber(row.number()); + return Futures.transform( + cp2InfoFuture, + cp2Info -> { + cache.put(row.number(), cp2Info); + if (!cp2Info.equals(Cp2Info.getDefaultInstance())) { + return Optional.of(applyCp2InfoToRow(cp2Info, row)); + } + return Optional.absent(); + }, + uiExecutor /* ensures the cache is updated on a single thread */); + } + + private CoalescedRow applyCp2InfoToRow(Cp2Info cp2Info, CoalescedRow row) { + PhoneLookupInfo phoneLookupInfo = PhoneLookupInfo.newBuilder().setCp2Info(cp2Info).build(); + // It is safe to overwrite any existing data because CP2 always has highest priority. + return row.toBuilder() + .setName(phoneLookupSelector.selectName(phoneLookupInfo)) + .setPhotoUri(phoneLookupSelector.selectPhotoUri(phoneLookupInfo)) + .setPhotoId(phoneLookupSelector.selectPhotoId(phoneLookupInfo)) + .setLookupUri(phoneLookupSelector.selectLookupUri(phoneLookupInfo)) + .setNumberTypeLabel(phoneLookupSelector.selectNumberLabel(phoneLookupInfo)) + .build(); + } +} diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index 0d312cbbe..5ae0fb68a 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -40,6 +40,7 @@ import com.android.dialer.phonelookup.PhoneLookupInfo; import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info; import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; +import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.android.dialer.phonenumberproto.PartitionedNumbers; import com.android.dialer.storage.Unencrypted; import com.android.dialer.telecom.TelecomCallUtil; @@ -51,6 +52,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.protobuf.InvalidProtocolBufferException; import java.util.ArrayList; import java.util.List; @@ -157,6 +159,34 @@ public final class Cp2PhoneLookup implements PhoneLookup { return Cp2Info.newBuilder().addAllCp2ContactInfo(cp2ContactInfos).build(); } + /** + * Queries ContactsContract.PhoneLookup for the {@link Cp2Info} associated with the provided + * {@link DialerPhoneNumber}. Returns {@link Cp2Info#getDefaultInstance()} if there is no + * information. + */ + public ListenableFuture lookupByNumber(DialerPhoneNumber dialerPhoneNumber) { + return backgroundExecutorService.submit( + () -> { + DialerPhoneNumberUtil dialerPhoneNumberUtil = + new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); + String rawNumber = dialerPhoneNumberUtil.normalizeNumber(dialerPhoneNumber); + if (rawNumber.isEmpty()) { + return Cp2Info.getDefaultInstance(); + } + Set cp2ContactInfos = new ArraySet<>(); + try (Cursor cursor = queryPhoneLookup(PHONE_LOOKUP_PROJECTION, rawNumber)) { + if (cursor == null) { + LogUtil.w("Cp2PhoneLookup.lookup", "null cursor"); + return Cp2Info.getDefaultInstance(); + } + while (cursor.moveToNext()) { + cp2ContactInfos.add(buildCp2ContactInfoFromPhoneCursor(appContext, cursor)); + } + } + return Cp2Info.newBuilder().addAllCp2ContactInfo(cp2ContactInfos).build(); + }); + } + @Override public ListenableFuture isDirty(ImmutableSet phoneNumbers) { PartitionedNumbers partitionedNumbers = new PartitionedNumbers(phoneNumbers); -- cgit v1.2.3