恢复2a74fd2c304b571ab5ae349ffc3b7f06c5b4daf7提交点历史

This commit is contained in:
2025-12-13 15:09:04 +08:00
parent 9a873bf162
commit 43ed19b364
3 changed files with 264 additions and 503 deletions

View File

@@ -8,28 +8,25 @@ import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.contacts.ActivityStack;
import cc.winboll.studio.contacts.MainActivity;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.listenphonecall.CallListenerService;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.Timer;
import java.util.TimerTask;
/**
* @Author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe 接打电话界面,仅支持 Android 6.0API 23及以上系统
*/
// Java7不支持@RequiresApi通过代码内校验替代注解
public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener {
// ====================== 常量定义区 ======================
public static final String TAG = "PhoneCallActivity";
// Intent传参键常量替代系统常量避免歧义
private static final String EXTRA_CALL_TYPE = "extra_call_type";
private static final String EXTRA_PHONE_NUM = "extra_phone_number";
import static cc.winboll.studio.contacts.listenphonecall.CallListenerService.formatPhoneNumber;
/**
* 提供接打电话的界面,仅支持 Android M (6.0, API 23) 及以上的系统
*
* @author aJIEw
*/
@RequiresApi(api = Build.VERSION_CODES.M)
public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener {
// ====================== 控件与成员变量区 ======================
private TextView tvCallNumberLabel;
private TextView tvCallNumber;
private TextView tvPickUp;
@@ -39,254 +36,126 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick
private PhoneCallManager phoneCallManager;
private PhoneCallService.CallType callType;
private String phoneNumber;
private Timer onGoingCallTimer;
private int callingTime;
// ====================== 静态跳转方法区 ======================
/**
* 启动通话界面
* @param context 上下文
* @param phoneNumber 通话号码
* @param callType 通话类型(来电/去电)
*/
public static void actionStart(Context context, String phoneNumber, PhoneCallService.CallType callType) {
if (context == null) {
LogUtils.e(TAG, "actionStart: 上下文为null无法启动通话界面");
return;
}
if (phoneNumber == null || phoneNumber.isEmpty()) {
LogUtils.w(TAG, "actionStart: 通话号码为空");
}
if (callType == null) {
LogUtils.w(TAG, "actionStart: 通话类型为null");
}
public static void actionStart(Context context, String phoneNumber,
PhoneCallService.CallType callType) {
Intent intent = new Intent(context, PhoneCallActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_CALL_TYPE, callType);
intent.putExtra(EXTRA_PHONE_NUM, phoneNumber);
intent.putExtra(Intent.EXTRA_MIME_TYPES, callType);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);
context.startActivity(intent);
LogUtils.d(TAG, "actionStart: 启动通话界面,号码=" + phoneNumber + ",类型=" + callType);
}
// ====================== 生命周期方法区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 通话界面创建");
// Java7不支持@RequiresApi此处补充系统版本校验
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
LogUtils.e(TAG, "onCreate: 系统版本低于Android 6.0,不支持该通话界面");
finish();
return;
}
setContentView(R.layout.activity_phone_call);
ActivityStack.getInstance().addActivity(this);
initData();
initView();
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 通话界面销毁");
stopTimer();
if (phoneCallManager != null) {
phoneCallManager.destroy();
}
ActivityStack.getInstance().removeActivity(this);
}
// ====================== 初始化方法区 ======================
/**
* 初始化数据
*/
private void initData() {
phoneCallManager = new PhoneCallManager(this);
onGoingCallTimer = new Timer();
parseIntentData();
LogUtils.d(TAG, "initData: 数据初始化完成,通话类型=" + callType + ",号码=" + phoneNumber);
}
/**
* 解析Intent传递的数据
*/
private void parseIntentData() {
Intent intent = getIntent();
if (intent == null) {
LogUtils.w(TAG, "parseIntentData: 未获取到启动Intent");
return;
if (getIntent() != null) {
phoneNumber = getIntent().getStringExtra(Intent.EXTRA_PHONE_NUMBER);
callType = (PhoneCallService.CallType) getIntent().getSerializableExtra(Intent.EXTRA_MIME_TYPES);
}
phoneNumber = intent.getStringExtra(EXTRA_PHONE_NUM);
callType = (PhoneCallService.CallType) intent.getSerializableExtra(EXTRA_CALL_TYPE);
}
/**
* 初始化界面控件
*/
@SuppressLint("SetTextI18n")
private void initView() {
// 设置沉浸式导航栏
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
LogUtils.d(TAG, "initView: 设置界面沉浸式样式");
// 绑定控件
tvCallNumberLabel = findViewById(R.id.tv_call_number_label);
tvCallNumber = findViewById(R.id.tv_call_number);
tvPickUp = findViewById(R.id.tv_phone_pick_up);
tvCallingTime = findViewById(R.id.tv_phone_calling_time);
tvHangUp = findViewById(R.id.tv_phone_hang_up);
// 设置控件事件与初始状态
tvCallNumber.setText(CallListenerService.formatPhoneNumber(phoneNumber));
tvCallNumber.setText(formatPhoneNumber(phoneNumber));
tvPickUp.setOnClickListener(this);
tvHangUp.setOnClickListener(this);
setCallTypeUi();
showOnLockScreen();
LogUtils.d(TAG, "initView: 界面控件初始化完成");
}
// ====================== 界面交互方法区 ======================
/**
* 根据通话类型设置界面样式
*/
private void setCallTypeUi() {
if (callType == null) {
LogUtils.w(TAG, "setCallTypeUi: 通话类型为null使用默认样式");
tvCallNumberLabel.setText("通话号码");
tvPickUp.setVisibility(View.GONE);
return;
}
if (PhoneCallService.CallType.CALL_IN == callType) {
// 打进的电话
if (callType == PhoneCallService.CallType.CALL_IN) {
tvCallNumberLabel.setText("来电号码");
tvPickUp.setVisibility(View.VISIBLE);
} else if (PhoneCallService.CallType.CALL_OUT == callType) {
} else if (callType == PhoneCallService.CallType.CALL_OUT) {
tvCallNumberLabel.setText("呼叫号码");
tvPickUp.setVisibility(View.GONE);
if (phoneCallManager != null) {
phoneCallManager.openSpeaker();
LogUtils.d(TAG, "setCallTypeUi: 去电模式,开启扬声器");
}
phoneCallManager.openSpeaker();
}
showOnLockScreen();
}
/**
* 设置界面锁屏显示
*/
private void showOnLockScreen() {
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
);
LogUtils.d(TAG, "showOnLockScreen: 设置界面支持锁屏显示");
public void showOnLockScreen() {
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
// ====================== 事件点击与计时方法区 ======================
@Override
public void onClick(View v) {
if (v == null) {
LogUtils.w(TAG, "onClick: 点击空控件");
return;
}
int id = v.getId();
if (id == R.id.tv_phone_pick_up) {
LogUtils.d(TAG, "onClick: 点击接听按钮");
answerCall();
} else if (id == R.id.tv_phone_hang_up) {
LogUtils.d(TAG, "onClick: 点击挂断按钮");
hangUpCall();
} else {
LogUtils.w(TAG, "onClick: 点击未知控件ID=" + id);
}
}
/**
* 接听通话
*/
private void answerCall() {
if (phoneCallManager != null) {
if (v.getId() == R.id.tv_phone_pick_up) {
phoneCallManager.answer();
tvPickUp.setVisibility(View.GONE);
tvCallingTime.setVisibility(View.VISIBLE);
startCallTimer();
LogUtils.d(TAG, "answerCall: 接听通话成功");
} else {
LogUtils.e(TAG, "answerCall: 通话管理器为空,无法接听");
}
}
/**
* 挂断通话
*/
private void hangUpCall() {
if (phoneCallManager != null) {
onGoingCallTimer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@SuppressLint("SetTextI18n")
@Override
public void run() {
callingTime++;
tvCallingTime.setText("通话中:" + getCallingTime());
}
});
}
}, 0, 1000);
} else if (v.getId() == R.id.tv_phone_hang_up) {
phoneCallManager.disconnect();
} else {
LogUtils.e(TAG, "hangUpCall: 通话管理器为空,无法挂断");
stopTimer();
}
stopTimer();
finish();
}
/**
* 启动通话计时器
*/
private void startCallTimer() {
stopTimer();
onGoingCallTimer = new Timer();
onGoingCallTimer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@SuppressLint("SetTextI18n")
@Override
public void run() {
callingTime++;
tvCallingTime.setText("通话中:" + getCallingTime());
}
});
}
}, 0, 1000);
LogUtils.d(TAG, "startCallTimer: 通话计时器启动");
}
/**
* 停止通话计时器
*/
private void stopTimer() {
if (onGoingCallTimer != null) {
onGoingCallTimer.cancel();
onGoingCallTimer = null;
}
callingTime = 0;
LogUtils.d(TAG, "stopTimer: 通话计时器停止");
}
/**
* 格式化通话时长
*/
private String getCallingTime() {
int minute = callingTime / 60;
int second = callingTime % 60;
String minuteStr = minute < 10 ? "0" + minute : String.valueOf(minute);
String secondStr = second < 10 ? "0" + second : String.valueOf(second);
return minuteStr + ":" + secondStr;
return (minute < 10 ? "0" + minute : minute) +
":" +
(second < 10 ? "0" + second : second);
}
private void stopTimer() {
if (onGoingCallTimer != null) {
onGoingCallTimer.cancel();
}
callingTime = 0;
}
@Override
protected void onDestroy() {
super.onDestroy();
//MainActivity.updateCallLogFragment();
phoneCallManager.destroy();
}
}

View File

@@ -5,139 +5,58 @@ import android.media.AudioManager;
import android.os.Build;
import android.telecom.Call;
import android.telecom.VideoProfile;
import cc.winboll.studio.libappbase.LogUtils;
import androidx.annotation.RequiresApi;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe 通话管理工具类负责接听、挂断通话及免提控制仅支持Android 6.0API 23及以上系统
*/
@RequiresApi(api = Build.VERSION_CODES.M)
public class PhoneCallManager {
// ====================== 常量定义区 ======================
private static final String TAG = "PhoneCallManager";
// 音频通话模式常量与VideoProfile.STATE_AUDIO_ONLY保持一致增强可读性
private static final int VIDEO_PROFILE_AUDIO_ONLY = VideoProfile.STATE_AUDIO_ONLY;
// ====================== 成员变量区 ======================
// 静态通话实例,用于全局关联当前通话
public static Call sCurrentCall;
// 上下文对象
private Context mContext;
// 音频管理器,用于控制免提等音频相关操作
private AudioManager mAudioManager;
public static Call call;
private Context context;
private AudioManager audioManager;
// ====================== 构造方法区 ======================
/**
* 构造通话管理实例
* @param context 上下文
*/
public PhoneCallManager(Context context) {
this.mContext = context;
initAudioManager();
LogUtils.d(TAG, "PhoneCallManager: 通话管理工具初始化");
this.context = context;
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
// ====================== 初始化辅助方法区 ======================
/**
* 初始化音频管理器
*/
private void initAudioManager() {
if (mContext == null) {
LogUtils.e(TAG, "initAudioManager: 上下文为空,无法初始化音频管理器");
return;
}
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) {
LogUtils.e(TAG, "initAudioManager: 获取音频管理器失败");
} else {
LogUtils.d(TAG, "initAudioManager: 音频管理器初始化成功");
}
}
// ====================== 通话核心操作方法区 ======================
/**
* 接听当前通话
* 接听电话
*/
public void answer() {
// 校验系统版本Android6.0以下不支持telecom.Call的answer方法
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
LogUtils.e(TAG, "answer: 系统版本低于Android6.0,不支持接听操作");
return;
}
if (sCurrentCall == null) {
LogUtils.w(TAG, "answer: 当前无通话实例,无法接听");
return;
}
try {
sCurrentCall.answer(VIDEO_PROFILE_AUDIO_ONLY);
if (call != null) {
call.answer(VideoProfile.STATE_AUDIO_ONLY);
openSpeaker();
LogUtils.d(TAG, "answer: 成功执行接听操作,通话模式为纯音频");
} catch (SecurityException e) {
LogUtils.e(TAG, "answer: 接听通话权限不足", e);
} catch (IllegalStateException e) {
LogUtils.e(TAG, "answer: 通话状态异常,无法接听", e);
}
}
/**
* 断开当前通话(拒接或挂断
* 断开电话,包括来电时的拒接以及接听后的挂断
*/
public void disconnect() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
LogUtils.e(TAG, "disconnect: 系统版本低于Android6.0,不支持挂断操作");
return;
}
if (sCurrentCall == null) {
LogUtils.w(TAG, "disconnect: 当前无通话实例,无法挂断");
return;
}
try {
sCurrentCall.disconnect();
LogUtils.d(TAG, "disconnect: 成功执行挂断操作");
} catch (SecurityException e) {
LogUtils.e(TAG, "disconnect: 挂断通话权限不足", e);
} catch (IllegalStateException e) {
LogUtils.e(TAG, "disconnect: 通话状态异常,无法挂断", e);
if (call != null) {
call.disconnect();
}
}
/**
* 打开通话免提
* 打开免提
*/
public void openSpeaker() {
if (mAudioManager == null) {
LogUtils.w(TAG, "openSpeaker: 音频管理器为空,无法打开免提");
return;
}
try {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mAudioManager.setSpeakerphoneOn(true);
LogUtils.d(TAG, "openSpeaker: 免提功能已开启");
} catch (IllegalStateException e) {
LogUtils.e(TAG, "openSpeaker: 音频模式设置失败,无法开启免提", e);
if (audioManager != null) {
audioManager.setMode(AudioManager.MODE_IN_CALL);
audioManager.setSpeakerphoneOn(true);
}
}
// ====================== 资源释放方法区 ======================
/**
* 销毁资源,释放引用
* 销毁资源
*/
public void destroy() {
// 关闭免提后再释放资源,避免音频状态异常
if (mAudioManager != null) {
mAudioManager.setSpeakerphoneOn(false);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
LogUtils.d(TAG, "destroy: 音频状态已恢复默认");
}
sCurrentCall = null;
mAudioManager = null;
mContext = null;
LogUtils.d(TAG, "destroy: 通话管理工具资源已释放");
call = null;
context = null;
audioManager = null;
}
}

View File

@@ -1,243 +1,216 @@
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;
import androidx.annotation.RequiresApi;
import cc.winboll.studio.contacts.ActivityStack;
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;
/**
* @Author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe 通话状态监听服务(需 Android 6.0+),负责通话状态回调、铃音控制及黑白名单校验
* @see PhoneCallActivity
* @see android.telecom.InCallService
*/
@RequiresApi(api = Build.VERSION_CODES.M)
public class PhoneCallService extends InCallService {
// ====================== 常量定义区 ======================
public static final String TAG = "PhoneCallService";
// Call 通话状态(固定正确值,避免冲突)
private static final int CALL_STATE_IDLE = Call.STATE_NEW;
private static final int CALL_STATE_RINGING = Call.STATE_RINGING; // 正确值=1
private static final int CALL_STATE_CONNECTING = Call.STATE_CONNECTING; // 正确值=3
private static final int CALL_STATE_ACTIVE = Call.STATE_ACTIVE; // 正确值=2
private static final int CALL_STATE_DISCONNECTED = Call.STATE_DISCONNECTED; // 正确值=4
// ====================== 成员变量区 ======================
private Call.Callback mCallCallback;
MediaRecorder mediaRecorder;
// ====================== 内部枚举类 ======================
public enum CallType {
CALL_IN,
CALL_OUT
}
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);
// ====================== 生命周期方法区 ======================
@Override
public void onCreate() {
super.onCreate();
initCallCallback();
LogUtils.d(TAG, "onCreate: 通话监听服务已创建");
}
// 电话接通,开始录音
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;
}
}
}
};
@Override
public void onCallAdded(Call call) {
super.onCallAdded(call);
if (call == null) {
LogUtils.w(TAG, "onCallAdded: 新增通话为null跳过处理");
return;
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;
}
call.registerCallback(mCallCallback);
PhoneCallManager.sCurrentCall = call;
LogUtils.d(TAG, "onCallAdded: 新增通话已注册回调 | 状态码=" + call.getState() + " | 描述=" + getCallStateDesc(call.getState()));
if (callType != null) {
Call.Details details = call.getDetails();
String phoneNumber = details.getHandle().getSchemeSpecificPart();
CallType callType = judgeCallType(call.getState());
if (callType == null) {
LogUtils.w(TAG, "onCallAdded: 无法识别通话类型 | 状态码=" + call.getState());
return;
// 记录原始铃声音量
//
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);
}
String phoneNumber = getPhoneNumberFromCall(call);
if (phoneNumber == null || phoneNumber.isEmpty()) {
LogUtils.w(TAG, "onCallAdded: 通话号码为空");
return;
}
LogUtils.d(TAG, "onCallAdded: 通话类型=" + callType.name() + " | 号码=" + phoneNumber);
handleRingerAndRuleCheck(phoneNumber, callType, call);
}
@Override
public void onCallRemoved(Call call) {
super.onCallRemoved(call);
if (call != null) {
call.unregisterCallback(mCallCallback);
LogUtils.d(TAG, "onCallRemoved: 通话回调已注销");
}
PhoneCallManager.sCurrentCall = null;
call.unregisterCallback(callback);
PhoneCallManager.call = null;
}
@Override
public void onDestroy() {
super.onDestroy();
CallLogFragment.updateCallLogFragment();
LogUtils.d(TAG, "onDestroy: 通话服务已销毁,资源已释放");
}
// ====================== 核心:通话状态回调 ======================
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));
switch (state) {
// 通话活跃状态(仅监听,无录音)
case CALL_STATE_ACTIVE:
LogUtils.d(TAG, "onStateChanged: 通话已接通/活跃");
break;
// 响铃状态
case CALL_STATE_RINGING:
LogUtils.d(TAG, "onStateChanged: 通话处于响铃状态");
break;
// 空闲状态(通话挂断后)
case 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_CONNECTING:
LogUtils.d(TAG, "onStateChanged: 通话正在连接");
break;
default:
LogUtils.w(TAG, "onStateChanged: 未处理的状态码=" + state);
break;
}
}
};
public enum CallType {
CALL_IN,
CALL_OUT,
}
// ====================== 核心业务方法区(铃音控制+黑白名单) ======================
private CallType judgeCallType(int callState) {
switch (callState) {
case CALL_STATE_RINGING:
return CallType.CALL_IN;
case CALL_STATE_CONNECTING:
return CallType.CALL_OUT;
default:
return null;
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 getPhoneNumberFromCall(Call call) {
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 {
Call.Details details = call.getDetails();
if (details == null || details.getHandle() == null) {
return null;
Cursor cursor = contentResolver.query(callLogUri, projection, selection, null, sortOrder);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndex("_id"));
}
return details.getHandle().getSchemeSpecificPart();
} catch (Exception e) {
LogUtils.e(TAG, "getPhoneNumberFromCall: 解析号码失败", e);
return null;
}
}
private void handleRingerAndRuleCheck(String phoneNumber, CallType callType, Call call) {
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioManager == null) {
LogUtils.e(TAG, "handleRingerAndRuleCheck: 音频管理器获取失败");
startPhoneCallActivity(phoneNumber, callType);
return;
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
RingTongBean ringTongBean = RingTongBean.loadBean(this, RingTongBean.class);
if (ringTongBean == null) {
ringTongBean = new RingTongBean();
RingTongBean.saveBean(this, ringTongBean);
LogUtils.d(TAG, "handleRingerAndRuleCheck: 铃音配置已初始化");
}
restoreRingerVolume(audioManager, ringTongBean);
if (!Rules.getInstance(this).isAllowed(phoneNumber)) {
handleForbiddenCall(audioManager, ringTongBean, call);
return;
}
startPhoneCallActivity(phoneNumber, callType);
}
private void restoreRingerVolume(AudioManager audioManager, RingTongBean bean) {
try {
int currentVol = audioManager.getStreamVolume(AudioManager.STREAM_RING);
int configVol = bean.getStreamVolume();
if (currentVol != configVol) {
audioManager.setStreamVolume(AudioManager.STREAM_RING, configVol, 0);
LogUtils.d(TAG, "restoreRingerVolume: 音量恢复 | 配置值=" + configVol + " | 当前值=" + currentVol);
}
} catch (SecurityException e) {
LogUtils.e(TAG, "restoreRingerVolume: 音量设置失败(权限不足)", e);
}
}
private void handleForbiddenCall(AudioManager audioManager, RingTongBean bean, Call call) {
try {
audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
call.disconnect();
Thread.sleep(500);
audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0);
LogUtils.d(TAG, "handleForbiddenCall: 禁止通话处理完成");
} catch (SecurityException e) {
LogUtils.e(TAG, "handleForbiddenCall: 音量操作失败", e);
} catch (InterruptedException e) {
LogUtils.e(TAG, "handleForbiddenCall: 延迟线程中断", e);
Thread.currentThread().interrupt();
}
}
private void startPhoneCallActivity(String phoneNumber, CallType callType) {
PhoneCallActivity.actionStart(this, phoneNumber, callType);
LogUtils.d(TAG, "startPhoneCallActivity: 通话界面已启动");
}
// ====================== 辅助工具方法区 ======================
private String getCallStateDesc(int state) {
switch (state) {
case CALL_STATE_RINGING:
return "响铃";
case CALL_STATE_CONNECTING:
return "连接中";
case CALL_STATE_ACTIVE:
return "活跃";
case CALL_STATE_IDLE:
return "空闲";
case CALL_STATE_DISCONNECTED:
return "已断开";
default:
return "未知状态";
}
}
// ====================== 静态内部类PhoneCallManager避免编译报错 ======================
public static class PhoneCallManager {
public static Call sCurrentCall;
private PhoneCallManager() {
// 私有构造,禁止实例化
}
public static Call getCurrentCall() {
return sCurrentCall;
}
return -1;
}
}