源码整理,移除通话录音功能。
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 "未知状态";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user