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