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