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