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.UserManager; 43 import android.provider.BlockedNumberContract; 44 import android.provider.BlockedNumberContract.SystemContract; 45 import android.telecom.TelecomManager; 46 import android.telephony.CarrierConfigManager; 47 import android.telephony.PhoneNumberUtils; 48 import android.telephony.TelephonyManager; 49 import android.text.TextUtils; 50 import android.util.Log; 51 52 import com.android.common.content.ProjectionMap; 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.providers.blockednumber.BlockedNumberDatabaseHelper.Tables; 55 56 import java.util.Arrays; 57 58 /** 59 * Blocked phone number provider. 60 * 61 * <p>Note the provider allows emergency numbers. The caller (telecom) should never call it with 62 * emergency numbers. 63 */ 64 public class BlockedNumberProvider extends ContentProvider { 65 static final String TAG = "BlockedNumbers"; 66 67 private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE. 68 69 private static final int BLOCKED_LIST = 1000; 70 private static final int BLOCKED_ID = 1001; 71 72 private static final UriMatcher sUriMatcher; 73 74 private static final String PREF_FILE = "block_number_provider_prefs"; 75 private static final String BLOCK_SUPPRESSION_EXPIRY_TIME_PREF = 76 "block_suppression_expiry_time_pref"; 77 private static final int MAX_BLOCKING_DISABLED_DURATION_SECONDS = 7 * 24 * 3600; // 1 week 78 private static final long BLOCKING_DISABLED_FOREVER = -1; 79 // Normally, we allow calls from self, *except* in unit tests, where we clear this flag 80 // to emulate calls from other apps. 81 @VisibleForTesting 82 static boolean ALLOW_SELF_CALL = true; 83 84 static { 85 sUriMatcher = new UriMatcher(0); sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked", BLOCKED_LIST)86 sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked", BLOCKED_LIST); sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked/#", BLOCKED_ID)87 sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked/#", BLOCKED_ID); 88 } 89 90 private static final ProjectionMap sBlockedNumberColumns = ProjectionMap.builder() 91 .add(BlockedNumberContract.BlockedNumbers.COLUMN_ID) 92 .add(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER) 93 .add(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER) 94 .build(); 95 96 private static final String ID_SELECTION = 97 BlockedNumberContract.BlockedNumbers.COLUMN_ID + "=?"; 98 99 private static final String ORIGINAL_NUMBER_SELECTION = 100 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?"; 101 102 private static final String E164_NUMBER_SELECTION = 103 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?"; 104 105 @VisibleForTesting 106 protected BlockedNumberDatabaseHelper mDbHelper; 107 @VisibleForTesting 108 protected BackupManager mBackupManager; 109 110 @Override onCreate()111 public boolean onCreate() { 112 mDbHelper = BlockedNumberDatabaseHelper.getInstance(getContext()); 113 mBackupManager = new BackupManager(getContext()); 114 return true; 115 } 116 117 @Override getType(@onNull Uri uri)118 public String getType(@NonNull Uri uri) { 119 final int match = sUriMatcher.match(uri); 120 switch (match) { 121 case BLOCKED_LIST: 122 return BlockedNumberContract.BlockedNumbers.CONTENT_TYPE; 123 case BLOCKED_ID: 124 return BlockedNumberContract.BlockedNumbers.CONTENT_ITEM_TYPE; 125 default: 126 throw new IllegalArgumentException("Unsupported URI: " + uri); 127 } 128 } 129 130 @Override insert(@onNull Uri uri, @Nullable ContentValues values)131 public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 132 enforceWritePermissionAndPrimaryUser(); 133 134 final int match = sUriMatcher.match(uri); 135 switch (match) { 136 case BLOCKED_LIST: 137 Uri blockedUri = insertBlockedNumber(values); 138 getContext().getContentResolver().notifyChange(blockedUri, null); 139 mBackupManager.dataChanged(); 140 return blockedUri; 141 default: 142 throw new IllegalArgumentException("Unsupported URI: " + uri); 143 } 144 } 145 146 /** 147 * Implements the "blocked/" insert. 148 */ insertBlockedNumber(ContentValues cv)149 private Uri insertBlockedNumber(ContentValues cv) { 150 throwIfSpecified(cv, BlockedNumberContract.BlockedNumbers.COLUMN_ID); 151 152 final String phoneNumber = cv.getAsString( 153 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER); 154 155 if (TextUtils.isEmpty(phoneNumber)) { 156 throw new IllegalArgumentException("Missing a required column " + 157 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER); 158 } 159 160 // Fill in with autogenerated columns. 161 final String e164Number = Utils.getE164Number(getContext(), phoneNumber, 162 cv.getAsString(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER)); 163 cv.put(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER, e164Number); 164 165 if (DEBUG) { 166 Log.d(TAG, String.format("inserted blocked number: %s", cv)); 167 } 168 169 // Then insert. 170 final long id = mDbHelper.getWritableDatabase().insertWithOnConflict( 171 BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS, null, cv, 172 SQLiteDatabase.CONFLICT_REPLACE); 173 174 return ContentUris.withAppendedId(BlockedNumberContract.BlockedNumbers.CONTENT_URI, id); 175 } 176 throwIfSpecified(ContentValues cv, String column)177 private static void throwIfSpecified(ContentValues cv, String column) { 178 if (cv.containsKey(column)) { 179 throw new IllegalArgumentException("Column " + column + " must not be specified"); 180 } 181 } 182 183 @Override update(@onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)184 public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, 185 @Nullable String[] selectionArgs) { 186 enforceWritePermissionAndPrimaryUser(); 187 188 throw new UnsupportedOperationException( 189 "Update is not supported. Use delete + insert instead"); 190 } 191 192 @Override delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)193 public int delete(@NonNull Uri uri, @Nullable String selection, 194 @Nullable String[] selectionArgs) { 195 enforceWritePermissionAndPrimaryUser(); 196 197 final int match = sUriMatcher.match(uri); 198 int numRows; 199 switch (match) { 200 case BLOCKED_LIST: 201 numRows = deleteBlockedNumber(selection, selectionArgs); 202 break; 203 case BLOCKED_ID: 204 numRows = deleteBlockedNumberWithId(ContentUris.parseId(uri), selection); 205 break; 206 default: 207 throw new IllegalArgumentException("Unsupported URI: " + uri); 208 } 209 getContext().getContentResolver().notifyChange(uri, null); 210 mBackupManager.dataChanged(); 211 return numRows; 212 } 213 214 /** 215 * Implements the "blocked/#" delete. 216 */ deleteBlockedNumberWithId(long id, String selection)217 private int deleteBlockedNumberWithId(long id, String selection) { 218 throwForNonEmptySelection(selection); 219 220 return deleteBlockedNumber(ID_SELECTION, new String[]{Long.toString(id)}); 221 } 222 223 /** 224 * Implements the "blocked/" delete. 225 */ deleteBlockedNumber(String selection, String[] selectionArgs)226 private int deleteBlockedNumber(String selection, String[] selectionArgs) { 227 final SQLiteDatabase db = mDbHelper.getWritableDatabase(); 228 229 // When selection is specified, compile it within (...) to detect SQL injection. 230 if (!TextUtils.isEmpty(selection)) { 231 db.validateSql("select 1 FROM " + Tables.BLOCKED_NUMBERS + " WHERE " + 232 Utils.wrapSelectionWithParens(selection), 233 /* cancellationSignal =*/ null); 234 } 235 236 return db.delete( 237 BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS, 238 selection, selectionArgs); 239 } 240 241 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)242 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 243 @Nullable String[] selectionArgs, @Nullable String sortOrder) { 244 enforceReadPermissionAndPrimaryUser(); 245 246 return query(uri, projection, selection, selectionArgs, sortOrder, null); 247 } 248 249 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)250 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, 251 @Nullable String[] selectionArgs, @Nullable String sortOrder, 252 @Nullable CancellationSignal cancellationSignal) { 253 enforceReadPermissionAndPrimaryUser(); 254 255 final int match = sUriMatcher.match(uri); 256 Cursor cursor; 257 switch (match) { 258 case BLOCKED_LIST: 259 cursor = queryBlockedList(projection, selection, selectionArgs, sortOrder, 260 cancellationSignal); 261 break; 262 case BLOCKED_ID: 263 cursor = queryBlockedListWithId(ContentUris.parseId(uri), projection, selection, 264 cancellationSignal); 265 break; 266 default: 267 throw new IllegalArgumentException("Unsupported URI: " + uri); 268 } 269 // Tell the cursor what uri to watch, so it knows when its source data changes 270 cursor.setNotificationUri(getContext().getContentResolver(), uri); 271 return cursor; 272 } 273 274 /** 275 * Implements the "blocked/#" query. 276 */ queryBlockedListWithId(long id, String[] projection, String selection, CancellationSignal cancellationSignal)277 private Cursor queryBlockedListWithId(long id, String[] projection, String selection, 278 CancellationSignal cancellationSignal) { 279 throwForNonEmptySelection(selection); 280 281 return queryBlockedList(projection, ID_SELECTION, new String[]{Long.toString(id)}, 282 null, cancellationSignal); 283 } 284 285 /** 286 * Implements the "blocked/" query. 287 */ queryBlockedList(String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)288 private Cursor queryBlockedList(String[] projection, String selection, String[] selectionArgs, 289 String sortOrder, CancellationSignal cancellationSignal) { 290 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 291 qb.setStrict(true); 292 qb.setTables(BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS); 293 qb.setProjectionMap(sBlockedNumberColumns); 294 295 return qb.query(mDbHelper.getReadableDatabase(), projection, selection, selectionArgs, 296 /* groupBy =*/ null, /* having =*/null, sortOrder, 297 /* limit =*/ null, cancellationSignal); 298 } 299 throwForNonEmptySelection(String selection)300 private void throwForNonEmptySelection(String selection) { 301 if (!TextUtils.isEmpty(selection)) { 302 throw new IllegalArgumentException( 303 "When ID is specified in URI, selection must be null"); 304 } 305 } 306 307 @Override call(@onNull String method, @Nullable String arg, @Nullable Bundle extras)308 public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { 309 final Bundle res = new Bundle(); 310 switch (method) { 311 case BlockedNumberContract.METHOD_IS_BLOCKED: 312 enforceReadPermissionAndPrimaryUser(); 313 boolean isBlocked = isBlocked(arg); 314 res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked); 315 res.putInt(BlockedNumberContract.RES_BLOCK_STATUS, 316 isBlocked ? BlockedNumberContract.STATUS_BLOCKED_IN_LIST 317 : BlockedNumberContract.STATUS_NOT_BLOCKED); 318 break; 319 case BlockedNumberContract.METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS: 320 // No permission checks: any app should be able to access this API. 321 res.putBoolean( 322 BlockedNumberContract.RES_CAN_BLOCK_NUMBERS, canCurrentUserBlockUsers()); 323 break; 324 case BlockedNumberContract.METHOD_UNBLOCK: 325 enforceWritePermissionAndPrimaryUser(); 326 327 res.putInt(BlockedNumberContract.RES_NUM_ROWS_DELETED, unblock(arg)); 328 break; 329 case SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT: 330 enforceSystemWritePermissionAndPrimaryUser(); 331 332 notifyEmergencyContact(); 333 break; 334 case SystemContract.METHOD_END_BLOCK_SUPPRESSION: 335 enforceSystemWritePermissionAndPrimaryUser(); 336 337 endBlockSuppression(); 338 break; 339 case SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS: 340 enforceSystemReadPermissionAndPrimaryUser(); 341 342 SystemContract.BlockSuppressionStatus status = getBlockSuppressionStatus(); 343 res.putBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, status.isSuppressed); 344 res.putLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 345 status.untilTimestampMillis); 346 break; 347 case SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER: 348 enforceSystemReadPermissionAndPrimaryUser(); 349 int blockReason = shouldSystemBlockNumber(arg, extras); 350 res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, 351 blockReason != BlockedNumberContract.STATUS_NOT_BLOCKED); 352 res.putInt(BlockedNumberContract.RES_BLOCK_STATUS, blockReason); 353 break; 354 case SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION: 355 enforceSystemReadPermissionAndPrimaryUser(); 356 res.putBoolean(BlockedNumberContract.RES_SHOW_EMERGENCY_CALL_NOTIFICATION, 357 shouldShowEmergencyCallNotification()); 358 break; 359 case SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING: 360 enforceSystemReadPermissionAndPrimaryUser(); 361 if (extras != null) { 362 String key = extras.getString(BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY); 363 boolean value = getEnhancedBlockSetting(key); 364 res.putBoolean(BlockedNumberContract.RES_ENHANCED_SETTING_IS_ENABLED, value); 365 } 366 break; 367 case SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING: 368 enforceSystemWritePermissionAndPrimaryUser(); 369 if (extras != null) { 370 String key = extras.getString(BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY); 371 boolean value = extras.getBoolean( 372 BlockedNumberContract.EXTRA_ENHANCED_SETTING_VALUE, false); 373 setEnhancedBlockSetting(key, value); 374 } 375 break; 376 default: 377 enforceReadPermissionAndPrimaryUser(); 378 379 throw new IllegalArgumentException("Unsupported method " + method); 380 } 381 return res; 382 } 383 unblock(String phoneNumber)384 private int unblock(String phoneNumber) { 385 if (TextUtils.isEmpty(phoneNumber)) { 386 return 0; 387 } 388 389 StringBuilder selectionBuilder = new StringBuilder(ORIGINAL_NUMBER_SELECTION); 390 String[] selectionArgs = new String[]{phoneNumber}; 391 final String e164Number = Utils.getE164Number(getContext(), phoneNumber, null); 392 if (!TextUtils.isEmpty(e164Number)) { 393 selectionBuilder.append(" or " + E164_NUMBER_SELECTION); 394 selectionArgs = new String[]{phoneNumber, e164Number}; 395 } 396 String selection = selectionBuilder.toString(); 397 if (DEBUG) { 398 Log.d(TAG, String.format("Unblocking numbers using selection: %s, args: %s", 399 selection, Arrays.toString(selectionArgs))); 400 } 401 return deleteBlockedNumber(selection, selectionArgs); 402 } 403 isEmergencyNumber(String phoneNumber)404 private boolean isEmergencyNumber(String phoneNumber) { 405 if (TextUtils.isEmpty(phoneNumber)) { 406 return false; 407 } 408 409 final String e164Number = Utils.getE164Number(getContext(), phoneNumber, null); 410 return PhoneNumberUtils.isEmergencyNumber(phoneNumber) 411 || PhoneNumberUtils.isEmergencyNumber(e164Number); 412 } 413 isBlocked(String phoneNumber)414 private boolean isBlocked(String phoneNumber) { 415 if (TextUtils.isEmpty(phoneNumber)) { 416 Log.i(TAG, "isBlocked: NOT BLOCKED; empty #"); 417 return false; 418 } 419 420 final String inE164 = Utils.getE164Number(getContext(), phoneNumber, null); // may be empty. 421 422 final Cursor c = mDbHelper.getReadableDatabase().rawQuery( 423 "SELECT " + 424 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "," + 425 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + 426 " FROM " + BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS + 427 " WHERE " + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?1" + 428 " OR (?2 != '' AND " + 429 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?2)", 430 new String[] {phoneNumber, inE164} 431 ); 432 try { 433 while (c.moveToNext()) { 434 final String original = c.getString(0); 435 final String e164 = c.getString(1); 436 Log.i(TAG, String.format("isBlocked: BLOCKED; number=%s, e164=%s, foundOrig=%s, " 437 + "foundE164=%s", 438 piiHandle(phoneNumber), 439 piiHandle(inE164), 440 piiHandle(original), 441 piiHandle(e164))); 442 return true; 443 } 444 } finally { 445 c.close(); 446 } 447 // No match found. 448 Log.i(TAG, String.format("isBlocked: NOT BLOCKED; number=%s, e164=%s", 449 piiHandle(phoneNumber), piiHandle(inE164))); 450 return false; 451 } 452 canCurrentUserBlockUsers()453 private boolean canCurrentUserBlockUsers() { 454 UserManager userManager = getContext().getSystemService(UserManager.class); 455 return userManager.isPrimaryUser(); 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