diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-01-05 21:51:17 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-01-05 21:51:17 +0000 |
commit | 00a3ca523b7ca892b00010c35f6b61b7a855f48e (patch) | |
tree | dbda20e83cb3458fefec613b56d4b9d0a4814e66 | |
parent | 417be6a9e3482472cce238e0a51b6367b86aba1f (diff) | |
parent | fb112d870c3a564d2dcb0e72dcdcabb6e0375520 (diff) |
Merge "Implement dialer blocked number phone lookup"
17 files changed, 532 insertions, 95 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b459a44c0..d19d0c9ab 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -16,8 +16,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" coreApp="true" package="com.android.dialer" - android:versionCode="190000" - android:versionName="14.0"> + android:versionCode="220000" + android:versionName="17.0"> <uses-sdk android:minSdkVersion="23" diff --git a/java/com/android/dialer/binary/google/AndroidManifest.xml b/java/com/android/dialer/binary/google/AndroidManifest.xml index 86f6bcb81..0ebc006d6 100644 --- a/java/com/android/dialer/binary/google/AndroidManifest.xml +++ b/java/com/android/dialer/binary/google/AndroidManifest.xml @@ -16,8 +16,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" coreApp="true" package="com.google.android.google_stub_dialer" - android:versionCode="190000" - android:versionName="14.0"> + android:versionCode="220000" + android:versionName="17.0"> <uses-sdk android:minSdkVersion="23" diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java index e4bb4c89a..c9d5f0907 100644 --- a/java/com/android/dialer/calllog/CallLogFramework.java +++ b/java/com/android/dialer/calllog/CallLogFramework.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.support.annotation.MainThread; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import com.android.dialer.buildtype.BuildType; import com.android.dialer.calllog.datasources.CallLogDataSource; import com.android.dialer.calllog.datasources.DataSources; @@ -38,7 +39,8 @@ import javax.inject.Singleton; @Singleton public final class CallLogFramework implements CallLogDataSource.ContentObserverCallbacks { - static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild"; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild"; private final DataSources dataSources; private final SharedPreferences sharedPreferences; diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 214862793..6ec11ad13 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -33,14 +33,15 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Ann 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.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; -import com.android.dialer.phonelookup.PhoneLookupSelector; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; +import com.android.dialer.phonelookup.selector.PhoneLookupSelector; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -63,9 +64,11 @@ import javax.inject.Inject; * Responsible for maintaining the columns in the annotated call log which are derived from phone * numbers. */ -public final class PhoneLookupDataSource implements CallLogDataSource { +public final class PhoneLookupDataSource + implements CallLogDataSource, PhoneLookup.ContentObserverCallbacks { private final PhoneLookup<PhoneLookupInfo> phoneLookup; + private final PhoneLookupSelector phoneLookupSelector; private final ListeningExecutorService backgroundExecutorService; private final ListeningExecutorService lightweightExecutorService; @@ -86,12 +89,16 @@ public final class PhoneLookupDataSource implements CallLogDataSource { */ private final Set<String> phoneLookupHistoryRowsToDelete = new ArraySet<>(); + private CallLogDataSource.ContentObserverCallbacks dataSourceContentObserverCallbacks; + @Inject PhoneLookupDataSource( PhoneLookup<PhoneLookupInfo> phoneLookup, + PhoneLookupSelector phoneLookupSelector, @BackgroundExecutor ListeningExecutorService backgroundExecutorService, @LightweightExecutor ListeningExecutorService lightweightExecutorService) { this.phoneLookup = phoneLookup; + this.phoneLookupSelector = phoneLookupSelector; this.backgroundExecutorService = backgroundExecutorService; this.lightweightExecutorService = lightweightExecutorService; } @@ -275,8 +282,16 @@ public final class PhoneLookupDataSource implements CallLogDataSource { @MainThread @Override public void registerContentObservers( - Context appContext, ContentObserverCallbacks contentObserverCallbacks) { - // No content observers required for this data source. + Context appContext, CallLogDataSource.ContentObserverCallbacks contentObserverCallbacks) { + dataSourceContentObserverCallbacks = contentObserverCallbacks; + phoneLookup.registerContentObservers(appContext, this); + } + + @MainThread + @Override + public void markDirtyAndNotify(Context appContext) { + Assert.isMainThread(); + dataSourceContentObserverCallbacks.markDirtyAndNotify(appContext); } private static ImmutableSet<DialerPhoneNumber> @@ -455,7 +470,7 @@ public final class PhoneLookupDataSource implements CallLogDataSource { })); } - private static void populateInserts( + private void populateInserts( ImmutableMap<Long, PhoneLookupInfo> existingInfo, CallLogMutations mutations) { for (Entry<Long, ContentValues> entry : mutations.getInserts().entrySet()) { long id = entry.getKey(); @@ -468,7 +483,7 @@ public final class PhoneLookupDataSource implements CallLogDataSource { } } - private static void updateMutations( + private void updateMutations( ImmutableMap<Long, PhoneLookupInfo> updatesToApply, CallLogMutations mutations) { for (Entry<Long, PhoneLookupInfo> entry : updatesToApply.entrySet()) { long id = entry.getKey(); @@ -554,17 +569,16 @@ public final class PhoneLookupDataSource implements CallLogDataSource { return normalizedNumbersToDelete; } - private static void updateContentValues( - ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) { - contentValues.put(AnnotatedCallLog.NAME, PhoneLookupSelector.selectName(phoneLookupInfo)); + private void updateContentValues(ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) { + contentValues.put(AnnotatedCallLog.NAME, phoneLookupSelector.selectName(phoneLookupInfo)); contentValues.put( - AnnotatedCallLog.PHOTO_URI, PhoneLookupSelector.selectPhotoUri(phoneLookupInfo)); + AnnotatedCallLog.PHOTO_URI, phoneLookupSelector.selectPhotoUri(phoneLookupInfo)); contentValues.put( - AnnotatedCallLog.PHOTO_ID, PhoneLookupSelector.selectPhotoId(phoneLookupInfo)); + AnnotatedCallLog.PHOTO_ID, phoneLookupSelector.selectPhotoId(phoneLookupInfo)); contentValues.put( - AnnotatedCallLog.LOOKUP_URI, PhoneLookupSelector.selectLookupUri(phoneLookupInfo)); + AnnotatedCallLog.LOOKUP_URI, phoneLookupSelector.selectLookupUri(phoneLookupInfo)); contentValues.put( - AnnotatedCallLog.NUMBER_TYPE_LABEL, PhoneLookupSelector.selectNumberLabel(phoneLookupInfo)); + AnnotatedCallLog.NUMBER_TYPE_LABEL, phoneLookupSelector.selectNumberLabel(phoneLookupInfo)); contentValues.put( AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER, PhoneLookupSelector.canReportAsInvalidNumber(phoneLookupInfo)); diff --git a/java/com/android/dialer/common/database/Selection.java b/java/com/android/dialer/common/database/Selection.java index b61472d2f..e449fd9f6 100644 --- a/java/com/android/dialer/common/database/Selection.java +++ b/java/com/android/dialer/common/database/Selection.java @@ -18,8 +18,11 @@ package com.android.dialer.common.database; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; import com.android.dialer.common.Assert; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -106,7 +109,14 @@ public final class Selection { * enclosed in a parenthesis. */ @NonNull + @SuppressWarnings("rawtypes") public static Selection fromString(@Nullable String selection, @Nullable String... args) { + return new Builder(selection, args == null ? Collections.emptyList() : Arrays.asList(args)) + .build(); + } + + @NonNull + public static Selection fromString(@Nullable String selection, Collection<String> args) { return new Builder(selection, args).build(); } @@ -149,6 +159,16 @@ public final class Selection { public Selection is(@NonNull String condition) { return fromString(column + " " + Assert.isNotNull(condition)); } + + public Selection in(String... values) { + return in(values == null ? Collections.emptyList() : Arrays.asList(values)); + } + + public Selection in(Collection<String> values) { + return fromString( + column + " IN (" + TextUtils.join(",", Collections.nCopies(values.size(), "?")) + ")", + values); + } } /** Builder for {@link Selection} */ @@ -159,14 +179,14 @@ public final class Selection { private Builder() {} - private Builder(@Nullable String selection, @Nullable String... args) { + private Builder(@Nullable String selection, Collection<String> args) { if (selection == null) { return; } checkArgsCount(selection, args); this.selection.append(parenthesized(selection)); if (args != null) { - Collections.addAll(selectionArgs, args); + selectionArgs.addAll(args); } } @@ -213,14 +233,14 @@ public final class Selection { return this; } - private static void checkArgsCount(@NonNull String selection, @Nullable String... args) { + private static void checkArgsCount(@NonNull String selection, Collection<String> args) { int argsInSelection = 0; for (int i = 0; i < selection.length(); i++) { if (selection.charAt(i) == '?') { argsInSelection++; } } - Assert.checkArgument(argsInSelection == (args == null ? 0 : args.length)); + Assert.checkArgument(argsInSelection == (args == null ? 0 : args.size())); } } 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<T> { * be efficiently implemented. */ ListenableFuture<Void> 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<PhoneLookup> providePhoneLookupList(Cp2PhoneLookup cp2PhoneLookup) { - return ImmutableList.of(cp2PhoneLookup); + @SuppressWarnings({"unchecked", "rawtype"}) + static ImmutableList<PhoneLookup> providePhoneLookupList( + Cp2PhoneLookup cp2PhoneLookup, + DialerBlockedNumberPhoneLookup dialerBlockedNumberPhoneLookup) { + return ImmutableList.of(cp2PhoneLookup, dialerBlockedNumberPhoneLookup); } @Provides 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<DialerBlockedNumberInfo> { + + 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<DialerBlockedNumberInfo> 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<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) { + // Dirty state is recorded with PhoneLookupDataSource.markDirtyAndNotify(), which will force + // rebuild with the CallLogFramework + return Futures.immediateFuture(false); + } + + @Override + public ListenableFuture<ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo>> + getMostRecentInfo(ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> 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<Void> onSuccessfulBulkUpdate() { + return Futures.immediateFuture(null); + } + + @WorkerThread + private ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> queryNumbers( + ImmutableSet<DialerPhoneNumber> numbers) { + Assert.isWorkerThread(); + PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers); + + Set<DialerPhoneNumber> 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<DialerPhoneNumber, DialerBlockedNumberInfo> 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<PhoneLookupInfo> * <p>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<PhoneLookupInfo> 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<PhoneLookupInfo> 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<Cp2Info> { }); } + @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<Cp2Info> { // 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<Cp2Info> { } 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<String, Set<DialerPhoneNumber>> e164NumbersToDialerPhoneNumbers = new ArrayMap<>(); - private Map<String, Set<DialerPhoneNumber>> unformattableNumbersToDialerPhoneNumbers = - new ArrayMap<>(); - - PartitionedNumbers(Set<DialerPhoneNumber> dialerPhoneNumbers) { - DialerPhoneNumberUtil dialerPhoneNumberUtil = - new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); - for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) { - Optional<String> e164 = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber); - if (e164.isPresent()) { - String validE164 = e164.get(); - Set<DialerPhoneNumber> currentNumbers = e164NumbersToDialerPhoneNumbers.get(validE164); - if (currentNumbers == null) { - currentNumbers = new ArraySet<>(); - e164NumbersToDialerPhoneNumbers.put(validE164, currentNumbers); - } - currentNumbers.add(dialerPhoneNumber); - } else { - String unformattableNumber = dialerPhoneNumber.getRawInput().getNumber(); - Set<DialerPhoneNumber> currentNumbers = - unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber); - if (currentNumbers == null) { - currentNumbers = new ArraySet<>(); - unformattableNumbersToDialerPhoneNumbers.put(unformattableNumber, currentNumbers); - } - currentNumbers.add(dialerPhoneNumber); - } - } - } - - Set<String> unformattableNumbers() { - return unformattableNumbersToDialerPhoneNumbers.keySet(); - } - - Set<String> validE164Numbers() { - return e164NumbersToDialerPhoneNumbers.keySet(); - } - - Set<DialerPhoneNumber> dialerPhoneNumbersForE164(String e164) { - return e164NumbersToDialerPhoneNumbers.get(e164); - } - - Set<DialerPhoneNumber> 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 @@ +<!-- + ~ 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 + --> +<manifest + package="com.android.dialer.phonelookup.selector"> +</manifest> diff --git a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java index c933af728..a960d4e96 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java +++ b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java @@ -13,13 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.dialer.phonelookup; +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}. @@ -35,12 +42,20 @@ import com.android.dialer.phonelookup.PhoneLookupInfo.PeopleApiInfo.InfoType; */ 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 static String selectName(PhoneLookupInfo phoneLookupInfo) { + public String selectName(PhoneLookupInfo phoneLookupInfo) { Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); if (firstLocalContact != null) { String name = firstLocalContact.getName(); @@ -56,7 +71,7 @@ public final class PhoneLookupSelector { /** Select the photo URI associated with this number. */ @NonNull - public static String selectPhotoUri(PhoneLookupInfo phoneLookupInfo) { + public String selectPhotoUri(PhoneLookupInfo phoneLookupInfo) { Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); if (firstLocalContact != null) { String photoUri = firstLocalContact.getPhotoUri(); @@ -68,7 +83,7 @@ public final class PhoneLookupSelector { } /** Select the photo ID associated with this number, or 0 if there is none. */ - public static long selectPhotoId(PhoneLookupInfo phoneLookupInfo) { + public long selectPhotoId(PhoneLookupInfo phoneLookupInfo) { Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); if (firstLocalContact != null) { long photoId = firstLocalContact.getPhotoId(); @@ -81,7 +96,7 @@ public final class PhoneLookupSelector { /** Select the lookup URI associated with this number. */ @NonNull - public static String selectLookupUri(PhoneLookupInfo phoneLookupInfo) { + public String selectLookupUri(PhoneLookupInfo phoneLookupInfo) { Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo); if (firstLocalContact != null) { String lookupUri = firstLocalContact.getLookupUri(); @@ -97,7 +112,11 @@ public final class PhoneLookupSelector { * set by the user. */ @NonNull - public static String selectNumberLabel(PhoneLookupInfo phoneLookupInfo) { + 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(); @@ -135,10 +154,15 @@ public final class PhoneLookupSelector { * show a synthesized photo containing photos of both "Mom" and "Dad"). */ @Nullable - private static Cp2ContactInfo firstLocalContact(PhoneLookupInfo phoneLookupInfo) { + 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 @@ +<!-- + ~ 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 + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Label under the name of a blocked number in the call log. [CHAR LIMIT=15] --> + <string name="blocked_number_new_call_log_label">Blocked</string> + +</resources> diff --git a/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java b/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java new file mode 100644 index 000000000..372f21ee8 --- /dev/null +++ b/java/com/android/dialer/phonenumberproto/PartitionedNumbers.java @@ -0,0 +1,114 @@ +/* + * 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.phonenumberproto; + +import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; +import android.support.v4.util.ArrayMap; +import android.support.v4.util.ArraySet; +import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.Assert; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import java.util.Map; +import java.util.Set; + +/** + * Divides a set of {@link DialerPhoneNumber DialerPhoneNumbers} by those that can be formatted to + * E164 and those that cannot. + */ +public final class PartitionedNumbers { + private final ImmutableMap<String, ImmutableSet<DialerPhoneNumber>> + e164NumbersToDialerPhoneNumbers; + private final ImmutableMap<String, ImmutableSet<DialerPhoneNumber>> + unformattableNumbersToDialerPhoneNumbers; + + @WorkerThread + public PartitionedNumbers(@NonNull ImmutableSet<DialerPhoneNumber> dialerPhoneNumbers) { + Assert.isWorkerThread(); + DialerPhoneNumberUtil dialerPhoneNumberUtil = + new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); + Map<String, Set<DialerPhoneNumber>> e164MapBuilder = new ArrayMap<>(); + Map<String, Set<DialerPhoneNumber>> unformattableMapBuilder = new ArrayMap<>(); + + for (DialerPhoneNumber dialerPhoneNumber : dialerPhoneNumbers) { + Optional<String> e164 = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber); + if (e164.isPresent()) { + String validE164 = e164.get(); + Set<DialerPhoneNumber> currentNumbers = e164MapBuilder.get(validE164); + if (currentNumbers == null) { + currentNumbers = new ArraySet<>(); + e164MapBuilder.put(validE164, currentNumbers); + } + currentNumbers.add(dialerPhoneNumber); + } else { + String unformattableNumber = dialerPhoneNumber.getRawInput().getNumber(); + Set<DialerPhoneNumber> currentNumbers = unformattableMapBuilder.get(unformattableNumber); + if (currentNumbers == null) { + currentNumbers = new ArraySet<>(); + unformattableMapBuilder.put(unformattableNumber, currentNumbers); + } + currentNumbers.add(dialerPhoneNumber); + } + } + + e164NumbersToDialerPhoneNumbers = makeImmutable(e164MapBuilder); + unformattableNumbersToDialerPhoneNumbers = makeImmutable(unformattableMapBuilder); + } + + /** Returns the set of formatted number from the original DialerPhoneNumbers */ + @NonNull + public ImmutableSet<String> unformattableNumbers() { + return unformattableNumbersToDialerPhoneNumbers.keySet(); + } + + /** Returns the set of raw number that is unformattable from the original DialerPhoneNumbers */ + @NonNull + public ImmutableSet<String> validE164Numbers() { + return e164NumbersToDialerPhoneNumbers.keySet(); + } + + /** + * Returns the corresponding set of original DialerPhoneNumber that maps to the e.164 number, or + * an empty set if the number is not found. + */ + @NonNull + public ImmutableSet<DialerPhoneNumber> dialerPhoneNumbersForE164(String e164) { + return Assert.isNotNull(e164NumbersToDialerPhoneNumbers.get(e164)); + } + + /** + * Returns the corresponding set of original DialerPhoneNumber that maps to the unformattable + * number returned by {@link #unformattableNumbers()}, or an empty set if the number is not found. + */ + @NonNull + public ImmutableSet<DialerPhoneNumber> dialerPhoneNumbersForUnformattable( + String unformattableNumber) { + return Assert.isNotNull(unformattableNumbersToDialerPhoneNumbers.get(unformattableNumber)); + } + + private static <K, V> ImmutableMap<K, ImmutableSet<V>> makeImmutable( + Map<K, Set<V>> mutableMapOfSet) { + ImmutableMap.Builder<K, ImmutableSet<V>> mapBuilder = ImmutableMap.builder(); + for (Map.Entry<K, Set<V>> entry : mutableMapOfSet.entrySet()) { + mapBuilder.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue())); + } + return mapBuilder.build(); + } +} diff --git a/java/com/android/dialer/telecom/TelecomCallUtil.java b/java/com/android/dialer/telecom/TelecomCallUtil.java index b877a7392..7d71b4b90 100644 --- a/java/com/android/dialer/telecom/TelecomCallUtil.java +++ b/java/com/android/dialer/telecom/TelecomCallUtil.java @@ -103,20 +103,27 @@ public class TelecomCallUtil { @WorkerThread public static Optional<String> getE164Number(Context appContext, Call call) { Assert.isWorkerThread(); - PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle(); - Optional<SubscriptionInfo> subscriptionInfo = - TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle); String rawNumber = getNumber(call); if (TextUtils.isEmpty(rawNumber)) { return Optional.absent(); } - String countryCode = - subscriptionInfo.isPresent() ? subscriptionInfo.get().getCountryIso() : null; - if (countryCode == null) { + Optional<String> countryCode = getCountryCode(appContext, call); + if (!countryCode.isPresent()) { LogUtil.w("TelecomCallUtil.getE164Number", "couldn't find a country code for call"); return Optional.absent(); } - return Optional.fromNullable( - PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.toUpperCase(Locale.US))); + return Optional.fromNullable(PhoneNumberUtils.formatNumberToE164(rawNumber, countryCode.get())); + } + + @WorkerThread + public static Optional<String> getCountryCode(Context appContext, Call call) { + Assert.isWorkerThread(); + PhoneAccountHandle phoneAccountHandle = call.getDetails().getAccountHandle(); + Optional<SubscriptionInfo> subscriptionInfo = + TelecomUtil.getSubscriptionInfo(appContext, phoneAccountHandle); + if (subscriptionInfo.isPresent() && subscriptionInfo.get().getCountryIso() != null) { + return Optional.of(subscriptionInfo.get().getCountryIso().toUpperCase(Locale.US)); + } + return Optional.absent(); } } diff --git a/packages.mk b/packages.mk index a1cf7fc93..03268fd57 100644 --- a/packages.mk +++ b/packages.mk @@ -39,6 +39,7 @@ LOCAL_AAPT_FLAGS := \ com.android.dialer.notification \ com.android.dialer.oem \ com.android.dialer.phonelookup.database \ + com.android.dialer.phonelookup.selector \ com.android.dialer.phonenumberutil \ com.android.dialer.postcall \ com.android.dialer.precall.impl \ |