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.provider.BlockedNumberContract.BlockedNumbers; 22 import android.support.annotation.NonNull; 23 import android.support.annotation.WorkerThread; 24 import android.util.ArraySet; 25 import com.android.dialer.DialerPhoneNumber; 26 import com.android.dialer.blocking.FilteredNumberCompat; 27 import com.android.dialer.calllog.observer.MarkDirtyObserver; 28 import com.android.dialer.common.Assert; 29 import com.android.dialer.common.LogUtil; 30 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 31 import com.android.dialer.common.database.Selection; 32 import com.android.dialer.inject.ApplicationContext; 33 import com.android.dialer.phonelookup.PhoneLookup; 34 import com.android.dialer.phonelookup.PhoneLookupInfo; 35 import com.android.dialer.phonelookup.PhoneLookupInfo.BlockedState; 36 import com.android.dialer.phonelookup.PhoneLookupInfo.Builder; 37 import com.android.dialer.phonelookup.PhoneLookupInfo.SystemBlockedNumberInfo; 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 system database. Requires N+ and migration from dialer database 49 * completed (need user consent to move data into system). 50 */ 51 public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlockedNumberInfo> { 52 53 private final Context appContext; 54 private final ListeningExecutorService executorService; 55 private final MarkDirtyObserver markDirtyObserver; 56 57 @Inject SystemBlockedNumberPhoneLookup( @pplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService executorService, MarkDirtyObserver markDirtyObserver)58 SystemBlockedNumberPhoneLookup( 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(@onNull DialerPhoneNumber number)68 public ListenableFuture<SystemBlockedNumberInfo> lookup(@NonNull DialerPhoneNumber number) { 69 if (!FilteredNumberCompat.useNewFiltering(appContext)) { 70 return Futures.immediateFuture(SystemBlockedNumberInfo.getDefaultInstance()); 71 } 72 return executorService.submit(() -> queryNumbers(ImmutableSet.of(number)).get(number)); 73 } 74 75 @Override isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers)76 public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) { 77 // Dirty state is recorded with PhoneLookupDataSource.markDirtyAndNotify(), which will force 78 // rebuild with the CallLogFramework 79 return Futures.immediateFuture(false); 80 } 81 82 @Override 83 public ListenableFuture<ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo>> getMostRecentInfo(ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> existingInfoMap)84 getMostRecentInfo(ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> existingInfoMap) { 85 LogUtil.enterBlock("SystemBlockedNumberPhoneLookup.getMostRecentPhoneLookupInfo"); 86 if (!FilteredNumberCompat.useNewFiltering(appContext)) { 87 return Futures.immediateFuture(existingInfoMap); 88 } 89 return executorService.submit(() -> queryNumbers(existingInfoMap.keySet())); 90 } 91 92 @WorkerThread queryNumbers( ImmutableSet<DialerPhoneNumber> numbers)93 private ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> queryNumbers( 94 ImmutableSet<DialerPhoneNumber> numbers) { 95 Assert.isWorkerThread(); 96 PartitionedNumbers partitionedNumbers = new PartitionedNumbers(numbers); 97 98 Set<DialerPhoneNumber> blockedNumbers = new ArraySet<>(); 99 100 Selection normalizedSelection = 101 Selection.column(BlockedNumbers.COLUMN_E164_NUMBER) 102 .in(partitionedNumbers.validE164Numbers()); 103 try (Cursor cursor = 104 appContext 105 .getContentResolver() 106 .query( 107 BlockedNumbers.CONTENT_URI, 108 new String[] {BlockedNumbers.COLUMN_E164_NUMBER}, 109 normalizedSelection.getSelection(), 110 normalizedSelection.getSelectionArgs(), 111 null)) { 112 while (cursor != null && cursor.moveToNext()) { 113 blockedNumbers.addAll( 114 partitionedNumbers.dialerPhoneNumbersForValidE164(cursor.getString(0))); 115 } 116 } 117 118 Selection rawSelection = 119 Selection.column(BlockedNumbers.COLUMN_ORIGINAL_NUMBER) 120 .in(partitionedNumbers.invalidNumbers()); 121 try (Cursor cursor = 122 appContext 123 .getContentResolver() 124 .query( 125 BlockedNumbers.CONTENT_URI, 126 new String[] {BlockedNumbers.COLUMN_ORIGINAL_NUMBER}, 127 rawSelection.getSelection(), 128 rawSelection.getSelectionArgs(), 129 null)) { 130 while (cursor != null && cursor.moveToNext()) { 131 blockedNumbers.addAll(partitionedNumbers.dialerPhoneNumbersForInvalid(cursor.getString(0))); 132 } 133 } 134 135 ImmutableMap.Builder<DialerPhoneNumber, SystemBlockedNumberInfo> result = 136 ImmutableMap.builder(); 137 138 for (DialerPhoneNumber number : numbers) { 139 result.put( 140 number, 141 SystemBlockedNumberInfo.newBuilder() 142 .setBlockedState( 143 blockedNumbers.contains(number) ? BlockedState.BLOCKED : BlockedState.NOT_BLOCKED) 144 .build()); 145 } 146 147 return result.build(); 148 } 149 150 @Override setSubMessage(Builder phoneLookupInfo, SystemBlockedNumberInfo subMessage)151 public void setSubMessage(Builder phoneLookupInfo, SystemBlockedNumberInfo subMessage) { 152 phoneLookupInfo.setSystemBlockedNumberInfo(subMessage); 153 } 154 155 @Override getSubMessage(PhoneLookupInfo phoneLookupInfo)156 public SystemBlockedNumberInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) { 157 return phoneLookupInfo.getSystemBlockedNumberInfo(); 158 } 159 160 @Override onSuccessfulBulkUpdate()161 public ListenableFuture<Void> onSuccessfulBulkUpdate() { 162 return Futures.immediateFuture(null); 163 } 164 165 @Override registerContentObservers()166 public void registerContentObservers() { 167 appContext 168 .getContentResolver() 169 .registerContentObserver( 170 BlockedNumbers.CONTENT_URI, 171 true, // BlockedNumbers notifies on the item 172 markDirtyObserver); 173 } 174 175 @Override unregisterContentObservers()176 public void unregisterContentObservers() { 177 appContext.getContentResolver().unregisterContentObserver(markDirtyObserver); 178 } 179 180 @Override clearData()181 public ListenableFuture<Void> clearData() { 182 return Futures.immediateFuture(null); 183 } 184 185 @Override getLoggingName()186 public String getLoggingName() { 187 return "SystemBlockedNumberPhoneLookup"; 188 } 189 } 190