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.PhoneNumberUtils;
49 import android.telephony.TelephonyManager;
50 import android.text.TextUtils;
51 import android.util.Log;
52 
53 import com.android.common.content.ProjectionMap;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.providers.blockednumber.BlockedNumberDatabaseHelper.Tables;
56 
57 import java.util.Arrays;
58 
59 /**
60  * Blocked phone number provider.
61  *
62  * <p>Note the provider allows emergency numbers.  The caller (telecom) should never call it with
63  * emergency numbers.
64  */
65 public class BlockedNumberProvider extends ContentProvider {
66     static final String TAG = "BlockedNumbers";
67 
68     private static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
69 
70     private static final int BLOCKED_LIST = 1000;
71     private static final int BLOCKED_ID = 1001;
72 
73     private static final UriMatcher sUriMatcher;
74 
75     private static final String PREF_FILE = "block_number_provider_prefs";
76     private static final String BLOCK_SUPPRESSION_EXPIRY_TIME_PREF =
77             "block_suppression_expiry_time_pref";
78     private static final int MAX_BLOCKING_DISABLED_DURATION_SECONDS = 7 * 24 * 3600; // 1 week
79     private static final long BLOCKING_DISABLED_FOREVER = -1;
80     // Normally, we allow calls from self, *except* in unit tests, where we clear this flag
81     // to emulate calls from other apps.
82     @VisibleForTesting
83     static boolean ALLOW_SELF_CALL = true;
84 
85     static {
86         sUriMatcher = new UriMatcher(0);
sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked", BLOCKED_LIST)87         sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked", BLOCKED_LIST);
sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked/#", BLOCKED_ID)88         sUriMatcher.addURI(BlockedNumberContract.AUTHORITY, "blocked/#", BLOCKED_ID);
89     }
90 
91     private static final ProjectionMap sBlockedNumberColumns = ProjectionMap.builder()
92             .add(BlockedNumberContract.BlockedNumbers.COLUMN_ID)
93             .add(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER)
94             .add(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER)
95             .build();
96 
97     private static final String ID_SELECTION =
98             BlockedNumberContract.BlockedNumbers.COLUMN_ID + "=?";
99 
100     private static final String ORIGINAL_NUMBER_SELECTION =
101             BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?";
102 
103     private static final String E164_NUMBER_SELECTION =
104             BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?";
105 
106     @VisibleForTesting
107     protected BlockedNumberDatabaseHelper mDbHelper;
108     @VisibleForTesting
109     protected BackupManager mBackupManager;
110 
111     @Override
onCreate()112     public boolean onCreate() {
113         mDbHelper = BlockedNumberDatabaseHelper.getInstance(getContext());
114         mBackupManager = new BackupManager(getContext());
115         return true;
116     }
117 
118     @Override
getType(@onNull Uri uri)119     public String getType(@NonNull Uri uri) {
120         final int match = sUriMatcher.match(uri);
121         switch (match) {
122             case BLOCKED_LIST:
123                 return BlockedNumberContract.BlockedNumbers.CONTENT_TYPE;
124             case BLOCKED_ID:
125                 return BlockedNumberContract.BlockedNumbers.CONTENT_ITEM_TYPE;
126             default:
127                 throw new IllegalArgumentException("Unsupported URI: " + uri);
128         }
129     }
130 
131     @Override
insert(@onNull Uri uri, @Nullable ContentValues values)132     public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
133         enforceWritePermissionAndPrimaryUser();
134 
135         final int match = sUriMatcher.match(uri);
136         switch (match) {
137             case BLOCKED_LIST:
138                 Uri blockedUri = insertBlockedNumber(values);
139                 getContext().getContentResolver().notifyChange(blockedUri, null);
140                 mBackupManager.dataChanged();
141                 return blockedUri;
142             default:
143                 throw new IllegalArgumentException("Unsupported URI: " + uri);
144         }
145     }
146 
147     /**
148      * Implements the "blocked/" insert.
149      */
insertBlockedNumber(ContentValues cv)150     private Uri insertBlockedNumber(ContentValues cv) {
151         throwIfSpecified(cv, BlockedNumberContract.BlockedNumbers.COLUMN_ID);
152 
153         final String phoneNumber = cv.getAsString(
154                 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER);
155 
156         if (TextUtils.isEmpty(phoneNumber)) {
157             throw new IllegalArgumentException("Missing a required column " +
158                     BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER);
159         }
160 
161         // Fill in with autogenerated columns.
162         final String e164Number = Utils.getE164Number(getContext(), phoneNumber,
163                 cv.getAsString(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER));
164         cv.put(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER, e164Number);
165 
166         if (DEBUG) {
167             Log.d(TAG, String.format("inserted blocked number: %s", cv));
168         }
169 
170         // Then insert.
171         final long id = mDbHelper.getWritableDatabase().insertWithOnConflict(
172                 BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS, null, cv,
173                 SQLiteDatabase.CONFLICT_REPLACE);
174 
175         return ContentUris.withAppendedId(BlockedNumberContract.BlockedNumbers.CONTENT_URI, id);
176     }
177 
throwIfSpecified(ContentValues cv, String column)178     private static void throwIfSpecified(ContentValues cv, String column) {
179         if (cv.containsKey(column)) {
180             throw new IllegalArgumentException("Column " + column + " must not be specified");
181         }
182     }
183 
184     @Override
update(@onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)185     public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
186             @Nullable String[] selectionArgs) {
187         enforceWritePermissionAndPrimaryUser();
188 
189         throw new UnsupportedOperationException(
190                 "Update is not supported.  Use delete + insert instead");
191     }
192 
193     @Override
delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)194     public int delete(@NonNull Uri uri, @Nullable String selection,
195             @Nullable String[] selectionArgs) {
196         enforceWritePermissionAndPrimaryUser();
197 
198         final int match = sUriMatcher.match(uri);
199         int numRows;
200         switch (match) {
201             case BLOCKED_LIST:
202                 numRows = deleteBlockedNumber(selection, selectionArgs);
203                 break;
204             case BLOCKED_ID:
205                 numRows = deleteBlockedNumberWithId(ContentUris.parseId(uri), selection);
206                 break;
207             default:
208                 throw new IllegalArgumentException("Unsupported URI: " + uri);
209         }
210         getContext().getContentResolver().notifyChange(uri, null);
211         mBackupManager.dataChanged();
212         return numRows;
213     }
214 
215     /**
216      * Implements the "blocked/#" delete.
217      */
deleteBlockedNumberWithId(long id, String selection)218     private int deleteBlockedNumberWithId(long id, String selection) {
219         throwForNonEmptySelection(selection);
220 
221         return deleteBlockedNumber(ID_SELECTION, new String[]{Long.toString(id)});
222     }
223 
224     /**
225      * Implements the "blocked/" delete.
226      */
deleteBlockedNumber(String selection, String[] selectionArgs)227     private int deleteBlockedNumber(String selection, String[] selectionArgs) {
228         final SQLiteDatabase db = mDbHelper.getWritableDatabase();
229 
230         // When selection is specified, compile it within (...) to detect SQL injection.
231         if (!TextUtils.isEmpty(selection)) {
232             db.validateSql("select 1 FROM " + Tables.BLOCKED_NUMBERS + " WHERE " +
233                     Utils.wrapSelectionWithParens(selection),
234                     /* cancellationSignal =*/ null);
235         }
236 
237         return db.delete(
238                 BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS,
239                 selection, selectionArgs);
240     }
241 
242     @Override
query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)243     public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
244             @Nullable String[] selectionArgs, @Nullable String sortOrder) {
245         enforceReadPermissionAndPrimaryUser();
246 
247         return query(uri, projection, selection, selectionArgs, sortOrder, null);
248     }
249 
250     @Override
query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)251     public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
252             @Nullable String[] selectionArgs, @Nullable String sortOrder,
253             @Nullable CancellationSignal cancellationSignal) {
254         enforceReadPermissionAndPrimaryUser();
255 
256         final int match = sUriMatcher.match(uri);
257         Cursor cursor;
258         switch (match) {
259             case BLOCKED_LIST:
260                 cursor = queryBlockedList(projection, selection, selectionArgs, sortOrder,
261                         cancellationSignal);
262                 break;
263             case BLOCKED_ID:
264                 cursor = queryBlockedListWithId(ContentUris.parseId(uri), projection, selection,
265                         cancellationSignal);
266                 break;
267             default:
268                 throw new IllegalArgumentException("Unsupported URI: " + uri);
269         }
270         // Tell the cursor what uri to watch, so it knows when its source data changes
271         cursor.setNotificationUri(getContext().getContentResolver(), uri);
272         return cursor;
273     }
274 
275     /**
276      * Implements the "blocked/#" query.
277      */
queryBlockedListWithId(long id, String[] projection, String selection, CancellationSignal cancellationSignal)278     private Cursor queryBlockedListWithId(long id, String[] projection, String selection,
279             CancellationSignal cancellationSignal) {
280         throwForNonEmptySelection(selection);
281 
282         return queryBlockedList(projection, ID_SELECTION, new String[]{Long.toString(id)},
283                 null, cancellationSignal);
284     }
285 
286     /**
287      * Implements the "blocked/" query.
288      */
queryBlockedList(String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)289     private Cursor queryBlockedList(String[] projection, String selection, String[] selectionArgs,
290             String sortOrder, CancellationSignal cancellationSignal) {
291         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
292         qb.setStrict(true);
293         qb.setTables(BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS);
294         qb.setProjectionMap(sBlockedNumberColumns);
295 
296         return qb.query(mDbHelper.getReadableDatabase(), projection, selection, selectionArgs,
297                 /* groupBy =*/ null, /* having =*/null, sortOrder,
298                 /* limit =*/ null, cancellationSignal);
299     }
300 
throwForNonEmptySelection(String selection)301     private void throwForNonEmptySelection(String selection) {
302         if (!TextUtils.isEmpty(selection)) {
303             throw new IllegalArgumentException(
304                     "When ID is specified in URI, selection must be null");
305         }
306     }
307 
308     @Override
call(@onNull String method, @Nullable String arg, @Nullable Bundle extras)309     public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
310         final Bundle res = new Bundle();
311         switch (method) {
312             case BlockedNumberContract.METHOD_IS_BLOCKED:
313                 enforceReadPermissionAndPrimaryUser();
314                 boolean isBlocked = isBlocked(arg);
315                 res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked);
316                 res.putInt(BlockedNumberContract.RES_BLOCK_STATUS,
317                         isBlocked ? BlockedNumberContract.STATUS_BLOCKED_IN_LIST
318                                 : BlockedNumberContract.STATUS_NOT_BLOCKED);
319                 break;
320             case BlockedNumberContract.METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS:
321                 // No permission checks: any app should be able to access this API.
322                 res.putBoolean(
323                         BlockedNumberContract.RES_CAN_BLOCK_NUMBERS, canCurrentUserBlockUsers());
324                 break;
325             case BlockedNumberContract.METHOD_UNBLOCK:
326                 enforceWritePermissionAndPrimaryUser();
327 
328                 res.putInt(BlockedNumberContract.RES_NUM_ROWS_DELETED, unblock(arg));
329                 break;
330             case SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT:
331                 enforceSystemWritePermissionAndPrimaryUser();
332 
333                 notifyEmergencyContact();
334                 break;
335             case SystemContract.METHOD_END_BLOCK_SUPPRESSION:
336                 enforceSystemWritePermissionAndPrimaryUser();
337 
338                 endBlockSuppression();
339                 break;
340             case SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS:
341                 enforceSystemReadPermissionAndPrimaryUser();
342 
343                 SystemContract.BlockSuppressionStatus status = getBlockSuppressionStatus();
344                 res.putBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, status.isSuppressed);
345                 res.putLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP,
346                         status.untilTimestampMillis);
347                 break;
348             case SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER:
349                 enforceSystemReadPermissionAndPrimaryUser();
350                 int blockReason = shouldSystemBlockNumber(arg, extras);
351                 res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED,
352                         blockReason != BlockedNumberContract.STATUS_NOT_BLOCKED);
353                 res.putInt(BlockedNumberContract.RES_BLOCK_STATUS, blockReason);
354                 break;
355             case SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION:
356                 enforceSystemReadPermissionAndPrimaryUser();
357                 res.putBoolean(BlockedNumberContract.RES_SHOW_EMERGENCY_CALL_NOTIFICATION,
358                         shouldShowEmergencyCallNotification());
359                 break;
360             case SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING:
361                 enforceSystemReadPermissionAndPrimaryUser();
362                 if (extras != null) {
363                     String key = extras.getString(BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY);
364                     boolean value = getEnhancedBlockSetting(key);
365                     res.putBoolean(BlockedNumberContract.RES_ENHANCED_SETTING_IS_ENABLED, value);
366                 }
367                 break;
368             case SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING:
369                 enforceSystemWritePermissionAndPrimaryUser();
370                 if (extras != null) {
371                     String key = extras.getString(BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY);
372                     boolean value = extras.getBoolean(
373                             BlockedNumberContract.EXTRA_ENHANCED_SETTING_VALUE, false);
374                     setEnhancedBlockSetting(key, value);
375                 }
376                 break;
377             default:
378                 enforceReadPermissionAndPrimaryUser();
379 
380                 throw new IllegalArgumentException("Unsupported method " + method);
381         }
382         return res;
383     }
384 
unblock(String phoneNumber)385     private int unblock(String phoneNumber) {
386         if (TextUtils.isEmpty(phoneNumber)) {
387             return 0;
388         }
389 
390         StringBuilder selectionBuilder = new StringBuilder(ORIGINAL_NUMBER_SELECTION);
391         String[] selectionArgs = new String[]{phoneNumber};
392         final String e164Number = Utils.getE164Number(getContext(), phoneNumber, null);
393         if (!TextUtils.isEmpty(e164Number)) {
394             selectionBuilder.append(" or " + E164_NUMBER_SELECTION);
395             selectionArgs = new String[]{phoneNumber, e164Number};
396         }
397         String selection = selectionBuilder.toString();
398         if (DEBUG) {
399             Log.d(TAG, String.format("Unblocking numbers using selection: %s, args: %s",
400                     selection, Arrays.toString(selectionArgs)));
401         }
402         return deleteBlockedNumber(selection, selectionArgs);
403     }
404 
isEmergencyNumber(String phoneNumber)405     private boolean isEmergencyNumber(String phoneNumber) {
406         if (TextUtils.isEmpty(phoneNumber)) {
407             return false;
408         }
409 
410         final String e164Number = Utils.getE164Number(getContext(), phoneNumber, null);
411         return PhoneNumberUtils.isEmergencyNumber(phoneNumber)
412                 || PhoneNumberUtils.isEmergencyNumber(e164Number);
413     }
414 
isBlocked(String phoneNumber)415     private boolean isBlocked(String phoneNumber) {
416         if (TextUtils.isEmpty(phoneNumber)) {
417             Log.i(TAG, "isBlocked: NOT BLOCKED; empty #");
418             return false;
419         }
420 
421         final String inE164 = Utils.getE164Number(getContext(), phoneNumber, null); // may be empty.
422 
423         final Cursor c = mDbHelper.getReadableDatabase().rawQuery(
424                 "SELECT " +
425                 BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "," +
426                 BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER +
427                 " FROM " + BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS +
428                 " WHERE " + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?1" +
429                 " OR (?2 != '' AND " +
430                         BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?2)",
431                 new String[] {phoneNumber, inE164}
432                 );
433         try {
434             while (c.moveToNext()) {
435                 final String original = c.getString(0);
436                 final String e164 = c.getString(1);
437                 Log.i(TAG, String.format("isBlocked: BLOCKED; number=%s, e164=%s, foundOrig=%s, "
438                                 + "foundE164=%s",
439                         piiHandle(phoneNumber),
440                         piiHandle(inE164),
441                         piiHandle(original),
442                         piiHandle(e164)));
443                 return true;
444             }
445         } finally {
446             c.close();
447         }
448         // No match found.
449         Log.i(TAG, String.format("isBlocked: NOT BLOCKED; number=%s, e164=%s",
450                 piiHandle(phoneNumber), piiHandle(inE164)));
451         return false;
452     }
453 
canCurrentUserBlockUsers()454     private boolean canCurrentUserBlockUsers() {
455         return getContext().getUserId() == UserHandle.USER_SYSTEM;
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