通话测试通过。

This commit is contained in:
2025-12-15 20:51:56 +08:00
parent 217a27cbcd
commit a4ab864381
5 changed files with 907 additions and 385 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Sun Dec 14 12:45:32 GMT 2025 #Mon Dec 15 12:48:19 GMT 2025
stageCount=1 stageCount=1
libraryProject= libraryProject=
baseVersion=15.14 baseVersion=15.14
publishVersion=15.14.0 publishVersion=15.14.0
buildCount=3 buildCount=15
baseBetaVersion=15.14.1 baseBetaVersion=15.14.1

View File

@@ -1,139 +1,313 @@
package cc.winboll.studio.contacts; package cc.winboll.studio.contacts;
import android.app.Activity; import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
/** /**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04 * @Date 2025/02/13 06:58:04
* @Describe Activity 栈管理工具,用于统一管理应用内 Activity 生命周期 * @Describe Activity 栈管理工具,统一管理应用内 Activity 生命周期
* 适配Java7 + Android API29-30 + 小米机型,优化并发安全与通话场景稳定性
*/ */
public class ActivityStack { public class ActivityStack {
// ====================== 常量定义区 ====================== // 常量定义(核心标识+版本兼容常量)
public static final String TAG = "ActivityStack"; public static final String TAG = "ActivityStack";
private static final int API_VERSION_O = 26; // Android 8.0 API26isDestroyed适配用
// ====================== 单例与成员变量区 ====================== // 单例与核心成员变量(按优先级排序)
private static final ActivityStack INSTANCE = new ActivityStack(); private static final ActivityStack INSTANCE = new ActivityStack();
private List<Activity> mActivityList = new ArrayList<Activity>(); // 替换为ArrayList+同步锁解决CopyOnWriteArrayList迭代器不能删除的崩溃兼顾并发安全
private final List<Activity> mActivityList = new ArrayList<Activity>();
private final Handler mMainHandler = new Handler(Looper.getMainLooper()); // 复用主线程Handler避免内存泄漏
// ====================== 单例获取方法区 ====================== // 单例对外暴露方法
public static ActivityStack getInstance() { public static ActivityStack getInstance() {
return INSTANCE; return INSTANCE;
} }
// ====================== 私有构造函数(防止外部实例化) ====================== // 私有构造,禁止外部实例化
private ActivityStack() { private ActivityStack() {
LogUtils.d(TAG, "ActivityStack: 初始化 Activity 栈管理工具"); LogUtils.d(TAG, "ActivityStack 初始化完成");
} }
// ====================== Activity 栈操作方法区 ====================== // ====================== 栈基础操作(添加/移除) ======================
/** /**
* 添加 Activity 到栈中 * 添加Activity到栈中,避免重复入栈
* @param activity 待添加的Activity
*/ */
public void addActivity(Activity activity) { public void addActivity(Activity activity) {
if (activity == null) { if (activity == null) {
LogUtils.w(TAG, "addActivity: 待添加的 Activity null,跳过添加"); LogUtils.w(TAG, "addActivity: activity is null, skip");
return; return;
} }
mActivityList.add(activity); // 同步锁:解决多线程并发添加冲突(小米机型多线程场景适配)
LogUtils.d(TAG, "addActivity: Activity入栈 | 类名=" + activity.getClass().getSimpleName() + " | 栈大小=" + mActivityList.size()); synchronized (mActivityList) {
} if (!mActivityList.contains(activity)) {
mActivityList.add(activity);
/** LogUtils.d(TAG, "addActivity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
* 获取栈顶 Activity
*/
public Activity getTopActivity() {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "getTopActivity: Activity 栈为空,返回 null");
return null;
}
Activity topActivity = mActivityList.get(mActivityList.size() - 1);
LogUtils.d(TAG, "getTopActivity: 获取栈顶 Activity | 类名=" + topActivity.getClass().getSimpleName());
return topActivity;
}
/**
* 移除并销毁栈顶 Activity
*/
public void finishTopActivity() {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishTopActivity: Activity 栈为空,无需操作");
return;
}
Activity topActivity = mActivityList.remove(mActivityList.size() - 1);
topActivity.finish();
LogUtils.d(TAG, "finishTopActivity: 销毁栈顶 Activity | 类名=" + topActivity.getClass().getSimpleName() + " | 栈大小=" + mActivityList.size());
}
/**
* 移除并销毁指定 Activity
*/
public void finishActivity(Activity activity) {
if (activity == null) {
LogUtils.w(TAG, "finishActivity: 待销毁的 Activity 为 null跳过操作");
return;
}
if (mActivityList.remove(activity)) {
activity.finish();
LogUtils.d(TAG, "finishActivity: 销毁指定 Activity | 类名=" + activity.getClass().getSimpleName() + " | 栈大小=" + mActivityList.size());
} else {
LogUtils.w(TAG, "finishActivity: 指定 Activity 不在栈中 | 类名=" + activity.getClass().getSimpleName());
}
}
/**
* 移除并销毁指定类的所有 Activity
*/
public void finishActivity(Class<?> activityClass) {
if (activityClass == null) {
LogUtils.w(TAG, "finishActivity: 待销毁的 Activity 类为 null跳过操作");
return;
}
// Java7 兼容:使用 Iterator 遍历避免 ConcurrentModificationException
Iterator<Activity> iterator = mActivityList.iterator();
while (iterator.hasNext()) {
Activity activity = iterator.next();
if (activity.getClass().equals(activityClass)) {
iterator.remove();
activity.finish();
LogUtils.d(TAG, "finishActivity: 销毁指定类 Activity | 类名=" + activityClass.getSimpleName() + " | 栈大小=" + mActivityList.size());
} }
} }
} }
/** /**
* 销毁栈中所有 Activity * 移除Activity(不销毁,用于正常退出场景)
*/ * @param activity 待移除的Activity
public void finishAllActivity() {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishAllActivity: Activity 栈为空,无需操作");
return;
}
// Java7 兼容:使用增强 for 循环遍历销毁,避免迭代器异常
for (Activity activity : mActivityList) {
if (!activity.isFinishing()) {
activity.finish();
LogUtils.d(TAG, "finishAllActivity: 销毁 Activity | 类名=" + activity.getClass().getSimpleName());
}
}
mActivityList.clear();
LogUtils.d(TAG, "finishAllActivity: 所有 Activity 已销毁,栈已清空");
}
/**
* 新增移除指定Activity但不销毁用于Activity正常退出
*/ */
public void removeActivity(Activity activity) { public void removeActivity(Activity activity) {
if (activity == null) { if (activity == null) {
LogUtils.w(TAG, "removeActivity: 待移除的 Activity null,跳过操作"); LogUtils.w(TAG, "removeActivity: activity is null, skip");
return; return;
} }
if (mActivityList.remove(activity)) { synchronized (mActivityList) {
LogUtils.d(TAG, "removeActivity: 移除 Activity | 类名=" + activity.getClass().getSimpleName() + " | 栈大小=" + mActivityList.size()); if (mActivityList.remove(activity)) {
LogUtils.d(TAG, "removeActivity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
} }
} }
// ====================== Activity状态查询获取/判断存活) ======================
/**
* 获取栈顶有效Activity迭代遍历替代递归避免栈溢出适配小米多页面场景
* @return 栈顶有效Activity无则返回null
*/
public Activity getTopActivity() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "getTopActivity: stack is empty, return null");
return null;
}
Activity validTopActivity = null;
// 倒序遍历优先取最顶层有效Activity同时清理无效残留
for (int i = mActivityList.size() - 1; i >= 0; i--) {
Activity activity = mActivityList.get(i);
// 版本兼容校验API26+才支持isDestroyed
if (activity != null && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
validTopActivity = activity;
break;
} else {
mActivityList.remove(i);
String className = (activity != null) ? activity.getClass().getSimpleName() : "null";
LogUtils.w(TAG, "getTopActivity: remove invalid activity: " + className);
}
}
if (validTopActivity != null) {
LogUtils.d(TAG, "getTopActivity: top activity: " + validTopActivity.getClass().getSimpleName());
}
return validTopActivity;
}
}
/**
* 获取指定类的有效Activity实例通话场景核心方法判断页面是否存活
* @param activityClass 目标Activity类
* @return 有效实例无则返回null
*/
public Activity getActivity(Class<?> activityClass) {
if (activityClass == null) {
LogUtils.w(TAG, "getActivity: activityClass is null, return null");
return null;
}
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "getActivity: stack empty, return null");
return null;
}
for (Activity activity : mActivityList) {
if (activity != null && activity.getClass().equals(activityClass) && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
LogUtils.d(TAG, "getActivity: find valid activity: " + activityClass.getSimpleName());
return activity;
}
}
LogUtils.w(TAG, "getActivity: no valid activity: " + activityClass.getSimpleName());
return null;
}
}
/**
* 判断指定Activity是否存活简化通话场景调用避免重复判空
* @param activityClass 目标Activity类
* @return true存活false未存活
*/
public boolean isActivityAlive(Class<?> activityClass) {
boolean isAlive = getActivity(activityClass) != null;
LogUtils.d(TAG, "isActivityAlive: " + activityClass.getSimpleName() + ", result: " + isAlive);
return isAlive;
}
// ====================== Activity销毁操作单/批量/全部) ======================
/**
* 销毁栈顶Activity主线程执行适配小米机型线程限制
*/
public void finishTopActivity() {
runOnMainThread(new Runnable() {
@Override
public void run() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishTopActivity: stack is empty, skip");
return;
}
// 先移除再校验,避免并发冲突(小米多线程场景适配)
Activity topActivity = mActivityList.remove(mActivityList.size() - 1);
if (topActivity == null) {
LogUtils.w(TAG, "finishTopActivity: top activity is null, skip");
return;
}
if (!topActivity.isFinishing() && (getSdkVersion() < API_VERSION_O || !topActivity.isDestroyed())) {
topActivity.finish();
LogUtils.d(TAG, "finishTopActivity: destroy top activity: " + topActivity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
}
}
});
}
/**
* 销毁指定Activity主线程执行避免跨线程异常
* @param activity 待销毁的Activity
*/
public void finishActivity(final Activity activity) {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (activity == null) {
LogUtils.w(TAG, "finishActivity: activity is null, skip");
return;
}
synchronized (mActivityList) {
if (mActivityList.contains(activity) && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
mActivityList.remove(activity);
activity.finish();
LogUtils.d(TAG, "finishActivity: destroy activity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
}
}
});
}
/**
* 销毁指定类的所有Activity核心修复迭代器删除崩溃通话场景核心
* @param activityClass 目标Activity类
*/
public void finishActivity(final Class<?> activityClass) {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (activityClass == null) {
LogUtils.w(TAG, "finishActivity: activityClass is null, skip");
return;
}
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishActivity: stack empty, skip");
return;
}
// 核心修复:用索引遍历+倒序删除替代迭代器删除避免UnsupportedOperationException
for (int i = mActivityList.size() - 1; i >= 0; i--) {
Activity activity = mActivityList.get(i);
if (activity != null && activity.getClass().equals(activityClass)) {
if (!activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
mActivityList.remove(i); // 索引删除支持ArrayList
activity.finish();
LogUtils.d(TAG, "finishActivity: destroy class activity: " + activityClass.getSimpleName() + ", stack size: " + mActivityList.size());
} else {
mActivityList.remove(i); // 清理无效残留
}
}
}
}
}
});
}
/**
* 销毁栈中所有Activity退出应用/清空栈场景用)
*/
public void finishAllActivity() {
runOnMainThread(new Runnable() {
@Override
public void run() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishAllActivity: stack is empty, skip");
return;
}
// 遍历销毁所有有效Activity逐个状态校验小米机型稳定性适配
for (Activity activity : mActivityList) {
if (activity != null && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
activity.finish();
LogUtils.d(TAG, "finishAllActivity: destroy activity: " + activity.getClass().getSimpleName());
}
}
mActivityList.clear();
LogUtils.d(TAG, "finishAllActivity: all activity destroyed, stack cleared");
}
}
});
}
// ====================== 栈优化与工具方法 ======================
/**
* 清理栈中所有无效Activitynull/已销毁/已结束),优化小米机型内存占用
*/
public void clearInvalidActivities() {
runOnMainThread(new Runnable() {
@Override
public void run() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
return;
}
// 倒序索引删除,避免遍历过程中索引错乱
for (int i = mActivityList.size() - 1; i >= 0; i--) {
Activity activity = mActivityList.get(i);
if (activity == null || activity.isFinishing() || (getSdkVersion() >= API_VERSION_O && activity.isDestroyed())) {
mActivityList.remove(i);
String className = (activity != null) ? activity.getClass().getSimpleName() : "null";
LogUtils.d(TAG, "clearInvalidActivities: remove invalid activity: " + className);
}
}
LogUtils.d(TAG, "clearInvalidActivities: done, stack size: " + mActivityList.size());
}
}
});
}
/**
* 确保任务在主线程执行Activity操作必须主线程小米机型严格限制
* @param runnable 待执行任务
*/
private void runOnMainThread(Runnable runnable) {
if (runnable == null) {
return;
}
// 避免不必要的线程切换,优化性能(小米机型流畅度适配)
if (Looper.getMainLooper() == Looper.myLooper()) {
runnable.run();
} else {
mMainHandler.post(runnable);
LogUtils.d(TAG, "runOnMainThread: post task to main thread");
}
}
/**
* 辅助方法获取当前系统SDK版本简化版本判断逻辑统一调用
* @return SDK版本号
*/
private int getSdkVersion() {
return android.os.Build.VERSION.SDK_INT;
}
} }

View File

@@ -1,162 +1,362 @@
package cc.winboll.studio.contacts.phonecallui; package cc.winboll.studio.contacts.phonecallui;
import android.annotation.SuppressLint; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View; import android.view.View;
import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.RequiresApi; import android.widget.Toast;
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.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; import static cc.winboll.studio.contacts.listenphonecall.CallListenerService.formatPhoneNumber;
/** /**
* 提供接打电话的界面,仅支持 Android M (6.0, API 23) 及以上的系统 * @Author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* * @Date 2025/12/14 21:01
* @author aJIEw * @Describe 接打电话界面(单例模式 + 适配API29 - 30 + 小米机型兼容性优化)
* 功能:单例通话窗口、来电/去电显示、通话计时、免提控制、锁屏显示
*/ */
@RequiresApi(api = Build.VERSION_CODES.M) public class PhoneCallActivity extends Activity implements View.OnClickListener {
public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener { // 常量定义区(核心常量+小米适配标识)
public static final String TAG = "PhoneCallActivity";
private static final int MSG_CLOSE_ACTIVITY = 0x001;
private static final String MI_ADAPT_TAG = "MiAdapt";
private static final String TOAST_CALLING = "通话进行中,无法重复创建通话窗口";
private static final long CLOSE_DELAY_MS = 100; // 小米机型关闭延迟时间
private TextView tvCallNumberLabel; // 静态属性区(单例核心+全局工具对象)
private TextView tvCallNumber; private static volatile boolean sIsActivityAlive = false;
private TextView tvPickUp; private static Handler sCloseHandler;
private TextView tvCallingTime;
private TextView tvHangUp;
private PhoneCallManager phoneCallManager; // 控件属性区(按界面布局顺序排列)
private PhoneCallService.CallType callType; private TextView mTvCallNumberLabel;
private String phoneNumber; private TextView mTvCallNumber;
private TextView mTvPickUp;
private TextView mTvCallingTime;
private TextView mTvHangUp;
private Timer onGoingCallTimer; // 业务属性区(按依赖优先级排列)
private int callingTime; private PhoneCallManager mPhoneCallManager;
private PhoneCallService.CallType mCallType;
private String mPhoneNumber;
private Timer mOnGoingCallTimer;
private int mCallingTime;
private boolean isClosing = false; // 新增:避免重复关闭页面
public static void actionStart(Context context, String phoneNumber, // 对外静态接口(单例启动+外部关闭)
PhoneCallService.CallType callType) { public static void actionStart(Context context, String phoneNumber, PhoneCallService.CallType callType) {
if (context == null || phoneNumber == null || callType == null) {
LogUtils.e(TAG, "actionStart: 入参为空,启动失败");
return;
}
if (sIsActivityAlive) {
LogUtils.w(TAG, MI_ADAPT_TAG + " 已有活跃通话窗口,拒绝重复启动");
Toast.makeText(context, TOAST_CALLING, Toast.LENGTH_SHORT).show();
return;
}
LogUtils.d(TAG, MI_ADAPT_TAG + " 启动通话界面,号码=" + phoneNumber + ",类型=" + callType.name());
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.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_MIME_TYPES, callType); intent.putExtra("call_type", callType);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber); intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);
context.startActivity(intent); context.startActivity(intent);
} }
public static void closePhoneCallActivity() {
LogUtils.d(TAG, "closePhoneCallActivity: 收到外部关闭指令");
if (sIsActivityAlive && sCloseHandler != null) {
sCloseHandler.sendEmptyMessage(MSG_CLOSE_ACTIVITY);
LogUtils.d(TAG, "closePhoneCallActivity: 关闭消息已发送");
} else {
LogUtils.w(TAG, "closePhoneCallActivity: 页面已销毁或Handler未初始化关闭跳过");
}
}
// 生命周期方法区(按执行流程排序)
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_phone_call); LogUtils.d(TAG, MI_ADAPT_TAG + " 通话界面开始创建SDK版本=" + Build.VERSION.SDK_INT);
// 单例双重校验,防止异常场景多实例
if (sIsActivityAlive) {
Toast.makeText(this, TOAST_CALLING, Toast.LENGTH_SHORT).show();
LogUtils.w(TAG, MI_ADAPT_TAG + " 拦截重复创建,即将关闭当前实例");
finish();
return;
}
sIsActivityAlive = false;
setContentView(R.layout.activity_phone_call);
ActivityStack.getInstance().addActivity(this); ActivityStack.getInstance().addActivity(this);
adaptLockScreenAndXiaomi();
initHandler();
initData(); initData();
initView(); initView();
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话界面创建完成");
}
private void initData() {
phoneCallManager = new PhoneCallManager(this);
onGoingCallTimer = new Timer();
if (getIntent() != null) {
phoneNumber = getIntent().getStringExtra(Intent.EXTRA_PHONE_NUMBER);
callType = (PhoneCallService.CallType) getIntent().getSerializableExtra(Intent.EXTRA_MIME_TYPES);
}
}
private void initView() {
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| 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);
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(formatPhoneNumber(phoneNumber));
tvPickUp.setOnClickListener(this);
tvHangUp.setOnClickListener(this);
// 打进的电话
if (callType == PhoneCallService.CallType.CALL_IN) {
tvCallNumberLabel.setText("来电号码");
tvPickUp.setVisibility(View.VISIBLE);
} else if (callType == PhoneCallService.CallType.CALL_OUT) {
tvCallNumberLabel.setText("呼叫号码");
tvPickUp.setVisibility(View.GONE);
phoneCallManager.openSpeaker();
}
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.getId() == R.id.tv_phone_pick_up) {
phoneCallManager.answer();
tvPickUp.setVisibility(View.GONE);
tvCallingTime.setVisibility(View.VISIBLE);
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();
stopTimer();
finish();
}
}
private String getCallingTime() {
int minute = callingTime / 60;
int second = callingTime % 60;
return (minute < 10 ? "0" + minute : minute) +
":" +
(second < 10 ? "0" + second : second);
}
private void stopTimer() {
if (onGoingCallTimer != null) {
onGoingCallTimer.cancel();
}
callingTime = 0;
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
//MainActivity.updateCallLogFragment(); LogUtils.d(TAG, MI_ADAPT_TAG + " 通话界面开始销毁");
phoneCallManager.destroy();
sIsActivityAlive = false;
isClosing = false;
stopTimer();
// 销毁通话管理器
if (mPhoneCallManager != null) {
mPhoneCallManager.destroy();
mPhoneCallManager = null;
LogUtils.d(TAG, "销毁通话管理器资源");
}
// 销毁Handler避免内存泄漏
if (sCloseHandler != null) {
sCloseHandler.removeCallbacksAndMessages(null);
sCloseHandler = null;
LogUtils.d(TAG, "销毁关闭Handler");
}
ActivityStack.getInstance().removeActivity(this);
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话界面销毁完成");
}
@Override
protected void onStop() {
super.onStop();
if (isFinishing()) {
sIsActivityAlive = false;
LogUtils.d(TAG, MI_ADAPT_TAG + " 页面即将关闭,重置单例标记");
}
}
// 点击事件回调
@Override
public void onClick(View v) {
if (v == null) {
LogUtils.w(TAG, "onClick: 点击控件为空,忽略操作");
return;
}
switch (v.getId()) {
case R.id.tv_phone_pick_up:
LogUtils.d(TAG, "onClick: 触发接听操作");
answerCall();
break;
case R.id.tv_phone_hang_up:
LogUtils.d(TAG, "onClick: 触发挂断操作,当前通话时长=" + mCallingTime + "");
hangUpCall();
break;
default:
LogUtils.w(TAG, "onClick: 未知点击事件控件ID=" + v.getId());
}
}
// 初始化方法区(按初始化顺序排列)
private void initHandler() {
sCloseHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == MSG_CLOSE_ACTIVITY) {
LogUtils.d(TAG, "handleMessage: 收到关闭消息,执行挂断逻辑");
hangUpCall();
}
}
};
LogUtils.d(TAG, "initHandler: 关闭Handler初始化完成");
}
private void initData() {
LogUtils.d(TAG, "initData: 开始初始化业务数据");
mPhoneCallManager = PhoneCallManager.getInstance(this);
Intent intent = getIntent();
if (intent == null) {
LogUtils.e(TAG, "initData: 启动Intent为空终止初始化");
removeFromRecentsAndFinish();
return;
}
mPhoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
mCallType = (PhoneCallService.CallType) intent.getSerializableExtra("call_type");
if (mPhoneNumber == null || mCallType == null) {
LogUtils.e(TAG, "initData: 通话号码或类型解析失败");
removeFromRecentsAndFinish();
return;
}
mOnGoingCallTimer = new Timer();
mCallingTime = 0;
LogUtils.d(TAG, "initData: 业务数据初始化完成,号码=" + mPhoneNumber);
}
private void initView() {
LogUtils.d(TAG, "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;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
// 绑定控件
mTvCallNumberLabel = findViewById(R.id.tv_call_number_label);
mTvCallNumber = findViewById(R.id.tv_call_number);
mTvPickUp = findViewById(R.id.tv_phone_pick_up);
mTvCallingTime = findViewById(R.id.tv_phone_calling_time);
mTvHangUp = findViewById(R.id.tv_phone_hang_up);
// 设置控件属性
mTvCallNumber.setText(formatPhoneNumber(mPhoneNumber));
mTvPickUp.setOnClickListener(this);
mTvHangUp.setOnClickListener(this);
// 区分来电/去电UI样式
if (PhoneCallService.CallType.CALL_IN == mCallType) {
mTvCallNumberLabel.setText("来电号码");
mTvPickUp.setVisibility(View.VISIBLE);
mTvCallingTime.setVisibility(View.GONE);
} else if (PhoneCallService.CallType.CALL_OUT == mCallType) {
mTvCallNumberLabel.setText("呼叫号码");
mTvPickUp.setVisibility(View.GONE);
mTvCallingTime.setVisibility(View.VISIBLE);
mTvCallingTime.setText("通话中00:00");
if (mPhoneCallManager != null) {
mPhoneCallManager.openSpeaker();
LogUtils.d(TAG, MI_ADAPT_TAG + " 去电模式自动开启免提");
}
startCallTimer();
}
LogUtils.d(TAG, "initView: 界面控件初始化完成");
}
// 小米机型专属适配方法
private void adaptLockScreenAndXiaomi() {
LogUtils.d(TAG, MI_ADAPT_TAG + " 执行锁屏适配逻辑");
Window window = getWindow();
if (window == null) {
LogUtils.e(TAG, MI_ADAPT_TAG + " Window对象为空适配失败");
return;
}
int flags = WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
// 小米机型额外添加解锁屏标志解决MIUI锁屏拦截问题
if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
LogUtils.d(TAG, MI_ADAPT_TAG + " 已添加小米机型专属锁屏适配标志");
}
window.addFlags(flags);
// 适配API29+锁屏新接口
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setShowWhenLocked(true);
setTurnScreenOn(true);
LogUtils.d(TAG, MI_ADAPT_TAG + " 适配API29+锁屏接口完成");
}
}
// 通话核心业务方法
private void answerCall() {
LogUtils.d(TAG, "answerCall: 执行接听操作");
if (mPhoneCallManager == null) {
LogUtils.e(TAG, "answerCall: 通话管理器为空,接听失败");
return;
}
mPhoneCallManager.answer();
mTvPickUp.setVisibility(View.GONE);
mTvCallingTime.setVisibility(View.VISIBLE);
mTvCallingTime.setText("通话中00:00");
startCallTimer();
LogUtils.d(TAG, "answerCall: 接听操作完成,启动通话计时");
}
private void hangUpCall() {
if (isClosing) {
LogUtils.w(TAG, "hangUpCall: 挂断操作已执行,无需重复调用");
return;
}
LogUtils.d(TAG, "hangUpCall: 执行挂断操作,当前时长=" + mCallingTime + "");
isClosing = true;
stopTimer();
if (mPhoneCallManager != null) {
mPhoneCallManager.disconnect();
LogUtils.d(TAG, "hangUpCall: 通话连接已断开");
}
// 延迟关闭页面,适配小米机型通话时序
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
removeFromRecentsAndFinish();
}
}, CLOSE_DELAY_MS);
}
// 任务栈清理方法
private void removeFromRecentsAndFinish() {
if (isFinishing()) {
LogUtils.d(TAG, "removeFromRecentsAndFinish: 页面已在关闭中,无需重复操作");
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
LogUtils.d(TAG, MI_ADAPT_TAG + " 移除任务栈并关闭页面");
} else {
finish();
LogUtils.d(TAG, "兼容低版本,关闭页面");
}
}
// 计时工具方法
private void startCallTimer() {
LogUtils.d(TAG, "startCallTimer: 启动通话计时器");
if (mOnGoingCallTimer == null) {
mOnGoingCallTimer = new Timer();
}
mOnGoingCallTimer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mCallingTime++;
mTvCallingTime.setText("通话中:" + formatCallingTime(mCallingTime));
}
});
}
}, 0, 1000);
}
private void stopTimer() {
LogUtils.d(TAG, "stopTimer: 停止通话计时器");
if (mOnGoingCallTimer != null) {
mOnGoingCallTimer.cancel();
mOnGoingCallTimer = null;
}
mCallingTime = 0;
}
// 辅助工具方法:格式化通话时长
private String formatCallingTime(int seconds) {
int minute = seconds / 60;
int second = seconds % 60;
String minuteStr = minute < 10 ? "0" + minute : String.valueOf(minute);
String secondStr = second < 10 ? "0" + second : String.valueOf(second);
return minuteStr + ":" + secondStr;
} }
} }

View File

@@ -6,57 +6,199 @@ import android.os.Build;
import android.telecom.Call; import android.telecom.Call;
import android.telecom.VideoProfile; import android.telecom.VideoProfile;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import cc.winboll.studio.libappbase.LogUtils;
/**
@RequiresApi(api = Build.VERSION_CODES.M) * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/15 20:11
* @Describe 通话核心管理类
* 功能:接听/挂断通话、免提控制、资源释放适配API29-30及小米机型
*/
@RequiresApi(api = Build.VERSION_CODES.Q) // 匹配目标适配区间API29
public class PhoneCallManager { public class PhoneCallManager {
// 常量定义区
public static final String TAG = "PhoneCallManager";
private static final String MI_ADAPT_TAG = "MiDeviceAdapt"; // 小米适配标识
private static final int VIDEO_PROFILE_AUDIO_ONLY = VideoProfile.STATE_AUDIO_ONLY;
private static final int AUDIO_MODE_BACKUP = -1; // 音频模式备份默认值
public static Call call; // 成员属性区按依赖优先级排序移除静态call避免跨组件冲突
private Context mContext;
private AudioManager mAudioManager;
private int mAudioModeBackup; // 备份原始音频模式,避免影响其他应用
private boolean mIsSpeakerOpened; // 免提状态标记,防止重复切换
private Context context; // 构造方法(单例化改造,避免多实例冲突)
private AudioManager audioManager; private static volatile PhoneCallManager sInstance;
public static PhoneCallManager getInstance(Context context) {
public PhoneCallManager(Context context) { if (context == null) {
this.context = context; LogUtils.e(TAG, "getInstance: 上下文为空,初始化失败");
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); return null;
}
if (sInstance == null) {
synchronized (PhoneCallManager.class) {
if (sInstance == null) {
sInstance = new PhoneCallManager(context.getApplicationContext()); // 用应用上下文,避免内存泄漏
}
}
}
return sInstance;
} }
// 私有构造,禁止外部实例化
private PhoneCallManager(Context context) {
LogUtils.d(TAG, MI_ADAPT_TAG + " 初始化通话管理类");
this.mContext = context;
this.mAudioModeBackup = AUDIO_MODE_BACKUP;
this.mIsSpeakerOpened = false;
initAudioManager();
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话管理类初始化完成");
}
// 初始化辅助方法
private void initAudioManager() {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager != null) {
// 备份原始音频模式(小米机型切换后需恢复,避免外放异常)
mAudioModeBackup = mAudioManager.getMode();
LogUtils.d(TAG, "音频管理器初始化成功,原始模式备份:" + mAudioModeBackup);
} else {
LogUtils.e(TAG, "音频管理器初始化失败,将影响通话音频控制");
}
}
// 核心业务方法(按使用场景排序,强化小米适配+容错)
/** /**
* 接听电话 * 接听电话,默认音频通话模式
*/ */
public void answer() { public void answer() {
if (call != null) { LogUtils.d(TAG, "执行接听通话操作");
call.answer(VideoProfile.STATE_AUDIO_ONLY); // 从PhoneCallService的静态管理器获取通话对象统一数据源
openSpeaker(); Call currentCall = PhoneCallService.PhoneCallManager.call;
if (currentCall == null) {
LogUtils.e(TAG, "接听失败:通话对象为空");
return;
}
// 校验通话状态,避免重复接听(小米机型状态变更延迟)
if (currentCall.getState() != Call.STATE_RINGING) {
LogUtils.w(TAG, MI_ADAPT_TAG + " 非响铃状态,无需接听,当前状态:" + currentCall.getState());
return;
}
try {
currentCall.answer(VIDEO_PROFILE_AUDIO_ONLY);
openSpeaker(); // 接听后自动开免提
LogUtils.d(TAG, "通话接听成功,自动开启免提");
} catch (SecurityException e) {
LogUtils.e(TAG, MI_ADAPT_TAG + " 接听权限不足需android.permission.ANSWER_PHONE_CALLS", e);
} catch (IllegalStateException e) {
LogUtils.e(TAG, MI_ADAPT_TAG + " 通话状态异常,无法接听", e);
} catch (Exception e) {
LogUtils.e(TAG, "接听通话异常", e);
} }
} }
/** /**
* 断开电话,包括来电时的拒接以及接听后的挂断 * 断开通话(支持来电拒接、通话中挂断
*/ */
public void disconnect() { public void disconnect() {
if (call != null) { LogUtils.d(TAG, "执行断开通话操作");
call.disconnect(); Call currentCall = PhoneCallService.PhoneCallManager.call;
if (currentCall == null) {
LogUtils.e(TAG, "挂断失败:通话对象为空");
return;
}
// 校验通话状态,避免重复挂断
if (currentCall.getState() == Call.STATE_DISCONNECTED) {
LogUtils.w(TAG, MI_ADAPT_TAG + " 通话已断开,无需重复操作");
return;
}
try {
currentCall.disconnect();
closeSpeaker(); // 挂断后关闭免提+恢复音频模式
LogUtils.d(TAG, "通话断开成功");
} catch (SecurityException e) {
LogUtils.e(TAG, MI_ADAPT_TAG + " 挂断权限不足需android.permission.CALL_PHONE", e);
} catch (IllegalStateException e) {
LogUtils.e(TAG, MI_ADAPT_TAG + " 通话状态异常,无法挂断", e);
} catch (Exception e) {
LogUtils.e(TAG, "断开通话异常", e);
} }
} }
/** /**
* 打开免提 * 打开免提适配小米机型音频通道切换解决MIUI音频混乱
*/ */
public void openSpeaker() { public void openSpeaker() {
if (audioManager != null) { LogUtils.d(TAG, "执行打开免提操作");
audioManager.setMode(AudioManager.MODE_IN_CALL); if (mAudioManager == null) {
audioManager.setSpeakerphoneOn(true); LogUtils.e(TAG, "打开免提失败:音频管理器未初始化");
return;
}
if (mIsSpeakerOpened) {
LogUtils.w(TAG, "免提已开启,无需重复操作");
return;
}
try {
// 小米机型适配步骤1. 设置通话模式 2. 关闭静音 3. 开启免提(固定顺序)
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mAudioManager.setStreamMute(AudioManager.STREAM_VOICE_CALL, false); // 确保通话音频不静音
mAudioManager.setSpeakerphoneOn(true);
mIsSpeakerOpened = true;
LogUtils.d(TAG, MI_ADAPT_TAG + " 免提开启成功,当前模式:" + mAudioManager.getMode());
} catch (SecurityException e) {
LogUtils.e(TAG, MI_ADAPT_TAG + " 音频控制权限不足", e);
} catch (Exception e) {
LogUtils.e(TAG, "打开免提异常", e);
} }
} }
/** /**
* 销毁资源 * 新增:关闭免提(挂断/切换场景调用,修复小米音频残留)
*/
public void closeSpeaker() {
LogUtils.d(TAG, "执行关闭免提操作");
if (mAudioManager == null || !mIsSpeakerOpened) {
LogUtils.w(TAG, "免提未开启或音频管理器为空,无需操作");
return;
}
try {
mAudioManager.setSpeakerphoneOn(false);
// 恢复原始音频模式(关键:小米机型不恢复会导致其他应用外放异常)
if (mAudioModeBackup != AUDIO_MODE_BACKUP) {
mAudioManager.setMode(mAudioModeBackup);
LogUtils.d(TAG, MI_ADAPT_TAG + " 恢复原始音频模式:" + mAudioModeBackup);
}
mIsSpeakerOpened = false;
LogUtils.d(TAG, "免提关闭成功");
} catch (Exception e) {
LogUtils.e(TAG, MI_ADAPT_TAG + " 关闭免提异常", e);
}
}
/**
* 销毁资源,避免内存泄漏+音频残留(适配小米内存管理)
*/ */
public void destroy() { public void destroy() {
call = null; LogUtils.d(TAG, "开始销毁通话管理资源");
context = null; closeSpeaker(); // 销毁前强制关闭免提+恢复音频模式
audioManager = null; // 释放资源(应用上下文无需主动置空,避免空指针)
mAudioManager = null;
sInstance = null; // 单例置空,下次重新初始化
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话管理资源销毁完成");
}
/**
* 新增获取当前免提状态供UI层同步显示
*/
public boolean isSpeakerOpened() {
return mIsSpeakerOpened;
} }
} }

View File

@@ -16,246 +16,247 @@ import cc.winboll.studio.libappbase.LogUtils;
* @author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com> * @author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @see PhoneCallActivity * @see PhoneCallActivity
* @see android.telecom.InCallService * @see android.telecom.InCallService
* 适配Java7 语法 + Android API29-30 | 移除录音功能 | 强化稳定性与容错性 * 适配Java7 语法 + Android API29 - 30 | 移除录音功能 | 强化小米设备稳定性与容错性
*/ */
@RequiresApi(api = 29) // 适配API29+替代Build.VERSION_CODES.M匹配InCallService实际最低要求 @RequiresApi(api = 29)
public class PhoneCallService extends InCallService { public class PhoneCallService extends InCallService {
// 常量定义区
// ====================== 常量定义区(精简必要常量,无冗余) ======================
public static final String TAG = "PhoneCallService"; public static final String TAG = "PhoneCallService";
// 小米设备适配标识,便于日志区分
private static final String MI_DEVICE_TAG = "MiDeviceAdapt";
// ====================== 成员属性区(按功能归类,命名规范) ====================== // 成员属性区(按依赖顺序排列)
private Call.Callback mCallCallback; // 通话状态回调(统一管理,便于销毁) private Call.Callback mCallCallback;
private AudioManager mAudioManager;
// ====================== 内部枚举类(提前定义,便于业务调用) ====================== // 内部枚举类(通话类型定义)
public enum CallType { public enum CallType {
CALL_IN, // 来电 CALL_IN, // 来电
CALL_OUT // 去电 CALL_OUT // 去电
} }
// ====================== Service生命周期方法区按执行顺序排列 ====================== // Service生命周期方法区按执行流程排序
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
LogUtils.d(TAG, "===== onCreate: 通话监听服务启动 ====="); LogUtils.d(TAG, MI_DEVICE_TAG + " 通话监听服务启动");
// 初始化通话状态回调(提前初始化,避免重复创建) initAudioManager();
initCallCallback(); initCallCallback();
LogUtils.d(TAG, "===== onCreate: 服务初始化完成 ====="); LogUtils.d(TAG, MI_DEVICE_TAG + " 服务初始化完成");
} }
@Override @Override
public void onCallAdded(Call call) { public void onCallAdded(Call call) {
super.onCallAdded(call); super.onCallAdded(call);
LogUtils.d(TAG, "onCallAdded: 检测到新通话,开始处理"); LogUtils.d(TAG, "检测到新通话");
// 空指针防护:避免通话对象为空导致崩溃
if (call == null) { if (call == null) {
LogUtils.e(TAG, "onCallAdded: 通话对象为空,跳过处理"); LogUtils.e(TAG, "通话对象为空,跳过处理");
return; return;
} }
// 判断通话类型(来电/去电)
// 双重校验回调,避免重复注册
if (mCallCallback != null) {
call.registerCallback(mCallCallback);
}
// 绑定通话对象到管理器供UI层调用
PhoneCallManager.call = call;
LogUtils.d(TAG, MI_DEVICE_TAG + " 通话回调注册成功,对象绑定完成");
CallType callType = judgeCallType(call); CallType callType = judgeCallType(call);
if (callType != null) { if (callType != null) {
// 处理有效通话(音量控制、规则校验、启动通话界面) handleValidCall(call, callType);
if (handleValidCall(call, callType)) {
// 注册通话状态回调
call.registerCallback(mCallCallback);
PhoneCallManager.call = call;
LogUtils.d(TAG, "onCallAdded: 已注册通话回调,通话对象绑定完成");
}
} else { } else {
LogUtils.w(TAG, "onCallAdded: 无法识别通话类型,通话状态=" + call.getState()); LogUtils.w(TAG, "无法识别通话类型,状态码:" + call.getState());
} }
} }
@Override @Override
public void onCallRemoved(Call call) { public void onCallRemoved(Call call) {
super.onCallRemoved(call); super.onCallRemoved(call);
LogUtils.d(TAG, "onCallRemoved: 通话结束,开始清理资源"); LogUtils.d(TAG, "通话结束,开始清理资源");
if (call != null && mCallCallback != null) {
// 空指针防护:避免通话对象为空导致崩溃
if (call != null) {
call.unregisterCallback(mCallCallback); call.unregisterCallback(mCallCallback);
LogUtils.d(TAG, "onCallRemoved: 已注销通话回调"); LogUtils.d(TAG, "通话回调已注销");
} }
PhoneCallManager.call = null; // 延迟置空通话对象避免UI层挂断时对象已被释放适配小米机型时序
LogUtils.d(TAG, "onCallRemoved: 通话资源清理完成"); new Thread(new Runnable() {
@Override
public void run() {
try {
// 延迟200ms确保PhoneCallActivity挂断逻辑执行完成
Thread.sleep(200);
PhoneCallManager.call = null;
} catch (InterruptedException e) {
LogUtils.e(TAG, MI_DEVICE_TAG + " 延迟置空通话对象异常", e);
}
}
}).start();
PhoneCallActivity.closePhoneCallActivity();
LogUtils.d(TAG, MI_DEVICE_TAG + " 通话资源清理完成");
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
LogUtils.d(TAG, "onDestroy: 通话监听服务开始销毁"); LogUtils.d(TAG, "服务开始销毁");
// 更新通话记录列表
CallLogFragment.updateCallLogFragment(); CallLogFragment.updateCallLogFragment();
LogUtils.d(TAG, "onDestroy: 通话监听服务销毁完成"); // 释放资源,适配小米设备内存管理,避免内存泄漏
mCallCallback = null;
mAudioManager = null;
LogUtils.d(TAG, MI_DEVICE_TAG + " 服务销毁完成");
}
// 初始化方法区
private void initAudioManager() {
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if (mAudioManager == null) {
LogUtils.e(TAG, MI_DEVICE_TAG + " 获取音频管理器失败");
} else {
LogUtils.d(TAG, MI_DEVICE_TAG + " 音频管理器初始化成功");
}
} }
// ====================== 核心初始化方法区 ======================
/**
* 初始化通话状态回调,统一管理通话状态变更逻辑
*/
private void initCallCallback() { private void initCallCallback() {
mCallCallback = new Call.Callback() { mCallCallback = new Call.Callback() {
@Override @Override
public void onStateChanged(Call call, int state) { public void onStateChanged(Call call, int state) {
super.onStateChanged(call, state); super.onStateChanged(call, state);
LogUtils.d(TAG, "onStateChanged: 通话状态变更,状态码=" + state + ",状态描述=" + getCallStateDesc(state)); if (call == null) {
LogUtils.e(TAG, "onStateChanged: 通话对象为空");
return;
}
String stateDesc = getCallStateDesc(state);
LogUtils.d(TAG, "通话状态变更:" + stateDesc + "(状态码:" + state + "");
switch (state) { switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
// 通话空闲(挂断后),无需额外处理(原录音停止逻辑已删除)
LogUtils.d(TAG, "onStateChanged: 通话进入空闲状态");
break;
case Call.STATE_DISCONNECTED: case Call.STATE_DISCONNECTED:
// 通话断开,关闭通话界 // 双重校验,避免重复关闭页
ActivityStack.getInstance().finishActivity(PhoneCallActivity.class); if (ActivityStack.getInstance().getActivity(PhoneCallActivity.class) != null) {
LogUtils.d(TAG, "onStateChanged: 通话断开,已关闭通话界面"); ActivityStack.getInstance().finishActivity(PhoneCallActivity.class);
LogUtils.d(TAG, "通话界面已关闭");
}
break; break;
// 保留其他状态分支,便于后续扩展,无冗余逻辑
case Call.STATE_ACTIVE: case Call.STATE_ACTIVE:
LogUtils.d(TAG, "onStateChanged: 通话进入活跃状态"); LogUtils.d(TAG, MI_DEVICE_TAG + " 通话进入活跃状态,适配音频通道");
break; break;
default: default:
break; break;
} }
} }
}; };
LogUtils.d(TAG, "initCallCallback: 通话状态回调初始化完成"); LogUtils.d(TAG, "通话状态回调初始化完成");
} }
// ====================== 核心业务处理方法区 ====================== // 核心业务处理方法区
/**
* 判断通话类型(来电/去电)
* @param call 通话对象
* @return 通话类型枚举无法识别返回null
*/
private CallType judgeCallType(Call call) { private CallType judgeCallType(Call call) {
if (call == null) {
LogUtils.e(TAG, "judgeCallType: 通话对象为空");
return null;
}
int callState = call.getState(); int callState = call.getState();
if (callState == Call.STATE_RINGING) { if (callState == Call.STATE_RINGING) {
LogUtils.d(TAG, "judgeCallType: 通话状态为响铃,识别为来电"); LogUtils.d(TAG, "识别为来电");
return CallType.CALL_IN; return CallType.CALL_IN;
} else if (callState == Call.STATE_CONNECTING) { } else if (callState == Call.STATE_CONNECTING) {
LogUtils.d(TAG, "judgeCallType: 通话状态为连接中,识别为去电"); LogUtils.d(TAG, "识别为去电");
return CallType.CALL_OUT; return CallType.CALL_OUT;
} }
return null; return null;
} }
/**
* 处理有效通话(音量控制、拦截规则校验、启动通话界面)
* @param call 通话对象
* @param callType 通话类型(来电/去电)
*/
private boolean handleValidCall(Call call, CallType callType) { private boolean handleValidCall(Call call, CallType callType) {
// 1. 获取通话详情与号码(多层空指针防护) if (call == null || callType == null) {
Call.Details callDetails = call.getDetails(); LogUtils.e(TAG, "handleValidCall: 通话对象或类型为空");
if (callDetails == null || callDetails.getHandle() == null) {
LogUtils.e(TAG, "handleValidCall: 通话详情或号码信息为空,跳过后续处理");
return false; return false;
} }
String phoneNumber = callDetails.getHandle().getSchemeSpecificPart();
LogUtils.d(TAG, "handleValidCall: 开始处理通话,号码=" + phoneNumber + ",类型=" + callType);
// 2. 初始化音频管理器(音量控制核心) Call.Details callDetails = call.getDetails();
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); if (callDetails == null || callDetails.getHandle() == null) {
if (audioManager == null) { LogUtils.e(TAG, "通话详情缺失,处理终止");
LogUtils.e(TAG, "handleValidCall: 获取音频管理器失败,无法处理音量控制"); return false;
// 音量控制失败仍启动通话界面,保障基础功能可用 }
String phoneNumber = callDetails.getHandle().getSchemeSpecificPart();
LogUtils.d(TAG, "处理通话:号码=" + phoneNumber + ",类型=" + callType.name());
if (mAudioManager == null) {
LogUtils.e(TAG, "音频管理器未初始化");
PhoneCallActivity.actionStart(this, phoneNumber, callType); PhoneCallActivity.actionStart(this, phoneNumber, callType);
return true; return true;
} }
// 3. 处理铃声音量(恢复配置音量、拦截时静音) if (checkRulesAndHandleRingerVolumeControl(phoneNumber, call)) {
if (handleRingerVolumeControl(audioManager, phoneNumber, call)) { PhoneCallActivity.actionStart(this, phoneNumber, callType);
// 4. 校验通过,启动通话界面(拦截场景已提前返回,此处直接启动) LogUtils.d(TAG, MI_DEVICE_TAG + " 通话界面启动成功");
PhoneCallActivity.actionStart(this, phoneNumber, callType); return true;
LogUtils.d(TAG, "handleValidCall: 通话校验通过,已启动通话界面"); }
return true; return false;
}
return false;
} }
/** private boolean checkRulesAndHandleRingerVolumeControl(String phoneNumber, Call call) {
* 铃声音量控制(恢复应用配置音量、拦截号码静音处理) if (mAudioManager == null || phoneNumber == null || call == null) {
* @param audioManager 音频管理器 LogUtils.e(TAG, "checkRulesAndHandleRingerVolumeControl: 入参为空");
* @param phoneNumber 通话号码 return false;
* @param call 通话对象(用于拦截时断开通话) }
* @return true : 继续通话。 false : 通话被中断。
*/ int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
private boolean handleRingerVolumeControl(AudioManager audioManager, String phoneNumber, Call call) { LogUtils.d(TAG, "当前铃声音量:" + currentVolume);
// 3.1 获取当前铃声音量
int currentRingerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
LogUtils.d(TAG, "handleRingerVolumeControl: 当前铃声音量=" + currentRingerVolume);
// 3.2 加载/初始化铃声音量配置
RingTongBean ringTongBean = RingTongBean.loadBean(this, RingTongBean.class); RingTongBean ringTongBean = RingTongBean.loadBean(this, RingTongBean.class);
if (ringTongBean == null) { if (ringTongBean == null) {
ringTongBean = new RingTongBean(); ringTongBean = new RingTongBean();
RingTongBean.saveBean(this, ringTongBean); RingTongBean.saveBean(this, ringTongBean);
LogUtils.d(TAG, "handleRingerVolumeControl: 铃声音量配置未初始化,已自动创建默认配置"); LogUtils.d(TAG, "初始化默认铃音配置");
} }
int configRingerVolume = ringTongBean.getStreamVolume(); final int configVolume = ringTongBean.getStreamVolume();
LogUtils.d(TAG, "handleRingerVolumeControl: 应用配置铃声音量=" + configRingerVolume);
// 3.3 恢复应用配置音量(当前音量与配置不一致时调整)
try { try {
if (currentRingerVolume != configRingerVolume) { // 小米机型适配:调整音量时添加权限校验
audioManager.setStreamVolume(AudioManager.STREAM_RING, configRingerVolume, 0); if (currentVolume != configVolume) {
LogUtils.d(TAG, "handleRingerVolumeControl: 已将铃声音量恢复为应用配置值"); mAudioManager.setStreamVolume(AudioManager.STREAM_RING, configVolume, 0);
} else { LogUtils.d(TAG, MI_DEVICE_TAG + " 铃声音量调整为配置值:" + configVolume);
LogUtils.d(TAG, "handleRingerVolumeControl: 当前音量与配置一致,无需调整");
} }
} catch (SecurityException e) { } catch (SecurityException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 恢复铃声音量失败,权限不足", e); LogUtils.e(TAG, "音量调整失败,权限不足", e);
return false; return false;
} }
// 3.4 校验拦截规则,拦截号码静音+断开通话 // 校验拦截规则
if (!Rules.getInstance(this).isAllowed(phoneNumber)) { if (!Rules.getInstance(this).isAllowed(phoneNumber)) {
LogUtils.d(TAG, "handleRingerVolumeControl: 号码=" + phoneNumber + " 命中拦截规则,开始拦截处理"); LogUtils.d(TAG, "号码" + phoneNumber + "命中拦截规则");
try { try {
// 静音处理 // 拦截时静音并挂断
audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
LogUtils.d(TAG, "handleRingerVolumeControl: 已将铃声音量设为0静音"); call.disconnect();
LogUtils.d(TAG, MI_DEVICE_TAG + " 拦截通话已挂断并静音");
// 延迟恢复音量,适配小米机型音频通道延迟
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
if (mAudioManager != null) {
mAudioManager.setStreamVolume(AudioManager.STREAM_RING, configVolume, 0);
LogUtils.d(TAG, MI_DEVICE_TAG + " 延迟恢复铃音配置");
}
} catch (InterruptedException e) {
LogUtils.e(TAG, "恢复音量线程中断", e);
}
}
}).start();
} catch (SecurityException e) { } catch (SecurityException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 拦截静音失败,权限不足", e); LogUtils.e(TAG, "拦截静音失败", e);
return false; return false;
} }
// 断开通话
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);
return false;
} catch (SecurityException e) {
LogUtils.e(TAG, "handleRingerVolumeControl: 恢复音量失败,权限不足", e);
return false;
}
// 拦截完成,直接返回,不启动通话界面
LogUtils.d(TAG, "handleRingerVolumeControl: 拦截处理完成,跳过通话界面启动");
return false; return false;
} }
return true;
LogUtils.d(TAG, "handleRingerVolumeControl: 号码=" + phoneNumber + " 未命中拦截规则,音量控制完成");
return true;
} }
// ====================== 辅助工具方法区 ====================== // 辅助工具方法区:解析通话状态描述
/**
* 通话状态码转文字描述(便于日志查看,快速定位状态)
* @param state 通话状态码TelephonyManager/Call 中的常量)
* @return 状态文字描述
*/
private String getCallStateDesc(int state) { private String getCallStateDesc(int state) {
switch (state) { switch (state) {
case TelephonyManager.CALL_STATE_RINGING: case TelephonyManager.CALL_STATE_RINGING:
@@ -263,16 +264,21 @@ public class PhoneCallService extends InCallService {
case TelephonyManager.CALL_STATE_OFFHOOK: case TelephonyManager.CALL_STATE_OFFHOOK:
return "通话中"; return "通话中";
case TelephonyManager.CALL_STATE_IDLE: case TelephonyManager.CALL_STATE_IDLE:
return "空闲(未通话/已挂断)"; return "空闲";
case Call.STATE_ACTIVE: case Call.STATE_ACTIVE:
return "通话活跃"; return "通话活跃";
case Call.STATE_CONNECTING: case Call.STATE_CONNECTING:
return "通话连接中"; return "连接中";
case Call.STATE_DISCONNECTED: case Call.STATE_DISCONNECTED:
return "通话已断开"; return "已断开";
default: default:
return "未知状态"; return "未知状态";
} }
} }
// 静态内部类:统一管理通话对象,避免跨组件对象混乱
public static class PhoneCallManager {
public static Call call;
}
} }