From a4ab864381b133ba43244ca652d41fb440bc9553 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 15 Dec 2025 20:51:56 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=9A=E8=AF=9D=E6=B5=8B=E8=AF=95=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contacts/build.properties | 4 +- .../studio/contacts/ActivityStack.java | 366 +++++++++++---- .../phonecallui/PhoneCallActivity.java | 442 +++++++++++++----- .../phonecallui/PhoneCallManager.java | 190 +++++++- .../phonecallui/PhoneCallService.java | 290 ++++++------ 5 files changed, 907 insertions(+), 385 deletions(-) diff --git a/contacts/build.properties b/contacts/build.properties index ebd21ac..aa717c2 100644 --- a/contacts/build.properties +++ b/contacts/build.properties @@ -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 diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/ActivityStack.java b/contacts/src/main/java/cc/winboll/studio/contacts/ActivityStack.java index 6bba8da..59a12dc 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/ActivityStack.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/ActivityStack.java @@ -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&豆包大模型 * @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 mActivityList = new ArrayList(); + // 替换为ArrayList+同步锁:解决CopyOnWriteArrayList迭代器不能删除的崩溃,兼顾并发安全 + private final List mActivityList = new ArrayList(); + 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 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; + } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java index 6583d28..4da49e7 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java @@ -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&豆包大模型 + * @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; } } + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java index 7a75273..9307cdf 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java @@ -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&豆包大模型 + * @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; } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java index 3ea7ff4..e5beede 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java @@ -16,246 +16,247 @@ import cc.winboll.studio.libappbase.LogUtils; * @author aJIEw, ZhanGSKen&豆包大模型 * @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; + } }