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