通话测试通过。
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#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
|
||||
libraryProject=
|
||||
baseVersion=15.14
|
||||
publishVersion=15.14.0
|
||||
buildCount=3
|
||||
buildCount=15
|
||||
baseBetaVersion=15.14.1
|
||||
|
||||
@@ -1,139 +1,313 @@
|
||||
package cc.winboll.studio.contacts;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 06:58:04
|
||||
* @Describe Activity 栈管理工具,用于统一管理应用内 Activity 生命周期
|
||||
* @Describe Activity 栈管理工具,统一管理应用内 Activity 生命周期
|
||||
* 适配:Java7 + Android API29-30 + 小米机型,优化并发安全与通话场景稳定性
|
||||
*/
|
||||
public class ActivityStack {
|
||||
// ====================== 常量定义区 ======================
|
||||
// 常量定义(核心标识+版本兼容常量)
|
||||
public static final String TAG = "ActivityStack";
|
||||
private static final int API_VERSION_O = 26; // Android 8.0 API26(isDestroyed适配用)
|
||||
|
||||
// ====================== 单例与成员变量区 ======================
|
||||
// 单例与核心成员变量(按优先级排序)
|
||||
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() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// ====================== 私有构造函数(防止外部实例化) ======================
|
||||
// 私有构造,禁止外部实例化
|
||||
private ActivityStack() {
|
||||
LogUtils.d(TAG, "ActivityStack: 初始化 Activity 栈管理工具");
|
||||
LogUtils.d(TAG, "ActivityStack 初始化完成");
|
||||
}
|
||||
|
||||
// ====================== Activity 栈操作方法区 ======================
|
||||
// ====================== 栈基础操作(添加/移除) ======================
|
||||
/**
|
||||
* 添加 Activity 到栈中
|
||||
* 添加Activity到栈中,避免重复入栈
|
||||
* @param activity 待添加的Activity
|
||||
*/
|
||||
public void addActivity(Activity activity) {
|
||||
if (activity == null) {
|
||||
LogUtils.w(TAG, "addActivity: 待添加的 Activity 为 null,跳过添加");
|
||||
LogUtils.w(TAG, "addActivity: activity is null, skip");
|
||||
return;
|
||||
}
|
||||
mActivityList.add(activity);
|
||||
LogUtils.d(TAG, "addActivity: Activity入栈 | 类名=" + activity.getClass().getSimpleName() + " | 栈大小=" + 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());
|
||||
// 同步锁:解决多线程并发添加冲突(小米机型多线程场景适配)
|
||||
synchronized (mActivityList) {
|
||||
if (!mActivityList.contains(activity)) {
|
||||
mActivityList.add(activity);
|
||||
LogUtils.d(TAG, "addActivity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁栈中所有 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正常退出)
|
||||
* 移除Activity(不销毁,用于正常退出场景)
|
||||
* @param activity 待移除的Activity
|
||||
*/
|
||||
public void removeActivity(Activity activity) {
|
||||
if (activity == null) {
|
||||
LogUtils.w(TAG, "removeActivity: 待移除的 Activity 为 null,跳过操作");
|
||||
LogUtils.w(TAG, "removeActivity: activity is null, skip");
|
||||
return;
|
||||
}
|
||||
if (mActivityList.remove(activity)) {
|
||||
LogUtils.d(TAG, "removeActivity: 移除 Activity | 类名=" + activity.getClass().getSimpleName() + " | 栈大小=" + mActivityList.size());
|
||||
synchronized (mActivityList) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ====================== 栈优化与工具方法 ======================
|
||||
/**
|
||||
* 清理栈中所有无效Activity(null/已销毁/已结束),优化小米机型内存占用
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,162 +1,362 @@
|
||||
package cc.winboll.studio.contacts.phonecallui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.contacts.ActivityStack;
|
||||
import cc.winboll.studio.contacts.MainActivity;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static cc.winboll.studio.contacts.listenphonecall.CallListenerService.formatPhoneNumber;
|
||||
|
||||
|
||||
/**
|
||||
* 提供接打电话的界面,仅支持 Android M (6.0, API 23) 及以上的系统
|
||||
*
|
||||
* @author aJIEw
|
||||
* @Author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/14 21:01
|
||||
* @Describe 接打电话界面(单例模式 + 适配API29 - 30 + 小米机型兼容性优化)
|
||||
* 功能:单例通话窗口、来电/去电显示、通话计时、免提控制、锁屏显示
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class PhoneCallActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
public class PhoneCallActivity extends Activity 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 TextView tvPickUp;
|
||||
private TextView tvCallingTime;
|
||||
private TextView tvHangUp;
|
||||
// 静态属性区(单例核心+全局工具对象)
|
||||
private static volatile boolean sIsActivityAlive = false;
|
||||
private static Handler sCloseHandler;
|
||||
|
||||
private PhoneCallManager phoneCallManager;
|
||||
private PhoneCallService.CallType callType;
|
||||
private String phoneNumber;
|
||||
// 控件属性区(按界面布局顺序排列)
|
||||
private TextView mTvCallNumberLabel;
|
||||
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.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, callType);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
intent.putExtra("call_type", callType);
|
||||
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, phoneNumber);
|
||||
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
|
||||
protected void onCreate(Bundle 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);
|
||||
adaptLockScreenAndXiaomi();
|
||||
initHandler();
|
||||
initData();
|
||||
initView();
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话界面创建完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
//MainActivity.updateCallLogFragment();
|
||||
phoneCallManager.destroy();
|
||||
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话界面开始销毁");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,57 +6,199 @@ import android.os.Build;
|
||||
import android.telecom.Call;
|
||||
import android.telecom.VideoProfile;
|
||||
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 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;
|
||||
|
||||
public PhoneCallManager(Context context) {
|
||||
this.context = context;
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
// 构造方法(单例化改造,避免多实例冲突)
|
||||
private static volatile PhoneCallManager sInstance;
|
||||
public static PhoneCallManager getInstance(Context context) {
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "getInstance: 上下文为空,初始化失败");
|
||||
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() {
|
||||
if (call != null) {
|
||||
call.answer(VideoProfile.STATE_AUDIO_ONLY);
|
||||
openSpeaker();
|
||||
LogUtils.d(TAG, "执行接听通话操作");
|
||||
// 从PhoneCallService的静态管理器获取通话对象,统一数据源
|
||||
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() {
|
||||
if (call != null) {
|
||||
call.disconnect();
|
||||
LogUtils.d(TAG, "执行断开通话操作");
|
||||
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() {
|
||||
if (audioManager != null) {
|
||||
audioManager.setMode(AudioManager.MODE_IN_CALL);
|
||||
audioManager.setSpeakerphoneOn(true);
|
||||
LogUtils.d(TAG, "执行打开免提操作");
|
||||
if (mAudioManager == null) {
|
||||
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() {
|
||||
call = null;
|
||||
context = null;
|
||||
audioManager = null;
|
||||
LogUtils.d(TAG, "开始销毁通话管理资源");
|
||||
closeSpeaker(); // 销毁前强制关闭免提+恢复音频模式
|
||||
// 释放资源(应用上下文无需主动置空,避免空指针)
|
||||
mAudioManager = null;
|
||||
sInstance = null; // 单例置空,下次重新初始化
|
||||
LogUtils.d(TAG, MI_ADAPT_TAG + " 通话管理资源销毁完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:获取当前免提状态(供UI层同步显示)
|
||||
*/
|
||||
public boolean isSpeakerOpened() {
|
||||
return mIsSpeakerOpened;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,246 +16,247 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
* @author aJIEw, ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @see PhoneCallActivity
|
||||
* @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 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 {
|
||||
CALL_IN, // 来电
|
||||
CALL_OUT // 去电
|
||||
}
|
||||
|
||||
// ====================== Service生命周期方法区(按执行顺序排列) ======================
|
||||
// Service生命周期方法区(按执行流程排序)
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "===== onCreate: 通话监听服务启动 =====");
|
||||
// 初始化通话状态回调(提前初始化,避免重复创建)
|
||||
LogUtils.d(TAG, MI_DEVICE_TAG + " 通话监听服务启动");
|
||||
initAudioManager();
|
||||
initCallCallback();
|
||||
LogUtils.d(TAG, "===== onCreate: 服务初始化完成 =====");
|
||||
LogUtils.d(TAG, MI_DEVICE_TAG + " 服务初始化完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallAdded(Call call) {
|
||||
super.onCallAdded(call);
|
||||
LogUtils.d(TAG, "onCallAdded: 检测到新通话,开始处理");
|
||||
|
||||
// 空指针防护:避免通话对象为空导致崩溃
|
||||
LogUtils.d(TAG, "检测到新通话");
|
||||
if (call == null) {
|
||||
LogUtils.e(TAG, "onCallAdded: 通话对象为空,跳过处理");
|
||||
LogUtils.e(TAG, "通话对象为空,跳过处理");
|
||||
return;
|
||||
}
|
||||
// 判断通话类型(来电/去电)
|
||||
|
||||
// 双重校验回调,避免重复注册
|
||||
if (mCallCallback != null) {
|
||||
call.registerCallback(mCallCallback);
|
||||
}
|
||||
// 绑定通话对象到管理器,供UI层调用
|
||||
PhoneCallManager.call = call;
|
||||
LogUtils.d(TAG, MI_DEVICE_TAG + " 通话回调注册成功,对象绑定完成");
|
||||
|
||||
CallType callType = judgeCallType(call);
|
||||
if (callType != null) {
|
||||
// 处理有效通话(音量控制、规则校验、启动通话界面)
|
||||
if (handleValidCall(call, callType)) {
|
||||
// 注册通话状态回调
|
||||
call.registerCallback(mCallCallback);
|
||||
PhoneCallManager.call = call;
|
||||
LogUtils.d(TAG, "onCallAdded: 已注册通话回调,通话对象绑定完成");
|
||||
}
|
||||
handleValidCall(call, callType);
|
||||
} else {
|
||||
LogUtils.w(TAG, "onCallAdded: 无法识别通话类型,通话状态=" + call.getState());
|
||||
LogUtils.w(TAG, "无法识别通话类型,状态码:" + call.getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallRemoved(Call call) {
|
||||
super.onCallRemoved(call);
|
||||
LogUtils.d(TAG, "onCallRemoved: 通话结束,开始清理资源");
|
||||
|
||||
// 空指针防护:避免通话对象为空导致崩溃
|
||||
if (call != null) {
|
||||
LogUtils.d(TAG, "通话结束,开始清理资源");
|
||||
if (call != null && mCallCallback != null) {
|
||||
call.unregisterCallback(mCallCallback);
|
||||
LogUtils.d(TAG, "onCallRemoved: 已注销通话回调");
|
||||
LogUtils.d(TAG, "通话回调已注销");
|
||||
}
|
||||
|
||||
PhoneCallManager.call = null;
|
||||
LogUtils.d(TAG, "onCallRemoved: 通话资源清理完成");
|
||||
// 延迟置空通话对象,避免UI层挂断时对象已被释放(适配小米机型时序)
|
||||
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
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy: 通话监听服务开始销毁");
|
||||
// 更新通话记录列表
|
||||
LogUtils.d(TAG, "服务开始销毁");
|
||||
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() {
|
||||
mCallCallback = new Call.Callback() {
|
||||
@Override
|
||||
public void onStateChanged(Call call, int 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) {
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
// 通话空闲(挂断后),无需额外处理(原录音停止逻辑已删除)
|
||||
LogUtils.d(TAG, "onStateChanged: 通话进入空闲状态");
|
||||
break;
|
||||
case Call.STATE_DISCONNECTED:
|
||||
// 通话断开,关闭通话界面
|
||||
ActivityStack.getInstance().finishActivity(PhoneCallActivity.class);
|
||||
LogUtils.d(TAG, "onStateChanged: 通话断开,已关闭通话界面");
|
||||
// 双重校验,避免重复关闭页面
|
||||
if (ActivityStack.getInstance().getActivity(PhoneCallActivity.class) != null) {
|
||||
ActivityStack.getInstance().finishActivity(PhoneCallActivity.class);
|
||||
LogUtils.d(TAG, "通话界面已关闭");
|
||||
}
|
||||
break;
|
||||
// 保留其他状态分支,便于后续扩展,无冗余逻辑
|
||||
case Call.STATE_ACTIVE:
|
||||
LogUtils.d(TAG, "onStateChanged: 通话进入活跃状态");
|
||||
LogUtils.d(TAG, MI_DEVICE_TAG + " 通话进入活跃状态,适配音频通道");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
LogUtils.d(TAG, "initCallCallback: 通话状态回调初始化完成");
|
||||
LogUtils.d(TAG, "通话状态回调初始化完成");
|
||||
}
|
||||
|
||||
// ====================== 核心业务处理方法区 ======================
|
||||
/**
|
||||
* 判断通话类型(来电/去电)
|
||||
* @param call 通话对象
|
||||
* @return 通话类型枚举,无法识别返回null
|
||||
*/
|
||||
// 核心业务处理方法区
|
||||
private CallType judgeCallType(Call call) {
|
||||
if (call == null) {
|
||||
LogUtils.e(TAG, "judgeCallType: 通话对象为空");
|
||||
return null;
|
||||
}
|
||||
int callState = call.getState();
|
||||
if (callState == Call.STATE_RINGING) {
|
||||
LogUtils.d(TAG, "judgeCallType: 通话状态为响铃,识别为来电");
|
||||
LogUtils.d(TAG, "识别为来电");
|
||||
return CallType.CALL_IN;
|
||||
} else if (callState == Call.STATE_CONNECTING) {
|
||||
LogUtils.d(TAG, "judgeCallType: 通话状态为连接中,识别为去电");
|
||||
LogUtils.d(TAG, "识别为去电");
|
||||
return CallType.CALL_OUT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理有效通话(音量控制、拦截规则校验、启动通话界面)
|
||||
* @param call 通话对象
|
||||
* @param callType 通话类型(来电/去电)
|
||||
*/
|
||||
private boolean handleValidCall(Call call, CallType callType) {
|
||||
// 1. 获取通话详情与号码(多层空指针防护)
|
||||
Call.Details callDetails = call.getDetails();
|
||||
if (callDetails == null || callDetails.getHandle() == null) {
|
||||
LogUtils.e(TAG, "handleValidCall: 通话详情或号码信息为空,跳过后续处理");
|
||||
if (call == null || callType == null) {
|
||||
LogUtils.e(TAG, "handleValidCall: 通话对象或类型为空");
|
||||
return false;
|
||||
}
|
||||
String phoneNumber = callDetails.getHandle().getSchemeSpecificPart();
|
||||
LogUtils.d(TAG, "handleValidCall: 开始处理通话,号码=" + phoneNumber + ",类型=" + callType);
|
||||
|
||||
// 2. 初始化音频管理器(音量控制核心)
|
||||
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||||
if (audioManager == null) {
|
||||
LogUtils.e(TAG, "handleValidCall: 获取音频管理器失败,无法处理音量控制");
|
||||
// 音量控制失败仍启动通话界面,保障基础功能可用
|
||||
Call.Details callDetails = call.getDetails();
|
||||
if (callDetails == null || callDetails.getHandle() == null) {
|
||||
LogUtils.e(TAG, "通话详情缺失,处理终止");
|
||||
return false;
|
||||
}
|
||||
|
||||
String phoneNumber = callDetails.getHandle().getSchemeSpecificPart();
|
||||
LogUtils.d(TAG, "处理通话:号码=" + phoneNumber + ",类型=" + callType.name());
|
||||
|
||||
if (mAudioManager == null) {
|
||||
LogUtils.e(TAG, "音频管理器未初始化");
|
||||
PhoneCallActivity.actionStart(this, phoneNumber, callType);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 处理铃声音量(恢复配置音量、拦截时静音)
|
||||
if (handleRingerVolumeControl(audioManager, phoneNumber, call)) {
|
||||
// 4. 校验通过,启动通话界面(拦截场景已提前返回,此处直接启动)
|
||||
PhoneCallActivity.actionStart(this, phoneNumber, callType);
|
||||
LogUtils.d(TAG, "handleValidCall: 通话校验通过,已启动通话界面");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (checkRulesAndHandleRingerVolumeControl(phoneNumber, call)) {
|
||||
PhoneCallActivity.actionStart(this, phoneNumber, callType);
|
||||
LogUtils.d(TAG, MI_DEVICE_TAG + " 通话界面启动成功");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 铃声音量控制(恢复应用配置音量、拦截号码静音处理)
|
||||
* @param audioManager 音频管理器
|
||||
* @param phoneNumber 通话号码
|
||||
* @param call 通话对象(用于拦截时断开通话)
|
||||
* @return true : 继续通话。 false : 通话被中断。
|
||||
*/
|
||||
private boolean handleRingerVolumeControl(AudioManager audioManager, String phoneNumber, Call call) {
|
||||
// 3.1 获取当前铃声音量
|
||||
int currentRingerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 当前铃声音量=" + currentRingerVolume);
|
||||
private boolean checkRulesAndHandleRingerVolumeControl(String phoneNumber, Call call) {
|
||||
if (mAudioManager == null || phoneNumber == null || call == null) {
|
||||
LogUtils.e(TAG, "checkRulesAndHandleRingerVolumeControl: 入参为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
|
||||
LogUtils.d(TAG, "当前铃声音量:" + currentVolume);
|
||||
|
||||
// 3.2 加载/初始化铃声音量配置
|
||||
RingTongBean ringTongBean = RingTongBean.loadBean(this, RingTongBean.class);
|
||||
if (ringTongBean == null) {
|
||||
ringTongBean = new RingTongBean();
|
||||
RingTongBean.saveBean(this, ringTongBean);
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 铃声音量配置未初始化,已自动创建默认配置");
|
||||
LogUtils.d(TAG, "初始化默认铃音配置");
|
||||
}
|
||||
int configRingerVolume = ringTongBean.getStreamVolume();
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 应用配置铃声音量=" + configRingerVolume);
|
||||
final int configVolume = ringTongBean.getStreamVolume();
|
||||
|
||||
// 3.3 恢复应用配置音量(当前音量与配置不一致时调整)
|
||||
try {
|
||||
if (currentRingerVolume != configRingerVolume) {
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_RING, configRingerVolume, 0);
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 已将铃声音量恢复为应用配置值");
|
||||
} else {
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 当前音量与配置一致,无需调整");
|
||||
// 小米机型适配:调整音量时添加权限校验
|
||||
if (currentVolume != configVolume) {
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_RING, configVolume, 0);
|
||||
LogUtils.d(TAG, MI_DEVICE_TAG + " 铃声音量调整为配置值:" + configVolume);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
LogUtils.e(TAG, "handleRingerVolumeControl: 恢复铃声音量失败,权限不足", e);
|
||||
LogUtils.e(TAG, "音量调整失败,权限不足", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3.4 校验拦截规则,拦截号码静音+断开通话
|
||||
// 校验拦截规则
|
||||
if (!Rules.getInstance(this).isAllowed(phoneNumber)) {
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 号码=" + phoneNumber + " 命中拦截规则,开始拦截处理");
|
||||
LogUtils.d(TAG, "号码" + phoneNumber + "命中拦截规则");
|
||||
try {
|
||||
// 静音处理
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 已将铃声音量设为0(静音)");
|
||||
// 拦截时静音并挂断
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 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) {
|
||||
LogUtils.e(TAG, "handleRingerVolumeControl: 拦截静音失败,权限不足", e);
|
||||
return false;
|
||||
LogUtils.e(TAG, "拦截静音失败", e);
|
||||
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;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "handleRingerVolumeControl: 号码=" + phoneNumber + " 未命中拦截规则,音量控制完成");
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ====================== 辅助工具方法区 ======================
|
||||
/**
|
||||
* 通话状态码转文字描述(便于日志查看,快速定位状态)
|
||||
* @param state 通话状态码(TelephonyManager/Call 中的常量)
|
||||
* @return 状态文字描述
|
||||
*/
|
||||
// 辅助工具方法区:解析通话状态描述
|
||||
private String getCallStateDesc(int state) {
|
||||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
@@ -263,16 +264,21 @@ public class PhoneCallService extends InCallService {
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
return "通话中";
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
return "空闲(未通话/已挂断)";
|
||||
return "空闲";
|
||||
case Call.STATE_ACTIVE:
|
||||
return "通话活跃";
|
||||
case Call.STATE_CONNECTING:
|
||||
return "通话连接中";
|
||||
return "连接中";
|
||||
case Call.STATE_DISCONNECTED:
|
||||
return "通话已断开";
|
||||
return "已断开";
|
||||
default:
|
||||
return "未知状态";
|
||||
}
|
||||
}
|
||||
|
||||
// 静态内部类:统一管理通话对象,避免跨组件对象混乱
|
||||
public static class PhoneCallManager {
|
||||
public static Call call;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user