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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章