美文网首页
Android8.1 源码添加黑名单拦截电话和短信记录

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

作者: cczhengv | 来源:发表于2019-07-08 18:13 被阅读0次

    知识储备

    • 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<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 调用所有需要解决这个权限问题

    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 中增加声明

    <provider android:name="InterceptInfoProvider"
            android:authorities="com.android.blockeddata"
            android:multiprocess="false"
            android:exported="true">
    </provider>
    
    

    好了,这样拦截记录 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 源码添加黑名单拦截电话和短信记录

          本文链接:https://www.haomeiwen.com/subject/gnbahctx.html