1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.phonelookup.blockednumber; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.support.annotation.WorkerThread; 22 import android.util.ArraySet; 23 import com.android.dialer.DialerPhoneNumber; 24 import com.android.dialer.blocking.FilteredNumberCompat; 25 import com.android.dialer.calllog.observer.MarkDirtyObserver; 26 import com.android.dialer.common.Assert; 27 import com.android.dialer.common.LogUtil; 28 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 29 import com.android.dialer.common.database.Selection; 30 import com.android.dialer.database.FilteredNumberContract.FilteredNumber; 31 import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns; 32 import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; 33 import com.android.dialer.inject.ApplicationContext; 34 import com.android.dialer.phonelookup.PhoneLookup; 35 import com.android.dialer.phonelookup.PhoneLookupInfo; 36 import com.android.dialer.phonelookup.PhoneLookupInfo.BlockedState; 37 import com.android.dialer.phonelookup.PhoneLookupInfo.DialerBlockedNumberInfo; 38 import com.android.dialer.phonenumberproto.PartitionedNumbers; 39 import com.google.common.collect.ImmutableMap; 40 import com.google.common.collect.ImmutableSet; 41 import com.google.common.util.concurrent.Futures; 42 import com.google.common.util.concurrent.ListenableFuture; 43 import com.google.common.util.concurrent.ListeningExecutorService; 44 import java.util.Set; 45 import javax.inject.Inject; 46 47 /** 48 * Lookup blocked numbers in the dialer internal database. This is used when the system database is 49 * not yet available. 50 */ 51 public final class DialerBlockedNumberPhoneLookup implements PhoneLookup<DialerBlockedNumberInfo> { 52 53 private final Context appContext; 54 private final ListeningExecutorService executorService; 55 private final MarkDirtyObserver markDirtyObserver; 56 57 @Inject DialerBlockedNumberPhoneLookup( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService executorService, MarkDirtyObserver markDirtyObserver)58 DialerBlockedNumberPhoneLookup( 59 @ApplicationContext Context appContext, 60 @BackgroundExecutor ListeningExecutorService executorService, 61 MarkDirtyObserver markDirtyObserver) { 62 this.appContext = appContext; 63 this.executorService = executorService; 64 this.markDirtyObserver = markDirtyObserver; 65 } 66 67 @Override lookup(DialerPhoneNumber dialerPhoneNumber)68 public ListenableFuture<DialerBlockedNumberInfo> lookup(DialerPhoneNumber dialerPhoneNumber) { 69 if (FilteredNumberCompat.useNewFiltering(appContext)) { 70 return Futures.immediateFuture(DialerBlockedNumberInfo.getDefaultInstance()); 71 } 72 return executorService.submit( 73 () -> queryNumbers(ImmutableSet.of(dialerPhoneNumber)).get(dialerPhoneNumber)); 74 } 75 76 @Override isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers)77 public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) { 78 // Dirty state is recorded with PhoneLookupDataSource.markDirtyAndNotify(), which will force 79 // rebuild with the CallLogFramework 80 return Futures.immediateFuture(false); 81 } 82 83 @Override 84 public ListenableFuture<ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo>> getMostRecentInfo(ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> existingInfoMap)85 getMostRecentInfo(ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> existingInfoMap) { 86 if (FilteredNumberCompat.useNewFiltering(appContext)) { 87 return Futures.immediateFuture(existingInfoMap); 88 } 89 LogUtil.enterBlock("DialerBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo"); 90 return executorService.submit(() -> queryNumbers(existingInfoMap.keySet())); 91 } 92 93 @Override setSubMessage( PhoneLookupInfo.Builder phoneLookupInfo, DialerBlockedNumberInfo subMessage)94 public void setSubMessage( 95 PhoneLookupInfo.Builder phoneLookupInfo, DialerBlockedNumberInfo subMessage) { 96 phoneLookupInfo.setDialerBlockedNumberInfo(subMessage); 97 } 98 99 @Override getSubMessage(PhoneLookupInfo phoneLookupInfo)100 public DialerBlockedNumberInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) { 101 return phoneLookupInfo.getDialerBlockedNumberInfo(); 102 } 103 104 @Override onSuccessfulBulkUpdate()105 public ListenableFuture<Void> onSuccessfulBulkUpdate() { 106 return Futures.immediateFuture(null); 107 } 108 109 @WorkerThread queryNumbers( ImmutableSet<DialerPhoneNumber> numbers)110 private ImmutableMap<DialerPhoneNumber, DialerBlockedNumberInfo> queryNumbers( 111 ImmutableSet<DialerPhoneNumber> numbers) { 112 Assert.isWorkerThread(); 113 PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers); 114 115 Set<DialerPhoneNumber> blockedNumbers = new ArraySet<>(); 116 117 Selection normalizedSelection = 118 Selection.column(FilteredNumberColumns.NORMALIZED_NUMBER) 119 .in(partitionedNumbers.validE164Numbers()); 120 try (Cursor cursor = 121 appContext 122 .getContentResolver() 123 .query( 124 FilteredNumber.CONTENT_URI, 125 new String[] {FilteredNumberColumns.NORMALIZED_NUMBER, FilteredNumberColumns.TYPE}, 126 normalizedSelection.getSelection(), 127 normalizedSelection.getSelectionArgs(), 128 null)) { 129 while (cursor != null && cursor.moveToNext()) { 130 if (cursor.getInt(1) == FilteredNumberTypes.BLOCKED_NUMBER) { 131 blockedNumbers.addAll( 132 partitionedNumbers.dialerPhoneNumbersForValidE164(cursor.getString(0))); 133 } 134 } 135 } 136 137 Selection rawSelection = 138 Selection.column(FilteredNumberColumns.NUMBER).in(partitionedNumbers.invalidNumbers()); 139 try (Cursor cursor = 140 appContext 141 .getContentResolver() 142 .query( 143 FilteredNumber.CONTENT_URI, 144 new String[] {FilteredNumberColumns.NUMBER, FilteredNumberColumns.TYPE}, 145 rawSelection.getSelection(), 146 rawSelection.getSelectionArgs(), 147 null)) { 148 while (cursor != null && cursor.moveToNext()) { 149 if (cursor.getInt(1) == FilteredNumberTypes.BLOCKED_NUMBER) { 150 blockedNumbers.addAll( 151 partitionedNumbers.dialerPhoneNumbersForInvalid(cursor.getString(0))); 152 } 153 } 154 } 155 156 ImmutableMap.Builder<DialerPhoneNumber, DialerBlockedNumberInfo> result = 157 ImmutableMap.builder(); 158 159 for (DialerPhoneNumber number : numbers) { 160 result.put( 161 number, 162 DialerBlockedNumberInfo.newBuilder() 163 .setBlockedState( 164 blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED) 165 .build()); 166 } 167 168 return result.build(); 169 } 170 171 @Override registerContentObservers(Context appContext)172 public void registerContentObservers(Context appContext) { 173 appContext 174 .getContentResolver() 175 .registerContentObserver( 176 FilteredNumber.CONTENT_URI, 177 true, // FilteredNumberProvider notifies on the item 178 markDirtyObserver); 179 } 180 } 181