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 --- .../DialerBlockedNumberPhoneLookup.java | 210 +++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java (limited to 'java/com/android/dialer/phonelookup/blockednumber/DialerBlockedNumberPhoneLookup.java') 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); + } + } +} -- cgit v1.2.3