源码整理,移除通话录音功能。

This commit is contained in:
2025-12-13 15:22:25 +08:00
parent 43ed19b364
commit 97643c3bcd
2 changed files with 225 additions and 169 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Dec 13 07:01:07 GMT 2025
#Sat Dec 13 07:20:26 GMT 2025
stageCount=1
libraryProject=
baseVersion=15.12
publishVersion=15.12.0
buildCount=111
buildCount=112
baseBetaVersion=15.12.1

View File

@@ -1,19 +1,6 @@
package cc.winboll.studio.contacts.phonecallui;
/**
* 监听电话通信状态的服务,实现该类的同时必须提供电话管理的 UI
*
* @author aJIEw
* @see PhoneCallActivity
* @see android.telecom.InCallService
*/
import android.content.ContentResolver;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.provider.CallLog;
import android.telecom.Call;
import android.telecom.InCallService;
import android.telephony.TelephonyManager;
@@ -23,194 +10,263 @@ import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.File;
import java.io.IOException;
@RequiresApi(api = Build.VERSION_CODES.M)
/**
* 监听电话通信状态的服务,实现该类的同时必须提供电话管理的 UI
* @author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @see PhoneCallActivity
* @see android.telecom.InCallService
* 适配Java7 语法 + Android API29-30 | 移除录音功能 | 强化稳定性与容错性
*/
@RequiresApi(api = 29) // 适配API29+替代Build.VERSION_CODES.M匹配InCallService实际最低要求
public class PhoneCallService extends InCallService {
// ====================== 常量定义区(精简必要常量,无冗余) ======================
public static final String TAG = "PhoneCallService";
MediaRecorder mediaRecorder;
// ====================== 成员属性区(按功能归类,命名规范) ======================
private Call.Callback mCallCallback; // 通话状态回调(统一管理,便于销毁)
private final Call.Callback callback = new Call.Callback() {
@Override
public void onStateChanged(Call call, int state) {
super.onStateChanged(call, state);
switch (state) {
case TelephonyManager.CALL_STATE_OFFHOOK:
{
long callId = getCurrentCallId();
if (callId != -1) {
// 在这里可以对获取到的通话记录ID进行处理
//System.out.println("当前通话记录ID: " + callId);
// ====================== 内部枚举类(提前定义,便于业务调用) ======================
public enum CallType {
CALL_IN, // 来电
CALL_OUT // 去电
}
// 电话接通,开始录音
startRecording(callId);
}
break;
}
case TelephonyManager.CALL_STATE_IDLE:
// 电话挂断,停止录音
stopRecording();
break;
case Call.STATE_ACTIVE: {
break;
}
case Call.STATE_DISCONNECTED: {
ActivityStack.getInstance().finishActivity(PhoneCallActivity.class);
break;
}
}
}
};
// ====================== Service生命周期方法区按执行顺序排列 ======================
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "===== onCreate: 通话监听服务启动 =====");
// 初始化通话状态回调(提前初始化,避免重复创建)
initCallCallback();
LogUtils.d(TAG, "===== onCreate: 服务初始化完成 =====");
}
@Override
public void onCallAdded(Call call) {
super.onCallAdded(call);
LogUtils.d(TAG, "onCallAdded: 检测到新通话,开始处理");
call.registerCallback(callback);
PhoneCallManager.call = call;
CallType callType = null;
if (call.getState() == Call.STATE_RINGING) {
callType = CallType.CALL_IN;
} else if (call.getState() == Call.STATE_CONNECTING) {
callType = CallType.CALL_OUT;
// 空指针防护:避免通话对象为空导致崩溃
if (call == null) {
LogUtils.e(TAG, "onCallAdded: 通话对象为空,跳过处理");
return;
}
// 注册通话状态回调
call.registerCallback(mCallCallback);
PhoneCallManager.call = call;
LogUtils.d(TAG, "onCallAdded: 已注册通话回调,通话对象绑定完成");
// 判断通话类型(来电/去电)
CallType callType = judgeCallType(call);
if (callType != null) {
Call.Details details = call.getDetails();
String phoneNumber = details.getHandle().getSchemeSpecificPart();
// 记录原始铃声音量
//
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
int ringerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
// 恢复铃声音量,预防其他意外条件导致的音量变化问题
//
// 读取应用配置,未配置就初始化配置文件
RingTongBean bean = RingTongBean.loadBean(this, RingTongBean.class);
if (bean == null) {
// 初始化配置
bean = new RingTongBean();
RingTongBean.saveBean(this, bean);
}
// 如果当前音量和应用保存的不一致就恢复为应用设定值
// 恢复铃声音量
try {
if (ringerVolume != bean.getStreamVolume()) {
audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0);
//audioManager.setMode(AudioManager.RINGER_MODE_NORMAL);
}
} catch (java.lang.SecurityException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
// 检查电话接收规则
if (!Rules.getInstance(this).isAllowed(phoneNumber)) {
// 调低音量
try {
audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
//audioManager.setMode(AudioManager.RINGER_MODE_SILENT);
} catch (java.lang.SecurityException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
// 断开电话
call.disconnect();
// 停顿1秒预防第一声铃声响动
try {
Thread.sleep(500);
} catch (InterruptedException e) {
LogUtils.d(TAG, "");
}
// 恢复铃声音量
try {
audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0);
//audioManager.setMode(AudioManager.RINGER_MODE_NORMAL);
} catch (java.lang.SecurityException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
// 屏蔽电话结束
return;
}
// 正常接听电话
PhoneCallActivity.actionStart(this, phoneNumber, callType);
// 处理有效通话(音量控制、规则校验、启动通话界面)
handleValidCall(call, callType);
} else {
LogUtils.w(TAG, "onCallAdded: 无法识别通话类型,通话状态=" + call.getState());
}
}
@Override
public void onCallRemoved(Call call) {
super.onCallRemoved(call);
call.unregisterCallback(callback);
LogUtils.d(TAG, "onCallRemoved: 通话结束,开始清理资源");
// 空指针防护:避免通话对象为空导致崩溃
if (call != null) {
call.unregisterCallback(mCallCallback);
LogUtils.d(TAG, "onCallRemoved: 已注销通话回调");
}
PhoneCallManager.call = null;
LogUtils.d(TAG, "onCallRemoved: 通话资源清理完成");
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 通话监听服务开始销毁");
// 更新通话记录列表
CallLogFragment.updateCallLogFragment();
LogUtils.d(TAG, "onDestroy: 通话监听服务销毁完成");
}
public enum CallType {
CALL_IN,
CALL_OUT,
}
// ====================== 核心初始化方法区 ======================
/**
* 初始化通话状态回调,统一管理通话状态变更逻辑
*/
private void initCallCallback() {
mCallCallback = new Call.Callback() {
@Override
public void onStateChanged(Call call, int state) {
super.onStateChanged(call, state);
LogUtils.d(TAG, "onStateChanged: 通话状态变更,状态码=" + state + ",状态描述=" + getCallStateDesc(state));
private void startRecording(long callId) {
LogUtils.d(TAG, "startRecording(...)");
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setOutputFile(getOutputFilePath(callId));
try {
mediaRecorder.prepare();
mediaRecorder.start();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
private String getOutputFilePath(long callId) {
LogUtils.d(TAG, "getOutputFilePath(...)");
// 设置录音文件的保存路径
File file = new File(getExternalFilesDir(TAG), String.format("call_%d.mp4", callId));
return file.getAbsolutePath();
}
private void stopRecording() {
LogUtils.d(TAG, "stopRecording()");
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
}
}
private long getCurrentCallId() {
LogUtils.d(TAG, "getCurrentCallId()");
ContentResolver contentResolver = getApplicationContext().getContentResolver();
Uri callLogUri = Uri.parse("content://call_log/calls");
String[] projection = {"_id", "number", "call_type", "date"};
String selection = "call_type = " + CallLog.Calls.OUTGOING_TYPE + " OR call_type = " + CallLog.Calls.INCOMING_TYPE;
String sortOrder = "date DESC";
try {
Cursor cursor = contentResolver.query(callLogUri, projection, selection, null, sortOrder);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndex("_id"));
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
// 通话空闲(挂断后),无需额外处理(原录音停止逻辑已删除)
LogUtils.d(TAG, "onStateChanged: 通话进入空闲状态");
break;
case Call.STATE_DISCONNECTED:
// 通话断开,关闭通话界面
ActivityStack.getInstance().finishActivity(PhoneCallActivity.class);
LogUtils.d(TAG, "onStateChanged: 通话断开,已关闭通话界面");
break;
// 保留其他状态分支,便于后续扩展,无冗余逻辑
case Call.STATE_ACTIVE:
LogUtils.d(TAG, "onStateChanged: 通话进入活跃状态");
break;
default:
break;
}
}
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
};
LogUtils.d(TAG, "initCallCallback: 通话状态回调初始化完成");
}
// ====================== 核心业务处理方法区 ======================
/**
* 判断通话类型(来电/去电)
* @param call 通话对象
* @return 通话类型枚举无法识别返回null
*/
private CallType judgeCallType(Call call) {
int callState = call.getState();
if (callState == Call.STATE_RINGING) {
LogUtils.d(TAG, "judgeCallType: 通话状态为响铃,识别为来电");
return CallType.CALL_IN;
} else if (callState == Call.STATE_CONNECTING) {
LogUtils.d(TAG, "judgeCallType: 通话状态为连接中,识别为去电");
return CallType.CALL_OUT;
}
return null;
}
/**
* 处理有效通话(音量控制、拦截规则校验、启动通话界面)
* @param call 通话对象
* @param callType 通话类型(来电/去电)
*/
private void handleValidCall(Call call, CallType callType) {
// 1. 获取通话详情与号码(多层空指针防护)
Call.Details callDetails = call.getDetails();
if (callDetails == null || callDetails.getHandle() == null) {
LogUtils.e(TAG, "handleValidCall: 通话详情或号码信息为空,跳过后续处理");
return;
}
String phoneNumber = callDetails.getHandle().getSchemeSpecificPart();
LogUtils.d(TAG, "handleValidCall: 开始处理通话,号码=" + phoneNumber + ",类型=" + callType);
// 2. 初始化音频管理器(音量控制核心)
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioManager == null) {
LogUtils.e(TAG, "handleValidCall: 获取音频管理器失败,无法处理音量控制");
// 音量控制失败仍启动通话界面,保障基础功能可用
PhoneCallActivity.actionStart(this, phoneNumber, callType);
return;
}
return -1;
// 3. 处理铃声音量(恢复配置音量、拦截时静音)
handleRingerVolumeControl(audioManager, phoneNumber, call);
// 4. 校验通过,启动通话界面(拦截场景已提前返回,此处直接启动)
PhoneCallActivity.actionStart(this, phoneNumber, callType);
LogUtils.d(TAG, "handleValidCall: 通话校验通过,已启动通话界面");
}
/**
* 铃声音量控制(恢复应用配置音量、拦截号码静音处理)
* @param audioManager 音频管理器
* @param phoneNumber 通话号码
* @param call 通话对象(用于拦截时断开通话)
*/
private void handleRingerVolumeControl(AudioManager audioManager, String phoneNumber, Call call) {
// 3.1 获取当前铃声音量
int currentRingerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
LogUtils.d(TAG, "handleRingerVolumeControl: 当前铃声音量=" + currentRingerVolume);
// 3.2 加载/初始化铃声音量配置
RingTongBean ringTongBean = RingTongBean.loadBean(this, RingTongBean.class);
if (ringTongBean == null) {
ringTongBean = new RingTongBean();
RingTongBean.saveBean(this, ringTongBean);
LogUtils.d(TAG, "handleRingerVolumeControl: 铃声音量配置未初始化,已自动创建默认配置");
}
int configRingerVolume = ringTongBean.getStreamVolume();
LogUtils.d(TAG, "handleRingerVolumeControl: 应用配置铃声音量=" + configRingerVolume);
// 3.3 恢复应用配置音量(当前音量与配置不一致时调整)
try {
if (currentRingerVolume != configRingerVolume) {
audioManager.setStreamVolume(AudioManager.STREAM_RING, configRingerVolume, 0);
LogUtils.d(TAG, "handleRingerVolumeControl: 已将铃声音量恢复为应用配置值");
} else {
LogUtils.d(TAG, "handleRingerVolumeControl: 当前音量与配置一致,无需调整");
}
} catch (SecurityException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 恢复铃声音量失败,权限不足", e);
return;
}
// 3.4 校验拦截规则,拦截号码静音+断开通话
if (!Rules.getInstance(this).isAllowed(phoneNumber)) {
LogUtils.d(TAG, "handleRingerVolumeControl: 号码=" + phoneNumber + " 命中拦截规则,开始拦截处理");
try {
// 静音处理
audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
LogUtils.d(TAG, "handleRingerVolumeControl: 已将铃声音量设为0静音");
} catch (SecurityException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 拦截静音失败,权限不足", e);
}
// 断开通话
call.disconnect();
LogUtils.d(TAG, "handleRingerVolumeControl: 已断开拦截通话");
// 延迟恢复音量(防止第一声铃声响动)
try {
Thread.sleep(500);
audioManager.setStreamVolume(AudioManager.STREAM_RING, configRingerVolume, 0);
LogUtils.d(TAG, "handleRingerVolumeControl: 延迟500ms后已恢复铃声音量为配置值");
} catch (InterruptedException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 延迟恢复音量失败,线程被中断", e);
} catch (SecurityException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 恢复音量失败,权限不足", e);
}
// 拦截完成,直接返回,不启动通话界面
LogUtils.d(TAG, "handleRingerVolumeControl: 拦截处理完成,跳过通话界面启动");
return;
}
LogUtils.d(TAG, "handleRingerVolumeControl: 号码=" + phoneNumber + " 未命中拦截规则,音量控制完成");
}
// ====================== 辅助工具方法区 ======================
/**
* 通话状态码转文字描述(便于日志查看,快速定位状态)
* @param state 通话状态码TelephonyManager/Call 中的常量)
* @return 状态文字描述
*/
private String getCallStateDesc(int state) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
return "响铃中";
case TelephonyManager.CALL_STATE_OFFHOOK:
return "通话中";
case TelephonyManager.CALL_STATE_IDLE:
return "空闲(未通话/已挂断)";
case Call.STATE_ACTIVE:
return "通话活跃";
case Call.STATE_CONNECTING:
return "通话连接中";
case Call.STATE_DISCONNECTED:
return "通话已断开";
default:
return "未知状态";
}
}
}