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