/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.provider; import static android.provider.BlockedNumberContract.AUTHORITY_URI; import static android.provider.BlockedNumberContract.EXTRA_ENHANCED_SETTING_KEY; import static android.provider.BlockedNumberContract.EXTRA_ENHANCED_SETTING_VALUE; import static android.provider.BlockedNumberContract.RES_BLOCK_STATUS; import static android.provider.BlockedNumberContract.RES_ENHANCED_SETTING_IS_ENABLED; import static android.provider.BlockedNumberContract.RES_SHOW_EMERGENCY_CALL_NOTIFICATION; import static android.provider.BlockedNumberContract.STATUS_NOT_BLOCKED; import static android.provider.BlockedNumberContract.SystemContract.METHOD_END_BLOCK_SUPPRESSION; import static android.provider.BlockedNumberContract.SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS; import static android.provider.BlockedNumberContract.SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING; import static android.provider.BlockedNumberContract.SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT; import static android.provider.BlockedNumberContract.SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING; import static android.provider.BlockedNumberContract.SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION; import static android.provider.BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER; import static android.provider.BlockedNumberContract.SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP; import static android.provider.BlockedNumberContract.SystemContract.RES_IS_BLOCKING_SUPPRESSED; import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; import android.os.Bundle; import android.telecom.Log; import android.telecom.TelecomManager; import com.android.server.telecom.flags.Flags; /** * Constants and methods to interact with the blocked numbers list. This class also serves as * a mediator between the BlockedNumber provider and the system: it manages blocking behavior * when the user contacts emergency services. Currently, this is only used internally by Telecom. * * Refer to {@link BlockedNumberContract} for more context. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public final class BlockedNumbersManager { private static final String LOG_TAG = BlockedNumbersManager.class.getSimpleName(); private Context mContext; /** * @hide */ public BlockedNumbersManager(Context context) { mContext = context; } /** * A protected broadcast intent action for letting components with * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression * status as returned by {@link #getBlockSuppressionStatus()} has been updated. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED = "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED"; /** * Preference key of block numbers not in contacts setting. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED = "block_numbers_not_in_contacts_setting"; /** * Preference key of block private number calls setting. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE = "block_private_number_calls_setting"; /** * Preference key of block payphone calls setting. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE = "block_payphone_calls_setting"; /** * Preference key of block unknown calls setting. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN = "block_unknown_calls_setting"; /** * Preference key for whether should show an emergency call notification. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION = "show_emergency_call_notification"; /** * Preference key of block unavailable calls setting. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE = "block_unavailable_calls_setting"; /** * Notifies the provider that emergency services were contacted by the user. *
This results in {@link #shouldSystemBlockNumber} returning {@code false} independent * of the contents of the provider for a duration defined by * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT} * the provider unless {@link #endBlockSuppression()} is called. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public void notifyEmergencyContact() { verifyBlockedNumbersPermission(); try { Log.i(LOG_TAG, "notifyEmergencyContact; caller=%s", mContext.getOpPackageName()); mContext.getContentResolver().call(AUTHORITY_URI, METHOD_NOTIFY_EMERGENCY_CONTACT, null, null); } catch (NullPointerException | IllegalArgumentException ex) { // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if // either of these happen. Log.w(null, "notifyEmergencyContact: provider not ready."); } } /** * Notifies the provider to disable suppressing blocking. If emergency services were not * contacted recently at all, calling this method is a no-op. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public void endBlockSuppression() { verifyBlockedNumbersPermission(); String caller = mContext.getOpPackageName(); Log.i(LOG_TAG, "endBlockSuppression: caller=%s", caller); mContext.getContentResolver().call(AUTHORITY_URI, METHOD_END_BLOCK_SUPPRESSION, null, null); } /** * Returns {@code true} if {@code phoneNumber} is blocked taking * {@link #notifyEmergencyContact()} into consideration. If emergency services * have not been contacted recently and enhanced call blocking not been enabled, this * method is equivalent to {@link BlockedNumberContract#isBlocked(Context, String)}. * * @param phoneNumber the number to check. * @param numberPresentation the presentation code associated with the call. * @param isNumberInContacts indicates if the provided number exists as a contact. * @return result code indicating if the number should be blocked, and if so why. * Valid values are: {@link BlockedNumberContract#STATUS_NOT_BLOCKED}, * {@link BlockedNumberContract#STATUS_BLOCKED_IN_LIST}, * {@link BlockedNumberContract#STATUS_BLOCKED_NOT_IN_CONTACTS}, * {@link BlockedNumberContract#STATUS_BLOCKED_PAYPHONE}, * {@link BlockedNumberContract#STATUS_BLOCKED_RESTRICTED}, * {@link BlockedNumberContract#STATUS_BLOCKED_UNKNOWN_NUMBER}. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public int shouldSystemBlockNumber(@NonNull String phoneNumber, @TelecomManager.Presentation int numberPresentation, boolean isNumberInContacts) { verifyBlockedNumbersPermission(); try { String caller = mContext.getOpPackageName(); Bundle extras = new Bundle(); extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, numberPresentation); extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, isNumberInContacts); final Bundle res = mContext.getContentResolver().call(AUTHORITY_URI, METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras); int blockResult = res != null ? res.getInt(RES_BLOCK_STATUS, STATUS_NOT_BLOCKED) : BlockedNumberContract.STATUS_NOT_BLOCKED; Log.d(LOG_TAG, "shouldSystemBlockNumber: number=%s, caller=%s, result=%s", Log.piiHandle(phoneNumber), caller, BlockedNumberContract.SystemContract.blockStatusToString(blockResult)); return blockResult; } catch (NullPointerException | IllegalArgumentException ex) { // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if // either of these happen. Log.w(null, "shouldSystemBlockNumber: provider not ready."); return BlockedNumberContract.STATUS_NOT_BLOCKED; } } /** * @return The current status of block suppression. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public @NonNull BlockSuppressionStatus getBlockSuppressionStatus() { verifyBlockedNumbersPermission(); final Bundle res = mContext.getContentResolver().call( AUTHORITY_URI, METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null); BlockSuppressionStatus blockSuppressionStatus = new BlockSuppressionStatus( res.getBoolean(RES_IS_BLOCKING_SUPPRESSED, false), res.getLong(RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0)); Log.d(LOG_TAG, "getBlockSuppressionStatus: caller=%s, status=%s", mContext.getOpPackageName(), blockSuppressionStatus); return blockSuppressionStatus; } /** * Check whether should show the emergency call notification. * * @return {@code true} if should show emergency call notification. {@code false} otherwise. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public boolean shouldShowEmergencyCallNotification() { verifyBlockedNumbersPermission(); try { final Bundle res = mContext.getContentResolver().call(AUTHORITY_URI, METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null); return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false); } catch (NullPointerException | IllegalArgumentException ex) { // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if // either of these happen. Log.w(null, "shouldShowEmergencyCallNotification: provider not ready."); return false; } } /** * Check whether the enhanced block setting is enabled. * * @param key the key of the setting to check, can be * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE} * {@link BlockedNumberContract.SystemContract * #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION} * @return {@code true} if the setting is enabled. {@code false} otherwise. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public boolean getBlockedNumberSetting(@NonNull String key) { verifyBlockedNumbersPermission(); Bundle extras = new Bundle(); extras.putString(EXTRA_ENHANCED_SETTING_KEY, key); try { final Bundle res = mContext.getContentResolver().call(AUTHORITY_URI, METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras); return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false); } catch (NullPointerException | IllegalArgumentException ex) { // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if // either of these happen. Log.w(null, "getEnhancedBlockSetting: provider not ready."); return false; } } /** * Set the enhanced block setting enabled status. * * @param key the key of the setting to set, can be * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN} * {@link BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE} * {@link BlockedNumberContract.SystemContract * #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION} * @param value the enabled statue of the setting to set. * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS }) @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public void setBlockedNumberSetting(@NonNull String key, boolean value) { verifyBlockedNumbersPermission(); Bundle extras = new Bundle(); extras.putString(EXTRA_ENHANCED_SETTING_KEY, key); extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value); mContext.getContentResolver().call(AUTHORITY_URI, METHOD_SET_ENHANCED_BLOCK_SETTING, null, extras); } /** * Represents the current status of * {@link #shouldSystemBlockNumber(String, int, boolean)}. If emergency services * have been contacted recently, {@link #mIsSuppressed} is {@code true}, and blocking * is disabled until the timestamp {@link #mUntilTimestampMillis}. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_MAINLINE_BLOCKED_NUMBERS_MANAGER) public static final class BlockSuppressionStatus { /** * Indicates if block suppression is enabled. */ private boolean mIsSuppressed; /** * Timestamp in milliseconds from epoch. */ private long mUntilTimestampMillis; public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) { this.mIsSuppressed = isSuppressed; this.mUntilTimestampMillis = untilTimestampMillis; } @Override public String toString() { return "[BlockSuppressionStatus; isSuppressed=" + mIsSuppressed + ", until=" + mUntilTimestampMillis + "]"; } /** * @return mIsSuppressed Indicates whether or not block suppression is enabled. */ public boolean getIsSuppressed() { return mIsSuppressed; } /** * @return mUntilTimestampMillis The timestamp until which block suppression would be * enabled for */ public long getUntilTimestampMillis() { return mUntilTimestampMillis; } } /** * Verifies that the caller holds both the * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} permission and the * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} permission. * * @throws SecurityException if the caller is missing the necessary permissions */ private void verifyBlockedNumbersPermission() { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_BLOCKED_NUMBERS, "Caller does not have the android.permission.READ_BLOCKED_NUMBERS permission"); mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_BLOCKED_NUMBERS, "Caller does not have the android.permission.WRITE_BLOCKED_NUMBERS permission"); } }