diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java index 42c9029..db50fbe 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java @@ -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&豆包大模型 - * @Date 2025/02/13 06:58:04 - * @Describe 接打电话界面,仅支持 Android 6.0(API 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(); } } - diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java index e872026..7a75273 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java @@ -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&豆包大模型 - * @Date 2025/02/13 06:58:04 - * @Describe 通话管理工具类,负责接听、挂断通话及免提控制,仅支持Android 6.0(API 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; } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java index e29a9dd..0a43f23 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java @@ -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&豆包大模型 - * @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; } }