Android8.1 源码添加黑名单拦截电话和短信记录

知识储备

  • 1、 8.1 原生黑名单功能

之前写过的 8.1 黑名单相关分析可看这篇 Android8.1 源码修改之通过黑名单屏蔽系统短信功能和来电功能

  • 2、 ContentProvider 的相关定义和使用

不太懂的可看这篇 Android:关于ContentProvider的知识都在这里了!

开始修改

1、黑名单的增、删、查

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 queryBlockedNumberList(Context context){
    List 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 调用所有需要解决这个权限问题

2、绕过黑名单权限问题

跟踪上面的异常信息,找到了异常的位置

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 异常。

3、增加拦截记录 ContentProvider

通过上面的两步我们的 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 中增加声明




好了,这样拦截记录 ContentProvider 就搞定了

4、来电拦截记录插入

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...");
}

5、短信拦截记录插入

通过抓取系统日志,发现短信收取的打印的地方

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...");
}

你可能感兴趣的:(Android8.1 源码添加黑名单拦截电话和短信记录)