之前写过的 8.1 黑名单相关分析可看这篇 Android8.1 源码修改之通过黑名单屏蔽系统短信功能和来电功能
不太懂的可看这篇 Android:关于ContentProvider的知识都在这里了!
7.0 开始系统提供了 BlockedNumberContract 类,方便上层 app 操作黑名单数据库,但这是有要求的,app 必须是系统级别的 APP 或默认的电话 APP 或默认的短信 APP
源码位置 packages\providers\BlockedNumberProvider\src\com\android\providers\blockednumber\BlockedNumberProvider.java
这个类就是操作黑名单数据库的 ContentProvider,但其中并不包含拦击记录的表,这就需要我们自己增加了
这里提供一些给 app 调用的增、删、查方法
/**
* 添加号码到黑名单数据库中 要求 minSdkVersion >=24
* @param context
* @param number
*/
public void addBlockedNumber(Context context, String number){
ContentResolver contentResolver = context.getContentResolver();
ContentValues newValues = new ContentValues();
newValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number);
contentResolver.insert(BlockedNumberContract.BlockedNumbers.CONTENT_URI, newValues);
}
/**
* 移除黑名单号码
* @param context
* @param number
*/
public void deleteBlockedNumber(Context context, String number) {
ContentResolver contentResolver = context.getContentResolver();
contentResolver.delete(BlockedNumberContract.BlockedNumbers.CONTENT_URI,
BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?",
new String[] {number});
}
/**
* 查询黑名单号码列表
* @param context
* @return
*/
public List<String> queryBlockedNumberList(Context context){
List<String> blockedNumberList = new ArrayList<>();
blockedNumberList.clear();
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(BlockedNumberContract.BlockedNumbers.CONTENT_URI,
new String[]{BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER},
null, null, null);
if (cursor != null){
while (cursor.moveToNext()){
String blockedNumber = cursor.getString(cursor.getColumnIndex(
BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER));
blockedNumberList.add(blockedNumber);
}
cursor.close();
}
return blockedNumberList;
}
当你在普通的上层 app 中调用以上任意方法时,你会发现如下错误 java.lang.SecurityException: Caller must be system, default dialer or default SMS app
Caused by: java.lang.SecurityException: Caller must be system, default dialer or default SMS app
at android.os.Parcel.readException(Parcel.java:2005)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.insert(ContentProviderNative.java:476)
at android.content.ContentResolver.insert(ContentResolver.java:1539)
at com.androiddemo.util.BlockedNumberHelper.addBlockedNumber(BlockedNumberHelper.java:58)
at com.androiddemo.activity.InCallActivity.onCreate(InCallActivity.java:125)
at android.app.Activity.performCreate(Activity.java:7023)
at android.app.Activity.performCreate(Activity.java:7014)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2758)
当然如果你是给系统的 app 调用就不存在这个问题,本文是为了给直接安装的 app 调用所有需要解决这个权限问题
跟踪上面的异常信息,找到了异常的位置
packages\providers\BlockedNumberProvider\src\com\android\providers\blockednumber\BlockedNumberProvider.java
private void throwSecurityException() {
throw new SecurityException("Caller must be system, default dialer or default SMS app");
}
查看代码中发现调用 throwSecurityException()有两个地方,分别是 checkForPermission() 和 enforceSystemPermissionAndUser()
通过打印日志发现异常是通过 checkForPermission() 调用的
private void checkForPermission(String permission) {
boolean permitted = passesSystemPermissionCheck(permission)
|| checkForPrivilegedApplications() || isSelf();
if (!permitted) {
throwSecurityException();
}
}
分析可知道 permitted = false, 分别查看三个方法的方法体,passesSystemPermissionCheck() 检查 app 是否已经授权,
android.Manifest.permission.READ_BLOCKED_NUMBERS,android.Manifest.permission.WRITE_BLOCKED_NUMBERS,这两权限你在 Manifest.xml 中声明了也是没用的
isSelf() 检查是否是当前进程调用,看来只能在 checkForPrivilegedApplications() 下手了。
private boolean checkForPrivilegedApplications() {
if (Binder.getCallingUid() == Process.ROOT_UID) {
return true;
}
final String callingPackage = getCallingPackage();
Log.e("ccz","checkForPrivilegedApplications callingPackage=="+callingPackage);
if (TextUtils.isEmpty(callingPackage)) {
Log.w(TAG, "callingPackage not accessible");
} else if (callingPackage.contains("xxx")) {//add for xxx app can use block database
return true;
} else {
final TelecomManager telecom = getContext().getSystemService(TelecomManager.class);
if (callingPackage.equals(telecom.getDefaultDialerPackage())
|| callingPackage.equals(telecom.getSystemDialerPackage())) {
return true;
}
final AppOpsManager appOps = getContext().getSystemService(AppOpsManager.class);
if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS,
Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED) {
return true;
}
final TelephonyManager telephonyManager =
getContext().getSystemService(TelephonyManager.class);
return telephonyManager.checkCarrierPrivilegesForPackage(callingPackage) ==
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
return false;
}
正好里面直接获取到了调用 app 的包名,我们可以添加一个包名白名单来过滤,callingPackage.contains(“xxx”) return true 即可,
这样 permitted = true, 就绕过了 Caller must be system, default dialer or default SMS app 异常。
通过上面的两步我们的 app 已经能正常操作黑名单数据库,接下来我们再添加一个拦截记录数据库
packages\providers\BlockedNumberProvider\src\com\android\providers\blockednumber\BlockedNumberDatabaseHelper.java
private static final String DATABASE_NAME = "blockednumbers.db";
public interface Tables {
String BLOCKED_NUMBERS = "blocked";//原来的黑名单数据表
String BLOCKED_INTERCEPT = "intercepted";//add
}
private void createTables(SQLiteDatabase db) {
....
db.execSQL("CREATE TABLE " + Tables.BLOCKED_INTERCEPT + " (" +
" id INTEGER PRIMARY KEY AUTOINCREMENT," +
" type INTEGER ," +
" number INTEGER ," +
" content TEXT ," +
" time TEXT" +
")");//add
....
}
在同级目录下增加 InterceptInfoProvider.java
package com.android.providers.blockednumber;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
/**
* Created by cczheng
*/
public class InterceptInfoProvider extends ContentProvider {
static final String TAG = "InterceptInfos";
public static final String AUTHORITY = "com.android.blockeddata";
public static final int User_Code = 2000;
public static final Uri CONTENT_URI = Uri.withAppendedPath(Uri.parse("content://" + AUTHORITY),
"intercept");
private static final UriMatcher mMatcher;
static{
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(AUTHORITY,"intercept", User_Code);
}
protected BlockedNumberDatabaseHelper mDbHelper;
@Override
public boolean onCreate() {
mDbHelper = BlockedNumberDatabaseHelper.getInstance(getContext());
return true;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Uri blockedUri = insertInterceptInfo(values);
getContext().getContentResolver().notifyChange(blockedUri, null);
Log.e(TAG, "insertInterceptInfo()....");
return blockedUri;
}
private Uri insertInterceptInfo(ContentValues values) {
final long id = mDbHelper.getWritableDatabase().insertWithOnConflict(
BlockedNumberDatabaseHelper.Tables.BLOCKED_INTERCEPT, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
return ContentUris.withAppendedId(CONTENT_URI, id);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
int ret = db.delete(BlockedNumberDatabaseHelper.Tables.BLOCKED_INTERCEPT, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return ret;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Cursor cursor = queryInterceptTelSmsData(projection, selection, selectionArgs, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
private Cursor queryInterceptTelSmsData(String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setStrict(true);
qb.setTables(BlockedNumberDatabaseHelper.Tables.BLOCKED_INTERCEPT);
return qb.query(mDbHelper.getReadableDatabase(), projection, selection, selectionArgs,
/* groupBy =*/ null, /* having =*/null, sortOrder,
/* limit =*/ null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
再在 AndroidManifest.xml 中增加声明
<provider android:name="InterceptInfoProvider"
android:authorities="com.android.blockeddata"
android:multiprocess="false"
android:exported="true">
</provider>
好了,这样拦截记录 ContentProvider 就搞定了
vendor\mediatek\proprietary\packages\services\Telecomm\src\com\android\server\telecom\callfiltering\AsyncBlockCheckFilter.java
AsyncBlockCheckFilter 中调用 BlockCheckerAdapter 的isBlocked()判断是否是黑名单,可在此处将拦截的电话插入数据库中
@Override
protected Boolean doInBackground(String... params) {
try {
Log.continueSession(mBackgroundTaskSubsession, "ABCF.dIB");
Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_INITIATED);
blockedNumber = params[0];// add
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) {
android.util.Log.e("InterceptInfos","blockedNumber=="+blockedNumber + " start add db..");
addInterceptNumber();//add
result = new CallFilteringResult(
false, // shouldAllowCall
true, //shouldReject
false, //shouldAddToCallLog
false // shouldShowNotification
);
} else {
result = new CallFilteringResult(
true, // shouldAllowCall
false, // shouldReject
true, // shouldAddToCallLog
true // shouldShowNotification
);
}
Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_FINISHED, result);
mCallback.onCallFilteringComplete(mIncomingCall, result);
} finally {
Log.endSession();
}
}
private String blockedNumber;//add
//add for intetcept telinfo to db
public void addInterceptNumber(){
android.content.ContentResolver contentResolver = mContext.getContentResolver();
android.content.ContentValues newValues = new android.content.ContentValues();
newValues.put("type", 1);//TEL
newValues.put("number", blockedNumber);//number
newValues.put("content", "");
java.text.SimpleDateFormat simpleDateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Date date = new java.util.Date(System.currentTimeMillis());
String time = simpleDateFormat.format(date);
newValues.put("time", time);
contentResolver.insert(android.net.Uri.parse("content://com.android.blockeddata/intercept"), newValues);
android.util.Log.e("InterceptInfos","add db end...");
}
通过抓取系统日志,发现短信收取的打印的地方
E InterceptInfos: address=01245713456684544 block5=true
07-06 10:28:49.194 1776 1970 D PPL/PplSmsFilterExtension: pplFilter(Bundle[{smsType=0, format=3gpp, pdus=[[B@eded288, subId=1}])
07-06 10:28:49.195 1776 1970 D PPL/PplSmsFilterExtension: subId = 1. simId = 0
07-06 10:28:49.196 1776 1970 D PPL/PplSmsFilterExtension: pplFilter: pdus length 1
07-06 10:28:49.196 1776 1970 D PPL/PplSmsFilterExtension: pplFilter: message content is 【百度帐号】验证码:433578 。您正在使用注册功能,该验证码仅用于身份验证,请勿泄露给他人使用。
07-06 10:28:49.198 457 479 I PPL/PPLAgent: OnTransact(1,16)
07-06 10:28:49.198 457 479 I PPL/PPLAgent: readControlData enter
07-06 10:28:49.198 457 479 W Parcel : **** enforceInterface() expected 'PPLAgent' but read 'com.mediatek.internal.telephony.ppl.IPplAgent'
07-06 10:28:49.198 457 479 I PPL/PPLAgent: enforceInterface fail
07-06 10:28:49.198 457 479 I PPL/PPLAgent: readControlData enter
07-06 10:28:49.198 457 479 D PPL/PPLAgent: open control data file error = No such file or directory
07-06 10:28:49.198 457 479 I PPL/PPLAgent: readControlData exit
vendor\mediatek\proprietary\frameworks\opt\telephony\src\java\com\mediatek\internal\telephony\ppl\PplSmsFilterExtension.java
vendor\mediatek\proprietary\frameworks\opt\telephony\src\java\com\mediatek\internal\telephony\cdma\MtkCdmaInboundSmsHandler.java
vendor\mediatek\proprietary\frameworks\opt\telephony\src\java\com\mediatek\internal\telephony\gsm\MtkGsmInboundSmsHandler.java
短信打印内容来自 PplSmsFilterExtension 中,但并没有发现短信号码, 短信号码在 MtkCdmaInboundSmsHandler 和 MtkGsmInboundSmsHandler 中,用于判断是否是黑名单
我们可以将号码传递到 PplSmsFilterExtension 中,然后再进行判断,最后再写入数据库
MtkCdmaInboundSmsHandler 和 MtkGsmInboundSmsHandler 大体的方法都是一致的,应该是给不同的运营商提供的,这里就只提供一个的修改了,另一个类似
@Override
protected boolean processMessagePart(InboundSmsTracker tracker) {
int messageCount = tracker.getMessageCount();
byte[][] pdus;
int destPort = tracker.getDestPort();
boolean block = false;
if (messageCount == 1) {//单条的情况,未拆分
// single-part message
pdus = new byte[][]{tracker.getPdu()};
block = BlockChecker.isBlocked(mContext, tracker.getDisplayAddress());
//add
addressNumber = tracker.getDisplayAddress();
android.util.Log.e("InterceptInfos","address="+ addressNumber + " block5="+block);
} else {//拆分的情况
....
// check if display address should be blocked or not
if (!block) {
// Depending on the nature of the gateway, the display
// origination address is either derived from the content of
// the SMS TP-OA field, or the TP-OA field contains a generic gateway
// address and the from address is added at the beginning
// in the message body. In that case onlythe first SMS
// (part of Multi-SMS) comes with the display originating address
// which could be used for block checking purpose.
block = BlockChecker.isBlocked(mContext,
cursor.getString(PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING
.get(DISPLAY_ADDRESS_COLUMN)));
//add
addressNumber = cursor.getString(PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING
.get(DISPLAY_ADDRESS_COLUMN));
android.util.Log.e("InterceptInfos","addressNumber=="+ addressNumber +" block6="+block);
}
}
....
}
//add for get number
private String addressNumber;
/**
* Phone Privacy Lock check if this MT sms has permission to dispatch
*/
protected int phonePrivacyLockCheck(byte[][] pdus, String format) {
int checkResult = PackageManager.PERMISSION_GRANTED;
if (MtkSmsCommonEventHelper.isPrivacyLockSupport()) {
/* CTA-level3 for phone privacy lock */
if (checkResult == PackageManager.PERMISSION_GRANTED) {
if (mPplSmsFilter == null) {
mPplSmsFilter = new PplSmsFilterExtension(mContext);
}
Bundle pplData = new Bundle();
pplData.putSerializable(mPplSmsFilter.KEY_PDUS, pdus);
pplData.putString(mPplSmsFilter.KEY_FORMAT, format);
pplData.putInt(mPplSmsFilter.KEY_SUB_ID, mPhone.getSubId());
pplData.putInt(mPplSmsFilter.KEY_SMS_TYPE, 0);
boolean pplResult = false;
//add 将短信号码传递给 PplSmsFilterExtension
mPplSmsFilter.setAddressNumber(addressNumber);
pplResult = mPplSmsFilter.pplFilter(pplData);
if (ENG) {
log("[Ppl] Phone privacy check end, Need to filter(result) = "
+ pplResult);
}
if (pplResult == true) {
checkResult = PackageManager.PERMISSION_DENIED;
}
}
}
return checkResult;
}
PplSmsFilterExtension 类的修改
import com.android.internal.telephony.BlockChecker;
// add for get number
private String addressNumber;
private Context mContext;
public void setAddressNumber(String addressNumber){
this.addressNumber = addressNumber;
}
public PplSmsFilterExtension(Context context) {
super(context);
mContext = context;
....
}
@Override
public boolean pplFilter(Bundle params) {
Log.d(TAG, "pplFilter(" + params + ")");
....
if (messages == null) {
content = params.getString(KEY_MSG_CONTENT);
src = params.getString(KEY_SRC_ADDR);
dst = params.getString(KEY_DST_ADDR);
Log.d(TAG, "pplFilter: Read msg directly and content is " + content);
} else {
byte[][] pdus = new byte[messages.length][];
for (int i = 0; i < messages.length; i++) {
pdus[i] = (byte[]) messages[i];
}
int pduCount = pdus.length;
if (pduCount > 1) {
Log.d(TAG, "pplFilter return false: ppl sms is short msg, count should <= 1 ");
return false;
}
MtkSmsMessage[] msgs = new MtkSmsMessage[pduCount];
for (int i = 0; i < pduCount; i++) {
msgs[i] = MtkSmsMessage.createFromPdu(pdus[i], format);
}
Log.d(TAG, "pplFilter: pdus length " + pdus.length);
if (msgs[0] == null) {
Log.d(TAG, "pplFilter returns false: message is null");
return false;
}
content = msgs[0].getMessageBody();
Log.d(TAG, "pplFilter: message address is " + addressNumber);
Log.d(TAG, "pplFilter: message content is " + content);
src = msgs[0].getOriginatingAddress();
dst = msgs[0].getDestinationAddress();
//add for intetcept smsinfo to db
if (BlockChecker.isBlocked(mContext, addressNumber)) {
android.util.Log.e("InterceptInfos","blockedNumber=="+addressNumber + " start add db sms..");
addInterceptSms(content);
}
}
....
}
//add for intetcept smsinfo to db
public void addInterceptSms(String content){
android.content.ContentResolver contentResolver = mContext.getContentResolver();
android.content.ContentValues newValues = new android.content.ContentValues();
newValues.put("type", 0);//sms
newValues.put("number", addressNumber);
newValues.put("content", content);
java.text.SimpleDateFormat simpleDateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Date date = new java.util.Date(System.currentTimeMillis());
String time = simpleDateFormat.format(date);
newValues.put("time", time);
contentResolver.insert(android.net.Uri.parse("content://com.android.blockeddata/intercept"), newValues);
android.util.Log.e("InterceptInfos","add db end sms...");
}
通过测试拨打紧急电话后5分钟内,黑名单功能会失效。BlockChecker.isBlocked() 不生效
需要屏蔽紧急回拨模式
frameworks/opt/telephony/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java
@Override
protected Void doInBackground(Void... params) {
try {
- BlockedNumberContract.SystemContract.notifyEmergencyContact(mContext);
+ //annotaion notifyEmergencyContact make sure blocknumber is normal
+ //BlockedNumberContract.SystemContract.notifyEmergencyContact(mContext);
+ android.util.Log.e("AsyncEmergencyContactNotifier", "don't notifyEmergencyContact mdoe");
} catch (Exception e) {
Rlog.e(TAG, "Exception notifying emergency contact: " + e);
}