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