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