Android N BlockedNumberContract原生黑名单(一)
Google 从Android N 开始从原生上支持号码屏蔽也就是电话黑名单功能,相关原文翻译如下:
号码屏蔽
Android N 现在支持在平台中进行号码屏蔽,提供框架 API,让服务提供商可以维护屏蔽的号码列表。 默认短信应用、默认手机应用和提供商应用可以对屏蔽的号码列表进行读取和写入操作。 其他应用则无法访问此列表。
通过使号码屏蔽成为平台的标准功能,Android 为应用提供一致的方式来支持广泛的设备上的号码屏蔽。 应用可以利用的其他优势包括:
屏蔽已屏蔽的来电号码发出的短信
通过 Backup &; Restore(备份和还原)功能可以跨重置和设备保留屏蔽的号码
多个应用可以使用相同的屏蔽号码列表 此外,通过 Android 的运营商应用集成表示运营商可以读取设备上屏蔽的号码列表,并为用户执行服务端屏蔽,以阻止不需要的来电和短信通过任何介质(如 VOIP 端点或转接电话)到达用户。
如需了解详细信息,请参阅可下载的 API 参考中的 android.provider.BlockedNumberContract。
来电拦截
Android N 允许默认的手机应用过滤来电。手机应用执行此操作的方式是实现新的 CallScreeningService,该方法允许手机应用基于来电的 Call.Details 执行大量操作,例如:
拒绝来电
不允许来电到达通话记录
不向用户显示来电通知 如需了解详细信息,请参阅可下载的 API 参考中的 android.telecom.CallScreeningService。
新增的两个API:
android.provider.BlockedNumberContract
android.telecom.CallScreeningService
/frameworks/base/core/java/android/provider/BlockedNumberContract.java中有两个重要方法
/**
* Returns whether a given number is in the blocked list.
*
* <p> This matches the {@code phoneNumber} against the
* {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column, and the E164 representation of the
* {@code phoneNumber} with the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
*
* <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
* context {@code context}, this method will throw a {@link SecurityException}.
*
* @return {@code true} if the {@code phoneNumber} is blocked.
*/
@WorkerThread
public static boolean isBlocked(Context context, String phoneNumber) {
final Bundle res = context.getContentResolver().call(
AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
}
/**
* Unblocks the {@code phoneNumber} if it is blocked.
*
* <p> This deletes all rows where the {@code phoneNumber} matches the
* {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or the E164 representation of the
* {@code phoneNumber} matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
*
* <p>To delete rows based on exact match with specific columns such as
* {@link BlockedNumbers#COLUMN_ID} use
* {@link android.content.ContentProvider#delete(Uri, String, String[])} with
* {@link BlockedNumbers#CONTENT_URI} URI.
*
* <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
* context {@code context}, this method will throw a {@link SecurityException}.
*
* @return the number of rows deleted in the blocked number provider as a result of unblock.
*/
@WorkerThread
public static int unblock(Context context, String phoneNumber) {
final Bundle res = context.getContentResolver().call(
AUTHORITY_URI, METHOD_UNBLOCK, phoneNumber, null);
return res.getInt(RES_NUM_ROWS_DELETED, 0);
}
isBlocked是判断号码是否在黑名单列表中,存在返回true,unblock是解除拦截,从黑名单列表删除号码
通过在framework中搜索 BlockedNumberContract类名查找被调用的地方,发现有2处:
/frameworks/opt/telephony/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/BlockChecker.java
从类名可知前一个是和emergency call相关的,我们暂且不管,看第二个
public class BlockChecker {
private static final String TAG = "BlockChecker";
private static final boolean VDBG = false; // STOPSHIP if true.
/**
* Returns {@code true} if {@code phoneNumber} is blocked.
* <p>
* This method catches all underlying exceptions to ensure that this method never throws any
* exception.
*/
public static boolean isBlocked(Context context, String phoneNumber) {
boolean isBlocked = false;
long startTimeNano = System.nanoTime();
try {
if (BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
context, phoneNumber)) {
Rlog.d(TAG, phoneNumber + " is blocked.");
isBlocked = true;
}
} catch (Exception e) {
Rlog.e(TAG, "Exception checking for blocked number: " + e);
}
int durationMillis = (int) ((System.nanoTime() - startTimeNano) / 1000000);
if (durationMillis > 500 || VDBG) {
Rlog.d(TAG, "Blocked number lookup took: " + durationMillis + " ms.");
}
return isBlocked;
}
}
此类调用了 BlockedNumberContract.SystemContract.shouldSystemBlockNumber(context, phoneNumber)方法,该方法也是
判断号码是否处在黑名单列表中。BlockChecker类很重要,他是framework层判断是否拦截来电和短信的主要类,稍后会着重分析。
我们现在看下在application 层到底是那些应用会用到BlockedNumberContract 这个API,和通话相关的我们首先看Dialer应用,
发现在dialer的settings中有call blocking设置,这里的布局非常简单,一个ADD A NUMBER按钮,点击弹出输入框输入要拦截的号码,
我们使用布局分析工具hierarchyviewer.bat迅速定位到相关代码在package/services/Telecom中,添加号码的Activity为
/packages/services/Telecomm/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
添加号码的对话框代码为
private void showAddBlockedNumberDialog() {
LayoutInflater inflater = this.getLayoutInflater();
View dialogView = inflater.inflate(R.xml.add_blocked_number_dialog, null);
final EditText editText = (EditText) dialogView.findViewById(R.id.add_blocked_number);
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
editText.addTextChangedListener(this);
AlertDialog dialog = new AlertDialog.Builder(this)
.setView(dialogView)
.setPositiveButton(R.string.block_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
addBlockedNumber(PhoneNumberUtils.stripSeparators(
editText.getText().toString()));
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
})
.create();
dialog.setOnShowListener(new AlertDialog.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
mBlockButton = ((AlertDialog) dialog)
.getButton(AlertDialog.BUTTON_POSITIVE);
mBlockButton.setEnabled(false);
// show keyboard
InputMethodManager inputMethodManager =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(editText,
InputMethodManager.SHOW_IMPLICIT);
}
});
dialog.show();
}
其中调用的addBlockedNumber(PhoneNumberUtils.stripSeparators(editText.getText().toString()));方法:
/**
* Add blocked number if it does not exist.
*/
private void addBlockedNumber(String number) {
if (PhoneNumberUtils.isEmergencyNumber(number)) {
Toast.makeText(
this,
getString(R.string.blocked_numbers_block_emergency_number_message),
Toast.LENGTH_SHORT).show();
} else {
// We disable the add button, to prevent the user from adding other numbers until the
// current number is added.
mAddButton.setEnabled(false);
mBlockNumberTaskFragment.blockIfNotAlreadyBlocked(number, this);
}
}
可见添加号码前判断此号码是否是紧急号码,不是的话会调用mBlockNumberTaskFragment的blockIfNotAlreadyBlocked方法,
进入/packages/services/Telecomm/src/com/android/server/telecom/settings/BlockNumberTaskFragment.java
blockIfNotAlreadyBlocked方法:
/**
* Runs an async task to write the number to the blocked numbers provider if it does not already
* exist.
*
* Triggers {@link Listener#onBlocked(String, boolean)} when task finishes to show proper UI.
*/
public void blockIfNotAlreadyBlocked(String number, Listener listener) {
mListener = listener;
mTask = new BlockNumberTask();
mTask.execute(number);
}
通过一个异步任务进行添加
/**
* Task to block a number.
*/
private class BlockNumberTask extends AsyncTask<String, Void, Boolean> {
private String mNumber;
/**
* @return true if number was blocked; false if number is already blocked.
*/
@Override
protected Boolean doInBackground(String... params) {
mNumber = params[0];
if (BlockedNumberContract.isBlocked(getContext(), mNumber)) {
return false;
} else {
ContentResolver contentResolver = getContext().getContentResolver();
ContentValues newValues = new ContentValues();
newValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
mNumber);
contentResolver.insert(BlockedNumberContract.BlockedNumbers.CONTENT_URI,
newValues);
return true;
}
}
@Override
protected void onPostExecute(Boolean result) {
mTask = null;
if (mListener != null) {
mListener.onBlocked(mNumber, !result /* alreadyBlocked */);
}
mListener = null;
}
}
在doInBackground方法中我们发现在添加添加号码前调用了我们前文所述的BlockedNumberContract的isBlocked方法来判断
此号码是否已经是黑名单中的号码,黑名单表中不存在此号码则使用ContentResolver将号码插入数据库中。
黑名单数据库的相关代码在/packages/providers/BlockedNumberProvider/中,这个provider代码相对简单,只有4个类
BlockedNumberBackupAgent.java
BlockedNumberDatabaseHelper.java
BlockedNumberProvider.java
Utils.java
BlockedNumberDatabaseHelper.java是sql helper类,创建黑名单数据库
private static final String DATABASE_NAME = "blockednumbers.db";
public interface Tables {
String BLOCKED_NUMBERS = "blocked";
}
private void createTables(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + Tables.BLOCKED_NUMBERS + " (" +
BlockedNumbers.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
BlockedNumbers.COLUMN_ORIGINAL_NUMBER + " TEXT NOT NULL UNIQUE," +
BlockedNumbers.COLUMN_E164_NUMBER + " TEXT" +
")");
db.execSQL("CREATE INDEX blocked_number_idx_original ON " + Tables.BLOCKED_NUMBERS +
" (" + BlockedNumbers.COLUMN_ORIGINAL_NUMBER + ");");
db.execSQL("CREATE INDEX blocked_number_idx_e164 ON " + Tables.BLOCKED_NUMBERS + " (" +
BlockedNumbers.COLUMN_E164_NUMBER +
");");
}
数据库名blockednumbers.db,表名blocked,表字段有BlockedNumbers.COLUMN_ID,BlockedNumbers.COLUMN_ORIGINAL_NUMBER和
BlockedNumbers.COLUMN_E164_NUMBER,这3个字段都定义在BlockedNumberContract类中,其中COLUMN_E164_NUMBER是国际化后的号码,
即如中国号码在前加上 +86 后的号码形式。
黑名单数据库增删改查操作类BlockedNumberProvider.java,这个类中方法的方法名很多和BlockedNumberContract中的方法对应着,
因为provider的方法是在BlockedNumberContract中被调用的,provider中的重要方法是
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
final Bundle res = new Bundle();
switch (method) {
case BlockedNumberContract.METHOD_IS_BLOCKED:
enforceReadPermissionAndPrimaryUser();
res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked(arg));
break;
case BlockedNumberContract.METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS:
// No permission checks: any app should be able to access this API.
res.putBoolean(
BlockedNumberContract.RES_CAN_BLOCK_NUMBERS, canCurrentUserBlockUsers());
break;
case BlockedNumberContract.METHOD_UNBLOCK:
enforceWritePermissionAndPrimaryUser();
res.putInt(BlockedNumberContract.RES_NUM_ROWS_DELETED, unblock(arg));
break;
case SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT:
enforceSystemWritePermissionAndPrimaryUser();
notifyEmergencyContact();
break;
case SystemContract.METHOD_END_BLOCK_SUPPRESSION:
enforceSystemWritePermissionAndPrimaryUser();
endBlockSuppression();
break;
case SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS:
enforceSystemReadPermissionAndPrimaryUser();
SystemContract.BlockSuppressionStatus status = getBlockSuppressionStatus();
res.putBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, status.isSuppressed);
res.putLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP,
status.untilTimestampMillis);
break;
case SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER:
enforceSystemReadPermissionAndPrimaryUser();
res.putBoolean(
BlockedNumberContract.RES_NUMBER_IS_BLOCKED, shouldSystemBlockNumber(arg));
break;
default:
enforceReadPermissionAndPrimaryUser();
throw new IllegalArgumentException("Unsupported method " + method);
}
return res;
}
在BlockedNumberContract.java的isBlocked方法中看出是这样子调用的
Bundle res = context.getContentResolver().call(
AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
代码进入BlockedNumberProvider.java的call方法,对应着
case BlockedNumberContract.METHOD_IS_BLOCKED:
enforceReadPermissionAndPrimaryUser();
res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked(arg));
break;
我们看这类里的isBlocked方法
private boolean isBlocked(String phoneNumber) {
if (TextUtils.isEmpty(phoneNumber)) {
return false;
}
final String inE164 = Utils.getE164Number(getContext(), phoneNumber, null); // may be empty.
if (DEBUG) {
Log.d(TAG, String.format("isBlocked: in=%s, e164=%s", phoneNumber, inE164));
}
final Cursor c = mDbHelper.getReadableDatabase().rawQuery(
"SELECT " +
BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "," +
BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER +
" FROM " + BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS +
" WHERE " + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?1" +
" OR (?2 != '' AND " +
BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?2)",
new String[] {phoneNumber, inE164}
);
try {
while (c.moveToNext()) {
if (DEBUG) {
final String original = c.getString(0);
final String e164 = c.getString(1);
Log.d(TAG, String.format("match found: original=%s, e164=%s", original, e164));
}
return true;
}
} finally {
c.close();
}
// No match found.
return false;
}
查询了数据库BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS,条件为原始号码或国际化后的号码。
我们前文提到BlockChecker.java的isBlocked方法是framework层判断拦截与否的重要方法,那么我们查下framework
层那些类调用到它,经过搜索发现有3处地方调用,分别如下
/packages/services/Telecomm/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/WapPushOverSms.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java
从类名可以看出前一个BlockCheckerAdapter.java是和call相关的,后两个是和sms相关的
BlockCheckerAdapter.java
public class BlockCheckerAdapter {
public BlockCheckerAdapter() { }
public boolean isBlocked(Context context, String number) {
return BlockChecker.isBlocked(context, number);
}
}
这个类很简单,直接调用了BlockChecker的isBlocked方法,继续看BlockCheckerAdapter的调用地方
/packages/services/Telecomm/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
CallsManager.java
@Override
public void onSuccessfulIncomingCall(Call incomingCall) {
Log.d(this, "onSuccessfulIncomingCall");
if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
Log.i(this, "Skipping call filtering due to ECBM");
onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
return;
}
List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter()));
filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
mDefaultDialerManagerAdapter,
new ParcelableCallUtils.Converter(), mLock));
new IncomingCallFilter(mContext, this, incomingCall, mLock,
mTimeoutsAdapter, filters).performFiltering();
}
当一个call来到这个方法后会进行filter,3个filter条件通过电话流程才能继续走下去,
AsyncBlockCheckFilter.java
@Override
protected Boolean doInBackground(String... params) {
try {
Log.continueSession(mBackgroundTaskSubsession, "ABCF.dIB");
Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
return mBlockCheckerAdapter.isBlocked(mContext, params[0]);
} finally {
Log.endSession();
}
}
@Override
protected void onPostExecute(Boolean isBlocked) {
Log.continueSession(mPostExecuteSubsession, "ABCF.oPE");
try {
CallFilteringResult result;
if (isBlocked) {
result = new CallFilteringResult(
false, // shouldAllowCall
true, //shouldReject
false, //shouldAddToCallLog
false // shouldShowNotification
);
} else {
result = new CallFilteringResult(
true, // shouldAllowCall
false, // shouldReject
true, // shouldAddToCallLog
true // shouldShowNotification
);
}
Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED, result);
mCallback.onCallFilteringComplete(mIncomingCall, result);
} finally {
Log.endSession();
}
}
AsyncBlockCheckFilter.java中调用BlockCheckerAdapter的isBlocked方法,通过返回Boolean至构建
CallFilteringResult,CallFilteringResult的四个参考写的非常清楚
/packages/services/Telecomm/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
public boolean shouldAllowCall;//是否允许通话
public boolean shouldReject;//是否拒绝通话
public boolean shouldAddToCallLog;//是否填加到通话记录
public boolean shouldShowNotification;//是否显示来电通知
public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
shouldAddToCallLog, boolean shouldShowNotification) {
this.shouldAllowCall = shouldAllowCall;
this.shouldReject = shouldReject;
this.shouldAddToCallLog = shouldAddToCallLog;
this.shouldShowNotification = shouldShowNotification;
}
构建完CallFilteringResult后调用通过callback将结果返回
mCallback.onCallFilteringComplete(mIncomingCall, result);
代码进入到CallsManager的onCallFilteringComplete方法
@Override
public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result) {
// Only set the incoming call as ringing if it isn't already disconnected. It is possible
// that the connection service disconnected the call before it was even added to Telecom, in
// which case it makes no sense to set it back to a ringing state.
if (incomingCall.getState() != CallState.DISCONNECTED &&
incomingCall.getState() != CallState.DISCONNECTING) {
setCallState(incomingCall, CallState.RINGING,
result.shouldAllowCall ? "successful incoming call" : "blocking call");
} else {
Log.i(this, "onCallFilteringCompleted: call already disconnected.");
return;
}
if (result.shouldAllowCall) {
if (hasMaximumRingingCalls()) {
Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"ringing calls.");
rejectCallAndLog(incomingCall);
} else if (hasMaximumDialingCalls()) {
Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"dialing calls.");
rejectCallAndLog(incomingCall);
} else {
addCall(incomingCall);
}
} else {
if (result.shouldReject) {
Log.i(this, "onCallFilteringCompleted: blocked call, rejecting.");
incomingCall.reject(false, null);
}
if (result.shouldAddToCallLog) {
Log.i(this, "onCallScreeningCompleted: blocked call, adding to call log.");
if (result.shouldShowNotification) {
Log.w(this, "onCallScreeningCompleted: blocked call, showing notification.");
}
mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
result.shouldShowNotification);
} else if (result.shouldShowNotification) {
Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
mMissedCallNotifier.showMissedCallNotification(incomingCall);
}
}
}
这里对回调回来的resulted进行判断,允许通话则调用addCall(incomingCall),拒绝通话调用incomingCall.reject(false, null);
到这里,电话的黑名单判断基本完了。我们来看下短信的
/frameworks/opt/telephony/src/java/com/android/internal/telephony/WapPushOverSms.java
if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
final NotificationInd nInd = (NotificationInd) parsedPdu;
if (nInd.getFrom() != null
&& BlockChecker.isBlocked(mContext, nInd.getFrom().getString())) {
result.statusCode = Intents.RESULT_SMS_HANDLED;
return result;
}
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java
if (BlockChecker.isBlocked(mContext, tracker.getAddress())) {
deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(),
DELETE_PERMANENTLY);
return false;
}
短信方面不是很了解,这两处也只是调用了BlockChecker的isBlocked方法,具体处理流程以后有空再分析。
参考
https://www.cnblogs.com/lance2016/archive/2016/11/06/6035351.html
http://blog.csdn.net/michael_yt/article/details/54573087?utm_source=itdadao&utm_medium=referral
Google 从Android N 开始从原生上支持号码屏蔽也就是电话黑名单功能,相关原文翻译如下:
号码屏蔽
Android N 现在支持在平台中进行号码屏蔽,提供框架 API,让服务提供商可以维护屏蔽的号码列表。 默认短信应用、默认手机应用和提供商应用可以对屏蔽的号码列表进行读取和写入操作。 其他应用则无法访问此列表。
通过使号码屏蔽成为平台的标准功能,Android 为应用提供一致的方式来支持广泛的设备上的号码屏蔽。 应用可以利用的其他优势包括:
屏蔽已屏蔽的来电号码发出的短信
通过 Backup &; Restore(备份和还原)功能可以跨重置和设备保留屏蔽的号码
多个应用可以使用相同的屏蔽号码列表 此外,通过 Android 的运营商应用集成表示运营商可以读取设备上屏蔽的号码列表,并为用户执行服务端屏蔽,以阻止不需要的来电和短信通过任何介质(如 VOIP 端点或转接电话)到达用户。
如需了解详细信息,请参阅可下载的 API 参考中的 android.provider.BlockedNumberContract。
来电拦截
Android N 允许默认的手机应用过滤来电。手机应用执行此操作的方式是实现新的 CallScreeningService,该方法允许手机应用基于来电的 Call.Details 执行大量操作,例如:
拒绝来电
不允许来电到达通话记录
不向用户显示来电通知 如需了解详细信息,请参阅可下载的 API 参考中的 android.telecom.CallScreeningService。
新增的两个API:
android.provider.BlockedNumberContract
android.telecom.CallScreeningService
/frameworks/base/core/java/android/provider/BlockedNumberContract.java中有两个重要方法
/**
* Returns whether a given number is in the blocked list.
*
* <p> This matches the {@code phoneNumber} against the
* {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column, and the E164 representation of the
* {@code phoneNumber} with the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
*
* <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
* context {@code context}, this method will throw a {@link SecurityException}.
*
* @return {@code true} if the {@code phoneNumber} is blocked.
*/
@WorkerThread
public static boolean isBlocked(Context context, String phoneNumber) {
final Bundle res = context.getContentResolver().call(
AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
}
/**
* Unblocks the {@code phoneNumber} if it is blocked.
*
* <p> This deletes all rows where the {@code phoneNumber} matches the
* {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or the E164 representation of the
* {@code phoneNumber} matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
*
* <p>To delete rows based on exact match with specific columns such as
* {@link BlockedNumbers#COLUMN_ID} use
* {@link android.content.ContentProvider#delete(Uri, String, String[])} with
* {@link BlockedNumbers#CONTENT_URI} URI.
*
* <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
* context {@code context}, this method will throw a {@link SecurityException}.
*
* @return the number of rows deleted in the blocked number provider as a result of unblock.
*/
@WorkerThread
public static int unblock(Context context, String phoneNumber) {
final Bundle res = context.getContentResolver().call(
AUTHORITY_URI, METHOD_UNBLOCK, phoneNumber, null);
return res.getInt(RES_NUM_ROWS_DELETED, 0);
}
isBlocked是判断号码是否在黑名单列表中,存在返回true,unblock是解除拦截,从黑名单列表删除号码
通过在framework中搜索 BlockedNumberContract类名查找被调用的地方,发现有2处:
/frameworks/opt/telephony/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/BlockChecker.java
从类名可知前一个是和emergency call相关的,我们暂且不管,看第二个
public class BlockChecker {
private static final String TAG = "BlockChecker";
private static final boolean VDBG = false; // STOPSHIP if true.
/**
* Returns {@code true} if {@code phoneNumber} is blocked.
* <p>
* This method catches all underlying exceptions to ensure that this method never throws any
* exception.
*/
public static boolean isBlocked(Context context, String phoneNumber) {
boolean isBlocked = false;
long startTimeNano = System.nanoTime();
try {
if (BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
context, phoneNumber)) {
Rlog.d(TAG, phoneNumber + " is blocked.");
isBlocked = true;
}
} catch (Exception e) {
Rlog.e(TAG, "Exception checking for blocked number: " + e);
}
int durationMillis = (int) ((System.nanoTime() - startTimeNano) / 1000000);
if (durationMillis > 500 || VDBG) {
Rlog.d(TAG, "Blocked number lookup took: " + durationMillis + " ms.");
}
return isBlocked;
}
}
此类调用了 BlockedNumberContract.SystemContract.shouldSystemBlockNumber(context, phoneNumber)方法,该方法也是
判断号码是否处在黑名单列表中。BlockChecker类很重要,他是framework层判断是否拦截来电和短信的主要类,稍后会着重分析。
我们现在看下在application 层到底是那些应用会用到BlockedNumberContract 这个API,和通话相关的我们首先看Dialer应用,
发现在dialer的settings中有call blocking设置,这里的布局非常简单,一个ADD A NUMBER按钮,点击弹出输入框输入要拦截的号码,
我们使用布局分析工具hierarchyviewer.bat迅速定位到相关代码在package/services/Telecom中,添加号码的Activity为
/packages/services/Telecomm/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
添加号码的对话框代码为
private void showAddBlockedNumberDialog() {
LayoutInflater inflater = this.getLayoutInflater();
View dialogView = inflater.inflate(R.xml.add_blocked_number_dialog, null);
final EditText editText = (EditText) dialogView.findViewById(R.id.add_blocked_number);
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
editText.addTextChangedListener(this);
AlertDialog dialog = new AlertDialog.Builder(this)
.setView(dialogView)
.setPositiveButton(R.string.block_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
addBlockedNumber(PhoneNumberUtils.stripSeparators(
editText.getText().toString()));
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
})
.create();
dialog.setOnShowListener(new AlertDialog.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
mBlockButton = ((AlertDialog) dialog)
.getButton(AlertDialog.BUTTON_POSITIVE);
mBlockButton.setEnabled(false);
// show keyboard
InputMethodManager inputMethodManager =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(editText,
InputMethodManager.SHOW_IMPLICIT);
}
});
dialog.show();
}
其中调用的addBlockedNumber(PhoneNumberUtils.stripSeparators(editText.getText().toString()));方法:
/**
* Add blocked number if it does not exist.
*/
private void addBlockedNumber(String number) {
if (PhoneNumberUtils.isEmergencyNumber(number)) {
Toast.makeText(
this,
getString(R.string.blocked_numbers_block_emergency_number_message),
Toast.LENGTH_SHORT).show();
} else {
// We disable the add button, to prevent the user from adding other numbers until the
// current number is added.
mAddButton.setEnabled(false);
mBlockNumberTaskFragment.blockIfNotAlreadyBlocked(number, this);
}
}
可见添加号码前判断此号码是否是紧急号码,不是的话会调用mBlockNumberTaskFragment的blockIfNotAlreadyBlocked方法,
进入/packages/services/Telecomm/src/com/android/server/telecom/settings/BlockNumberTaskFragment.java
blockIfNotAlreadyBlocked方法:
/**
* Runs an async task to write the number to the blocked numbers provider if it does not already
* exist.
*
* Triggers {@link Listener#onBlocked(String, boolean)} when task finishes to show proper UI.
*/
public void blockIfNotAlreadyBlocked(String number, Listener listener) {
mListener = listener;
mTask = new BlockNumberTask();
mTask.execute(number);
}
通过一个异步任务进行添加
/**
* Task to block a number.
*/
private class BlockNumberTask extends AsyncTask<String, Void, Boolean> {
private String mNumber;
/**
* @return true if number was blocked; false if number is already blocked.
*/
@Override
protected Boolean doInBackground(String... params) {
mNumber = params[0];
if (BlockedNumberContract.isBlocked(getContext(), mNumber)) {
return false;
} else {
ContentResolver contentResolver = getContext().getContentResolver();
ContentValues newValues = new ContentValues();
newValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
mNumber);
contentResolver.insert(BlockedNumberContract.BlockedNumbers.CONTENT_URI,
newValues);
return true;
}
}
@Override
protected void onPostExecute(Boolean result) {
mTask = null;
if (mListener != null) {
mListener.onBlocked(mNumber, !result /* alreadyBlocked */);
}
mListener = null;
}
}
在doInBackground方法中我们发现在添加添加号码前调用了我们前文所述的BlockedNumberContract的isBlocked方法来判断
此号码是否已经是黑名单中的号码,黑名单表中不存在此号码则使用ContentResolver将号码插入数据库中。
黑名单数据库的相关代码在/packages/providers/BlockedNumberProvider/中,这个provider代码相对简单,只有4个类
BlockedNumberBackupAgent.java
BlockedNumberDatabaseHelper.java
BlockedNumberProvider.java
Utils.java
BlockedNumberDatabaseHelper.java是sql helper类,创建黑名单数据库
private static final String DATABASE_NAME = "blockednumbers.db";
public interface Tables {
String BLOCKED_NUMBERS = "blocked";
}
private void createTables(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + Tables.BLOCKED_NUMBERS + " (" +
BlockedNumbers.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
BlockedNumbers.COLUMN_ORIGINAL_NUMBER + " TEXT NOT NULL UNIQUE," +
BlockedNumbers.COLUMN_E164_NUMBER + " TEXT" +
")");
db.execSQL("CREATE INDEX blocked_number_idx_original ON " + Tables.BLOCKED_NUMBERS +
" (" + BlockedNumbers.COLUMN_ORIGINAL_NUMBER + ");");
db.execSQL("CREATE INDEX blocked_number_idx_e164 ON " + Tables.BLOCKED_NUMBERS + " (" +
BlockedNumbers.COLUMN_E164_NUMBER +
");");
}
数据库名blockednumbers.db,表名blocked,表字段有BlockedNumbers.COLUMN_ID,BlockedNumbers.COLUMN_ORIGINAL_NUMBER和
BlockedNumbers.COLUMN_E164_NUMBER,这3个字段都定义在BlockedNumberContract类中,其中COLUMN_E164_NUMBER是国际化后的号码,
即如中国号码在前加上 +86 后的号码形式。
黑名单数据库增删改查操作类BlockedNumberProvider.java,这个类中方法的方法名很多和BlockedNumberContract中的方法对应着,
因为provider的方法是在BlockedNumberContract中被调用的,provider中的重要方法是
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
final Bundle res = new Bundle();
switch (method) {
case BlockedNumberContract.METHOD_IS_BLOCKED:
enforceReadPermissionAndPrimaryUser();
res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked(arg));
break;
case BlockedNumberContract.METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS:
// No permission checks: any app should be able to access this API.
res.putBoolean(
BlockedNumberContract.RES_CAN_BLOCK_NUMBERS, canCurrentUserBlockUsers());
break;
case BlockedNumberContract.METHOD_UNBLOCK:
enforceWritePermissionAndPrimaryUser();
res.putInt(BlockedNumberContract.RES_NUM_ROWS_DELETED, unblock(arg));
break;
case SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT:
enforceSystemWritePermissionAndPrimaryUser();
notifyEmergencyContact();
break;
case SystemContract.METHOD_END_BLOCK_SUPPRESSION:
enforceSystemWritePermissionAndPrimaryUser();
endBlockSuppression();
break;
case SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS:
enforceSystemReadPermissionAndPrimaryUser();
SystemContract.BlockSuppressionStatus status = getBlockSuppressionStatus();
res.putBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, status.isSuppressed);
res.putLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP,
status.untilTimestampMillis);
break;
case SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER:
enforceSystemReadPermissionAndPrimaryUser();
res.putBoolean(
BlockedNumberContract.RES_NUMBER_IS_BLOCKED, shouldSystemBlockNumber(arg));
break;
default:
enforceReadPermissionAndPrimaryUser();
throw new IllegalArgumentException("Unsupported method " + method);
}
return res;
}
在BlockedNumberContract.java的isBlocked方法中看出是这样子调用的
Bundle res = context.getContentResolver().call(
AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
代码进入BlockedNumberProvider.java的call方法,对应着
case BlockedNumberContract.METHOD_IS_BLOCKED:
enforceReadPermissionAndPrimaryUser();
res.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, isBlocked(arg));
break;
我们看这类里的isBlocked方法
private boolean isBlocked(String phoneNumber) {
if (TextUtils.isEmpty(phoneNumber)) {
return false;
}
final String inE164 = Utils.getE164Number(getContext(), phoneNumber, null); // may be empty.
if (DEBUG) {
Log.d(TAG, String.format("isBlocked: in=%s, e164=%s", phoneNumber, inE164));
}
final Cursor c = mDbHelper.getReadableDatabase().rawQuery(
"SELECT " +
BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "," +
BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER +
" FROM " + BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS +
" WHERE " + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?1" +
" OR (?2 != '' AND " +
BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER + "=?2)",
new String[] {phoneNumber, inE164}
);
try {
while (c.moveToNext()) {
if (DEBUG) {
final String original = c.getString(0);
final String e164 = c.getString(1);
Log.d(TAG, String.format("match found: original=%s, e164=%s", original, e164));
}
return true;
}
} finally {
c.close();
}
// No match found.
return false;
}
查询了数据库BlockedNumberDatabaseHelper.Tables.BLOCKED_NUMBERS,条件为原始号码或国际化后的号码。
我们前文提到BlockChecker.java的isBlocked方法是framework层判断拦截与否的重要方法,那么我们查下framework
层那些类调用到它,经过搜索发现有3处地方调用,分别如下
/packages/services/Telecomm/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/WapPushOverSms.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java
从类名可以看出前一个BlockCheckerAdapter.java是和call相关的,后两个是和sms相关的
BlockCheckerAdapter.java
public class BlockCheckerAdapter {
public BlockCheckerAdapter() { }
public boolean isBlocked(Context context, String number) {
return BlockChecker.isBlocked(context, number);
}
}
这个类很简单,直接调用了BlockChecker的isBlocked方法,继续看BlockCheckerAdapter的调用地方
/packages/services/Telecomm/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
CallsManager.java
@Override
public void onSuccessfulIncomingCall(Call incomingCall) {
Log.d(this, "onSuccessfulIncomingCall");
if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
Log.i(this, "Skipping call filtering due to ECBM");
onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
return;
}
List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter()));
filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
mDefaultDialerManagerAdapter,
new ParcelableCallUtils.Converter(), mLock));
new IncomingCallFilter(mContext, this, incomingCall, mLock,
mTimeoutsAdapter, filters).performFiltering();
}
当一个call来到这个方法后会进行filter,3个filter条件通过电话流程才能继续走下去,
AsyncBlockCheckFilter.java
@Override
protected Boolean doInBackground(String... params) {
try {
Log.continueSession(mBackgroundTaskSubsession, "ABCF.dIB");
Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
return mBlockCheckerAdapter.isBlocked(mContext, params[0]);
} finally {
Log.endSession();
}
}
@Override
protected void onPostExecute(Boolean isBlocked) {
Log.continueSession(mPostExecuteSubsession, "ABCF.oPE");
try {
CallFilteringResult result;
if (isBlocked) {
result = new CallFilteringResult(
false, // shouldAllowCall
true, //shouldReject
false, //shouldAddToCallLog
false // shouldShowNotification
);
} else {
result = new CallFilteringResult(
true, // shouldAllowCall
false, // shouldReject
true, // shouldAddToCallLog
true // shouldShowNotification
);
}
Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED, result);
mCallback.onCallFilteringComplete(mIncomingCall, result);
} finally {
Log.endSession();
}
}
AsyncBlockCheckFilter.java中调用BlockCheckerAdapter的isBlocked方法,通过返回Boolean至构建
CallFilteringResult,CallFilteringResult的四个参考写的非常清楚
/packages/services/Telecomm/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
public boolean shouldAllowCall;//是否允许通话
public boolean shouldReject;//是否拒绝通话
public boolean shouldAddToCallLog;//是否填加到通话记录
public boolean shouldShowNotification;//是否显示来电通知
public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
shouldAddToCallLog, boolean shouldShowNotification) {
this.shouldAllowCall = shouldAllowCall;
this.shouldReject = shouldReject;
this.shouldAddToCallLog = shouldAddToCallLog;
this.shouldShowNotification = shouldShowNotification;
}
构建完CallFilteringResult后调用通过callback将结果返回
mCallback.onCallFilteringComplete(mIncomingCall, result);
代码进入到CallsManager的onCallFilteringComplete方法
@Override
public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result) {
// Only set the incoming call as ringing if it isn't already disconnected. It is possible
// that the connection service disconnected the call before it was even added to Telecom, in
// which case it makes no sense to set it back to a ringing state.
if (incomingCall.getState() != CallState.DISCONNECTED &&
incomingCall.getState() != CallState.DISCONNECTING) {
setCallState(incomingCall, CallState.RINGING,
result.shouldAllowCall ? "successful incoming call" : "blocking call");
} else {
Log.i(this, "onCallFilteringCompleted: call already disconnected.");
return;
}
if (result.shouldAllowCall) {
if (hasMaximumRingingCalls()) {
Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"ringing calls.");
rejectCallAndLog(incomingCall);
} else if (hasMaximumDialingCalls()) {
Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
"dialing calls.");
rejectCallAndLog(incomingCall);
} else {
addCall(incomingCall);
}
} else {
if (result.shouldReject) {
Log.i(this, "onCallFilteringCompleted: blocked call, rejecting.");
incomingCall.reject(false, null);
}
if (result.shouldAddToCallLog) {
Log.i(this, "onCallScreeningCompleted: blocked call, adding to call log.");
if (result.shouldShowNotification) {
Log.w(this, "onCallScreeningCompleted: blocked call, showing notification.");
}
mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
result.shouldShowNotification);
} else if (result.shouldShowNotification) {
Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
mMissedCallNotifier.showMissedCallNotification(incomingCall);
}
}
}
这里对回调回来的resulted进行判断,允许通话则调用addCall(incomingCall),拒绝通话调用incomingCall.reject(false, null);
到这里,电话的黑名单判断基本完了。我们来看下短信的
/frameworks/opt/telephony/src/java/com/android/internal/telephony/WapPushOverSms.java
if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
final NotificationInd nInd = (NotificationInd) parsedPdu;
if (nInd.getFrom() != null
&& BlockChecker.isBlocked(mContext, nInd.getFrom().getString())) {
result.statusCode = Intents.RESULT_SMS_HANDLED;
return result;
}
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java
if (BlockChecker.isBlocked(mContext, tracker.getAddress())) {
deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(),
DELETE_PERMANENTLY);
return false;
}
短信方面不是很了解,这两处也只是调用了BlockChecker的isBlocked方法,具体处理流程以后有空再分析。
参考
https://www.cnblogs.com/lance2016/archive/2016/11/06/6035351.html
http://blog.csdn.net/michael_yt/article/details/54573087?utm_source=itdadao&utm_medium=referral