1 /*
2  * Copyright (C) 2019 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.server.telecom.callfiltering;
18 
19 import android.content.Context;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.provider.BlockedNumberContract;
25 import android.provider.CallLog;
26 import android.telecom.CallerInfo;
27 import android.telecom.Log;
28 import android.telecom.TelecomManager;
29 
30 import com.android.server.telecom.Call;
31 import com.android.server.telecom.CallerInfoLookupHelper;
32 import com.android.server.telecom.LogUtils;
33 import com.android.server.telecom.LoggedHandlerExecutor;
34 import com.android.server.telecom.settings.BlockedNumbersUtil;
35 
36 import java.util.concurrent.CompletableFuture;
37 import java.util.concurrent.CompletionStage;
38 
39 public class BlockCheckerFilter extends CallFilter {
40     private final Call mCall;
41     private final Context mContext;
42     private final CallerInfoLookupHelper mCallerInfoLookupHelper;
43     private final BlockCheckerAdapter mBlockCheckerAdapter;
44     private final String TAG = "BlockCheckerFilter";
45     private boolean mContactExists;
46     private HandlerThread mHandlerThread;
47     private Handler mHandler;
48 
49     public static final long CALLER_INFO_QUERY_TIMEOUT = 5000;
50 
51     /**
52      * Integer reason indicating whether a call was blocked, and if so why.
53      * @hide
54      */
55     public static final String RES_BLOCK_STATUS = "block_status";
56 
57     /**
58      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was not
59      * blocked.
60      * @hide
61      */
62     public static final int STATUS_NOT_BLOCKED = 0;
63 
64     /**
65      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
66      * because it is in the list of blocked numbers maintained by the provider.
67      * @hide
68      */
69     public static final int STATUS_BLOCKED_IN_LIST = 1;
70 
71     /**
72      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
73      * because it is from a restricted number.
74      * @hide
75      */
76     public static final int STATUS_BLOCKED_RESTRICTED = 2;
77 
78     /**
79      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
80      * because it is from an unknown number.
81      * @hide
82      */
83     public static final int STATUS_BLOCKED_UNKNOWN_NUMBER = 3;
84 
85     /**
86      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
87      * because it is from a pay phone.
88      * @hide
89      */
90     public static final int STATUS_BLOCKED_PAYPHONE = 4;
91 
92     /**
93      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
94      * because it is from a number not in the users contacts.
95      * @hide
96      */
97     public static final int STATUS_BLOCKED_NOT_IN_CONTACTS = 5;
98 
99     /**
100      * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
101      * because it is from a number not available.
102      * @hide
103      */
104     public static final int STATUS_BLOCKED_UNAVAILABLE = 6;
105 
BlockCheckerFilter(Context context, Call call, CallerInfoLookupHelper callerInfoLookupHelper, BlockCheckerAdapter blockCheckerAdapter)106     public BlockCheckerFilter(Context context, Call call,
107             CallerInfoLookupHelper callerInfoLookupHelper,
108             BlockCheckerAdapter blockCheckerAdapter) {
109         mCall = call;
110         mContext = context;
111         mCallerInfoLookupHelper = callerInfoLookupHelper;
112         mBlockCheckerAdapter = blockCheckerAdapter;
113         mContactExists = false;
114         mHandlerThread = new HandlerThread(TAG);
115         mHandlerThread.start();
116         mHandler = new Handler(mHandlerThread.getLooper());
117     }
118 
119     @Override
startFilterLookup(CallFilteringResult result)120     public CompletionStage<CallFilteringResult> startFilterLookup(CallFilteringResult result) {
121         Log.addEvent(mCall, LogUtils.Events.BLOCK_CHECK_INITIATED);
122         CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
123         Bundle extras = new Bundle();
124         if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
125             int presentation = mCall.getHandlePresentation();
126             extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, presentation);
127             if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
128                 mCallerInfoLookupHelper.startLookup(mCall.getHandle(),
129                         new CallerInfoLookupHelper.OnQueryCompleteListener() {
130                             @Override
131                             public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
132                                 if (info != null && info.contactExists) {
133                                     mContactExists = true;
134                                 }
135                                 getBlockStatus(resultFuture);
136                             }
137 
138                             @Override
139                             public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
140                                 // Ignore
141                             }
142                         });
143             } else {
144                 getBlockStatus(resultFuture);
145             }
146         } else {
147             getBlockStatus(resultFuture);
148         }
149         return resultFuture;
150     }
151 
getBlockStatus( CompletableFuture<CallFilteringResult> resultFuture)152     private void getBlockStatus(
153             CompletableFuture<CallFilteringResult> resultFuture) {
154         // Set presentation and if contact exists. Used in determining if the system should block
155         // the passed in number. Use default values as they would be returned if the keys didn't
156         // exist in the extras to maintain existing behavior.
157         int presentation;
158         boolean isNumberInContacts;
159         if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
160             presentation = mCall.getHandlePresentation();
161         } else {
162             presentation = 0;
163         }
164 
165         if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
166             isNumberInContacts = mContactExists;
167         } else {
168             isNumberInContacts = false;
169         }
170 
171         // Set number
172         final String number = mCall.getHandle() == null ? null :
173                 mCall.getHandle().getSchemeSpecificPart();
174 
175         CompletableFuture.supplyAsync(
176                 () -> mBlockCheckerAdapter.getBlockStatus(mContext, number,
177                         presentation, isNumberInContacts),
178                 new LoggedHandlerExecutor(mHandler, "BCF.gBS", null))
179                 .thenApplyAsync((x) -> completeResult(resultFuture, x),
180                         new LoggedHandlerExecutor(mHandler, "BCF.gBS", null));
181     }
182 
completeResult(CompletableFuture<CallFilteringResult> resultFuture, int blockStatus)183     private int completeResult(CompletableFuture<CallFilteringResult> resultFuture,
184             int blockStatus) {
185         CallFilteringResult result;
186         if (blockStatus != STATUS_NOT_BLOCKED) {
187             result = new CallFilteringResult.Builder()
188                     .setShouldAllowCall(false)
189                     .setShouldReject(true)
190                     .setShouldAddToCallLog(true)
191                     .setShouldShowNotification(false)
192                     .setShouldSilence(true)
193                     .setCallBlockReason(getBlockReason(blockStatus))
194                     .setCallScreeningAppName(null)
195                     .setCallScreeningComponentName(null)
196                     .setContactExists(mContactExists)
197                     .build();
198         } else {
199             result = new CallFilteringResult.Builder()
200                     .setShouldAllowCall(true)
201                     .setShouldReject(false)
202                     .setShouldSilence(false)
203                     .setShouldAddToCallLog(true)
204                     .setShouldShowNotification(true)
205                     .setContactExists(mContactExists)
206                     .build();
207         }
208         Log.addEvent(mCall, LogUtils.Events.BLOCK_CHECK_FINISHED,
209                 blockStatusToString(blockStatus) + " " + result);
210         resultFuture.complete(result);
211         mHandlerThread.quitSafely();
212         return blockStatus;
213     }
214 
getBlockReason(int blockStatus)215     private int getBlockReason(int blockStatus) {
216         switch (blockStatus) {
217             case STATUS_BLOCKED_IN_LIST:
218                 return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
219 
220             case STATUS_BLOCKED_UNKNOWN_NUMBER:
221             case STATUS_BLOCKED_UNAVAILABLE:
222                 return CallLog.Calls.BLOCK_REASON_UNKNOWN_NUMBER;
223 
224             case STATUS_BLOCKED_RESTRICTED:
225                 return CallLog.Calls.BLOCK_REASON_RESTRICTED_NUMBER;
226 
227             case STATUS_BLOCKED_PAYPHONE:
228                 return CallLog.Calls.BLOCK_REASON_PAY_PHONE;
229 
230             case STATUS_BLOCKED_NOT_IN_CONTACTS:
231                 return CallLog.Calls.BLOCK_REASON_NOT_IN_CONTACTS;
232 
233             default:
234                 Log.w(this,
235                         "There's no call log block reason can be converted");
236                 return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
237         }
238     }
239 
240     /**
241      * Converts a block status constant to a string equivalent for logging.
242      */
blockStatusToString(int blockStatus)243     private String blockStatusToString(int blockStatus) {
244         switch (blockStatus) {
245             case STATUS_NOT_BLOCKED:
246                 return "not blocked";
247             case STATUS_BLOCKED_IN_LIST:
248                 return "blocked - in list";
249             case STATUS_BLOCKED_RESTRICTED:
250                 return "blocked - restricted";
251             case STATUS_BLOCKED_UNKNOWN_NUMBER:
252                 return "blocked - unknown";
253             case STATUS_BLOCKED_PAYPHONE:
254                 return "blocked - payphone";
255             case STATUS_BLOCKED_NOT_IN_CONTACTS:
256                 return "blocked - not in contacts";
257             case STATUS_BLOCKED_UNAVAILABLE:
258                 return "blocked - unavailable";
259         }
260         return "unknown";
261     }
262 }
263