1 /* 2 * Copyright (C) 2016 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 package com.android.providers.blockednumber; 17 18 import static com.android.providers.blockednumber.Utils.piiHandle; 19 20 import android.Manifest; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.AppOpsManager; 24 import android.app.backup.BackupManager; 25 import android.content.ContentProvider; 26 import android.content.ContentUris; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.SharedPreferences; 31 import android.content.UriMatcher; 32 import android.content.pm.PackageManager; 33 import android.database.Cursor; 34 import android.database.sqlite.SQLiteDatabase; 35 import android.database.sqlite.SQLiteQueryBuilder; 36 import android.net.Uri; 37 import android.os.Binder; 38 import android.os.Bundle; 39 import android.os.CancellationSignal; 40 import android.os.PersistableBundle; 41 import android.os.Process; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.provider.BlockedNumberContract; 45 import android.provider.BlockedNumberContract.SystemContract; 46 import android.telecom.TelecomManager; 47 import android.telephony.CarrierConfigManager; 48 import android.telephony.PhoneNumberUtils; 49 import android.telephony.TelephonyManager; 50 import android.text.TextUtils; 51 import android.util.Log; 52 53 import com.android.common.content.ProjectionMap; 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.providers.blockednumber.BlockedNumberDatabaseHelper.Tables; 56 57 import java.util.Arrays; 58 59 /** 60 * Blocked phone number provider. 61 * 62 * <p>Note the provider allows emergency numbers. The caller (telecom) should never call it with 63 * emergency numbers. 64 */ 65 public class BlockedNumberProvider extends ContentProvider { 66 static final String TAG = "BlockedNumbers"; 67 68 private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE. 69 70 private static final int BLOCKED_LIST = 1000; 71 private static final int BLOCKED_ID = 1001; 72 73 private static final UriMatcher sUriMatcher; 74 75 private static final String PREF_FILE = "block_number_provider_prefs"; 76 private static final String BLOCK_SUPPRESSION_EXPIRY_TIME_PREF = 77 "block_suppression_expiry_time_pref"; 78 private static final int MAX_BLOCKING_DISABLED_DURATION_SECONDS = 7 * 24 * 3600; // 1 week 79 private static final long BLOCKING_DISABLED_FOREVER = -1; 80 // Normally, we allow calls from self, *except* in unit tests, where we clear this flag 81 // to emulate calls from other apps. 82 @VisibleForTesting 83 static boolean ALLOW_SELF_CALL = true; 84 85 static { 86 sUriMatcher = new UriMatcher(0); sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked", BLOCKED_LIST)87 sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked", BLOCKED_LIST); sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked/#", BLOCKED_ID)88 sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked/#", BLOCKED_ID); 89 } 90 91 private static final ProjectionMap sBlockedNumberColumns = ProjectionMap.builder() 92 .add(BlockedNumberContract.BlockedNumbers.COLUMN_ID) 93 .add(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER) 94 .add(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER) 95 .build(); 96 97 private static final String ID_SELECTION = 98 BlockedNumberContract.BlockedNumbers.COLUMN_ID + "=?"; 99 100 private static final String ORIGINAL_NUMBER_SELECTION = 101 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?"; 102 103 private static final String E164_NUMBER_SELECTION = 104 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?"; 105 106 @VisibleForTesting 107 protected BlockedNumberDatabaseHelper mDbHelper; 108 @VisibleForTesting 109 protected BackupManager mBackupManager; 110 111 @Override onCreate()112 public boolean onCreate() { 113 mDbHelper = BlockedNumberDatabaseHelper.getInstance(getContext()); 114 mBackupManager = new BackupManager(getContext()); 115 return true; 116 } 117 118 @Override getType(@onNull Uri uri)119 public String getType(@NonNull Uri uri) { 120 final int match = sUriMatcher.match(uri); 121 switch (match) { 122 case BLOCKED_LIST: 123 return BlockedNumberContract.BlockedNumbers.CONTENT_TYPE; 124 case BLOCKED_ID: 125 return BlockedNumberContract.BlockedNumbers.CONTENT_ITEM_TYPE; 126 default: 127 throw new IllegalArgumentException("Unsupported URI: " + uri); 128 } 129 } 130 131 @Override insert(@onNull Uri uri, @Nullable ContentValues values)132 public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 133 enforceWritePermissionAndPrimaryUser(); 134 135 final int match = sUriMatcher.match(uri); 136 switch (match) { 137 case BLOCKED_LIST: 138 Uri blockedUri = insertBlockedNumber(values); 139 getContext().getContentResolver().notifyChange(blockedUri, null); 140 mBackupManager.dataChanged(); 141 return blockedUri; 142 default: 143 throw new IllegalArgumentException("Unsupported URI: " + uri); 144 } 145 } 146 147 /** 148 * Implements the "blocked/" insert. 149 */ insertBlockedNumber(ContentValues cv)150 private Uri insertBlockedNumber(ContentValues cv) { 151 throwIfSpecified(cv, BlockedNumberContract.BlockedNumbers.COLUMN_ID); 152 153 final String phoneNumber = cv.getAsString( 154 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER); 155 156 if (TextUtils.isEmpty(phoneNumber)) { 157 throw new IllegalArgumentException("Missing a required column " + 158 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER); 159 } 160 161 // Fill in with autogenerated columns. 162 final String e164Number = Utils.getE164Number(getContext(), phoneNumber, 163 cv.getAsString(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER)); 164 cv.put(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER, e164Number); 165 166 if (DEBUG) { 167 Log.d(TAG, String.format("inserted blocked number: %s", cv)); 168 } 169 170 // Then insert. 171 final long id = mDbHelper.getWritableDatabase().insertWithOnConflict( 172 BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS, null, cv, 173 SQLiteDatabase.CONFLICT_REPLACE); 174 175 return ContentUris.withAppendedId(BlockedNumberContract.BlockedNumbers.CONTENT_URI, id); 176 } 177 throwIfSpecified(ContentValues cv, String column)178 private static void throwIfSpecified(ContentValues cv, String column) { 179 if (cv.containsKey(column)) { 180 throw new IllegalArgumentException("Column " + column + " must not be specified"); 181 } 182 } 183 184 @Override update(@onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)185 public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, 186 @Nullable String[] selectionArgs) { 187 enforceWritePermissionAndPrimaryUser(); 188 189 throw new UnsupportedOperationException( 190 "Update is not supported. Use delete + insert instead"); 191 } 192 193 @Override delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)194 public int delete(@NonNull Uri uri, @Nullable String selection, 195 @Nullable String[] selectionArgs) { 196 enforceWritePermissionAndPrimaryUser(); 197 198 final int match = sUriMatcher.match(uri); 199 int numRows; 200 switch (match) { 201 case BLOCKED_LIST: 202 numRows = deleteBlockedNumber(selection, selectionArgs); 203 break; 204 case BLOCKED_ID: 205 numRows = deleteBlockedNumberWithId(ContentUris.parseId(uri), selection); 206 break; 207 default: 208 throw new IllegalArgumentException("Unsupported URI: " + uri); 209 } 210 getContext().getContentResolver().notifyChange(uri, null); 211 mBackupManager.dataChanged(); 212 return numRows; 213 } 214 215 /** 216 * Implements the "blocked/#" delete. 217 */ deleteBlockedNumberWithId(long id, String selection)218 private int deleteBlockedNumberWithId(long id, String selection) { 219 throwForNonEmptySelection(selection); 220 221 return deleteBlockedNumber(ID_SELECTION, new String[]{Long.toString(id)}); 222 } 223 224 /** 225 * Implements the "blocked/" delete. 226 */ deleteBlockedNumber(String selection, String[] selectionArgs)227 private int deleteBlockedNumber(String selection, String[] selectionArgs) { 228 final SQLiteDatabase db = mDbHelper.getWritableDatabase(); 229 230 // When selection is specified, compile it within (...) to detect SQL injection. 231 if (!TextUtils.isEmpty(selection)) { 232 db.validateSql("select 1 FROM " + Tables.BLOCKED_NUMBERS + " WHERE " + 233 Utils.wrapSelectionWithParens(selection), 234 /* cancellationSignal =*/ null); 235 } 236 237 return db.delete( 238 BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS, 239 selection, selectionArgs); 240 } 241 242 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)243 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 244 @Nullable String[] selectionArgs, @Nullable String sortOrder) { 245 enforceReadPermissionAndPrimaryUser(); 246 247 return query(uri, projection, selection, selectionArgs, sortOrder, null); 248 } 249 250 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)251 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 252 @Nullable String[] selectionArgs, @Nullable String sortOrder, 253 @Nullable CancellationSignal cancellationSignal) { 254 enforceReadPermissionAndPrimaryUser(); 255 256 final int match = sUriMatcher.match(uri); 257 Cursor cursor; 258 switch (match) { 259 case BLOCKED_LIST: 260 cursor = queryBlockedList(projection, selection, selectionArgs, sortOrder, 261 cancellationSignal); 262 break; 263 case BLOCKED_ID: 264 cursor = queryBlockedListWithId(ContentUris.parseId(uri), projection, selection, 265 cancellationSignal); 266 break; 267 default: 268 throw new IllegalArgumentException("Unsupported URI: " + uri); 269 } 270 // Tell the cursor what uri to watch, so it knows when its source data changes 271 cursor.setNotificationUri(getContext().getContentResolver(), uri); 272 return cursor; 273 } 274 275 /** 276 * Implements the "blocked/#" query. 277 */ queryBlockedListWithId(long id, String[] projection, String selection, CancellationSignal cancellationSignal)278 private Cursor queryBlockedListWithId(long id, String[] projection, String selection, 279 CancellationSignal cancellationSignal) { 280 throwForNonEmptySelection(selection); 281 282 return queryBlockedList(projection, ID_SELECTION, new String[]{Long.toString(id)}, 283 null, cancellationSignal); 284 } 285 286 /** 287 * Implements the "blocked/" query. 288 */ queryBlockedList(String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)289 private Cursor queryBlockedList(String[] projection, String selection, String[] selectionArgs, 290 String sortOrder, CancellationSignal cancellationSignal) { 291 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 292 qb.setStrict(true); 293 qb.setTables(BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS); 294 qb.setProjectionMap(sBlockedNumberColumns); 295 296 return qb.query(mDbHelper.getReadableDatabase(), projection, selection, selectionArgs, 297 /* groupBy =*/ null, /* having =*/null, sortOrder, 298 /* limit =*/ null, cancellationSignal); 299 } 300 throwForNonEmptySelection(String selection)301 private void throwForNonEmptySelection(String selection) { 302 if (!TextUtils.isEmpty(selection)) { 303 throw new IllegalArgumentException( 304 "When ID is specified in URI, selection must be null"); 305 } 306 } 307 308 @Override call(@onNull String method, @Nullable String arg, @Nullable Bundle extras)309 public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { 310 final Bundle res = new Bundle(); 311 switch (method) { 312 case BlockedNumberContract.METHOD_IS_BLOCKED: 313 enforceReadPermissionAndPrimaryUser(); 314 boolean isBlocked = isBlocked(arg); 315 res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked); 316 res.putInt(BlockedNumberContract.RES_BLOCK_STATUS, 317 isBlocked ? BlockedNumberContract.STATUS_BLOCKED_IN_LIST 318 : BlockedNumberContract.STATUS_NOT_BLOCKED); 319 break; 320 case BlockedNumberContract.METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS: 321 // No permission checks: any app should be able to access this API. 322 res.putBoolean( 323 BlockedNumberContract.RES_CAN_BLOCK_NUMBERS, canCurrentUserBlockUsers()); 324 break; 325 case BlockedNumberContract.METHOD_UNBLOCK: 326 enforceWritePermissionAndPrimaryUser(); 327 328 res.putInt(BlockedNumberContract.RES_NUM_ROWS_DELETED, unblock(arg)); 329 break; 330 case SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT: 331 enforceSystemWritePermissionAndPrimaryUser(); 332 333 notifyEmergencyContact(); 334 break; 335 case SystemContract.METHOD_END_BLOCK_SUPPRESSION: 336 enforceSystemWritePermissionAndPrimaryUser(); 337 338 endBlockSuppression(); 339 break; 340 case SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS: 341 enforceSystemReadPermissionAndPrimaryUser(); 342 343 SystemContract.BlockSuppressionStatus status = getBlockSuppressionStatus(); 344 res.putBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, status.isSuppressed); 345 res.putLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 346 status.untilTimestampMillis); 347 break; 348 case SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER: 349 enforceSystemReadPermissionAndPrimaryUser(); 350 int blockReason = shouldSystemBlockNumber(arg, extras); 351 res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, 352 blockReason != BlockedNumberContract.STATUS_NOT_BLOCKED); 353 res.putInt(BlockedNumberContract.RES_BLOCK_STATUS, blockReason); 354 break; 355 case SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION: 356 enforceSystemReadPermissionAndPrimaryUser(); 357 res.putBoolean(BlockedNumberContract.RES_SHOW_EMERGENCY_CALL_NOTIFICATION, 358 shouldShowEmergencyCallNotification()); 359 break; 360 case SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING: 361 enforceSystemReadPermissionAndPrimaryUser(); 362 if (extras != null) { 363 String key = extras.getString(BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY); 364 boolean value = getEnhancedBlockSetting(key); 365 res.putBoolean(BlockedNumberContract.RES_ENHANCED_SETTING_IS_ENABLED, value); 366 } 367 break; 368 case SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING: 369 enforceSystemWritePermissionAndPrimaryUser(); 370 if (extras != null) { 371 String key = extras.getString(BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY); 372 boolean value = extras.getBoolean( 373 BlockedNumberContract.EXTRA_ENHANCED_SETTING_VALUE, false); 374 setEnhancedBlockSetting(key, value); 375 } 376 break; 377 default: 378 enforceReadPermissionAndPrimaryUser(); 379 380 throw new IllegalArgumentException("Unsupported method " + method); 381 } 382 return res; 383 } 384 unblock(String phoneNumber)385 private int unblock(String phoneNumber) { 386 if (TextUtils.isEmpty(phoneNumber)) { 387 return 0; 388 } 389 390 StringBuilder selectionBuilder = new StringBuilder(ORIGINAL_NUMBER_SELECTION); 391 String[] selectionArgs = new String[]{phoneNumber}; 392 final String e164Number = Utils.getE164Number(getContext(), phoneNumber, null); 393 if (!TextUtils.isEmpty(e164Number)) { 394 selectionBuilder.append(" or " + E164_NUMBER_SELECTION); 395 selectionArgs = new String[]{phoneNumber, e164Number}; 396 } 397 String selection = selectionBuilder.toString(); 398 if (DEBUG) { 399 Log.d(TAG, String.format("Unblocking numbers using selection: %s, args: %s", 400 selection, Arrays.toString(selectionArgs))); 401 } 402 return deleteBlockedNumber(selection, selectionArgs); 403 } 404 isEmergencyNumber(String phoneNumber)405 private boolean isEmergencyNumber(String phoneNumber) { 406 if (TextUtils.isEmpty(phoneNumber)) { 407 return false; 408 } 409 410 final String e164Number = Utils.getE164Number(getContext(), phoneNumber, null); 411 return PhoneNumberUtils.isEmergencyNumber(phoneNumber) 412 || PhoneNumberUtils.isEmergencyNumber(e164Number); 413 } 414 isBlocked(String phoneNumber)415 private boolean isBlocked(String phoneNumber) { 416 if (TextUtils.isEmpty(phoneNumber)) { 417 Log.i(TAG, "isBlocked: NOT BLOCKED; empty #"); 418 return false; 419 } 420 421 final String inE164 = Utils.getE164Number(getContext(), phoneNumber, null); // may be empty. 422 423 final Cursor c = mDbHelper.getReadableDatabase().rawQuery( 424 "SELECT " + 425 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "," + 426 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + 427 " FROM " + BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS + 428 " WHERE " + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?1" + 429 " OR (?2 != '' AND " + 430 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?2)", 431 new String[] {phoneNumber, inE164} 432 ); 433 try { 434 while (c.moveToNext()) { 435 final String original = c.getString(0); 436 final String e164 = c.getString(1); 437 Log.i(TAG, String.format("isBlocked: BLOCKED; number=%s, e164=%s, foundOrig=%s, " 438 + "foundE164=%s", 439 piiHandle(phoneNumber), 440 piiHandle(inE164), 441 piiHandle(original), 442 piiHandle(e164))); 443 return true; 444 } 445 } finally { 446 c.close(); 447 } 448 // No match found. 449 Log.i(TAG, String.format("isBlocked: NOT BLOCKED; number=%s, e164=%s", 450 piiHandle(phoneNumber), piiHandle(inE164))); 451 return false; 452 } 453 canCurrentUserBlockUsers()454 private boolean canCurrentUserBlockUsers() { 455 return getContext().getUserId() == UserHandle.USER_SYSTEM; 456 } 457 notifyEmergencyContact()458 private void notifyEmergencyContact() { 459 long sec = getBlockSuppressSecondsFromCarrierConfig(); 460 long millisToWrite = sec < 0 461 ? BLOCKING_DISABLED_FOREVER : System.currentTimeMillis() + (sec * 1000); 462 writeBlockSuppressionExpiryTimePref(millisToWrite); 463 writeEmergencyCallNotificationPref(true); 464 notifyBlockSuppressionStateChange(); 465 } 466 467 private void endBlockSuppression() { 468 // Nothing to do if blocks are not being suppressed. 469 if (getBlockSuppressionStatus().isSuppressed) { 470 writeBlockSuppressionExpiryTimePref(0); 471 writeEmergencyCallNotificationPref(false); 472 notifyBlockSuppressionStateChange(); 473 } 474 } 475 476 private SystemContract.BlockSuppressionStatus getBlockSuppressionStatus() { 477 SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 478 long blockSuppressionExpiryTimeMillis = pref.getLong(BLOCK_SUPPRESSION_EXPIRY_TIME_PREF, 0); 479 boolean isSuppressed = blockSuppressionExpiryTimeMillis == BLOCKING_DISABLED_FOREVER 480 || System.currentTimeMillis() < blockSuppressionExpiryTimeMillis; 481 return new SystemContract.BlockSuppressionStatus(isSuppressed, 482 blockSuppressionExpiryTimeMillis); 483 } 484 485 private int shouldSystemBlockNumber(String phoneNumber, Bundle extras) { 486 if (getBlockSuppressionStatus().isSuppressed) { 487 return BlockedNumberContract.STATUS_NOT_BLOCKED; 488 } 489 if (isEmergencyNumber(phoneNumber)) { 490 return BlockedNumberContract.STATUS_NOT_BLOCKED; 491 } 492 493 boolean isBlocked = false; 494 int blockReason = BlockedNumberContract.STATUS_NOT_BLOCKED; 495 if (extras != null && !extras.isEmpty()) { 496 // check enhanced blocking setting 497 boolean contactExist = extras.getBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST); 498 int presentation = extras.getInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION); 499 switch (presentation) { 500 case TelecomManager.PRESENTATION_ALLOWED: 501 if (getEnhancedBlockSetting( 502 SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED) 503 && !contactExist) { 504 blockReason = BlockedNumberContract.STATUS_BLOCKED_NOT_IN_CONTACTS; 505 } 506 break; 507 case TelecomManager.PRESENTATION_RESTRICTED: 508 if (getEnhancedBlockSetting( 509 SystemContract.ENHANCED_SETTING_KEY_BLOCK_PRIVATE)) { 510 blockReason = BlockedNumberContract.STATUS_BLOCKED_RESTRICTED; 511 } 512 break; 513 case TelecomManager.PRESENTATION_PAYPHONE: 514 if (getEnhancedBlockSetting( 515 SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE)) { 516 blockReason = BlockedNumberContract.STATUS_BLOCKED_PAYPHONE; 517 } 518 break; 519 case TelecomManager.PRESENTATION_UNKNOWN: 520 if (getEnhancedBlockSetting( 521 SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN)) { 522 blockReason = BlockedNumberContract.STATUS_BLOCKED_UNKNOWN_NUMBER; 523 } 524 break; 525 default: 526 break; 527 } 528 } 529 if (blockReason == BlockedNumberContract.STATUS_NOT_BLOCKED && isBlocked(phoneNumber)) { 530 blockReason = BlockedNumberContract.STATUS_BLOCKED_IN_LIST; 531 } 532 return blockReason; 533 } 534 535 private boolean shouldShowEmergencyCallNotification() { 536 return isEnhancedCallBlockingEnabledByPlatform() 537 && (isShowCallBlockingDisabledNotificationAlways() 538 || isAnyEnhancedBlockingSettingEnabled()) 539 && getBlockSuppressionStatus().isSuppressed 540 && getEnhancedBlockSetting( 541 SystemContract.ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION); 542 } 543 544 private PersistableBundle getCarrierConfig() { 545 CarrierConfigManager configManager = (CarrierConfigManager) getContext().getSystemService( 546 Context.CARRIER_CONFIG_SERVICE); 547 PersistableBundle carrierConfig = configManager.getConfig(); 548 if (carrierConfig == null) { 549 carrierConfig = configManager.getDefaultConfig(); 550 } 551 return carrierConfig; 552 } 553 554 private boolean isEnhancedCallBlockingEnabledByPlatform() { 555 return getCarrierConfig().getBoolean( 556 CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL); 557 } 558 559 private boolean isShowCallBlockingDisabledNotificationAlways() { 560 return getCarrierConfig().getBoolean( 561 CarrierConfigManager.KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL); 562 } 563 564 private boolean isAnyEnhancedBlockingSettingEnabled() { 565 return getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED) 566 || getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PRIVATE) 567 || getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE) 568 || getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN); 569 } 570 571 private boolean getEnhancedBlockSetting(String key) { 572 SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 573 return pref.getBoolean(key, false); 574 } 575 576 private void setEnhancedBlockSetting(String key, boolean value) { 577 SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 578 SharedPreferences.Editor editor = pref.edit(); 579 editor.putBoolean(key, value); 580 editor.apply(); 581 } 582 583 private void writeEmergencyCallNotificationPref(boolean show) { 584 if (!isEnhancedCallBlockingEnabledByPlatform()) { 585 return; 586 } 587 setEnhancedBlockSetting( 588 SystemContract.ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION, show); 589 } 590 591 private void writeBlockSuppressionExpiryTimePref(long expiryTimeMillis) { 592 SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 593 SharedPreferences.Editor editor = pref.edit(); 594 editor.putLong(BLOCK_SUPPRESSION_EXPIRY_TIME_PREF, expiryTimeMillis); 595 editor.apply(); 596 } 597 598 private long getBlockSuppressSecondsFromCarrierConfig() { 599 CarrierConfigManager carrierConfigManager = 600 getContext().getSystemService(CarrierConfigManager.class); 601 int carrierConfigValue = carrierConfigManager.getConfig().getInt 602 (CarrierConfigManager.KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT); 603 boolean isValidValue = carrierConfigValue <= MAX_BLOCKING_DISABLED_DURATION_SECONDS; 604 return isValidValue ? carrierConfigValue : CarrierConfigManager.getDefaultConfig().getInt( 605 CarrierConfigManager.KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT); 606 } 607 608 /** 609 * Returns {@code false} when the caller is not root, the user selected dialer, the 610 * default SMS app or a carrier app. 611 */ 612 private boolean checkForPrivilegedApplications() { 613 if (Binder.getCallingUid() == Process.ROOT_UID) { 614 return true; 615 } 616 617 final String callingPackage = getCallingPackage(); 618 if (TextUtils.isEmpty(callingPackage)) { 619 Log.w(TAG, "callingPackage not accessible"); 620 } else { 621 final TelecomManager telecom = getContext().getSystemService(TelecomManager.class); 622 623 if (callingPackage.equals(telecom.getDefaultDialerPackage()) 624 || callingPackage.equals(telecom.getSystemDialerPackage())) { 625 return true; 626 } 627 final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class); 628 if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS, 629 Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED) { 630 return true; 631 } 632 633 final TelephonyManager telephonyManager = 634 getContext().getSystemService(TelephonyManager.class); 635 return telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) == 636 TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; 637 } 638 return false; 639 } 640 641 private void notifyBlockSuppressionStateChange() { 642 Intent intent = new Intent(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED); 643 getContext().sendBroadcast(intent, Manifest.permission.READ_BLOCKED_NUMBERS); 644 } 645 646 private void enforceReadPermission() { 647 checkForPermission(android.Manifest.permission.READ_BLOCKED_NUMBERS); 648 } 649 650 private void enforceReadPermissionAndPrimaryUser() { 651 checkForPermissionAndPrimaryUser(android.Manifest.permission.READ_BLOCKED_NUMBERS); 652 } 653 654 private void enforceWritePermissionAndPrimaryUser() { 655 checkForPermissionAndPrimaryUser(android.Manifest.permission.WRITE_BLOCKED_NUMBERS); 656 } 657 658 private void checkForPermissionAndPrimaryUser(String permission) { 659 checkForPermission(permission); 660 if (!canCurrentUserBlockUsers()) { 661 throwCurrentUserNotPermittedSecurityException(); 662 } 663 } 664 665 private void checkForPermission(String permission) { 666 boolean permitted = passesSystemPermissionCheck(permission) 667 || checkForPrivilegedApplications() || isSelf(); 668 if (!permitted) { 669 throwSecurityException(); 670 } 671 } 672 673 private void enforceSystemReadPermissionAndPrimaryUser() { 674 enforceSystemPermissionAndUser(android.Manifest.permission.READ_BLOCKED_NUMBERS); 675 } 676 677 private void enforceSystemWritePermissionAndPrimaryUser() { 678 enforceSystemPermissionAndUser(android.Manifest.permission.WRITE_BLOCKED_NUMBERS); 679 } 680 681 private void enforceSystemPermissionAndUser(String permission) { 682 if (!canCurrentUserBlockUsers()) { 683 throwCurrentUserNotPermittedSecurityException(); 684 } 685 686 if (!passesSystemPermissionCheck(permission)) { 687 throwSecurityException(); 688 } 689 } 690 691 private boolean passesSystemPermissionCheck(String permission) { 692 return getContext().checkCallingPermission(permission) 693 == PackageManager.PERMISSION_GRANTED; 694 } 695 696 private boolean isSelf() { 697 return ALLOW_SELF_CALL && Binder.getCallingPid() == Process.myPid(); 698 } 699 700 private void throwSecurityException() { 701 throw new SecurityException("Caller must be system, default dialer or default SMS app"); 702 } 703 704 private void throwCurrentUserNotPermittedSecurityException() { 705 throw new SecurityException("The current user cannot perform this operation"); 706 } 707 } 708