• 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         enforceWritePermissionAndPrimaryUser();
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         enforceWritePermissionAndPrimaryUser();
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         enforceWritePermissionAndPrimaryUser();
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         enforceReadPermissionAndPrimaryUser();
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         enforceReadPermissionAndPrimaryUser();
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                 enforceReadPermissionAndPrimaryUser();
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                 enforceWritePermissionAndPrimaryUser();
328 
329                 res.putInt(BlockedNumberContract.RES_NUM_ROWS_DELETED, unblock(arg));
330                 break;
331             case SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT:
332                 enforceSystemWritePermissionAndPrimaryUser();
333 
334                 notifyEmergencyContact();
335                 break;
336             case SystemContract.METHOD_END_BLOCK_SUPPRESSION:
337                 enforceSystemWritePermissionAndPrimaryUser();
338 
339                 endBlockSuppression();
340                 break;
341             case SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS:
342                 enforceSystemReadPermissionAndPrimaryUser();
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                 enforceSystemReadPermissionAndPrimaryUser();
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                 enforceSystemReadPermissionAndPrimaryUser();
358                 res.putBoolean(BlockedNumberContract.RES_SHOW_EMERGENCY_CALL_NOTIFICATION,
359                         shouldShowEmergencyCallNotification());
360                 break;
361             case SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING:
362                 enforceSystemReadPermissionAndPrimaryUser();
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                 enforceSystemWritePermissionAndPrimaryUser();
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                 enforceReadPermissionAndPrimaryUser();
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         return tm.isEmergencyNumber(phoneNumber) || tm.isEmergencyNumber(e164Number);
415     }
416 
isBlocked(String phoneNumber)417     private boolean isBlocked(String phoneNumber) {
418         if (TextUtils.isEmpty(phoneNumber)) {
419             Log.i(TAG, "isBlocked: NOT BLOCKED; empty #");
420             return false;
421         }
422 
423         final String inE164 = Utils.getE164Number(getContext(), phoneNumber, null); // may be empty.
424 
425         final Cursor c = mDbHelper.getReadableDatabase().rawQuery(
426                 "SELECT " +
427                 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "," +
428                 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER +
429                 " FROM " + BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS +
430                 " WHERE " + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?1" +
431                 " OR (?2 != '' AND " +
432                         BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?2)",
433                 new String[] {phoneNumber, inE164}
434                 );
435         try {
436             while (c.moveToNext()) {
437                 final String original = c.getString(0);
438                 final String e164 = c.getString(1);
439                 Log.i(TAG, String.format("isBlocked: BLOCKED; number=%s, e164=%s, foundOrig=%s, "
440                                 + "foundE164=%s",
441                         piiHandle(phoneNumber),
442                         piiHandle(inE164),
443                         piiHandle(original),
444                         piiHandle(e164)));
445                 return true;
446             }
447         } finally {
448             c.close();
449         }
450         // No match found.
451         Log.i(TAG, String.format("isBlocked: NOT BLOCKED; number=%s, e164=%s",
452                 piiHandle(phoneNumber), piiHandle(inE164)));
453         return false;
454     }
455 
canCurrentUserBlockUsers()456     private boolean canCurrentUserBlockUsers() {
457         int currentUserId = getContext().getUserId();
458         UserManager userManager = getContext().getSystemService(UserManager.class);
459         // Allow USER_SYSTEM and managed profile to block users
460         return (currentUserId == UserHandle.USER_SYSTEM ||
461                 (userManager != null && userManager.isManagedProfile(currentUserId)));
462     }
463 
notifyEmergencyContact()464     private void notifyEmergencyContact() {
465         long sec = getBlockSuppressSecondsFromCarrierConfig();
466         long millisToWrite = sec < 0
467                 ? BLOCKING_DISABLED_FOREVER : System.currentTimeMillis() + (sec * 1000);
468         writeBlockSuppressionExpiryTimePref(millisToWrite);
469         writeEmergencyCallNotificationPref(true);
470         notifyBlockSuppressionStateChange();
471     }
472 
473     private void endBlockSuppression() {
474         // Nothing to do if blocks are not being suppressed.
475         if (getBlockSuppressionStatus().isSuppressed) {
476             writeBlockSuppressionExpiryTimePref(0);
477             writeEmergencyCallNotificationPref(false);
478             notifyBlockSuppressionStateChange();
479         }
480     }
481 
482     private SystemContract.BlockSuppressionStatus getBlockSuppressionStatus() {
483         SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
484         long blockSuppressionExpiryTimeMillis = pref.getLong(BLOCK_SUPPRESSION_EXPIRY_TIME_PREF, 0);
485         boolean isSuppressed = blockSuppressionExpiryTimeMillis == BLOCKING_DISABLED_FOREVER
486                 || System.currentTimeMillis() < blockSuppressionExpiryTimeMillis;
487         return new SystemContract.BlockSuppressionStatus(isSuppressed,
488                 blockSuppressionExpiryTimeMillis);
489     }
490 
491     private int shouldSystemBlockNumber(String phoneNumber, Bundle extras) {
492         if (getBlockSuppressionStatus().isSuppressed) {
493             return BlockedNumberContract.STATUS_NOT_BLOCKED;
494         }
495         if (isEmergencyNumber(phoneNumber)) {
496             return BlockedNumberContract.STATUS_NOT_BLOCKED;
497         }
498 
499         int blockReason = BlockedNumberContract.STATUS_NOT_BLOCKED;
500         if (extras != null && !extras.isEmpty()) {
501             // check enhanced blocking setting
502             boolean contactExist = extras.getBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST);
503             int presentation = extras.getInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION);
504             switch (presentation) {
505                 case TelecomManager.PRESENTATION_ALLOWED:
506                     if (getEnhancedBlockSetting(
507                             SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED)
508                                     && !contactExist) {
509                         blockReason = BlockedNumberContract.STATUS_BLOCKED_NOT_IN_CONTACTS;
510                     }
511                     break;
512                 case TelecomManager.PRESENTATION_RESTRICTED:
513                     if (getEnhancedBlockSetting(
514                             SystemContract.ENHANCED_SETTING_KEY_BLOCK_PRIVATE)) {
515                         blockReason = BlockedNumberContract.STATUS_BLOCKED_RESTRICTED;
516                     }
517                     break;
518                 case TelecomManager.PRESENTATION_PAYPHONE:
519                     if (getEnhancedBlockSetting(
520                             SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE)) {
521                         blockReason = BlockedNumberContract.STATUS_BLOCKED_PAYPHONE;
522                     }
523                     break;
524                 case TelecomManager.PRESENTATION_UNKNOWN:
525                     if (getEnhancedBlockSetting(
526                             SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN)) {
527                         blockReason = BlockedNumberContract.STATUS_BLOCKED_UNKNOWN_NUMBER;
528                     }
529                     break;
530                 case TelecomManager.PRESENTATION_UNAVAILABLE:
531                     if (getEnhancedBlockSetting(
532                                     SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN)) {
533                         blockReason = BlockedNumberContract.STATUS_BLOCKED_UNAVAILABLE;
534                     }
535                     break;
536                 default:
537                     break;
538             }
539         }
540         if (blockReason == BlockedNumberContract.STATUS_NOT_BLOCKED && isBlocked(phoneNumber)) {
541             blockReason = BlockedNumberContract.STATUS_BLOCKED_IN_LIST;
542         }
543         return blockReason;
544     }
545 
546     private boolean shouldShowEmergencyCallNotification() {
547         return isEnhancedCallBlockingEnabledByPlatform()
548                 && (isShowCallBlockingDisabledNotificationAlways()
549                         || isAnyEnhancedBlockingSettingEnabled())
550                 && getBlockSuppressionStatus().isSuppressed
551                 && getEnhancedBlockSetting(
552                         SystemContract.ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION);
553     }
554 
555     private PersistableBundle getCarrierConfig() {
556         CarrierConfigManager configManager = (CarrierConfigManager) getContext().getSystemService(
557                 Context.CARRIER_CONFIG_SERVICE);
558         PersistableBundle carrierConfig = configManager.getConfig();
559         if (carrierConfig == null) {
560             carrierConfig = configManager.getDefaultConfig();
561         }
562         return carrierConfig;
563     }
564 
565     private boolean isEnhancedCallBlockingEnabledByPlatform() {
566         return getCarrierConfig().getBoolean(
567                 CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL);
568     }
569 
570     private boolean isShowCallBlockingDisabledNotificationAlways() {
571         return getCarrierConfig().getBoolean(
572                 CarrierConfigManager.KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL);
573     }
574 
575     private boolean isAnyEnhancedBlockingSettingEnabled() {
576         return getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED)
577                 || getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PRIVATE)
578                 || getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_PAYPHONE)
579                 || getEnhancedBlockSetting(SystemContract.ENHANCED_SETTING_KEY_BLOCK_UNKNOWN);
580     }
581 
582     private boolean getEnhancedBlockSetting(String key) {
583         SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
584         return pref.getBoolean(key, false);
585     }
586 
587     private void setEnhancedBlockSetting(String key, boolean value) {
588         SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
589         SharedPreferences.Editor editor = pref.edit();
590         editor.putBoolean(key, value);
591         editor.apply();
592     }
593 
594     private void writeEmergencyCallNotificationPref(boolean show) {
595         if (!isEnhancedCallBlockingEnabledByPlatform()) {
596             return;
597         }
598         setEnhancedBlockSetting(
599                 SystemContract.ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION, show);
600     }
601 
602     private void writeBlockSuppressionExpiryTimePref(long expiryTimeMillis) {
603         SharedPreferences pref = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
604         SharedPreferences.Editor editor = pref.edit();
605         editor.putLong(BLOCK_SUPPRESSION_EXPIRY_TIME_PREF, expiryTimeMillis);
606         editor.apply();
607     }
608 
609     private long getBlockSuppressSecondsFromCarrierConfig() {
610         CarrierConfigManager carrierConfigManager =
611                 getContext().getSystemService(CarrierConfigManager.class);
612         int carrierConfigValue = carrierConfigManager.getConfig().getInt
613                 (CarrierConfigManager.KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT);
614         boolean isValidValue = carrierConfigValue <= MAX_BLOCKING_DISABLED_DURATION_SECONDS;
615         return isValidValue ? carrierConfigValue : CarrierConfigManager.getDefaultConfig().getInt(
616                 CarrierConfigManager.KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT);
617     }
618 
619     /**
620      * Returns {@code false} when the caller is not root, the user selected dialer, the
621      * default SMS app or a carrier app.
622      */
623     private boolean checkForPrivilegedApplications() {
624         if (Binder.getCallingUid() == Process.ROOT_UID) {
625             return true;
626         }
627 
628         final String callingPackage = getCallingPackage();
629         if (TextUtils.isEmpty(callingPackage)) {
630             Log.w(TAG, "callingPackage not accessible");
631         } else {
632             final TelecomManager telecom = getContext().getSystemService(TelecomManager.class);
633 
634             if (callingPackage.equals(telecom.getDefaultDialerPackage())
635                     || callingPackage.equals(telecom.getSystemDialerPackage())) {
636                 return true;
637             }
638             final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class);
639             if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS,
640                     Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED) {
641                 return true;
642             }
643 
644             final TelephonyManager telephonyManager =
645                     getContext().getSystemService(TelephonyManager.class);
646             final long token = Binder.clearCallingIdentity();
647             try {
648                 return telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) ==
649                         TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
650             } finally {
651                 Binder.restoreCallingIdentity(token);
652             }
653         }
654         return false;
655     }
656 
657     private void notifyBlockSuppressionStateChange() {
658         Intent intent = new Intent(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
659         getContext().sendBroadcast(intent, Manifest.permission.READ_BLOCKED_NUMBERS);
660     }
661 
662     private void enforceReadPermission() {
663         checkForPermission(android.Manifest.permission.READ_BLOCKED_NUMBERS);
664     }
665 
666     private void enforceReadPermissionAndPrimaryUser() {
667         checkForPermissionAndPrimaryUser(android.Manifest.permission.READ_BLOCKED_NUMBERS);
668     }
669 
670     private void enforceWritePermissionAndPrimaryUser() {
671         checkForPermissionAndPrimaryUser(android.Manifest.permission.WRITE_BLOCKED_NUMBERS);
672     }
673 
674     private void checkForPermissionAndPrimaryUser(String permission) {
675         checkForPermission(permission);
676         if (!canCurrentUserBlockUsers()) {
677             throwCurrentUserNotPermittedSecurityException();
678         }
679     }
680 
681     private void checkForPermission(String permission) {
682         boolean permitted = passesSystemPermissionCheck(permission)
683                 || checkForPrivilegedApplications() || isSelf();
684         if (!permitted) {
685             throwSecurityException();
686         }
687     }
688 
689     private void enforceSystemReadPermissionAndPrimaryUser() {
690         enforceSystemPermissionAndUser(android.Manifest.permission.READ_BLOCKED_NUMBERS);
691     }
692 
693     private void enforceSystemWritePermissionAndPrimaryUser() {
694         enforceSystemPermissionAndUser(android.Manifest.permission.WRITE_BLOCKED_NUMBERS);
695     }
696 
697     private void enforceSystemPermissionAndUser(String permission) {
698         if (!canCurrentUserBlockUsers()) {
699             throwCurrentUserNotPermittedSecurityException();
700         }
701 
702         if (!passesSystemPermissionCheck(permission)) {
703             throwSecurityException();
704         }
705     }
706 
707     private boolean passesSystemPermissionCheck(String permission) {
708         return getContext().checkCallingPermission(permission)
709                 == PackageManager.PERMISSION_GRANTED;
710     }
711 
712     private boolean isSelf() {
713         return ALLOW_SELF_CALL && Binder.getCallingPid() == Process.myPid();
714     }
715 
716     private void throwSecurityException() {
717         throw new SecurityException("Caller must be system, default dialer or default SMS app");
718     }
719 
720     private void throwCurrentUserNotPermittedSecurityException() {
721         throw new SecurityException("The current user cannot perform this operation");
722     }
723 }
724