From fb112d870c3a564d2dcb0e72dcdcabb6e0375520 Mon Sep 17 00:00:00 2001 From: twyen Date: Fri, 5 Jan 2018 11:52:45 -0800 Subject: Implement dialer blocked number phone lookup This CL implements looking up the dialer internal database for blocked numbers when the system database is not available yet. Data is only invalidated when dialer is alive since that is the only time blocked numbers can be set and removed. Bug: 70989538,70989547 Test: DialerBlockedNumberPhoneLookupTest PiperOrigin-RevId: 180956355 Change-Id: Ie7acf091bf58a074d0a1ee39613fad035d2e6e60 --- .../android/dialer/phonelookup/PhoneLookup.java | 14 ++ .../dialer/phonelookup/PhoneLookupModule.java | 8 +- .../dialer/phonelookup/PhoneLookupSelector.java | 144 -------------- .../DialerBlockedNumberPhoneLookup.java | 210 +++++++++++++++++++++ .../composite/CompositePhoneLookup.java | 13 +- .../dialer/phonelookup/cp2/Cp2PhoneLookup.java | 64 +------ .../dialer/phonelookup/phone_lookup_info.proto | 22 +++ .../phonelookup/selector/AndroidManifest.xml | 18 ++ .../phonelookup/selector/PhoneLookupSelector.java | 168 +++++++++++++++++ .../phonelookup/selector/res/values/strings.xml | 22 +++ 10 files changed, 481 insertions(+), 202 deletions(-) delete mode 100644 java/com/android/dialer/phonelookup/PhoneLookupSelector.java create mode 100644 java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java create mode 100644 java/com/android/dialer/phonelookup/selector/AndroidManifest.xml create mode 100644 java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java create mode 100644 java/com/android/dialer/phonelookup/selector/res/values/strings.xml (limited to 'java/com/android/dialer/phonelookup') diff --git a/java/com/android/dialer/phonelookup/PhoneLookup.java b/java/com/android/dialer/phonelookup/PhoneLookup.java index 859085e7b..118ae603e 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/PhoneLookup.java @@ -16,6 +16,8 @@ package com.android.dialer.phonelookup; +import android.content.Context; +import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.telecom.Call; import com.android.dialer.DialerPhoneNumber; @@ -82,4 +84,16 @@ public interface PhoneLookup { * be efficiently implemented. */ ListenableFuture onSuccessfulBulkUpdate(); + + @MainThread + void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks); + + /** + * Methods which may optionally be called as a result of a phone lookup's content observer firing. + */ + interface ContentObserverCallbacks { + @MainThread + void markDirtyAndNotify(Context appContext); + } } diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java index 8a590052d..b4f37872e 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java +++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java @@ -16,6 +16,7 @@ package com.android.dialer.phonelookup; +import com.android.dialer.phonelookup.blockednumber.DialerBlockedNumberPhoneLookup; import com.android.dialer.phonelookup.composite.CompositePhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2PhoneLookup; import com.google.common.collect.ImmutableList; @@ -27,8 +28,11 @@ import dagger.Provides; public abstract class PhoneLookupModule { @Provides - static ImmutableList providePhoneLookupList(Cp2PhoneLookup cp2PhoneLookup) { - return ImmutableList.of(cp2PhoneLookup); + @SuppressWarnings({"unchecked", "rawtype"}) + static ImmutableList providePhoneLookupList( + Cp2PhoneLookup cp2PhoneLookup, + DialerBlockedNumberPhoneLookup dialerBlockedNumberPhoneLookup) { + return ImmutableList.of(cp2PhoneLookup, dialerBlockedNumberPhoneLookup); } @Provides diff --git a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java deleted file mode 100644 index c933af728..000000000 --- a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ -package com.android.dialer.phonelookup; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo; -import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo; -import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo.InfoType; - -/** - * Prioritizes information from a {@link PhoneLookupInfo}. - * - *

For example, a single {@link PhoneLookupInfo} may contain different name information from many - * different {@link PhoneLookup} sources. This class defines the rules for deciding which name - * should be selected for display to the user, by prioritizing the data from some {@link PhoneLookup - * PhoneLookups} over others. - * - *

Note that the logic in this class may be highly coupled with the logic in {@code - * CompositePhoneLookup}, because {@code CompositePhoneLookup} may also include prioritization logic - * for short-circuiting low-priority {@link PhoneLookup PhoneLookups}. - */ -public final class PhoneLookupSelector { - - /** - * Select the name associated with this number. Examples of this are a local contact's name or a - * business name received from caller ID. - */ - @NonNull - public static String selectName(PhoneLookupInfo phoneLookupInfo) { - Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); - if (firstLocalContact != null) { - String name = firstLocalContact.getName(); - if (!name.isEmpty()) { - return firstLocalContact.getName(); - } - } - if (phoneLookupInfo.hasPeopleApiInfo()) { - return phoneLookupInfo.getPeopleApiInfo().getDisplayName(); - } - 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 ""; - } - - /** - * Returns true if the number associated with the given {@link PhoneLookupInfo} can be reported as - * invalid. - * - *

As we currently report invalid numbers via the People API, only numbers from the People API - * can be reported as invalid. - */ - public static boolean canReportAsInvalidNumber(PhoneLookupInfo phoneLookupInfo) { - // The presence of Cp2ContactInfo means the number associated with the given PhoneLookupInfo - // matches that of a Cp2 (local) contact, and PeopleApiInfo will not be used to display - // information like name, photo, etc. We should not allow the user to report the number in this - // case as the info displayed is not from the People API. - if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { - return false; - } - - PeopleApiInfo peopleApiInfo = phoneLookupInfo.getPeopleApiInfo(); - return peopleApiInfo.getInfoType() != InfoType.UNKNOWN - && !peopleApiInfo.getPersonId().isEmpty(); - } - - /** - * 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/blockednumber/DialerBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java new file mode 100644 index 000000000..54df3995c --- /dev/null +++ b/java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java @@ -0,0 +1,210 @@ +/* + * 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.phonelookup.blockednumber; + +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; +import android.telecom.Call; +import android.util.ArraySet; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.common.database.Selection; +import com.android.dialer.database.FilteredNumberContract.FilteredNumber; +import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; +import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.phonelookup.PhoneLookup; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.BlockedState; +import com.android.dialer.phonelookup.PhoneLookupInfo.DialerBlockedNumberInfo; +import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; +import com.android.dialer.phonenumberproto.PartitionedNumbers; +import com.android.dialer.telecom.TelecomCallUtil; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import java.util.Set; +import javax.inject.Inject; + +/** + * Lookup blocked numbers in the dialer internal database. This is used when the system database is + * not yet available. + */ +public final class DialerBlockedNumberPhoneLookup implements PhoneLookup { + + private final Context appContext; + private final ListeningExecutorService executorService; + + @Inject + DialerBlockedNumberPhoneLookup( + @ApplicationContext Context appContext, + @BackgroundExecutor ListeningExecutorService executorService) { + this.appContext = appContext; + this.executorService = executorService; + } + + @Override + public ListenableFuture lookup(@NonNull Call call) { + return executorService.submit( + () -> { + DialerPhoneNumberUtil dialerPhoneNumberUtil = + new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); + + DialerPhoneNumber number = + dialerPhoneNumberUtil.parse( + TelecomCallUtil.getNumber(call), + TelecomCallUtil.getCountryCode(appContext, call).orNull()); + return queryNumbers(ImmutableSet.of(number)).get(number); + }); + } + + @Override + public ListenableFuture isDirty(ImmutableSet phoneNumbers) { + // Dirty state is recorded with PhoneLookupDataSource.markDirtyAndNotify(), which will force + // rebuild with the CallLogFramework + return Futures.immediateFuture(false); + } + + @Override + public ListenableFuture> + getMostRecentInfo(ImmutableMap existingInfoMap) { + LogUtil.enterBlock("DialerBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo"); + return executorService.submit(() -> queryNumbers(existingInfoMap.keySet())); + } + + @Override + public void setSubMessage( + PhoneLookupInfo.Builder phoneLookupInfo, DialerBlockedNumberInfo subMessage) { + phoneLookupInfo.setDialerBlockedNumberInfo(subMessage); + } + + @Override + public DialerBlockedNumberInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) { + return phoneLookupInfo.getDialerBlockedNumberInfo(); + } + + @Override + public ListenableFuture onSuccessfulBulkUpdate() { + return Futures.immediateFuture(null); + } + + @WorkerThread + private ImmutableMap queryNumbers( + ImmutableSet numbers) { + Assert.isWorkerThread(); + PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers); + + Set blockedNumbers = new ArraySet<>(); + + Selection normalizedSelection = + Selection.column(FilteredNumberColumns.NORMALIZED_NUMBER) + .in(partitionedNumbers.validE164Numbers()); + try (Cursor cursor = + appContext + .getContentResolver() + .query( + FilteredNumber.CONTENT_URI, + new String[] {FilteredNumberColumns.NORMALIZED_NUMBER, FilteredNumberColumns.TYPE}, + normalizedSelection.getSelection(), + normalizedSelection.getSelectionArgs(), + null)) { + while (cursor != null && cursor.moveToNext()) { + if (cursor.getInt(1) == FilteredNumberTypes.BLOCKED_NUMBER) { + blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForE164(cursor.getString(0))); + } + } + } + + Selection rawSelection = + Selection.column(FilteredNumberColumns.NUMBER) + .in( + partitionedNumbers + .unformattableNumbers() + .toArray(new String[partitionedNumbers.unformattableNumbers().size()])); + try (Cursor cursor = + appContext + .getContentResolver() + .query( + FilteredNumber.CONTENT_URI, + new String[] {FilteredNumberColumns.NUMBER, FilteredNumberColumns.TYPE}, + rawSelection.getSelection(), + rawSelection.getSelectionArgs(), + null)) { + while (cursor != null && cursor.moveToNext()) { + if (cursor.getInt(1) == FilteredNumberTypes.BLOCKED_NUMBER) { + blockedNumbers.addAll( + partitionedNumbers.dialerPhoneNumbersForUnformattable(cursor.getString(0))); + } + } + } + + ImmutableMap.Builder result = + ImmutableMap.builder(); + + for (DialerPhoneNumber number : numbers) { + result.put( + number, + DialerBlockedNumberInfo.newBuilder() + .setBlockedState( + blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED) + .build()); + } + + return result.build(); + } + + @Override + public void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + appContext + .getContentResolver() + .registerContentObserver( + FilteredNumber.CONTENT_URI, + true, // FilteredNumberProvider notifies on the item + new FilteredNumberObserver(appContext, contentObserverCallbacks)); + } + + private static class FilteredNumberObserver extends ContentObserver { + private final Context appContext; + private final ContentObserverCallbacks contentObserverCallbacks; + + FilteredNumberObserver(Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + super(null); + this.appContext = appContext; + this.contentObserverCallbacks = contentObserverCallbacks; + } + + @MainThread + @Override + @SuppressWarnings("FutureReturnValueIgnored") // never throws. + public void onChange(boolean selfChange, Uri uri) { + Assert.isMainThread(); + LogUtil.enterBlock("DialerBlockedNumberPhoneLookup.FilteredNumberObserver.onChange"); + contentObserverCallbacks.markDirtyAndNotify(appContext); + } + } +} diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index da4378bb7..34f35311f 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -16,6 +16,8 @@ package com.android.dialer.phonelookup.composite; +import android.content.Context; +import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.telecom.Call; import com.android.dialer.DialerPhoneNumber; @@ -60,7 +62,7 @@ public final class CompositePhoneLookup implements PhoneLookup *

Note: If any of the dependent lookups fails, the returned future will also fail. If any of * the dependent lookups does not complete, the returned future will also not complete. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtype"}) @Override public ListenableFuture lookup(@NonNull Call call) { // TODO(zachh): Add short-circuiting logic so that this call is not blocked on low-priority @@ -164,4 +166,13 @@ public final class CompositePhoneLookup implements PhoneLookup return Futures.transform( Futures.allAsList(futures), unused -> null, lightweightExecutorService); } + + @Override + @MainThread + public void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + for (PhoneLookup phoneLookup : phoneLookups) { + phoneLookup.registerContentObservers(appContext, contentObserverCallbacks); + } + } } diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index 60c934ace..307e0a434 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -37,7 +37,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; import com.google.common.base.Optional; @@ -45,7 +45,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.protobuf.InvalidProtocolBufferException; import java.util.Map; import java.util.Map.Entry; @@ -392,6 +391,12 @@ public final class Cp2PhoneLookup implements PhoneLookup { }); } + @Override + public void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + // Do nothing since CP2 changes are too noisy. + } + /** * 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 @@ -475,7 +480,8 @@ public final class Cp2PhoneLookup implements PhoneLookup { // Divide the numbers into those we can format to E164 and those we can't. Then run separate // queries against the contacts table using the NORMALIZED_NUMBER and NUMBER columns. // TODO(zachh): These queries are inefficient without a lastModified column to filter on. - PartitionedNumbers partitionedNumbers = new PartitionedNumbers(updatedNumbers); + PartitionedNumbers partitionedNumbers = + new PartitionedNumbers(ImmutableSet.copyOf(updatedNumbers)); if (!partitionedNumbers.validE164Numbers().isEmpty()) { try (Cursor cursor = queryPhoneTableBasedOnE164(CP2_INFO_PROJECTION, partitionedNumbers.validE164Numbers())) { @@ -701,56 +707,4 @@ public final class Cp2PhoneLookup implements PhoneLookup { } return where.toString(); } - - /** - * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} by those that can be formatted to - * E164 and those that cannot. - */ - private static class PartitionedNumbers { - private Map> e164NumbersToDialerPhoneNumbers = new ArrayMap<>(); - private Map> unformattableNumbersToDialerPhoneNumbers = - new ArrayMap<>(); - - PartitionedNumbers(Set dialerPhoneNumbers) { - DialerPhoneNumberUtil dialerPhoneNumberUtil = - new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); - for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) { - Optional e164 = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber); - if (e164.isPresent()) { - String validE164 = e164.get(); - Set currentNumbers = e164NumbersToDialerPhoneNumbers.get(validE164); - if (currentNumbers == null) { - currentNumbers = new ArraySet<>(); - e164NumbersToDialerPhoneNumbers.put(validE164, currentNumbers); - } - currentNumbers.add(dialerPhoneNumber); - } else { - String unformattableNumber = dialerPhoneNumber.getRawInput().getNumber(); - Set currentNumbers = - unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber); - if (currentNumbers == null) { - currentNumbers = new ArraySet<>(); - unformattableNumbersToDialerPhoneNumbers.put(unformattableNumber, currentNumbers); - } - currentNumbers.add(dialerPhoneNumber); - } - } - } - - Set unformattableNumbers() { - return unformattableNumbersToDialerPhoneNumbers.keySet(); - } - - Set validE164Numbers() { - return e164NumbersToDialerPhoneNumbers.keySet(); - } - - Set dialerPhoneNumbersForE164(String e164) { - return e164NumbersToDialerPhoneNumbers.get(e164); - } - - Set dialerPhoneNumbersForUnformattable(String unformattableNumber) { - return unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber); - } - } } diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto index c42faf49d..75423b9ee 100644 --- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto +++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto @@ -83,4 +83,26 @@ message PhoneLookupInfo { optional InfoType info_type = 6; } optional PeopleApiInfo people_api_info = 3; + + // Whether a number is blocked or not. Used by both the system blacklist and + // dialer fallback + enum BlockedState { + UNKNOWN = 0; + BLOCKED = 1; + NOT_BLOCKED = 2; + } + + // Message for the android system BlockedNumber lookup. Available starting in + // N. + message SystemBlockedNumberInfo { + optional BlockedState blocked_state = 1; + } + optional SystemBlockedNumberInfo system_blocked_number_info = 4; + + // Message for the dialer fallback for blocked number. Used in M or when the + // migration to the system has not been completed. + message DialerBlockedNumberInfo { + optional BlockedState blocked_state = 1; + } + optional DialerBlockedNumberInfo dialer_blocked_number_info = 5; } \ No newline at end of file diff --git a/java/com/android/dialer/phonelookup/selector/AndroidManifest.xml b/java/com/android/dialer/phonelookup/selector/AndroidManifest.xml new file mode 100644 index 000000000..5d836c791 --- /dev/null +++ b/java/com/android/dialer/phonelookup/selector/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java new file mode 100644 index 000000000..a960d4e96 --- /dev/null +++ b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.dialer.phonelookup.selector; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.phonelookup.PhoneLookup; +import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.BlockedState; +import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo; +import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo.InfoType; +import javax.inject.Inject; + +/** + * Prioritizes information from a {@link PhoneLookupInfo}. + * + *

For example, a single {@link PhoneLookupInfo} may contain different name information from many + * different {@link PhoneLookup} sources. This class defines the rules for deciding which name + * should be selected for display to the user, by prioritizing the data from some {@link PhoneLookup + * PhoneLookups} over others. + * + *

Note that the logic in this class may be highly coupled with the logic in {@code + * CompositePhoneLookup}, because {@code CompositePhoneLookup} may also include prioritization logic + * for short-circuiting low-priority {@link PhoneLookup PhoneLookups}. + */ +public final class PhoneLookupSelector { + + private final Context appContext; + + @Inject + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public PhoneLookupSelector(@ApplicationContext Context appContext) { + this.appContext = appContext; + } + + /** + * Select the name associated with this number. Examples of this are a local contact's name or a + * business name received from caller ID. + */ + @NonNull + public String selectName(PhoneLookupInfo phoneLookupInfo) { + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + String name = firstLocalContact.getName(); + if (!name.isEmpty()) { + return firstLocalContact.getName(); + } + } + if (phoneLookupInfo.hasPeopleApiInfo()) { + return phoneLookupInfo.getPeopleApiInfo().getDisplayName(); + } + return ""; + } + + /** Select the photo URI associated with this number. */ + @NonNull + public 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 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 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 String selectNumberLabel(PhoneLookupInfo phoneLookupInfo) { + if (isBlocked(phoneLookupInfo)) { + return appContext.getString(R.string.blocked_number_new_call_log_label); + } + + Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); + if (firstLocalContact != null) { + String label = firstLocalContact.getLabel(); + if (!label.isEmpty()) { + return label; + } + } + return ""; + } + + /** + * Returns true if the number associated with the given {@link PhoneLookupInfo} can be reported as + * invalid. + * + *

As we currently report invalid numbers via the People API, only numbers from the People API + * can be reported as invalid. + */ + public static boolean canReportAsInvalidNumber(PhoneLookupInfo phoneLookupInfo) { + // The presence of Cp2ContactInfo means the number associated with the given PhoneLookupInfo + // matches that of a Cp2 (local) contact, and PeopleApiInfo will not be used to display + // information like name, photo, etc. We should not allow the user to report the number in this + // case as the info displayed is not from the People API. + if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { + return false; + } + + PeopleApiInfo peopleApiInfo = phoneLookupInfo.getPeopleApiInfo(); + return peopleApiInfo.getInfoType() != InfoType.UNKNOWN + && !peopleApiInfo.getPersonId().isEmpty(); + } + + /** + * 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 Cp2ContactInfo firstLocalContact(PhoneLookupInfo phoneLookupInfo) { + if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) { + return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0); + } + return null; + } + + private static boolean isBlocked(PhoneLookupInfo info) { + return info.hasDialerBlockedNumberInfo() + && info.getDialerBlockedNumberInfo().getBlockedState().equals(BlockedState.BLOCKED); + } +} diff --git a/java/com/android/dialer/phonelookup/selector/res/values/strings.xml b/java/com/android/dialer/phonelookup/selector/res/values/strings.xml new file mode 100644 index 000000000..2080b3975 --- /dev/null +++ b/java/com/android/dialer/phonelookup/selector/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + + Blocked + + -- cgit v1.2.3