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