基于Android12實現(xiàn)虛擬來電(無SIM卡)

先看實現(xiàn)后效果

這是來電效果如下:


微信截圖_20240410151009.png

這是接聽來電效果如下:


微信截圖_20240410150943.png

所有來電都會自動在通話記錄里留下數(shù)據(jù)


微信截圖_20240410151234.png

實際效果可以在沒有SIM卡的機器上也能接收來電,實現(xiàn)思路來源于系統(tǒng)源碼 packages/services/Telecomm/testapps, 這個模塊是用來測試通話功能的,基于這個測試代碼我們稍加修改即可達到以上效果.以下大部分操作需要在有完整的系統(tǒng)源碼和機器的root權限的前提下執(zhí)行.

1. 單獨編譯testapps,即可在out目錄下得到TelecomTestApps.apk

mmm packages/services/Telecomm/testapps

2. 將TelecomTestApps.apk push到機器的/system/app目錄下,該操作需要root權限以及掛載system目錄,然后再重啟機器.

 adb push TelecomTestApps.apk /system/app

3. 重啟機器后在桌面可以看到多出很多APP,我們只需要啟動其中的Test Connection Service App即可.

該界面啟動后添加了兩個通知交互UI,效果如下:


微信截圖_20240410162326.png

展開第一個通知可以選擇添加PhoneAccount,這一步也是必須.
展開第二個通知可以選擇Add Call,點擊后即可在上方顯示一個虛擬的來電.

在操作第一步添加PhoneAccount的時候可能在logcat上出現(xiàn)如下錯誤:


微信截圖_20240410154437.png

這說明系統(tǒng)中缺少android.software.connectionservice feature,在系統(tǒng)源碼中找到frameworks/native/data/etc/android.software.connectionservice.xml文件,導入到機器的/vendor/etc/permissions目錄下然后重啟下系統(tǒng)即可.

4.修改testapps2源碼,以契合自己的業(yè)務需求.

目前業(yè)務需求是
1.不顯示任何多余的通知
2.在桌面不顯示任何testapps相關的APP
3.使用廣播來啟動來電

我們先在packages/services/Telecomm目錄下復制一份testapps代碼命名為testapps2,z這樣可以保留一份原始的系統(tǒng)源碼.

我們現(xiàn)在查看下程序入口com.android.server.telecom.testapps.TestCallActivity內容.

  @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        final Intent intent = getIntent();
        final String action = intent != null ? intent.getAction() : null;
        final Uri data = intent != null ? intent.getData() : null;
        if (ACTION_NEW_INCOMING_CALL.equals(action) && data != null) {
            CallNotificationReceiver.sendIncomingCallIntent(this, data,
                    VideoProfile.STATE_AUDIO_ONLY);
        } else if (ACTION_NEW_UNKNOWN_CALL.equals(action) && data != null) {
            CallNotificationReceiver.addNewUnknownCall(this, data, intent.getExtras());
        } else if (ACTION_HANGUP_CALLS.equals(action)) {
            CallNotificationReceiver.hangupCalls(this);
        } else if (ACTION_RTT_CALL.equals(action)) {
            CallNotificationReceiver.sendIncomingRttCallIntent(
                    this, data, VideoProfile.STATE_AUDIO_ONLY);
        } else if (ACTION_SEND_UPGRADE_REQUEST.equals(action)) {
            CallNotificationReceiver.sendUpgradeRequest(this, data);
        } else if (ACTION_REMOTE_RTT_UPGRADE.equals(action)) {
            CallNotificationReceiver.remoteRttUpgrade(this);
        } else {
            CallServiceNotifier.getInstance().updateNotification(this);
        }
        finish();
    }

這部分代碼是顯示通知以及響應通知回調,這塊不用注重查看.關鍵位置在com.android.server.telecom.testapps.CallNotificationReceiver中,查看下核心代碼:

 @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ACTION_CALL_SERVICE_EXIT.equals(action)) {
            CallServiceNotifier.getInstance().cancelNotifications(context);
        } else if (ACTION_REGISTER_PHONE_ACCOUNT.equals(action)) {
            CallServiceNotifier.getInstance().registerPhoneAccount(context);
        } else if (ACTION_SHOW_ALL_PHONE_ACCOUNTS.equals(action)) {
            CallServiceNotifier.getInstance().showAllPhoneAccounts(context);
        } else if (ACTION_ONE_WAY_VIDEO_CALL.equals(action)) {
            sendIncomingCallIntent(context, null, VideoProfile.STATE_RX_ENABLED);
        } else if (ACTION_TWO_WAY_VIDEO_CALL.equals(action)) {
            sendIncomingCallIntent(context, null, VideoProfile.STATE_BIDIRECTIONAL);
        } else if (ACTION_RTT_CALL.equals(action)) {
            sendIncomingRttCallIntent(context, null, VideoProfile.STATE_AUDIO_ONLY);
        } else if (ACTION_AUDIO_CALL.equals(action)) {
            sendIncomingCallIntent(context, null, VideoProfile.STATE_AUDIO_ONLY);
        }
    }

public static void sendIncomingCallIntent(Context context, Uri handle, int videoState) {
        PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
                new ComponentName(context, TestConnectionService.class),
                CallServiceNotifier.SIM_SUBSCRIPTION_ID);

        // For the purposes of testing, indicate whether the incoming call is a video call by
        // stashing an indicator in the EXTRA_INCOMING_CALL_EXTRAS.
        Bundle extras = new Bundle();
        extras.putInt(TestConnectionService.EXTRA_START_VIDEO_STATE, videoState);
        if (handle != null) {
            extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
        }

        TelecomManager.from(context).addNewIncomingCall(phoneAccount, extras);
    }

以上只貼出了SIM卡的語音通話響應,其他類型可自行研究,由以上代碼可知添加PhoneAccount和響應Add Call是由

CallServiceNotifier.getInstance().registerPhoneAccount(context);
sendIncomingCallIntent(context, null, VideoProfile.STATE_AUDIO_ONLY);

這兩個函數(shù)實現(xiàn)的,那么事情就簡單了,我們可以在testapps2中添加一個廣播接收器MyBroadcastReceiver.java
用于接收我們自定義的廣播,當接受到自定義廣播后調用以上兩個函數(shù)即可觸發(fā)來電顯示.

關于如何處理去掉多余的桌面APP和移除通知,我們只需將APP的清單文件中包含<action android:name="android.intent.action.MAIN"/>的組件全部注釋掉即可.

如何在系統(tǒng)源碼全量編譯時自動將我們修改的APP編譯進鏡像中:

1.修改testapps/android.bp 中的android_test { .... } 為 android_app { .... },并且注釋掉APP清單文件中的<uses-library android:name="android.test.runner"/>,修改android_app {} 中的模塊名稱為TelecomTestApps2,這是為了防止編譯時與之前備份的testapp出現(xiàn)同名模塊沖突.

2.在device/廠商/廠商.mk中添加如下

PRODUCT_PACKAGES += \
              TelecomTestApps2

PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.software.connectionservice.xml:vendor/etc/permissions/android.software.connectionservice.xml

完成以上步驟后進行全量編譯即可.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容