Home | History | Annotate | Download | only in blockednumber
      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