恢复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.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.contacts.ActivityStack; import cc.winboll.studio.contacts.ActivityStack;
import cc.winboll.studio.contacts.MainActivity;
import cc.winboll.studio.contacts.R; 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.Timer;
import java.util.TimerTask; import java.util.TimerTask;
/** import static cc.winboll.studio.contacts.listenphonecall.CallListenerService.formatPhoneNumber;
* @Author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe 接打电话界面,仅支持 Android 6.0API 23及以上系统 /**
*/ * 提供接打电话的界面,仅支持 Android M (6.0, API 23) 及以上的系统
// Java7不支持@RequiresApi通过代码内校验替代注解 *
public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener { * @author aJIEw
// ====================== 常量定义区 ====================== */
public static final String TAG = "PhoneCallActivity"; @RequiresApi(api = Build.VERSION_CODES.M)
// Intent传参键常量替代系统常量避免歧义 public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener {
private static final String EXTRA_CALL_TYPE = "extra_call_type";
private static final String EXTRA_PHONE_NUM = "extra_phone_number";
// ====================== 控件与成员变量区 ======================
private TextView tvCallNumberLabel; private TextView tvCallNumberLabel;
private TextView tvCallNumber; private TextView tvCallNumber;
private TextView tvPickUp; private TextView tvPickUp;
@@ -39,217 +36,87 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick
private PhoneCallManager phoneCallManager; private PhoneCallManager phoneCallManager;
private PhoneCallService.CallType callType; private PhoneCallService.CallType callType;
private String phoneNumber; private String phoneNumber;
private Timer onGoingCallTimer; private Timer onGoingCallTimer;
private int callingTime; private int callingTime;
// ====================== 静态跳转方法区 ====================== public static void actionStart(Context context, String phoneNumber,
/** PhoneCallService.CallType callType) {
* 启动通话界面
* @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");
}
Intent intent = new Intent(context, PhoneCallActivity.class); Intent intent = new Intent(context, PhoneCallActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_CALL_TYPE, callType); intent.putExtra(Intent.EXTRA_MIME_TYPES, callType);
intent.putExtra(EXTRA_PHONE_NUM, phoneNumber); intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);
context.startActivity(intent); context.startActivity(intent);
LogUtils.d(TAG, "actionStart: 启动通话界面,号码=" + phoneNumber + ",类型=" + callType);
} }
// ====================== 生命周期方法区 ======================
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(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); setContentView(R.layout.activity_phone_call);
ActivityStack.getInstance().addActivity(this); ActivityStack.getInstance().addActivity(this);
initData(); initData();
initView(); initView();
} }
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 通话界面销毁");
stopTimer();
if (phoneCallManager != null) {
phoneCallManager.destroy();
}
ActivityStack.getInstance().removeActivity(this);
}
// ====================== 初始化方法区 ======================
/**
* 初始化数据
*/
private void initData() { private void initData() {
phoneCallManager = new PhoneCallManager(this); phoneCallManager = new PhoneCallManager(this);
onGoingCallTimer = new Timer(); onGoingCallTimer = new Timer();
parseIntentData(); if (getIntent() != null) {
LogUtils.d(TAG, "initData: 数据初始化完成,通话类型=" + callType + ",号码=" + phoneNumber); phoneNumber = getIntent().getStringExtra(Intent.EXTRA_PHONE_NUMBER);
callType = (PhoneCallService.CallType) getIntent().getSerializableExtra(Intent.EXTRA_MIME_TYPES);
}
} }
/**
* 解析Intent传递的数据
*/
private void parseIntentData() {
Intent intent = getIntent();
if (intent == null) {
LogUtils.w(TAG, "parseIntentData: 未获取到启动Intent");
return;
}
phoneNumber = intent.getStringExtra(EXTRA_PHONE_NUM);
callType = (PhoneCallService.CallType) intent.getSerializableExtra(EXTRA_CALL_TYPE);
}
/**
* 初始化界面控件
*/
@SuppressLint("SetTextI18n")
private void initView() { private void initView() {
// 设置沉浸式导航栏
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE; | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(uiOptions); getWindow().getDecorView().setSystemUiVisibility(uiOptions);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
LogUtils.d(TAG, "initView: 设置界面沉浸式样式");
// 绑定控件
tvCallNumberLabel = findViewById(R.id.tv_call_number_label); tvCallNumberLabel = findViewById(R.id.tv_call_number_label);
tvCallNumber = findViewById(R.id.tv_call_number); tvCallNumber = findViewById(R.id.tv_call_number);
tvPickUp = findViewById(R.id.tv_phone_pick_up); tvPickUp = findViewById(R.id.tv_phone_pick_up);
tvCallingTime = findViewById(R.id.tv_phone_calling_time); tvCallingTime = findViewById(R.id.tv_phone_calling_time);
tvHangUp = findViewById(R.id.tv_phone_hang_up); tvHangUp = findViewById(R.id.tv_phone_hang_up);
// 设置控件事件与初始状态 tvCallNumber.setText(formatPhoneNumber(phoneNumber));
tvCallNumber.setText(CallListenerService.formatPhoneNumber(phoneNumber));
tvPickUp.setOnClickListener(this); tvPickUp.setOnClickListener(this);
tvHangUp.setOnClickListener(this); tvHangUp.setOnClickListener(this);
setCallTypeUi(); // 打进的电话
showOnLockScreen(); if (callType == PhoneCallService.CallType.CALL_IN) {
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) {
tvCallNumberLabel.setText("来电号码"); tvCallNumberLabel.setText("来电号码");
tvPickUp.setVisibility(View.VISIBLE); tvPickUp.setVisibility(View.VISIBLE);
} else if (PhoneCallService.CallType.CALL_OUT == callType) { } else if (callType == PhoneCallService.CallType.CALL_OUT) {
tvCallNumberLabel.setText("呼叫号码"); tvCallNumberLabel.setText("呼叫号码");
tvPickUp.setVisibility(View.GONE); tvPickUp.setVisibility(View.GONE);
if (phoneCallManager != null) {
phoneCallManager.openSpeaker(); phoneCallManager.openSpeaker();
LogUtils.d(TAG, "setCallTypeUi: 去电模式,开启扬声器");
}
}
} }
/** showOnLockScreen();
* 设置界面锁屏显示 }
*/
private void showOnLockScreen() { public void showOnLockScreen() {
getWindow().setFlags( this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON WindowManager.LayoutParams.FLAG_FULLSCREEN |
| WindowManager.LayoutParams.FLAG_FULLSCREEN WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON WindowManager.LayoutParams.FLAG_FULLSCREEN |
| WindowManager.LayoutParams.FLAG_FULLSCREEN WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
);
LogUtils.d(TAG, "showOnLockScreen: 设置界面支持锁屏显示");
} }
// ====================== 事件点击与计时方法区 ======================
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == null) { if (v.getId() == R.id.tv_phone_pick_up) {
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) {
phoneCallManager.answer(); phoneCallManager.answer();
tvPickUp.setVisibility(View.GONE); tvPickUp.setVisibility(View.GONE);
tvCallingTime.setVisibility(View.VISIBLE); tvCallingTime.setVisibility(View.VISIBLE);
startCallTimer();
LogUtils.d(TAG, "answerCall: 接听通话成功");
} else {
LogUtils.e(TAG, "answerCall: 通话管理器为空,无法接听");
}
}
/**
* 挂断通话
*/
private void hangUpCall() {
if (phoneCallManager != null) {
phoneCallManager.disconnect();
} else {
LogUtils.e(TAG, "hangUpCall: 通话管理器为空,无法挂断");
}
stopTimer();
finish();
}
/**
* 启动通话计时器
*/
private void startCallTimer() {
stopTimer();
onGoingCallTimer = new Timer();
onGoingCallTimer.schedule(new TimerTask() { onGoingCallTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
@@ -263,30 +130,32 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick
}); });
} }
}, 0, 1000); }, 0, 1000);
LogUtils.d(TAG, "startCallTimer: 通话计时器启动"); } else if (v.getId() == R.id.tv_phone_hang_up) {
phoneCallManager.disconnect();
stopTimer();
}
} }
/**
* 停止通话计时器
*/
private void stopTimer() {
if (onGoingCallTimer != null) {
onGoingCallTimer.cancel();
onGoingCallTimer = null;
}
callingTime = 0;
LogUtils.d(TAG, "stopTimer: 通话计时器停止");
}
/**
* 格式化通话时长
*/
private String getCallingTime() { private String getCallingTime() {
int minute = callingTime / 60; int minute = callingTime / 60;
int second = callingTime % 60; int second = callingTime % 60;
String minuteStr = minute < 10 ? "0" + minute : String.valueOf(minute); return (minute < 10 ? "0" + minute : minute) +
String secondStr = second < 10 ? "0" + second : String.valueOf(second); ":" +
return minuteStr + ":" + secondStr; (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.os.Build;
import android.telecom.Call; import android.telecom.Call;
import android.telecom.VideoProfile; import android.telecom.VideoProfile;
import cc.winboll.studio.libappbase.LogUtils; import androidx.annotation.RequiresApi;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> @RequiresApi(api = Build.VERSION_CODES.M)
* @Date 2025/02/13 06:58:04
* @Describe 通话管理工具类负责接听、挂断通话及免提控制仅支持Android 6.0API 23及以上系统
*/
public class PhoneCallManager { 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 call;
// 静态通话实例,用于全局关联当前通话
public static Call sCurrentCall; private Context context;
// 上下文对象 private AudioManager audioManager;
private Context mContext;
// 音频管理器,用于控制免提等音频相关操作
private AudioManager mAudioManager;
// ====================== 构造方法区 ======================
/**
* 构造通话管理实例
* @param context 上下文
*/
public PhoneCallManager(Context context) { public PhoneCallManager(Context context) {
this.mContext = context; this.context = context;
initAudioManager(); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
LogUtils.d(TAG, "PhoneCallManager: 通话管理工具初始化");
} }
// ====================== 初始化辅助方法区 ======================
/** /**
* 初始化音频管理器 * 接听电话
*/
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() { public void answer() {
// 校验系统版本Android6.0以下不支持telecom.Call的answer方法 if (call != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { call.answer(VideoProfile.STATE_AUDIO_ONLY);
LogUtils.e(TAG, "answer: 系统版本低于Android6.0,不支持接听操作");
return;
}
if (sCurrentCall == null) {
LogUtils.w(TAG, "answer: 当前无通话实例,无法接听");
return;
}
try {
sCurrentCall.answer(VIDEO_PROFILE_AUDIO_ONLY);
openSpeaker(); openSpeaker();
LogUtils.d(TAG, "answer: 成功执行接听操作,通话模式为纯音频");
} catch (SecurityException e) {
LogUtils.e(TAG, "answer: 接听通话权限不足", e);
} catch (IllegalStateException e) {
LogUtils.e(TAG, "answer: 通话状态异常,无法接听", e);
} }
} }
/** /**
* 断开当前通话(拒接或挂断 * 断开电话,包括来电时的拒接以及接听后的挂断
*/ */
public void disconnect() { public void disconnect() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (call != null) {
LogUtils.e(TAG, "disconnect: 系统版本低于Android6.0,不支持挂断操作"); call.disconnect();
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);
} }
} }
/** /**
* 打开通话免提 * 打开免提
*/ */
public void openSpeaker() { public void openSpeaker() {
if (mAudioManager == null) { if (audioManager != null) {
LogUtils.w(TAG, "openSpeaker: 音频管理器为空,无法打开免提"); audioManager.setMode(AudioManager.MODE_IN_CALL);
return; audioManager.setSpeakerphoneOn(true);
}
try {
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mAudioManager.setSpeakerphoneOn(true);
LogUtils.d(TAG, "openSpeaker: 免提功能已开启");
} catch (IllegalStateException e) {
LogUtils.e(TAG, "openSpeaker: 音频模式设置失败,无法开启免提", e);
} }
} }
// ====================== 资源释放方法区 ======================
/** /**
* 销毁资源,释放引用 * 销毁资源
*/ */
public void destroy() { public void destroy() {
// 关闭免提后再释放资源,避免音频状态异常 call = null;
if (mAudioManager != null) { context = null;
mAudioManager.setSpeakerphoneOn(false); audioManager = null;
mAudioManager.setMode(AudioManager.MODE_NORMAL);
LogUtils.d(TAG, "destroy: 音频状态已恢复默认");
}
sCurrentCall = null;
mAudioManager = null;
mContext = null;
LogUtils.d(TAG, "destroy: 通话管理工具资源已释放");
} }
} }

View File

@@ -1,243 +1,216 @@
package cc.winboll.studio.contacts.phonecallui; 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.AudioManager;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.provider.CallLog;
import android.telecom.Call; import android.telecom.Call;
import android.telecom.InCallService; import android.telecom.InCallService;
import android.telephony.TelephonyManager;
import androidx.annotation.RequiresApi;
import cc.winboll.studio.contacts.ActivityStack; import cc.winboll.studio.contacts.ActivityStack;
import cc.winboll.studio.contacts.dun.Rules; import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.fragments.CallLogFragment; import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.contacts.model.RingTongBean; import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import java.io.File;
import java.io.IOException;
/** @RequiresApi(api = Build.VERSION_CODES.M)
* @Author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe 通话状态监听服务(需 Android 6.0+),负责通话状态回调、铃音控制及黑白名单校验
* @see PhoneCallActivity
* @see android.telecom.InCallService
*/
public class PhoneCallService extends InCallService { public class PhoneCallService extends InCallService {
// ====================== 常量定义区 ======================
public static final String TAG = "PhoneCallService"; 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
// ====================== 成员变量区 ====================== MediaRecorder mediaRecorder;
private Call.Callback mCallCallback;
// ====================== 内部枚举类 ====================== private final Call.Callback callback = new Call.Callback() {
public enum CallType {
CALL_IN,
CALL_OUT
}
// ====================== 生命周期方法区 ======================
@Override @Override
public void onCreate() { public void onStateChanged(Call call, int state) {
super.onCreate(); super.onStateChanged(call, state);
initCallCallback(); switch (state) {
LogUtils.d(TAG, "onCreate: 通话监听服务已创建"); case TelephonyManager.CALL_STATE_OFFHOOK:
{
long callId = getCurrentCallId();
if (callId != -1) {
// 在这里可以对获取到的通话记录ID进行处理
//System.out.println("当前通话记录ID: " + callId);
// 电话接通,开始录音
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 @Override
public void onCallAdded(Call call) { public void onCallAdded(Call call) {
super.onCallAdded(call); super.onCallAdded(call);
if (call == null) {
LogUtils.w(TAG, "onCallAdded: 新增通话为null跳过处理"); 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 (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; return;
} }
call.registerCallback(mCallCallback); // 正常接听电话
PhoneCallManager.sCurrentCall = call; PhoneCallActivity.actionStart(this, phoneNumber, callType);
LogUtils.d(TAG, "onCallAdded: 新增通话已注册回调 | 状态码=" + call.getState() + " | 描述=" + getCallStateDesc(call.getState()));
CallType callType = judgeCallType(call.getState());
if (callType == null) {
LogUtils.w(TAG, "onCallAdded: 无法识别通话类型 | 状态码=" + call.getState());
return;
} }
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 @Override
public void onCallRemoved(Call call) { public void onCallRemoved(Call call) {
super.onCallRemoved(call); super.onCallRemoved(call);
if (call != null) { call.unregisterCallback(callback);
call.unregisterCallback(mCallCallback); PhoneCallManager.call = null;
LogUtils.d(TAG, "onCallRemoved: 通话回调已注销");
}
PhoneCallManager.sCurrentCall = null;
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
CallLogFragment.updateCallLogFragment(); CallLogFragment.updateCallLogFragment();
LogUtils.d(TAG, "onDestroy: 通话服务已销毁,资源已释放");
} }
// ====================== 核心:通话状态回调 ====================== public enum CallType {
private void initCallCallback() { CALL_IN,
mCallCallback = new Call.Callback() { CALL_OUT,
@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;
}
}
};
} }
// ====================== 核心业务方法区(铃音控制+黑白名单) ======================
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 String getPhoneNumberFromCall(Call call) { 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 { try {
Call.Details details = call.getDetails(); mediaRecorder.prepare();
if (details == null || details.getHandle() == null) { mediaRecorder.start();
return null; } 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"));
} }
return details.getHandle().getSchemeSpecificPart();
} catch (Exception e) { } catch (Exception e) {
LogUtils.e(TAG, "getPhoneNumberFromCall: 解析号码失败", e); LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
return null;
}
} }
private void handleRingerAndRuleCheck(String phoneNumber, CallType callType, Call call) { return -1;
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioManager == null) {
LogUtils.e(TAG, "handleRingerAndRuleCheck: 音频管理器获取失败");
startPhoneCallActivity(phoneNumber, callType);
return;
}
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;
}
} }
} }