20251213_150313_142

This commit is contained in:
2025-12-13 15:03:18 +08:00
parent 2b7108940b
commit 9a873bf162
7 changed files with 1228 additions and 860 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Dec 13 05:41:14 GMT 2025
#Sat Dec 13 07:01:07 GMT 2025
stageCount=1
libraryProject=
baseVersion=15.12
publishVersion=15.12.0
buildCount=99
buildCount=111
baseBetaVersion=15.12.1

View File

@@ -98,19 +98,22 @@
<service
android:name=".services.MainService"
android:foregroundServiceType="dataSync"
android:exported="false" />
android:exported="false"
android:stopWithTask="false"/>
<!-- 辅助服务dataSync 类型 -->
<service
android:name=".services.AssistantService"
android:foregroundServiceType="dataSync"
android:exported="false" />
android:exported="false"
android:stopWithTask="false"/>
<!-- 通话UI服务系统绑定 -->
<service
android:name=".phonecallui.PhoneCallService"
android:permission="android.permission.BIND_INCALL_SERVICE"
android:exported="false">
android:exported="false"
android:stopWithTask="false">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
@@ -127,7 +130,7 @@
android:name=".listenphonecall.CallListenerService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="phoneCall">
android:stopWithTask="false">
<intent-filter android:priority="1000">
<action android:name=".service.CallShowService"/>
@@ -137,15 +140,16 @@
<!-- API 30+ 通话筛选服务(替代 PROCESS_OUTGOING_CALLS 权限) -->
<service
android:name=".services.MyCallScreeningService"
android:foregroundServiceType="phoneCall"
android:permission="android.permission.BIND_CALL_SCREENING_SERVICE"
android:exported="true">
android:permission="android.permission.BIND_CALL_SCREENING_SERVICE"
android:exported="true"
android:stopWithTask="false">
<intent-filter>
<action android:name="android.telecom.CallScreeningService"/>
</intent-filter>
</service>
<receiver android:name=".receivers.MainReceiver">
<receiver android:name=".receivers.MainReceiver"
android:stopWithTask="false">
<intent-filter>
<action android:name="cc.winboll.studio.contacts.receivers.MainReceiver"/>
</intent-filter>
@@ -153,7 +157,8 @@
<receiver
android:name=".widgets.APPStatusWidget"
android:exported="true">
android:exported="true"
android:stopWithTask="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -167,7 +172,8 @@
</receiver>
<receiver android:name=".widgets.APPStatusWidgetClickListener">
<receiver android:name=".widgets.APPStatusWidgetClickListener"
android:stopWithTask="false">
<intent-filter>
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidgetClickListener.ACTION_APPICON_CLICK"/>
</intent-filter>

View File

@@ -41,39 +41,38 @@ import java.util.List;
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/21 05:37:42
* @Describe Contacts 设置页面(完全适配 API 30 + Java 7 语法)
* 核心优化1. 移除高版本API依赖 2. Java7规范写法 3. 强化内存泄漏防护 4. 版本判断硬编码
* 核心优化1. 移除高版本API依赖 2. Java7规范写法 3. 强化内存泄漏防护 4. 版本判断硬编码 5. LogUtils统一日志管理
*/
public class SettingsActivity extends WinBollActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
// ====================== 常量定义区(置顶,统一管理) ======================
public static final String TAG = "SettingsActivity";
// API版本硬编码常量替代Build.VERSION_CODES适配API30
// API版本硬编码替代Build.VERSION_CODES适配Java7
private static final int ANDROID_6_API = 23;
// ====================== 静态控件区 ======================
static DuInfoTextView _DuInfoTextView;
// ====================== 静态成员属性区 ======================
private static DuInfoTextView sDuInfoTextView; // 规范命名静态属性加s前缀
// ====================== UI控件区 ======================
private Toolbar mToolbar;
private Switch swSilent;
private SeekBar msbVolume;
private TextView mtvVolume;
private Switch mswMainService;
private EditText etDunTotalCount;
private EditText etDunResumeSecondCount;
private EditText etDunResumeCount;
private Switch swIsEnableDun;
private RecyclerView recyclerView;
private EditText etBoBullToonURL;
private EditText etPhone;
// ====================== 数据业务属性区 ======================
private int mStreamMaxVolume; // 铃音最大音量
private int mStreamVolume; // 当前铃音音量
private List<PhoneConnectRuleBean> mRuleList; // 通话规则列表
private PhoneConnectRuleAdapter mRuleAdapter; // 规则列表适配器
// ====================== 数据与业务参数区 ======================
private int mnStreamMaxVolume;
private int mnStreamVolume;
private PhoneConnectRuleAdapter adapter;
private List<PhoneConnectRuleBean> ruleList;
// ====================== UI控件属性区统一归类规范命名 ======================
private Toolbar mToolbar; // 顶部工具栏
private Switch mSwMainService; // 主服务开关
private SeekBar mSbVolume; // 音量调节条
private TextView mTvVolume; // 音量显示文本
private Switch mSwEnableDun; // 云盾功能开关
private EditText mEtDunTotalCount; // 云盾总次数输入框
private EditText mEtDunResumeSecondCount; // 云盾恢复秒数输入框
private EditText mEtDunResumeCount; // 云盾恢复次数输入框
private RecyclerView mRvRuleList; // 规则列表RecyclerView
private EditText mEtBoBullToonUrl; // BoBullToon地址输入框
private EditText mEtSearchPhone; // 号码查询输入框
// ====================== 接口实现区 ======================
// ====================== 接口实现区IWinBoLLActivity规范实现 ======================
@Override
public AppCompatActivity getActivity() {
return this;
@@ -84,25 +83,20 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
return TAG;
}
// ====================== 生命周期函数区 ======================
// ====================== 生命周期函数区(按执行顺序排列) ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 设置页面开始创建");
LogUtils.d(TAG, "onCreate: 设置页面启动");
setContentView(R.layout.activity_settings);
// 初始化工具栏
initToolbar();
// 初始化主服务开关
initMainServiceSwitch();
// 初始化铃声音量调节
initVolumeSeekBar();
// 初始化规则列表
initRuleRecyclerView();
// 初始化云盾设置
initDunSettings();
// 初始化BoBullToon相关控件
initBoBullToonViews();
// 初始化核心流程(按优先级执行)
initToolbar(); // 工具栏初始化(优先)
initMainServiceSwitch();// 主服务开关初始化
initVolumeControl(); // 音量控制初始化
initRuleRecyclerView(); // 规则列表初始化
initDunSettings(); // 云盾设置初始化
initBoBullToonViews(); // BoBullToon功能初始化
LogUtils.d(TAG, "onCreate: 设置页面初始化完成");
}
@@ -110,56 +104,70 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 设置页面开始销毁");
// 清空静态引用 + 置空所有控件引用,彻底避免内存泄漏
_DuInfoTextView = null;
LogUtils.d(TAG, "onDestroy: 设置页面销毁");
// 内存泄漏防护:清空所有引用(静态+成员+UI
sDuInfoTextView = null;
mRuleList = null;
mRuleAdapter = null;
mToolbar = null;
mswMainService = null;
msbVolume = null;
mtvVolume = null;
recyclerView = null;
adapter = null;
ruleList = null;
LogUtils.d(TAG, "onDestroy: 设置页面销毁完成");
mSwMainService = null;
mSbVolume = null;
mTvVolume = null;
mSwEnableDun = null;
mEtDunTotalCount = null;
mEtDunResumeSecondCount = null;
mEtDunResumeCount = null;
mRvRuleList = null;
mEtBoBullToonUrl = null;
mEtSearchPhone = null;
LogUtils.d(TAG, "onDestroy: 设置页面资源清理完成");
}
// ====================== 控件初始化函数区 ======================
// ====================== 初始化函数区(按功能模块归类) ======================
/**
* 初始化顶部工具栏(后退按钮+标题)
*/
private void initToolbar() {
LogUtils.d(TAG, "initToolbar: 初始化工具栏");
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
setSupportActionBar(mToolbar);
// 显示后退按钮(添加空指针防护)
// 显示后退按钮(空指针防护)
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setSubtitle(getTag());
getSupportActionBar().setSubtitle(TAG);
}
// Java7 匿名内部类实现点击监听禁止Lambda
// 后退按钮点击事件Java7匿名内部类
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "initToolbar: 点击导航栏返回按钮");
LogUtils.d(TAG, "initToolbar: 点击后退按钮,关闭页面");
finish();
}
});
}
/**
* 初始化主服务开关联动MainService启停
*/
private void initMainServiceSwitch() {
LogUtils.d(TAG, "initMainServiceSwitch: 初始化主服务开关");
mswMainService = (Switch) findViewById(R.id.sw_mainservice);
MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
// 空指针防护避免MainServiceBean为null导致崩溃
if (mMainServiceBean != null) {
mswMainService.setChecked(mMainServiceBean.isEnable());
} else {
mswMainService.setChecked(false);
LogUtils.w(TAG, "initMainServiceSwitch: MainServiceBean为null默认关闭开关");
}
mSwMainService = (Switch) findViewById(R.id.sw_mainservice);
MainServiceBean serviceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
mswMainService.setOnClickListener(new View.OnClickListener() {
// 加载开关状态(空指针防护)
boolean isServiceEnable = serviceBean != null && serviceBean.isEnable();
mSwMainService.setChecked(isServiceEnable);
LogUtils.d(TAG, "initMainServiceSwitch: 主服务当前状态:" + (isServiceEnable ? "启用" : "禁用"));
// 开关点击事件
mSwMainService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
LogUtils.d(TAG, "initMainServiceSwitch: 主服务开关状态变更checked=" + mswMainService.isChecked());
if (mswMainService.isChecked()) {
public void onClick(View v) {
boolean isChecked = mSwMainService.isChecked();
LogUtils.d(TAG, "initMainServiceSwitch: 主服务开关切换:" + (isChecked ? "启用" : "禁用"));
if (isChecked) {
MainService.startMainServiceAndSaveStatus(SettingsActivity.this);
} else {
MainService.stopMainServiceAndSaveStatus(SettingsActivity.this);
@@ -168,40 +176,44 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
});
}
private void initVolumeSeekBar() {
LogUtils.d(TAG, "initVolumeSeekBar: 初始化音量调节条");
msbVolume = (SeekBar) findViewById(R.id.bellvolume);
mtvVolume = (TextView) findViewById(R.id.tv_volume);
/**
* 初始化音量控制SeekBar+音量显示+配置保存)
*/
private void initVolumeControl() {
LogUtils.d(TAG, "initVolumeControl: 初始化音量控制");
mSbVolume = (SeekBar) findViewById(R.id.bellvolume);
mTvVolume = (TextView) findViewById(R.id.tv_volume);
final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// 空指针防护AudioManager可能为null
// 空指针防护AudioManager获取失败直接返回
if (audioManager == null) {
LogUtils.e(TAG, "initVolumeSeekBar: AudioManager获取失败跳过音量初始化");
LogUtils.e(TAG, "initVolumeControl: AudioManager获取失败音量控制初始化失败");
return;
}
// 设置SeekBar最大值和初始进度
mnStreamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
msbVolume.setMax(mnStreamMaxVolume);
mnStreamVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
msbVolume.setProgress(mnStreamVolume);
updateStreamVolumeTextView();
// 初始化音量参数
mStreamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
mStreamVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
mSbVolume.setMax(mStreamMaxVolume);
mSbVolume.setProgress(mStreamVolume);
updateVolumeDisplay(); // 更新音量文本显示
// 音量调节监听Java7匿名内部类
msbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
// 音量调节监听
mSbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
LogUtils.d(TAG, "initVolumeSeekBar: 音量调节至" + progress);
LogUtils.d(TAG, "initVolumeControl: 音量调节至" + progress + "/" + mStreamMaxVolume);
// 实时更新系统音量+保存配置
audioManager.setStreamVolume(AudioManager.STREAM_RING, progress, 0);
RingTongBean bean = RingTongBean.loadBean(SettingsActivity.this, RingTongBean.class);
if (bean == null) {
bean = new RingTongBean();
RingTongBean ringBean = RingTongBean.loadBean(SettingsActivity.this, RingTongBean.class);
if (ringBean == null) {
ringBean = new RingTongBean();
}
bean.setStreamVolume(progress);
RingTongBean.saveBean(SettingsActivity.this, bean);
mnStreamVolume = progress;
updateStreamVolumeTextView();
ringBean.setStreamVolume(progress);
RingTongBean.saveBean(SettingsActivity.this, ringBean);
mStreamVolume = progress;
updateVolumeDisplay();
}
}
@@ -213,253 +225,340 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
});
}
/**
* 初始化通话规则列表(加载黑白名单规则)
*/
private void initRuleRecyclerView() {
LogUtils.d(TAG, "initRuleRecyclerView: 初始化规则列表");
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
ruleList = Rules.getInstance(this).getPhoneBlacRuleBeanList();
adapter = new PhoneConnectRuleAdapter(this, ruleList);
recyclerView.setAdapter(adapter);
LogUtils.d(TAG, "initRuleRecyclerView: 规则列表加载完成,共" + ruleList.size() + "条规则");
mRvRuleList = (RecyclerView) findViewById(R.id.recycler_view);
mRvRuleList.setLayoutManager(new LinearLayoutManager(this));
// 加载规则数据
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "initRuleRecyclerView: Rules实例获取失败列表初始化失败");
return;
}
mRuleList = rules.getPhoneBlacRuleBeanList();
mRuleAdapter = new PhoneConnectRuleAdapter(this, mRuleList);
mRvRuleList.setAdapter(mRuleAdapter);
LogUtils.d(TAG, "initRuleRecyclerView: 规则列表加载完成,共" + mRuleList.size() + "条规则");
}
/**
* 初始化云盾设置(参数加载+开关联动)
*/
private void initDunSettings() {
LogUtils.d(TAG, "initDunSettings: 初始化云盾设置");
_DuInfoTextView = (DuInfoTextView) findViewById(R.id.tv_DunInfo);
etDunTotalCount = (EditText) findViewById(R.id.et_DunTotalCount);
etDunResumeSecondCount = (EditText) findViewById(R.id.et_DunResumeSecondCount);
etDunResumeCount = (EditText) findViewById(R.id.et_DunResumeCount);
swIsEnableDun = (Switch) findViewById(R.id.sw_IsEnableDun);
sDuInfoTextView = (DuInfoTextView) findViewById(R.id.tv_DunInfo);
mSwEnableDun = (Switch) findViewById(R.id.sw_IsEnableDun);
mEtDunTotalCount = (EditText) findViewById(R.id.et_DunTotalCount);
mEtDunResumeSecondCount = (EditText) findViewById(R.id.et_DunResumeSecondCount);
mEtDunResumeCount = (EditText) findViewById(R.id.et_DunResumeCount);
SettingsBean settingsModel = Rules.getInstance(this).getSettingsModel();
// 空指针防护避免SettingsModel为null
if (settingsModel != null) {
etDunTotalCount.setText(Integer.toString(settingsModel.getDunTotalCount()));
etDunResumeSecondCount.setText(Integer.toString(settingsModel.getDunResumeSecondCount()));
etDunResumeCount.setText(Integer.toString(settingsModel.getDunResumeCount()));
swIsEnableDun.setChecked(settingsModel.isEnableDun());
boolean isEnableDun = settingsModel.isEnableDun();
etDunTotalCount.setEnabled(!isEnableDun);
etDunResumeSecondCount.setEnabled(!isEnableDun);
etDunResumeCount.setEnabled(!isEnableDun);
} else {
LogUtils.e(TAG, "initDunSettings: SettingsModel为null云盾设置初始化失败");
}
}
private void initBoBullToonViews() {
LogUtils.d(TAG, "initBoBullToonViews: 初始化BoBullToon相关控件");
etBoBullToonURL = (EditText) findViewById(R.id.bobulltoonurl_et);
etPhone = (EditText) findViewById(R.id.activitysettingsEditText1);
// 空指针防护避免Rules.getInstance返回null
// 加载云盾配置
Rules rules = Rules.getInstance(this);
if (rules != null) {
etBoBullToonURL.setText(rules.getBoBullToonURL());
if (rules == null) {
LogUtils.e(TAG, "initDunSettings: Rules实例获取失败云盾初始化失败");
return;
}
}
// ====================== 工具函数区 ======================
private void updateStreamVolumeTextView() {
// Java7 字符串拼接避免String.format可能的空指针
mtvVolume.setText(mnStreamVolume + "/" + mnStreamMaxVolume);
}
// ====================== 静态通知函数区 ======================
public static void notifyDunInfoUpdate() {
if (_DuInfoTextView != null) {
LogUtils.d(TAG, "notifyDunInfoUpdate: 更新云盾信息展示");
_DuInfoTextView.notifyInfoUpdate();
} else {
LogUtils.w(TAG, "notifyDunInfoUpdate: 云盾信息控件为空,跳过更新");
}
}
// ====================== 点击事件回调函数区 ======================
public void onSW_IsEnableDun(View view) {
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾开关状态变更checked=" + swIsEnableDun.isChecked());
boolean isEnableDun = swIsEnableDun.isChecked();
etDunTotalCount.setEnabled(!isEnableDun);
etDunResumeSecondCount.setEnabled(!isEnableDun);
etDunResumeCount.setEnabled(!isEnableDun);
SettingsBean settingsModel = Rules.getInstance(this).getSettingsModel();
// 空指针防护SettingsModel为null时直接返回
if (settingsModel == null) {
LogUtils.e(TAG, "onSW_IsEnableDun: SettingsModel为null操作失败");
swIsEnableDun.setChecked(false);
SettingsBean dunSettings = rules.getSettingsModel();
if (dunSettings == null) {
LogUtils.e(TAG, "initDunSettings: 云盾配置获取失败");
return;
}
if (isEnableDun) {
// 填充配置参数
mEtDunTotalCount.setText(String.valueOf(dunSettings.getDunTotalCount()));
mEtDunResumeSecondCount.setText(String.valueOf(dunSettings.getDunResumeSecondCount()));
mEtDunResumeCount.setText(String.valueOf(dunSettings.getDunResumeCount()));
mSwEnableDun.setChecked(dunSettings.isEnableDun());
// 开关联动:启用云盾时禁用参数编辑
boolean isDunEnable = dunSettings.isEnableDun();
mEtDunTotalCount.setEnabled(!isDunEnable);
mEtDunResumeSecondCount.setEnabled(!isDunEnable);
mEtDunResumeCount.setEnabled(!isDunEnable);
LogUtils.d(TAG, "initDunSettings: 云盾当前状态:" + (isDunEnable ? "启用" : "禁用"));
}
/**
* 初始化BoBullToon功能地址配置+号码查询)
*/
private void initBoBullToonViews() {
LogUtils.d(TAG, "initBoBullToonViews: 初始化BoBullToon功能");
mEtBoBullToonUrl = (EditText) findViewById(R.id.bobulltoonurl_et);
mEtSearchPhone = (EditText) findViewById(R.id.activitysettingsEditText1);
// 加载保存的地址
Rules rules = Rules.getInstance(this);
if (rules != null) {
mEtBoBullToonUrl.setText(rules.getBoBullToonURL());
LogUtils.d(TAG, "initBoBullToonViews: 加载BoBullToon地址完成");
} else {
LogUtils.e(TAG, "initBoBullToonViews: Rules实例获取失败地址加载失败");
}
}
// ====================== 点击事件回调区(按功能模块归类) ======================
/**
* 云盾开关点击事件(联动参数编辑权限+配置保存)
*/
public void onSW_IsEnableDun(View view) {
boolean isChecked = mSwEnableDun.isChecked();
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾开关切换:" + (isChecked ? "启用" : "禁用"));
// 联动参数编辑权限
mEtDunTotalCount.setEnabled(!isChecked);
mEtDunResumeSecondCount.setEnabled(!isChecked);
mEtDunResumeCount.setEnabled(!isChecked);
// 保存配置
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onSW_IsEnableDun: Rules实例获取失败配置保存失败");
mSwEnableDun.setChecked(false);
return;
}
SettingsBean dunSettings = rules.getSettingsModel();
if (dunSettings == null) {
LogUtils.e(TAG, "onSW_IsEnableDun: 云盾配置获取失败,保存失败");
mSwEnableDun.setChecked(false);
return;
}
// 启用云盾时校验参数合法性
if (isChecked) {
try {
String totalCountStr = etDunTotalCount.getText().toString().trim();
String resumeSecondStr = etDunResumeSecondCount.getText().toString().trim();
String resumeCountStr = etDunResumeCount.getText().toString().trim();
// 空字符串校验
if (totalCountStr.isEmpty() || resumeSecondStr.isEmpty() || resumeCountStr.isEmpty()) {
String totalCountStr = mEtDunTotalCount.getText().toString().trim();
String resumeSecStr = mEtDunResumeSecondCount.getText().toString().trim();
String resumeCountStr = mEtDunResumeCount.getText().toString().trim();
// 空参数校验
if (totalCountStr.isEmpty() || resumeSecStr.isEmpty() || resumeCountStr.isEmpty()) {
throw new NumberFormatException("参数不能为空");
}
// 转换参数并保存
int totalCount = Integer.parseInt(totalCountStr);
int resumeSecond = Integer.parseInt(resumeSecondStr);
int resumeSec = Integer.parseInt(resumeSecStr);
int resumeCount = Integer.parseInt(resumeCountStr);
settingsModel.setDunTotalCount(totalCount);
settingsModel.setDunResumeSecondCount(resumeSecond);
settingsModel.setDunResumeCount(resumeCount);
// Java7 条件判断 + 字符串拼接
String toastMsg = (totalCount == 1) ? "电话骚扰防御力几乎为0。" : "以下设置将在连拨" + totalCount + "次后接通电话。";
dunSettings.setDunTotalCount(totalCount);
dunSettings.setDunResumeSecondCount(resumeSec);
dunSettings.setDunResumeCount(resumeCount);
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾参数保存完成,总次数:" + totalCount + ",恢复秒数:" + resumeSec);
// 提示信息
String toastMsg = totalCount == 1 ? "电话骚扰防御力几乎为0" : "连拨" + totalCount + "次后接通电话";
ToastUtils.show(toastMsg);
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾参数更新完成totalCount=" + totalCount);
} catch (NumberFormatException e) {
LogUtils.e(TAG, "onSW_IsEnableDun: 云盾参数格式错误", e);
ToastUtils.show("参数格式错误,请输入整数");
swIsEnableDun.setChecked(false);
mSwEnableDun.setChecked(false);
return;
}
}
settingsModel.setIsEnableDun(isEnableDun);
Rules.getInstance(this).saveDun();
Rules.getInstance(this).reload();
// 刷新参数显示
etDunTotalCount.setText(Integer.toString(settingsModel.getDunTotalCount()));
etDunResumeSecondCount.setText(Integer.toString(settingsModel.getDunResumeSecondCount()));
etDunResumeCount.setText(Integer.toString(settingsModel.getDunResumeCount()));
}
public void onUnitTest(View view) {
LogUtils.d(TAG, "onUnitTest: 点击进入单元测试页面");
Intent intent = new Intent(this, UnitTestActivity.class);
startActivity(intent);
// 保存开关状态并刷新配置
dunSettings.setIsEnableDun(isChecked);
rules.saveDun();
rules.reload();
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾配置保存完成");
}
/**
* 添加新通话规则(黑白名单)
*/
public void onAddNewConnectionRule(View view) {
LogUtils.d(TAG, "onAddNewConnectionRule: 添加新的连接规则");
LogUtils.d(TAG, "onAddNewConnectionRule: 添加新通话规则");
Rules rules = Rules.getInstance(this);
if (rules != null) {
rules.getPhoneBlacRuleBeanList().add(new PhoneConnectRuleBean());
rules.saveRules();
adapter.notifyDataSetChanged();
} else {
LogUtils.e(TAG, "onAddNewConnectionRule: Rules实例为null添加规则失败");
if (rules == null) {
LogUtils.e(TAG, "onAddNewConnectionRule: Rules实例获取失败添加失败");
return;
}
mRuleList.add(new PhoneConnectRuleBean());
rules.saveRules();
mRuleAdapter.notifyDataSetChanged();
LogUtils.d(TAG, "onAddNewConnectionRule: 规则添加完成,当前共" + mRuleList.size() + "条规则");
}
/**
* 跳转默认电话应用设置
*/
public void onDefaultPhone(View view) {
LogUtils.d(TAG, "onDefaultPhone: 跳转默认电话应用设置");
Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS);
startActivity(intent);
LogUtils.d(TAG, "onDefaultPhone: 跳转默认电话应用设置");
startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS));
}
/**
* 悬浮窗权限检查与请求
*/
public void onCanDrawOverlays(View view) {
LogUtils.d(TAG, "onCanDrawOverlays: 检查悬浮窗权限");
// 版本判断硬编码替代Build.VERSION_CODES.M适配API30
// API6.0+校验权限
if (Build.VERSION.SDK_INT >= ANDROID_6_API && !Settings.canDrawOverlays(this)) {
askForDrawOverlay();
LogUtils.d(TAG, "onCanDrawOverlays: 未开启悬浮窗权限,发起请求");
showDrawOverlayRequestDialog();
} else {
ToastUtils.show("悬浮窗已开启");
ToastUtils.show("悬浮窗权限已开启");
}
}
/**
* 清理BoBullToon本地数据
*/
public void onCleanBoBullToonData(View view) {
LogUtils.d(TAG, "onCleanBoBullToonData: 清理BoBullToon数据");
TomCat tomCat = TomCat.getInstance(this);
if (tomCat != null) {
tomCat.cleanBoBullToon();
ToastUtils.show("BoBullToon数据已清理");
LogUtils.d(TAG, "onCleanBoBullToonData: 数据清理完成");
} else {
LogUtils.e(TAG, "onCleanBoBullToonData: TomCat实例获取失败清理失败");
}
}
/**
* 重置BoBullToon默认地址
*/
public void onResetBoBullToonURL(View view) {
LogUtils.d(TAG, "onResetBoBullToonURL: 重置BoBullToon URL");
LogUtils.d(TAG, "onResetBoBullToonURL: 重置BoBullToon地址");
Rules rules = Rules.getInstance(this);
if (rules != null) {
rules.resetDefaultBoBullToonURL();
etBoBullToonURL.setText(rules.getBoBullToonURL());
ToastUtils.show("已重置 BoBullToon URL。");
if (rules == null) {
LogUtils.e(TAG, "onResetBoBullToonURL: Rules实例获取失败重置失败");
return;
}
rules.resetDefaultBoBullToonURL();
mEtBoBullToonUrl.setText(rules.getBoBullToonURL());
ToastUtils.show("BoBullToon地址已重置为默认");
LogUtils.d(TAG, "onResetBoBullToonURL: 地址重置完成");
}
/**
* 下载BoBullToon数据子线程执行避免阻塞UI
*/
public void onDownloadBoBullToon(View view) {
LogUtils.d(TAG, "onDownloadBoBullToon: 开始下载BoBullToon数据");
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onDownloadBoBullToon: Rules实例为null,下载失败");
LogUtils.e(TAG, "onDownloadBoBullToon: Rules实例获取失败,下载失败");
return;
}
String inputUrl = etBoBullToonURL.getText().toString().trim();
// 校验并更新地址
String inputUrl = mEtBoBullToonUrl.getText().toString().trim();
String savedUrl = rules.getBoBullToonURL();
if (!inputUrl.equals(savedUrl)) {
rules.setBoBullToonURL(inputUrl);
LogUtils.d(TAG, "onDownloadBoBullToon: BoBullToon URL已更新为" + inputUrl);
LogUtils.d(TAG, "onDownloadBoBullToon: BoBullToon地址更新为" + inputUrl);
}
// 子线程下载Java7匿名内部类
final TomCat tomCat = TomCat.getInstance(this);
// Java7 匿名内部类实现Runnable禁止Lambda
new Thread(new Runnable() {
@Override
public void run() {
if (tomCat != null && tomCat.downloadBoBullToon()) {
LogUtils.d(TAG, "onDownloadBoBullToon: BoBullToon 下载成功");
// 主线程更新UIJava7 匿名内部类)
boolean downloadSuccess = tomCat != null && tomCat.downloadBoBullToon();
if (downloadSuccess) {
LogUtils.d(TAG, "onDownloadBoBullToon: 数据下载成功");
// 主线程更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
ToastUtils.show("BoBullToon download OK!");
ToastUtils.show("BoBullToon下载成功");
}
});
// 重启主服务+刷新配置
MainService.restartMainService(SettingsActivity.this);
Rules.getInstance(SettingsActivity.this).reload();
} else {
LogUtils.e(TAG, "onDownloadBoBullToon: BoBullToon 下载失败");
LogUtils.e(TAG, "onDownloadBoBullToon: 数据下载失败");
runOnUiThread(new Runnable() {
@Override
public void run() {
ToastUtils.show("BoBullToon下载失败");
}
});
}
}
}).start();
}
/**
* 查询号码是否为BoBullToon号码
*/
public void onSearchBoBullToonPhone(View view) {
LogUtils.d(TAG, "onSearchBoBullToonPhone: 搜索BoBullToon号码");
TomCat tomCat = TomCat.getInstance(this);
String phone = etPhone.getText().toString().trim();
LogUtils.d(TAG, "onSearchBoBullToonPhone: 执行号码查询");
String phone = mEtSearchPhone.getText().toString().trim();
// 空号码校验
if (phone.isEmpty()) {
ToastUtils.show("请输入要查询号码");
LogUtils.w(TAG, "onSearchBoBullToonPhone: 查询号码为空,取消查询");
ToastUtils.show("请输入查询号码");
return;
}
if (tomCat != null && tomCat.loadPhoneBoBullToon()) {
boolean isBoBullToon = tomCat.isPhoneBoBullToon(phone);
String toastMsg = isBoBullToon ? "It is a BoBullToon Phone!" : "Not in BoBullToon.";
ToastUtils.show(toastMsg);
LogUtils.d(TAG, "onSearchBoBullToonPhone: 号码" + phone + "查询结果:" + (isBoBullToon ? "" : "") + "为BoBullToon号码");
} else {
ToastUtils.show("没有下载 BoBullToon。");
// 执行查询
TomCat tomCat = TomCat.getInstance(this);
if (tomCat == null || !tomCat.loadPhoneBoBullToon()) {
LogUtils.w(TAG, "onSearchBoBullToonPhone: BoBullToon数据未加载查询失败");
ToastUtils.show("请先下载BoBullToon数据");
return;
}
boolean isBoBullToon = tomCat.isPhoneBoBullToon(phone);
String resultMsg = isBoBullToon ? "是BoBullToon号码" : "非BoBullToon号码";
ToastUtils.show(resultMsg);
LogUtils.d(TAG, "onSearchBoBullToonPhone: 号码" + phone + "查询结果:" + resultMsg);
}
/**
* 跳转单元测试页面
*/
public void onUnitTest(View view) {
LogUtils.d(TAG, "onUnitTest: 跳转单元测试页面");
startActivity(new Intent(this, UnitTestActivity.class));
}
/**
* 跳转关于页面
*/
public void onAbout(View view) {
LogUtils.d(TAG, "onAbout: 跳转关于页面");
LogUtils.d(TAG, "onAbout: 跳转关于页面");
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, AboutActivity.class);
}
/**
* 跳转日志查看页面
*/
public void onLogView(View view) {
LogUtils.d(TAG, "onLogView: 跳转日志页面");
LogUtils.d(TAG, "onLogView: 跳转日志页面");
WinBoLLActivityManager.getInstance().startLogActivity(this);
}
// ====================== 悬浮窗权限请求函数区 ======================
private void askForDrawOverlay() {
LogUtils.d(TAG, "askForDrawOverlay: 弹出悬浮窗权限请求对话框");
// Java7 匿名内部类实现Dialog监听
AlertDialog alertDialog = new AlertDialog.Builder(this)
.setTitle("允许显示悬浮框")
.setMessage("为了使电话监听服务正常工作,请允许这项权限")
// ====================== 工具方法区(通用功能+权限相关) ======================
/**
* 更新音量显示文本(当前音量/最大音量)
*/
private void updateVolumeDisplay() {
mTvVolume.setText(mStreamVolume + "/" + mStreamMaxVolume);
}
/**
* 显示悬浮窗权限请求对话框
*/
private void showDrawOverlayRequestDialog() {
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("权限请求")
.setMessage("为保证通话监听功能正常,需开启悬浮窗权限")
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
openDrawOverlaySettings();
dialog.dismiss();
jumpToDrawOverlaySettings();
}
})
.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {
.setNegativeButton("稍后", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
@@ -467,30 +566,47 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
})
.create();
if (alertDialog.getWindow() != null) {
alertDialog.getWindow().setFlags(
// 解决对话框焦点问题
if (dialog.getWindow() != null) {
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
alertDialog.show();
dialog.show();
}
private void openDrawOverlaySettings() {
LogUtils.d(TAG, "openDrawOverlaySettings: 跳转悬浮窗管理设置界面");
// 版本判断硬编码
if (Build.VERSION.SDK_INT >= ANDROID_6_API) {
try {
Context context = this;
Class<?> clazz = Settings.class;
Field field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
Intent intent = new Intent(field.get(null).toString());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(intent);
} catch (Exception e) {
LogUtils.e(TAG, "openDrawOverlaySettings: 跳转悬浮窗设置失败", e);
Toast.makeText(this, "请在悬浮窗管理中打开权限", Toast.LENGTH_LONG).show();
}
/**
* 跳转悬浮窗权限设置页面(反射适配低版本)
*/
private void jumpToDrawOverlaySettings() {
LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转悬浮窗权限设置");
try {
// 反射获取设置页面Action避免高版本API依赖
Class<?> settingsClazz = Settings.class;
Field actionField = settingsClazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
String action = (String) actionField.get(null);
// 跳转当前应用权限设置页
Intent intent = new Intent(action);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (Exception e) {
LogUtils.e(TAG, "jumpToDrawOverlaySettings: 跳转权限设置失败", e);
Toast.makeText(this, "请手动在设置中开启悬浮窗权限", Toast.LENGTH_LONG).show();
}
}
// ====================== 静态通知方法区(云盾信息更新) ======================
/**
* 通知云盾信息刷新(外部调用)
*/
public static void notifyDunInfoUpdate() {
if (sDuInfoTextView != null) {
LogUtils.d(TAG, "notifyDunInfoUpdate: 刷新云盾信息显示");
sDuInfoTextView.notifyInfoUpdate();
} else {
LogUtils.w(TAG, "notifyDunInfoUpdate: 云盾信息控件未初始化,刷新失败");
}
}
}

View File

@@ -1,8 +1,5 @@
package cc.winboll.studio.contacts.listenphonecall;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -25,253 +22,371 @@ import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.phonecallui.PhoneCallActivity;
import cc.winboll.studio.contacts.phonecallui.PhoneCallService;
import cc.winboll.studio.libappbase.LogUtils;
/**
* 终极优化版onCreate 仅注册前台服务,其他逻辑延迟初始化,避免阻塞
* 适配 Java7 + Android API30
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Describe 通话监听服务(无前台服务),负责监听通话状态、显示通话悬浮窗、跳转通话界面
* 严格适配 Java7 语法 + Android API29-30 | 轻量稳定 | 避免内存泄漏
*/
public class CallListenerService extends Service {
private static final String TAG = "CallListenerService";
private static final String CHANNEL_ID = "call_listener_channel";
private static final int NOTIFICATION_ID = 1003;
private static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 0x80;
private static final int ANDROID_8_API = 26;
private static final int ANDROID_10_API = 29;
private static final int ANDROID_19_API = 19;
// View 与服务相关属性
private View phoneCallView;
private TextView tvCallNumber;
private Button btnOpenApp;
private WindowManager windowManager;
private WindowManager.LayoutParams params;
private PhoneStateListener phoneStateListener;
private TelephonyManager telephonyManager;
private String callNumber;
private boolean hasShown;
private boolean isCallingIn;
// 延迟初始化 Handler
private Handler mDelayHandler;
// ====================== 常量定义区精准适配API29-30无冗余 ======================
public static final String TAG = "CallListenerService";
// Android版本常量仅保留适配必需版本精简无用定义
private static final int ANDROID_8_API = 26; // 悬浮窗类型适配API26+必需)
private static final int ANDROID_10_API = 29; // API29+ 悬浮窗权限/参数适配
private static final int ANDROID_19_API = 19; // 透明状态栏/导航栏适配
// 延迟初始化参数(让出主线程,避免启动阻塞)
private static final long DELAY_INIT_MS = 100L;
// ====================== 成员属性区(按功能归类,命名规范) ======================
// 延迟初始化核心
private Handler mDelayHandler; // 延迟处理器避免onCreate阻塞
// 通话监听核心
private TelephonyManager mTelephonyManager; // 电话管理器(监听通话状态)
private PhoneStateListener mPhoneStateListener;// 通话状态监听回调
private String mCallNumber; // 当前通话号码
private boolean mIsCallingIn; // 是否为来电true=来电false=去电)
// 悬浮窗核心
private WindowManager mWindowManager; // 窗口管理器(添加/移除悬浮窗)
private WindowManager.LayoutParams mWindowParams;// 悬浮窗参数配置
private View mPhoneCallView; // 通话悬浮窗根视图
private TextView mTvCallNumber; // 悬浮窗号码显示控件
private Button mBtnOpenApp; // 悬浮窗跳转APP按钮
private boolean mHasShown; // 悬浮窗显示状态标记(避免重复操作)
// ====================== Service生命周期方法区按执行顺序排列 ======================
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate: 服务启动,立即注册前台服务");
// ========== 第一步:无条件优先执行前台服务注册,这是唯一必须同步做的事 ==========
boolean foregroundSuccess = startForegroundImmediately();
if (!foregroundSuccess) {
LogUtils.e(TAG, "onCreate: 前台服务注册失败,直接停止服务");
stopSelf();
return;
}
LogUtils.d(TAG, "===== onCreate: 通话监听服务启动 =====");
// ========== 第二步:所有其他逻辑延迟 100ms 执行,让出主线程,避免阻塞 ==========
mDelayHandler = new Handler(Looper.getMainLooper());
mDelayHandler.postDelayed(new Runnable() {
@Override
public void run() {
initDelayedLogic();
}
}, 100);
// 延迟初始化所有逻辑(让出主线程,避免启动阻塞,提升启动速度)
initDelayHandlerAndLogic();
LogUtils.d(TAG, "===== onCreate: 通话监听服务启动完成 =====");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand: 服务被启动startId=" + startId);
// 加载服务配置,决定重启策略(启用则自动重启,禁用则默认)
MainServiceBean serviceConfig = MainServiceBean.loadBean(this, MainServiceBean.class);
int startMode = (serviceConfig != null && serviceConfig.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand: 服务启动模式:" + (startMode == START_STICKY ? "START_STICKY自动重启" : "默认模式"));
return startMode;
}
@Override
public IBinder onBind(Intent intent) {
LogUtils.d(TAG, "onBind: 服务无需绑定返回null");
return null;
}
/**
* 核心:仅负责前台服务注册,无任何其他逻辑,确保 5 秒内完成
* @return true 注册成功false 失败
*/
private boolean startForegroundImmediately() {
try {
Notification notification = createForegroundNotification();
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_PHONE_CALL);
} else {
startForeground(NOTIFICATION_ID, notification);
}
LogUtils.d(TAG, "startForegroundImmediately: 前台服务注册成功");
return true;
} catch (Exception e) {
LogUtils.e(TAG, "startForegroundImmediately: 注册失败", e);
return false;
}
}
/**
* 简化通知创建逻辑,只保留必要参数,确保最快创建
*/
private Notification createForegroundNotification() {
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 8.0+ 创建渠道,极简配置
if (Build.VERSION.SDK_INT >= ANDROID_8_API && manager != null) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, "通话监听", NotificationManager.IMPORTANCE_LOW);
channel.setSound(null, null);
channel.enableVibration(false);
manager.createNotificationChannel(channel);
}
// 通知构建:必须确保小图标存在!!!替换为你项目中真实存在的图标资源
Notification.Builder builder = new Notification.Builder(this);
// 重点:这里的 ic_launcher 必须是 res/drawable 下存在的图标,否则通知创建失败
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentTitle("通话监听");
builder.setContentText("运行中");
builder.setPriority(Notification.PRIORITY_LOW);
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
builder.setChannelId(CHANNEL_ID);
}
return builder.build();
}
/**
* 延迟初始化所有非核心逻辑:电话监听、悬浮窗
*/
private void initDelayedLogic() {
LogUtils.d(TAG, "initDelayedLogic: 开始初始化延迟逻辑");
initPhoneStateListener();
initPhoneCallView();
LogUtils.d(TAG, "initDelayedLogic: 延迟逻辑初始化完成");
}
private void initPhoneStateListener() {
phoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
callNumber = incomingNumber;
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
dismiss();
break;
case TelephonyManager.CALL_STATE_RINGING:
isCallingIn = true;
updateUI();
show();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
updateUI();
show();
break;
}
}
};
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if (telephonyManager != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
private void initPhoneCallView() {
windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
if (windowManager == null) {
return;
}
int width = windowManager.getDefaultDisplay().getWidth();
params = new WindowManager.LayoutParams();
params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
params.width = width;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
params.format = PixelFormat.TRANSLUCENT;
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
if (Build.VERSION.SDK_INT >= ANDROID_19_API) {
params.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
}
FrameLayout interceptorLayout = new FrameLayout(this) {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return true;
}
return super.dispatchKeyEvent(event);
}
};
phoneCallView = LayoutInflater.from(this).inflate(R.layout.view_phone_call, interceptorLayout);
tvCallNumber = (TextView) phoneCallView.findViewById(R.id.tv_call_number);
btnOpenApp = (Button) phoneCallView.findViewById(R.id.btn_open_app);
btnOpenApp.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
PhoneCallService.CallType type = isCallingIn ? PhoneCallService.CallType.CALL_IN : PhoneCallService.CallType.CALL_OUT;
PhoneCallActivity.actionStart(CallListenerService.this, callNumber, type);
}
});
}
private void show() {
if (!hasShown && phoneCallView != null && windowManager != null) {
try {
windowManager.addView(phoneCallView, params);
hasShown = true;
} catch (Exception e) {
LogUtils.e(TAG, "show: 悬浮窗添加失败", e);
}
}
}
private void dismiss() {
if (hasShown && phoneCallView != null && windowManager != null) {
try {
windowManager.removeView(phoneCallView);
} catch (Exception e) {
LogUtils.e(TAG, "dismiss: 悬浮窗移除失败", e);
} finally {
hasShown = false;
isCallingIn = false;
}
}
}
private void updateUI() {
if (tvCallNumber == null) {
return;
}
String formatNumber = formatPhoneNumber(callNumber);
tvCallNumber.setText(formatNumber);
int drawableId = isCallingIn ? R.drawable.ic_phone_call_in : R.drawable.ic_phone_call_out;
tvCallNumber.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(drawableId), null);
}
public static String formatPhoneNumber(String phoneNum) {
if (!TextUtils.isEmpty(phoneNum) && phoneNum.length() == 11) {
return phoneNum.substring(0, 3) + "-" + phoneNum.substring(3, 7) + "-" + phoneNum.substring(7);
}
return phoneNum;
}
@Override
public void onDestroy() {
super.onDestroy();
dismiss();
if (telephonyManager != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
LogUtils.d(TAG, "===== onDestroy: 通话监听服务开始销毁 =====");
// 全量清理资源,彻底避免内存泄漏
dismissFloatWindow(); // 移除悬浮窗
unregisterPhoneStateListener();// 注销通话监听
clearDelayHandler(); // 清空延迟任务
resetAllReferences(); // 置空所有成员属性
LogUtils.d(TAG, "===== onDestroy: 通话监听服务销毁完成 =====");
}
// ====================== 延迟初始化方法区(非阻塞启动,提升稳定性) ======================
/**
* 初始化延迟处理器,执行核心逻辑(通话监听+悬浮窗)
*/
private void initDelayHandlerAndLogic() {
mDelayHandler = new Handler(Looper.getMainLooper());
mDelayHandler.postDelayed(new Runnable() {
@Override
public void run() {
LogUtils.d(TAG, "initDelayHandlerAndLogic: 开始延迟初始化核心逻辑");
initPhoneStateListener(); // 初始化通话状态监听
initFloatWindow(); // 初始化通话悬浮窗
LogUtils.d(TAG, "initDelayHandlerAndLogic: 延迟初始化完成,服务就绪");
}
}, DELAY_INIT_MS);
}
/**
* 初始化通话状态监听注册TelephonyManager响应通话状态变化
*/
private void initPhoneStateListener() {
// 1. 创建通话状态监听回调
mPhoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int callState, String incomingNumber) {
super.onCallStateChanged(callState, incomingNumber);
mCallNumber = incomingNumber;
LogUtils.d(TAG, "onCallStateChanged: 通话状态变化,状态=" + getCallStateDesc(callState) + ",号码=" + incomingNumber);
// 响应不同通话状态
switch (callState) {
case TelephonyManager.CALL_STATE_IDLE:
// 通话空闲(挂断/未通话):隐藏悬浮窗
dismissFloatWindow();
break;
case TelephonyManager.CALL_STATE_RINGING:
// 来电响铃标记来电状态更新UI并显示悬浮窗
mIsCallingIn = true;
updateFloatWindowUI();
showFloatWindow();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
// 通话中(接听/拨号更新UI并显示悬浮窗
updateFloatWindowUI();
showFloatWindow();
break;
}
}
};
// 2. 注册通话监听(非空校验,避免崩溃)
mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
if (mTelephonyManager != null) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
LogUtils.d(TAG, "initPhoneStateListener: 通话状态监听注册成功");
} else {
LogUtils.e(TAG, "initPhoneStateListener: TelephonyManager获取失败监听注册失败");
}
}
/**
* 初始化通话悬浮窗(配置参数+加载布局+绑定事件适配API29-30
*/
private void initFloatWindow() {
// 1. 获取窗口管理器(非空校验,避免后续崩溃)
mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
if (mWindowManager == null) {
LogUtils.e(TAG, "initFloatWindow: WindowManager获取失败悬浮窗初始化失败");
return;
}
// 2. 配置悬浮窗参数精准适配API29+,兼容悬浮窗权限)
initFloatWindowParams();
// 3. 加载悬浮窗布局(添加返回键拦截,避免误关闭)
FrameLayout keyInterceptorLayout = new FrameLayout(this) {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// 拦截返回键,保障通话时悬浮窗正常显示
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
LogUtils.d(TAG, "dispatchKeyEvent: 拦截悬浮窗返回键事件");
return true;
}
return super.dispatchKeyEvent(event);
}
};
mPhoneCallView = LayoutInflater.from(this).inflate(R.layout.view_phone_call, keyInterceptorLayout);
// 4. 绑定悬浮窗控件,设置跳转按钮事件
bindFloatWindowViews();
LogUtils.d(TAG, "initFloatWindow: 悬浮窗初始化完成");
}
/**
* 配置悬浮窗参数适配API29+窗口类型,确保正常显示)
*/
private void initFloatWindowParams() {
mWindowParams = new WindowManager.LayoutParams();
// 窗口位置:顶部居中
mWindowParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
// 窗口大小:宽度全屏,高度自适应
mWindowParams.width = WindowManager.LayoutParams.MATCH_PARENT;
mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 固定竖屏显示
mWindowParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
// 窗口格式:半透明
mWindowParams.format = PixelFormat.TRANSLUCENT;
// 窗口类型API29+ 强制用 TYPE_APPLICATION_OVERLAY需悬浮窗权限
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
LogUtils.d(TAG, "initFloatWindowParams: API29+ 悬浮窗类型=TYPE_APPLICATION_OVERLAY需开启悬浮窗权限");
} else if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
// 窗口标志:无焦点(不抢占输入)、全屏布局、兼容透明状态栏/导航栏
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
if (Build.VERSION.SDK_INT >= ANDROID_19_API) {
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
}
}
/**
* 绑定悬浮窗控件,设置跳转通话详情页事件
*/
private void bindFloatWindowViews() {
mTvCallNumber = (TextView) mPhoneCallView.findViewById(R.id.tv_call_number);
mBtnOpenApp = (Button) mPhoneCallView.findViewById(R.id.btn_open_app);
// 跳转按钮点击事件
mBtnOpenApp.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (TextUtils.isEmpty(mCallNumber)) {
LogUtils.w(TAG, "bindFloatWindowViews: 通话号码为空,跳过跳转");
return;
}
LogUtils.d(TAG, "bindFloatWindowViews: 点击跳转通话详情页,号码=" + mCallNumber);
PhoneCallService.CallType callType = mIsCallingIn ? PhoneCallService.CallType.CALL_IN : PhoneCallService.CallType.CALL_OUT;
PhoneCallActivity.actionStart(CallListenerService.this, mCallNumber, callType);
}
});
}
// ====================== 悬浮窗功能逻辑区(显示/隐藏/更新UI ======================
/**
* 显示通话悬浮窗(避免重复添加,防止窗口泄露)
*/
private void showFloatWindow() {
if (!mHasShown && mPhoneCallView != null && mWindowManager != null) {
try {
mWindowManager.addView(mPhoneCallView, mWindowParams);
mHasShown = true;
LogUtils.d(TAG, "showFloatWindow: 悬浮窗显示成功");
} catch (SecurityException e) {
LogUtils.e(TAG, "showFloatWindow: 悬浮窗显示失败(无悬浮窗权限,需引导用户开启)", e);
} catch (Exception e) {
LogUtils.e(TAG, "showFloatWindow: 悬浮窗显示异常", e);
}
} else {
LogUtils.d(TAG, "showFloatWindow: 悬浮窗已显示/组件未初始化,跳过显示");
}
}
/**
* 隐藏通话悬浮窗(避免重复移除,防止崩溃)
*/
private void dismissFloatWindow() {
if (mHasShown && mPhoneCallView != null && mWindowManager != null) {
try {
mWindowManager.removeView(mPhoneCallView);
LogUtils.d(TAG, "dismissFloatWindow: 悬浮窗隐藏成功");
} catch (Exception e) {
LogUtils.e(TAG, "dismissFloatWindow: 悬浮窗隐藏异常", e);
} finally {
mHasShown = false;
mIsCallingIn = false; // 重置来电状态标记
}
} else {
LogUtils.d(TAG, "dismissFloatWindow: 悬浮窗已隐藏/组件未初始化,跳过隐藏");
}
}
/**
* 更新悬浮窗UI显示格式化号码+通话类型图标)
*/
private void updateFloatWindowUI() {
if (mTvCallNumber == null || TextUtils.isEmpty(mCallNumber)) {
LogUtils.w(TAG, "updateFloatWindowUI: 控件未初始化/号码为空,更新失败");
return;
}
// 格式化11位手机号3-4-4分隔提升可读性
String formattedNumber = formatPhoneNumber(mCallNumber);
mTvCallNumber.setText(formattedNumber);
// 设置通话类型图标(来电/去电区分)
int iconResId = mIsCallingIn ? R.drawable.ic_phone_call_in : R.drawable.ic_phone_call_out;
mTvCallNumber.setCompoundDrawablesWithIntrinsicBounds(
null, null, getResources().getDrawable(iconResId), null
);
LogUtils.d(TAG, "updateFloatWindowUI: 悬浮窗UI更新完成号码=" + formattedNumber + ",类型=" + (mIsCallingIn ? "来电" : "去电"));
}
// ====================== 资源清理方法区(服务销毁时全量释放) ======================
/**
* 注销通话状态监听释放TelephonyManager资源
*/
private void unregisterPhoneStateListener() {
if (mTelephonyManager != null && mPhoneStateListener != null) {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
LogUtils.d(TAG, "unregisterPhoneStateListener: 通话监听已注销");
}
mTelephonyManager = null;
mPhoneStateListener = null;
}
/**
* 清空延迟处理器(移除未执行任务,避免内存泄漏)
*/
private void clearDelayHandler() {
if (mDelayHandler != null) {
mDelayHandler.removeCallbacksAndMessages(null);
mDelayHandler = null;
LogUtils.d(TAG, "clearDelayHandler: 延迟处理器已清空");
}
}
/**
* 置空所有成员属性(彻底释放引用,避免内存泄漏)
*/
private void resetAllReferences() {
mCallNumber = null;
mPhoneCallView = null;
mWindowParams = null;
mWindowManager = null;
mTvCallNumber = null;
mBtnOpenApp = null;
}
// ====================== 工具方法区(通用辅助功能,独立归类) ======================
/**
* 格式化手机号11位手机号3-4-4分隔非11位保持原格式
* @param phoneNum 待格式化的手机号
* @return 格式化后的号码
*/
public static String formatPhoneNumber(String phoneNum) {
if (!TextUtils.isEmpty(phoneNum) && phoneNum.length() == 11) {
String formatted = phoneNum.substring(0, 3) + "-"
+ phoneNum.substring(3, 7) + "-"
+ phoneNum.substring(7);
LogUtils.d(TAG, "formatPhoneNumber: 号码格式化,原=" + phoneNum + ",新=" + formatted);
return formatted;
}
LogUtils.d(TAG, "formatPhoneNumber: 非11位号码无需格式化号码=" + phoneNum);
return phoneNum;
}
/**
* 转换通话状态为文字描述(便于日志查看,快速定位问题)
* @param callState 通话状态TelephonyManager常量
* @return 状态描述文字
*/
private String getCallStateDesc(int callState) {
switch (callState) {
case TelephonyManager.CALL_STATE_IDLE:
return "空闲(挂断/未通话)";
case TelephonyManager.CALL_STATE_RINGING:
return "响铃(来电)";
case TelephonyManager.CALL_STATE_OFFHOOK:
return "通话中(接听/拨号)";
default:
return "未知状态";
}
// 清空所有引用
phoneStateListener = null;
telephonyManager = null;
windowManager = null;
phoneCallView = null;
}
}

View File

@@ -252,12 +252,8 @@ public class AssistantService extends Service {
Intent intent = new Intent(this, MainService.class);
// 根据应用前后台状态选择启动方式Android 12+ 后台用 startForegroundService
if (Build.VERSION.SDK_INT >= ANDROID_12_API) {
LogUtils.d(TAG, "wakeupAndBindMain: 应用后台,启动主服务为前台服务");
startForegroundService(intent);
} else {
startService(intent);
}
startForegroundService(intent);
// BIND_IMPORTANT提高绑定优先级主服务被杀时会回调断开
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
LogUtils.d(TAG, "wakeupAndBindMain: 已启动并绑定主服务 MainService");

View File

@@ -32,68 +32,76 @@ import java.util.TimerTask;
* @Date 2025/02/13 06:56:41
* @Describe 拨号主服务,负责核心业务逻辑、守护进程绑定、铃声音量监控及通话监听启动
* 严格适配 Android API 30 + Java 7 语法规范 | 解决前台服务启动超时崩溃
* 优化:移除延迟启动,非核心服务直接在 mainService() 中启动
* 核心优化:1. 移除延迟启动逻辑 2. 标准化日志管理 3. 强化资源清理 4. 结构分层重构
*/
public class MainService extends Service {
// ====================== 常量定义区(全硬编码,杜绝高版本 API 依赖) ======================
// ====================== 常量定义区全硬编码无高版本API依赖 ======================
public static final String TAG = "MainService";
public static final int MSG_UPDATE_STATUS = 0;
// 铃声音量检查定时器参数
private static final long VOLUME_CHECK_DELAY = 1000L;
private static final long VOLUME_CHECK_PERIOD = 60000L;
// 前台服务通知配置
// 铃声音量监控参数(定时检查+恢复)
private static final long VOLUME_CHECK_DELAY = 1000L; // 首次检查延迟1s
private static final long VOLUME_CHECK_PERIOD = 60000L; // 后续每60s检查一次
// 前台服务配置固定ID+渠道,避免重复创建)
private static final String FOREGROUND_CHANNEL_ID = "main_service_foreground_channel";
private static final int FOREGROUND_NOTIFICATION_ID = 1001;
// 前台服务类型硬编码dataSync(0x01) | 替代 Build.VERSION_CODES 高版本常量
private static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 0x00000001;
// Android 版本常量硬编码 | Java 7 不支持 Build.VERSION_CODES 高版本引用
private static final int ANDROID_8_API = 26;
private static final int ANDROID_10_API = 29;
private static final int ANDROID_12_API = 31;
// 重试延迟时间(仅保留守护服务重绑定延迟,移除非核心服务延迟)
private static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 0x00000001; // dataSync类型硬编码
// Android版本常量(替代Build.VERSION_CODES适配Java7
private static final int ANDROID_8_API = 26; // Android 8.0
private static final int ANDROID_10_API = 29; // Android 10
private static final int ANDROID_12_API = 31; // Android 12
// 守护服务重绑定延迟(仅保留核心重试逻辑)
private static final long RETRY_DELAY_MS = 3000L;
// ====================== 静态成员变量区 ======================
private static MainService sMainServiceInstance;
private static volatile TomCat sTomCatInstance;
// ====================== 静态成员属性区全局共享实例统一前缀s ======================
private static MainService sMainServiceInstance; // 主服务全局实例
private static volatile TomCat sTomCatInstance; // 号码识别核心实例volatile保证可见性
// ====================== 成员变量区 ======================
private volatile boolean mIsServiceRunning;
private MainServiceBean mMainServiceBean;
//private MainServiceThread mMainServiceThread;
private MainServiceHandler mMainServiceHandler;
private MyServiceConnection mMyServiceConnection;
private AssistantService mAssistantService;
private boolean mIsBound;
private MainReceiver mMainReceiver;
private Timer mStreamVolumeCheckTimer;
// ====================== 成员属性区(业务+UI+资源统一前缀m ======================
private volatile boolean mIsServiceRunning; // 服务运行状态标记volatile防指令重排
private MainServiceBean mMainServiceBean; // 服务配置实体(启用状态存储)
private MainServiceHandler mMainServiceHandler; // 服务消息处理器(主线程通信)
private MyServiceConnection mServiceConnection; // 守护服务连接实例
private AssistantService mAssistantService; // 绑定的守护服务实例
private boolean mIsAssistantBound; // 守护服务绑定状态标记
private MainReceiver mMainReceiver; // 全局广播接收器(监听系统事件)
private Timer mVolumeCheckTimer; // 铃声音量检查定时器(定时恢复配置)
// ====================== Binder 内部类 ======================
// ====================== 内部类:Binder(服务绑定通信,优先定义) ======================
public class MyBinder extends Binder {
/**
* 外部组件绑定服务时,获取主服务实例
* @return MainService 主服务实例
*/
public MainService getService() {
LogUtils.d(TAG, "MyBinder.getService: 获取 MainService 实例");
LogUtils.d(TAG, "MyBinder.getService: 外部获取主服务实例");
return MainService.this;
}
}
// ====================== ServiceConnection 内部类 ======================
// ====================== 内部类:ServiceConnection(守护服务绑定回调) ======================
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service == null) {
LogUtils.w(TAG, "MyServiceConnection.onServiceConnected: 绑定的 IBinder 为 null");
mIsBound = false;
LogUtils.w(TAG, "MyServiceConnection.onServiceConnected: 绑定的IBinder为空,绑定失败");
mIsAssistantBound = false;
return;
}
try {
// 类型转换获取守护服务实例
AssistantService.MyBinder binder = (AssistantService.MyBinder) service;
mAssistantService = binder.getService();
mIsBound = true;
LogUtils.d(TAG, "MyServiceConnection.onServiceConnected: 守护服务绑定成功 | AssistantService=" + mAssistantService);
mIsAssistantBound = true;
LogUtils.d(TAG, "MyServiceConnection.onServiceConnected: 守护服务绑定成功");
} catch (ClassCastException e) {
LogUtils.e(TAG, "MyServiceConnection.onServiceConnected: IBinder 类型转换失败", e);
mIsBound = false;
LogUtils.e(TAG, "MyServiceConnection.onServiceConnected: IBinder类型转换失败", e);
mIsAssistantBound = false;
}
}
@@ -101,134 +109,166 @@ public class MainService extends Service {
public void onServiceDisconnected(ComponentName name) {
LogUtils.w(TAG, "MyServiceConnection.onServiceDisconnected: 守护服务连接断开");
mAssistantService = null;
mIsBound = false;
mIsAssistantBound = false;
// 服务启用则重试绑定
// 服务启用状态下,重试绑定守护服务(主服务存活核心保障)
if (mMainServiceBean != null && mMainServiceBean.isEnable()) {
LogUtils.d(TAG, "MyServiceConnection.onServiceDisconnected: 重新唤醒并绑定守护服务");
LogUtils.d(TAG, "MyServiceConnection.onServiceDisconnected: " + RETRY_DELAY_MS + "ms后重试绑定守护服务");
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
wakeupAndBindAssistant();
wakeupAndBindAssistantService();
}
}, RETRY_DELAY_MS);
} else {
LogUtils.w(TAG, "MyServiceConnection.onServiceDisconnected: 服务未启用,跳过重试");
LogUtils.w(TAG, "MyServiceConnection.onServiceDisconnected: 服务已禁用,跳过重试绑定");
}
}
}
// ====================== 对外静态方法区 ======================
// ====================== 对外静态方法区(服务启停/重启/状态查询,全局调用) ======================
/**
* 检查号码是否在BoBullToon库中外部组件调用静态入口
* @param phone 待查询号码
* @return true=是BoBullToon号码false=否/初始化失败
*/
public static boolean isPhoneInBoBullToon(String phone) {
if (sTomCatInstance != null && phone != null && !phone.isEmpty()) {
return sTomCatInstance.isPhoneBoBullToon(phone);
boolean result = sTomCatInstance.isPhoneBoBullToon(phone);
LogUtils.d(TAG, "isPhoneInBoBullToon: 号码" + phone + "查询结果:" + (result ? "" : ""));
return result;
}
LogUtils.w(TAG, "isPhoneInBoBullToon: TomCat 未初始化或号码为空");
LogUtils.w(TAG, "isPhoneInBoBullToon: TomCat未初始化或号码为空,查询失败");
return false;
}
/**
* 停止主服务(仅停止,不修改配置)
* @param context 上下文(非空校验)
*/
public static void stopMainService(Context context) {
if (context == null) {
LogUtils.e(TAG, "stopMainService: Context 为 null无法执行");
LogUtils.e(TAG, "stopMainService: 上下文为空,无法停止服务");
return;
}
LogUtils.d(TAG, "stopMainService: 执行停止主服务操作");
context.stopService(new Intent(context, MainService.class));
}
/**
* 启动主服务(仅启动,不修改配置)
* @param context 上下文(非空校验)
*/
public static void startMainService(Context context) {
if (context == null) {
LogUtils.e(TAG, "startMainService: Context 为 null无法执行");
LogUtils.e(TAG, "startMainService: 上下文为空,无法启动服务");
return;
}
LogUtils.d(TAG, "startMainService: 执行启动主服务操作");
LogUtils.d(TAG, "startMainService: 执行启动主服务操作(前台服务模式)");
Intent intent = new Intent(context, MainService.class);
context.startForegroundService(intent);
context.startForegroundService(intent);
}
/**
* 重启主服务(先停后启,需服务已启用)
* @param context 上下文(非空校验)
*/
public static void restartMainService(Context context) {
if (context == null) {
LogUtils.e(TAG, "restartMainService: Context 为 null无法执行");
LogUtils.e(TAG, "restartMainService: 上下文为空,无法重启服务");
return;
}
LogUtils.d(TAG, "restartMainService: 执行重启主服务操作");
LogUtils.d(TAG, "restartMainService: 执行主服务重启流程");
MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class);
if (bean != null && bean.isEnable()) {
MainServiceBean config = MainServiceBean.loadBean(context, MainServiceBean.class);
if (config != null && config.isEnable()) {
stopMainService(context);
startMainService(context);
LogUtils.i(TAG, "restartMainService: 主服务重启完成");
} else {
LogUtils.w(TAG, "restartMainService: 服务配置未启用,跳过重启");
LogUtils.w(TAG, "restartMainService: 服务未启用或配置为空,跳过重启");
}
}
/**
* 停止服务并保存禁用状态(更新配置+停止服务)
* @param context 上下文(非空校验)
*/
public static void stopMainServiceAndSaveStatus(Context context) {
if (context == null) {
LogUtils.e(TAG, "stopMainServiceAndSaveStatus: Context 为 null无法执行");
LogUtils.e(TAG, "stopMainServiceAndSaveStatus: 上下文为空,操作失败");
return;
}
LogUtils.d(TAG, "stopMainServiceAndSaveStatus: 停止服务并保存禁用状态");
MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(false);
MainServiceBean.saveBean(context, bean);
LogUtils.d(TAG, "stopMainServiceAndSaveStatus: 保存禁用状态并停止服务");
MainServiceBean config = new MainServiceBean();
config.setIsEnable(false);
MainServiceBean.saveBean(context, config);
stopMainService(context);
}
/**
* 启动服务并保存启用状态(更新配置+启动服务,先停后启避免重复)
* @param context 上下文(非空校验)
*/
public static void startMainServiceAndSaveStatus(Context context) {
if (context == null) {
LogUtils.e(TAG, "startMainServiceAndSaveStatus: Context 为 null无法执行");
LogUtils.e(TAG, "startMainServiceAndSaveStatus: 上下文为空,操作失败");
return;
}
LogUtils.d(TAG, "startMainServiceAndSaveStatus: 启动服务并保存启用状态");
MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(true);
MainServiceBean.saveBean(context, bean);
stopMainService(context);
LogUtils.d(TAG, "startMainServiceAndSaveStatus: 保存启用状态并启动服务");
MainServiceBean config = new MainServiceBean();
config.setIsEnable(true);
MainServiceBean.saveBean(context, config);
stopMainService(context); // 先停止旧服务,避免冲突
startMainService(context);
}
// ====================== 成员方法区 ======================
// ====================== 核心工具方法区(服务状态检查+前台通知创建,通用功能) ======================
/**
* 补充缺失的 appenMessage 方法
* 补充消息追加方法(外部组件向服务发送消息)
* @param message 待追加消息(空值防护)
*/
public void appenMessage(String message) {
String msg = message == null ? "null" : message;
LogUtils.d(TAG, "追加消息: " + msg);
LogUtils.d(TAG, "appenMessage: 接收外部消息:" + msg);
if (mMainServiceHandler != null) {
android.os.Message handlerMsg = android.os.Message.obtain();
handlerMsg.what = MSG_UPDATE_STATUS;
handlerMsg.obj = msg;
mMainServiceHandler.sendMessage(handlerMsg);
LogUtils.d(TAG, "appenMessage: 消息已发送至Handler处理");
} else {
LogUtils.w(TAG, "appenMessage: MainServiceHandler未初始化消息发送失败");
}
}
/**
* 创建前台服务通知
* 创建前台服务通知Android8.0+需渠道,低版本兼容)
* @return Notification 前台服务通知实例
*/
private Notification createForegroundNotification() {
// 1. 创建通知渠道(Android 8.0+ 必需
// 1. Android8.0+创建通知渠道(必需,否则通知不显示
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
NotificationChannel channel = new NotificationChannel(
FOREGROUND_CHANNEL_ID,
"拨号主服务",
NotificationManager.IMPORTANCE_LOW
FOREGROUND_CHANNEL_ID,
"拨号主服务",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("主服务后台运行,保障拨号功能正常");
channel.setSound(null, null);
channel.enableVibration(false);
channel.setDescription("主服务后台运行,保障通话监听与号码识别功能正常");
channel.setSound(null, null); // 关闭通知声音
channel.enableVibration(false); // 关闭振动
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null) {
manager.createNotificationChannel(channel);
LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
LogUtils.d(TAG, "createForegroundNotification: Android8.0+通知渠道创建成功");
} else {
LogUtils.e(TAG, "createForegroundNotification: NotificationManager 获取失败");
LogUtils.e(TAG, "createForegroundNotification: NotificationManager获取失败,渠道创建失败");
}
}
// 2. 构建通知
// 2. 构建通知实例分版本兼容Builder
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
builder = new Notification.Builder(this, FOREGROUND_CHANNEL_ID);
@@ -237,37 +277,43 @@ public class MainService extends Service {
}
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentTitle("拨号服务运行中");
builder.setContentText("正在后台保障通话监听与号码识别");
builder.setPriority(Notification.PRIORITY_LOW);
builder.setOngoing(true);
builder.setContentText("后台保障通话监听与号码识别,请勿手动关闭");
builder.setPriority(Notification.PRIORITY_LOW); // 低优先级,不打扰用户
builder.setOngoing(true); // 不可手动清除,保障服务存活
LogUtils.d(TAG, "createForegroundNotification: 前台服务通知构建完成");
return builder.build();
}
/**
* 检查服务是否正在运行
* 检查指定服务是否正在运行通过ActivityManager查询
* @param serviceClass 待检查服务类
* @return true=运行中false=未运行/查询失败
*/
private boolean isServiceRunning(Class<?> serviceClass) {
if (serviceClass == null) {
LogUtils.e(TAG, "isServiceRunning: 服务类参数为 null");
LogUtils.e(TAG, "isServiceRunning: 服务类为空,检查失败");
return false;
}
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (manager == null) {
LogUtils.w(TAG, "isServiceRunning: ActivityManager 获取失败");
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager == null) {
LogUtils.w(TAG, "isServiceRunning: ActivityManager获取失败,检查失败");
return false;
}
for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(info.service.getClassName())) {
// 遍历运行中服务,匹配类名
for (ActivityManager.RunningServiceInfo serviceInfo : activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(serviceInfo.service.getClassName())) {
LogUtils.d(TAG, "isServiceRunning: 服务" + serviceClass.getSimpleName() + "正在运行");
return true;
}
}
LogUtils.d(TAG, "isServiceRunning: 服务" + serviceClass.getSimpleName() + "未运行");
return false;
}
// ====================== Service 生命周期方法区 ======================
// ====================== Service生命周期方法区(按执行顺序:创建→绑定→启动→销毁) ======================
@Override
public void onCreate() {
super.onCreate();
@@ -275,74 +321,72 @@ public class MainService extends Service {
sMainServiceInstance = this;
mIsServiceRunning = false;
// 初始化核心组件(移除 mDelayHandler 初始化,无需延迟启动
// 初始化核心组件(无延迟,直接初始化
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
mMyServiceConnection = new MyServiceConnection();
mServiceConnection = new MyServiceConnection();
mMainServiceHandler = new MainServiceHandler(this);
// 初始化铃声音量检查定时器
// 初始化音量监控定时器(服务启动即开启,保障音量配置)
initVolumeCheckTimer();
// 启动服务
mainService();
// 执行核心业务启动逻辑(无延迟,优先启动)
startCoreBusiness();
LogUtils.d(TAG, "===== onCreate: 主服务创建完成(仅初始化组件) =====");
LogUtils.d(TAG, "===== onCreate: 主服务创建完成 =====");
}
@Override
public IBinder onBind(Intent intent) {
LogUtils.d(TAG, "onBind: 服务被绑定 | Intent=" + intent);
LogUtils.d(TAG, "onBind: 服务被外部组件绑定,Intent=" + (intent != null ? intent.getAction() : "null"));
return new MyBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand: 服务被启动 | startId=" + startId);
mainService();
return (mMainServiceBean != null && mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand: 服务被启动startId=" + startId);
// 重复启动时再次执行核心业务(避免服务被杀死后重启失败)
startCoreBusiness();
// 服务启用则返回START_STICKY系统杀死后自动重启否则默认行为
int result = (mMainServiceBean != null && mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand: 服务启动模式:" + (result == START_STICKY ? "START_STICKY自动重启" : "默认模式"));
return result;
}
// ====================== onDestroy强制全量清理不改动 Bean 配置 ======================
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "===== onDestroy: 主服务开始销毁(强制清理所有资源 =====");
LogUtils.d(TAG, "===== onDestroy: 主服务开始销毁,全量清理资源 =====");
// 2. 释放定时器资源
// 1. 停止音量监控定时器(释放线程资源
cancelVolumeCheckTimer();
// 3. 强制解除守护服务绑定
if (mIsBound && mMyServiceConnection != null) {
// 2. 解除守护服务绑定(避免内存泄漏)
if (mIsAssistantBound && mServiceConnection != null) {
try {
unbindService(mMyServiceConnection);
LogUtils.d(TAG, "onDestroy: 解除守护服务绑定成功");
unbindService(mServiceConnection);
LogUtils.d(TAG, "onDestroy: 守护服务绑定已解除");
} catch (IllegalArgumentException e) {
LogUtils.w(TAG, "onDestroy: 解除绑定失败服务未绑定", e);
LogUtils.w(TAG, "onDestroy: 解除守护服务绑定失败服务未绑定", e);
}
mIsBound = false;
mMyServiceConnection = null;
mIsAssistantBound = false;
mServiceConnection = null;
}
// 4. 强制注销广播接收器
// 3. 注销广播接收器(释放系统资源)
if (mMainReceiver != null) {
mMainReceiver.unregisterAction(this);
mMainReceiver = null;
LogUtils.d(TAG, "onDestroy: 广播接收器已注销");
LogUtils.d(TAG, "onDestroy: 全局广播接收器已注销");
}
// 5. 强制停止主业务线程
// if (mMainServiceThread != null) {
// mMainServiceThread.setIsExit(true);
// mMainServiceThread = null;
// }
// 6. 强制停止所有子服务
// 4. 停止所有子服务(强制停止,避免子服务残留)
stopService(new Intent(this, AssistantService.class));
stopService(new Intent(this, CallListenerService.class));
stopService(new Intent(this, MyCallScreeningService.class));
LogUtils.d(TAG, "onDestroy: 所有子服务已强制停止");
// 7. 清空静态实例,避免内存泄漏(不改动 Bean 配置
// 5. 清空所有引用(静态+成员,彻底避免内存泄漏,不修改配置Bean
sMainServiceInstance = null;
sTomCatInstance = null;
mMainServiceHandler = null;
@@ -351,155 +395,201 @@ public class MainService extends Service {
// 标记服务为未运行
mIsServiceRunning = false;
LogUtils.d(TAG, "===== onDestroy: 主服务销毁完成资源全清理Bean 配置无改动) =====");
LogUtils.d(TAG, "===== onDestroy: 主服务销毁完成资源全清理 =====");
}
// ====================== 核心业务逻辑:非核心服务直接启动,无延迟 ======================
private synchronized void mainService() {
LogUtils.d(TAG, "mainService: 执行核心业务逻辑");
// 第一步:优先判断服务是否已运行,已运行则直接退出(不做任何动作)
if (!mIsServiceRunning) {
mIsServiceRunning = true;
// 第二步:加载最新配置,校验服务是否启用(未启用则退出)
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean == null && mMainServiceBean.isEnable()) {
// 第三步:配置启用且服务未运行,执行启动流程
LogUtils.i(TAG, "mainService: 服务未运行且配置已启用,开始启动核心流程");
// 1. 启动前台服务(优先执行,避免超时)
try {
Notification foregroundNotification = createForegroundNotification();
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
//startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification);
} catch (IllegalArgumentException e) {
LogUtils.e(TAG, "mainService: 启动前台服务失败,类型不匹配", e);
mIsServiceRunning = false;
stopSelf();
return;
} catch (Exception e) {
LogUtils.e(TAG, "mainService: 启动前台服务异常", e);
mIsServiceRunning = false;
stopSelf();
return;
}
// 2. 绑定守护服务
wakeupAndBindAssistant();
// 3. 初始化业务组件
initTomCat();
initMainReceiver();
Rules.getInstance(this).loadRules();
LogUtils.d(TAG, "mainService: 黑白名单规则已加载");
// 4. 直接启动电话监听服务(移除延迟,校验通过则立即启动)
Intent callListenerIntent = new Intent(this, CallListenerService.class);
try {
startForegroundService(callListenerIntent);
//startService(callListenerIntent);
} catch (Exception e) {
LogUtils.e(TAG, "mainService: CallListenerService 启动失败", e);
}
// 5. 直接启动通话筛选服务API 10+ 生效,无延迟)
Intent screeningIntent = new Intent(this, MyCallScreeningService.class);
try {
startForegroundService(screeningIntent);
//startService(screeningIntent);
} catch (Exception e) {
LogUtils.e(TAG, "mainService: MyCallScreeningService 启动失败", e);
}
// 6. 启动主业务线程
// mMainServiceThread = MainServiceThread.getInstance(this, mMainServiceHandler);
// mMainServiceThread.start();
LogUtils.i(TAG, "mainService: 核心流程启动完成,主服务正常运行");
}
}
}
private void wakeupAndBindAssistant() {
if (mMyServiceConnection == null) {
LogUtils.e(TAG, "wakeupAndBindAssistant: 初始化失败,绑定失败");
// ====================== 核心业务逻辑区(服务启动核心流程,无延迟 ======================
/**
* 启动核心业务(主服务核心入口,避免重复启动)
*/
private synchronized void startCoreBusiness() {
// 服务已运行则直接返回,避免重复执行启动流程
if (mIsServiceRunning) {
LogUtils.d(TAG, "startCoreBusiness: 主服务已运行,跳过重复启动");
return;
}
Intent intent = new Intent(this, AssistantService.class);
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
LogUtils.d(TAG, "wakeupAndBindAssistant: 应用后台,启动 AssistantService 为前台服务");
startForegroundService(intent);
} else {
startService(intent);
mIsServiceRunning = true;
// 重新加载最新配置(避免配置修改后未生效)
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean == null || !mMainServiceBean.isEnable()) {
LogUtils.w(TAG, "startCoreBusiness: 服务配置未启用或配置为空,启动流程终止");
mIsServiceRunning = false;
stopSelf(); // 未启用则主动停止服务
return;
}
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
LogUtils.d(TAG, "wakeupAndBindAssistant: 已启动并绑定守护服务");
LogUtils.i(TAG, "startCoreBusiness: 服务配置已启用,启动核心流程");
// 1. 优先启动前台服务(避免前台服务启动超时崩溃,核心优先级)
try {
Notification foregroundNotification = createForegroundNotification();
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
LogUtils.i(TAG, "startCoreBusiness: 前台服务启动成功通知ID=" + FOREGROUND_NOTIFICATION_ID);
} catch (IllegalArgumentException e) {
LogUtils.e(TAG, "startCoreBusiness: 前台服务启动失败(服务类型不匹配)", e);
mIsServiceRunning = false;
stopSelf();
return;
} catch (Exception e) {
LogUtils.e(TAG, "startCoreBusiness: 前台服务启动异常", e);
mIsServiceRunning = false;
stopSelf();
return;
}
// 2. 绑定守护服务(保障主服务存活,防系统杀死)
wakeupAndBindAssistantService();
// 3. 初始化核心业务组件(号码识别+黑白名单规则)
initTomCatComponent();
initRulesConfig();
// 4. 启动通话监听相关服务(无延迟,直接启动,保障功能生效)
startCallRelatedServices();
LogUtils.i(TAG, "startCoreBusiness: 主服务核心流程启动完成,服务正常运行");
}
// ====================== 铃声音量监控相关方法区 ======================
/**
* 唤醒并绑定守护服务分版本适配启动方式Android10+前台服务启动)
*/
private void wakeupAndBindAssistantService() {
if (mServiceConnection == null) {
LogUtils.e(TAG, "wakeupAndBindAssistantService: 服务连接实例未初始化,绑定失败");
return;
}
Intent assistantIntent = new Intent(this, AssistantService.class);
// Android10+应用后台启动服务需用前台服务模式
LogUtils.d(TAG, "wakeupAndBindAssistantService: Android10+,前台服务模式启动守护服务");
startForegroundService(assistantIntent);
// 绑定守护服务BIND_IMPORTANT高优先级绑定断开时回调
bindService(assistantIntent, mServiceConnection, Context.BIND_IMPORTANT);
LogUtils.d(TAG, "wakeupAndBindAssistantService: 守护服务启动并发起绑定请求");
}
// ====================== 铃声音量监控方法区(定时检查+恢复配置) ======================
/**
* 初始化音量检查定时器(先取消旧定时器,避免重复创建)
*/
private void initVolumeCheckTimer() {
cancelVolumeCheckTimer();
mStreamVolumeCheckTimer = new Timer();
mStreamVolumeCheckTimer.schedule(new TimerTask() {
mVolumeCheckTimer = new Timer();
mVolumeCheckTimer.schedule(new TimerTask() {
@Override
public void run() {
checkAndRestoreRingerVolume();
}
}, VOLUME_CHECK_DELAY, VOLUME_CHECK_PERIOD);
LogUtils.d(TAG, "initVolumeCheckTimer: 铃声音量检查定时器启动");
LogUtils.d(TAG, "initVolumeCheckTimer: 铃声音量监控定时器启动,周期" + VOLUME_CHECK_PERIOD + "ms");
}
/**
* 检查并恢复铃声音量(对比配置值,不一致则恢复,保障用户配置生效)
*/
private void checkAndRestoreRingerVolume() {
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioManager == null) {
LogUtils.e(TAG, "checkAndRestoreRingerVolume: 获取 AudioManager 失败");
LogUtils.e(TAG, "checkAndRestoreRingerVolume: AudioManager获取失败,音量检查终止");
return;
}
// 加载音配置(无配置则初始化)
RingTongBean ringTongBean = RingTongBean.loadBean(this, RingTongBean.class);
if (ringTongBean == null) {
ringTongBean = new RingTongBean();
RingTongBean.saveBean(this, ringTongBean);
LogUtils.d(TAG, "checkAndRestoreRingerVolume: 音配置未存在,初始化默认配置");
// 加载音配置(无配置则初始化默认值
RingTongBean ringConfig = RingTongBean.loadBean(this, RingTongBean.class);
if (ringConfig == null) {
ringConfig = new RingTongBean();
RingTongBean.saveBean(this, ringConfig);
LogUtils.d(TAG, "checkAndRestoreRingerVolume: 音配置未存在,初始化默认配置");
return;
}
// 检查并恢复音量
try {
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
int configVolume = ringTongBean.getStreamVolume();
int configVolume = ringConfig.getStreamVolume();
// 音量不一致则恢复配置值
if (currentVolume != configVolume) {
audioManager.setStreamVolume(AudioManager.STREAM_RING, configVolume, 0);
LogUtils.d(TAG, "checkAndRestoreRingerVolume: 铃声音量已恢复 | 配置值=" + configVolume + " | 当前值=" + currentVolume);
LogUtils.d(TAG, "checkAndRestoreRingerVolume: 铃声音量已恢复配置值=" + configVolume + ",原当前值=" + currentVolume);
} else {
LogUtils.v(TAG, "checkAndRestoreRingerVolume: 铃声音量与配置一致,无需调整");
}
} catch (SecurityException e) {
LogUtils.e(TAG, "checkAndRestoreRingerVolume: 音量设置权限不足", e);
LogUtils.e(TAG, "checkAndRestoreRingerVolume: 音量设置权限不足,恢复失败", e);
}
}
/**
* 取消音量检查定时器释放Timer资源避免内存泄漏
*/
private void cancelVolumeCheckTimer() {
if (mStreamVolumeCheckTimer != null) {
mStreamVolumeCheckTimer.cancel();
mStreamVolumeCheckTimer = null;
LogUtils.d(TAG, "cancelVolumeCheckTimer: 铃声音量检查定时器已取消");
if (mVolumeCheckTimer != null) {
mVolumeCheckTimer.cancel();
mVolumeCheckTimer = null;
LogUtils.d(TAG, "cancelVolumeCheckTimer: 铃声音量监控定时器已取消");
}
}
// ====================== 辅助初始化方法区 ======================
private void initTomCat() {
// ====================== 辅助初始化方法区(业务组件初始化,统一归类) ======================
/**
* 初始化TomCat组件号码识别核心加载本地数据
*/
private void initTomCatComponent() {
sTomCatInstance = TomCat.getInstance(this);
if (!sTomCatInstance.loadPhoneBoBullToon()) {
LogUtils.w(TAG, "initTomCat: 未下载 BoBullToon 数据,参数加载失败");
if (sTomCatInstance.loadPhoneBoBullToon()) {
LogUtils.d(TAG, "initTomCatComponent: BoBullToon号码库加载成功");
} else {
LogUtils.d(TAG, "initTomCat: BoBullToon 数据加载成功");
LogUtils.w(TAG, "initTomCatComponent: BoBullToon号码库未下载,加载失败(不影响服务运行)");
}
}
/**
* 初始化黑白名单规则配置(加载本地规则,保障通话筛选生效)
*/
private void initRulesConfig() {
Rules rules = Rules.getInstance(this);
if (rules != null) {
rules.loadRules();
LogUtils.d(TAG, "initRulesConfig: 黑白名单通话规则加载完成");
} else {
LogUtils.e(TAG, "initRulesConfig: Rules实例获取失败通话规则加载失败");
}
}
/**
* 初始化广播接收器(监听系统事件,如开机启动、网络变化)
*/
private void initMainReceiver() {
if (mMainReceiver == null) {
mMainReceiver = new MainReceiver(this);
mMainReceiver.registerAction(this);
LogUtils.d(TAG, "initMainReceiver: 广播接收器注册");
LogUtils.d(TAG, "initMainReceiver: 全局广播接收器注册完成");
} else {
LogUtils.d(TAG, "initMainReceiver: 广播接收器已初始化,跳过重复注册");
}
}
/**
* 启动通话相关服务(通话监听+通话筛选,核心功能服务)
*/
private void startCallRelatedServices() {
// 启动通话监听服务
try {
Intent callListenerIntent = new Intent(this, CallListenerService.class);
startService(callListenerIntent);
LogUtils.d(TAG, "startCallRelatedServices: CallListenerService启动成功");
} catch (Exception e) {
LogUtils.e(TAG, "startCallRelatedServices: CallListenerService启动失败", e);
}
// 启动通话筛选服务API10+生效,低版本兼容)
try {
Intent screeningIntent = new Intent(this, MyCallScreeningService.class);
startService(screeningIntent);
LogUtils.d(TAG, "startCallRelatedServices: MyCallScreeningService启动成功");
} catch (Exception e) {
LogUtils.e(TAG, "startCallRelatedServices: MyCallScreeningService启动失败", e);
}
}
}

View File

@@ -1,147 +1,237 @@
package cc.winboll.studio.contacts.services;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.telecom.CallScreeningService;
import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/12 19:00
* @Describe 通话筛选服务(完全匹配 API 30 父类方法原型,严格 Java 7 兼容)
* 修复1. 前台服务启动超时异常 2. 移除所有 Build.VERSION_CODES 高版本依赖 3. 强化空指针防护
* @Describe 通话筛选服务(无前台服务),负责识别通话类型、拦截指定号码、处理正常通话逻辑
* 严格适配 Java7 语法 + Android API29-30 | 轻量稳定 | 强化空指针防护 | 无冗余代码
*/
@RequiresApi(api = 29) // API 29 = Android 10 = Build.VERSION_CODES.Q直接写数值避免依赖
@RequiresApi(api = 29) // 父类 CallScreeningService 最低要求 API29明确标注
public class MyCallScreeningService extends CallScreeningService {
// ====================== 常量定义区(全硬编码,杜绝高版本 API 依赖) ======================
// ====================== 常量定义区全硬编码无高版本API依赖 ======================
public static final String TAG = "MyCallScreeningService";
// 前台服务配置
private static final int FOREGROUND_NOTIFICATION_ID = 1003;
private static final String FOREGROUND_CHANNEL_ID = "call_screening_service_channel";
// API 版本常量(直接写数值,替代 Build.VERSION_CODES
private static final int ANDROID_8_API = 26; // Android 8.0,通知渠道最低版本
private static final int ANDROID_12_API = 31; // Android 12stopForeground 带参数版本
// stopForeground 参数常量(硬编码 1替代 STOP_FOREGROUND_REMOVE
private static final int STOP_FOREGROUND_REMOVE = 1;
// 通话方向常量(直接写数值,替代 Call.Details 中的常量,适配 API 30
private static final int CALL_DIRECTION_INCOMING = 1;
private static final int CALL_DIRECTION_OUTGOING = 2;
// 成员变量
private Context mContext;
// 通话方向常量(硬编码替代 Call.Details 高版本字段适配API29-30
private static final int CALL_DIRECTION_INCOMING = 1; // 来电
private static final int CALL_DIRECTION_OUTGOING = 2; // 外拨
// ====================== Service 生命周期方法 ======================
// ====================== 成员属性区(精简必要属性,命名规范) ======================
private Context mContext; // 上下文对象,避免重复调用 getApplicationContext()
// ====================== Service生命周期方法区按执行顺序排列 ======================
@Override
public void onCreate() {
super.onCreate();
mContext = this;
LogUtils.d(TAG, "onCreate: 通话筛选服务启动");
// ========== 核心优化:优先启动前台服务,放在 onCreate 最前端,避免超时 ==========
try {
Notification foregroundNotification = createForegroundNotification();
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification);
LogUtils.d(TAG, "onCreate: 前台服务启动成功");
} catch (Exception e) {
LogUtils.e(TAG, "onCreate: 前台服务启动失败", e);
stopSelf(); // 启动失败直接停止服务,避免系统抛出异常
return;
}
LogUtils.d(TAG, "===== onCreate: 通话筛选服务启动 =====");
}
/**
* 100% 匹配 API 30 CallScreeningService 抽象方法原型
* 完全移除高版本依赖,使用硬编码常量
*/
@Override
@RequiresApi(api = 29) // 与类注解一致,明确最低 API 要求
public void onScreenCall(@NonNull android.telecom.Call.Details details) {
// 1. 获取通话号码多层空指针防护Java 7 规范写法
String phoneNumber = "未知号码";
Uri handle = details.getHandle();
if (handle != null) {
String schemePart = handle.getSchemeSpecificPart();
if (schemePart != null && !schemePart.trim().isEmpty()) {
phoneNumber = schemePart.trim();
}
}
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand: 服务被启动startId=" + startId);
// 2. 判断通话方向:用硬编码常量替代 API 高版本字段
String callTypeStr;
int callType;
int direction = details.getCallDirection();
if (direction == CALL_DIRECTION_INCOMING) {
callTypeStr = "来电";
callType = TelephonyManager.CALL_STATE_RINGING;
} else if (direction == CALL_DIRECTION_OUTGOING) {
callTypeStr = "外拨";
callType = TelephonyManager.CALL_STATE_OFFHOOK;
} else {
callTypeStr = "未知类型";
callType = TelephonyManager.CALL_STATE_IDLE;
}
// 加载服务配置,决定重启策略(启用则自动重启,禁用则默认)
MainServiceBean serviceConfig = MainServiceBean.loadBean(this, MainServiceBean.class);
int startMode = (serviceConfig != null && serviceConfig.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand: 服务启动模式:" + (startMode == START_STICKY ? "START_STICKY自动重启" : "默认模式"));
// Java 7 字符串拼接,避免 String.format 兼容性问题
LogUtils.d(TAG, "检测到" + callTypeStr + "" + phoneNumber);
// 3. 自定义拦截逻辑:示例拦截 10086
boolean shouldBlock = "10086".equals(phoneNumber);
// 4. 构建筛选响应Java 7 分步调用,不使用链式写法
CallResponse.Builder responseBuilder = new CallResponse.Builder();
responseBuilder.setDisallowCall(shouldBlock);
responseBuilder.setRejectCall(shouldBlock);
responseBuilder.setSkipCallLog(!shouldBlock);
responseBuilder.setSkipNotification(!shouldBlock);
CallResponse response = responseBuilder.build();
// 5. 提交筛选结果API 30 父类方法,直接调用)
respondToCall(details, response);
// 6. 处理业务逻辑
if (!shouldBlock) {
handleNormalCall(phoneNumber, callType);
} else {
LogUtils.d(TAG, "已拦截通话:" + phoneNumber);
}
return startMode;
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 通话筛选服务销毁");
// 适配 Android 12+ stopForeground 参数,硬编码常量避免依赖
if (Build.VERSION.SDK_INT >= ANDROID_12_API) {
stopForeground(STOP_FOREGROUND_REMOVE);
// 置空上下文,释放引用,避免内存泄漏
mContext = null;
LogUtils.d(TAG, "===== onDestroy: 通话筛选服务销毁完成 =====");
}
// ====================== 核心通话筛选方法区(父类抽象方法实现) ======================
/**
* 核心通话筛选入口API29-30标准方法100%兼容)
* 功能:识别通话号码/类型、拦截指定号码、处理正常通话
*/
@Override
@RequiresApi(api = 29)
public void onScreenCall(@NonNull android.telecom.Call.Details details) {
LogUtils.d(TAG, "===== onScreenCall: 开始筛选通话 =====");
// 1. 安全获取通话号码多层空指针防护Java7规范写法
String phoneNumber = getSafePhoneNumber(details);
// 2. 识别通话方向(来电/外拨/未知)
int callDirection = details.getCallDirection();
String callTypeDesc = getCallDirectionDesc(callDirection);
int callState = getCallStateByDirection(callDirection);
LogUtils.d(TAG, "筛选结果:通话类型=" + callTypeDesc + ",号码=" + phoneNumber);
// 3. 自定义拦截逻辑示例拦截指定号码10086可按需扩展黑白名单
boolean isNeedBlock = isTargetBlockNumber(phoneNumber);
// 4. 构建筛选响应Java7分步调用不使用链式写法避免兼容问题
CallResponse callResponse = buildCallScreeningResponse(isNeedBlock);
// 5. 提交筛选结果(必须调用父类方法,完成拦截/放行逻辑)
respondToCall(details, callResponse);
// 6. 分场景处理后续逻辑(拦截日志/正常通话业务)
handleCallAfterScreening(phoneNumber, callTypeDesc, isNeedBlock);
LogUtils.d(TAG, "===== onScreenCall: 通话筛选完成 =====");
}
// ====================== 业务逻辑方法区(按功能拆分,低耦合) ======================
/**
* 安全获取通话号码(多层空指针+空字符串防护,避免崩溃)
*/
private String getSafePhoneNumber(android.telecom.Call.Details details) {
String phoneNumber = "未知号码";
if (details == null) {
LogUtils.w(TAG, "getSafePhoneNumber: 通话详情为空,无法获取号码");
return phoneNumber;
}
Uri handle = details.getHandle();
if (handle != null) {
String schemePart = handle.getSchemeSpecificPart();
if (schemePart != null && !schemePart.trim().isEmpty()) {
phoneNumber = schemePart.trim();
LogUtils.d(TAG, "getSafePhoneNumber: 成功获取号码,原始值=" + schemePart + ",处理后=" + phoneNumber);
} else {
LogUtils.w(TAG, "getSafePhoneNumber: 号码格式异常schemePart=" + schemePart);
}
} else {
LogUtils.w(TAG, "getSafePhoneNumber: 通话 handle 为空,无法获取号码");
}
return phoneNumber;
}
/**
* 判断是否为目标拦截号码可扩展黑白名单逻辑当前示例拦截10086
*/
private boolean isTargetBlockNumber(String phoneNumber) {
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
LogUtils.w(TAG, "isTargetBlockNumber: 号码为空,不拦截");
return false;
}
// 示例拦截逻辑:拦截 10086实际可替换为黑白名单查询
boolean isBlock = "10086".equals(phoneNumber.trim());
if (isBlock) {
LogUtils.d(TAG, "isTargetBlockNumber: 命中拦截规则,号码=" + phoneNumber);
} else {
LogUtils.d(TAG, "isTargetBlockNumber: 未命中拦截规则,号码=" + phoneNumber);
}
return isBlock;
}
/**
* 构建通话筛选响应(按需配置拦截/放行参数适配API29-30
*/
private CallResponse buildCallScreeningResponse(boolean isNeedBlock) {
CallResponse.Builder responseBuilder = new CallResponse.Builder();
// 拦截配置:是否禁止通话+是否拒绝通话(两者配合实现拦截)
responseBuilder.setDisallowCall(isNeedBlock);
responseBuilder.setRejectCall(isNeedBlock);
// 日志/通知配置:拦截的通话跳过日志和通知,正常通话保留
responseBuilder.setSkipCallLog(isNeedBlock);
responseBuilder.setSkipNotification(isNeedBlock);
CallResponse response = responseBuilder.build();
LogUtils.d(TAG, "buildCallScreeningResponse: 响应构建完成,拦截状态=" + isNeedBlock);
return response;
}
/**
* 筛选后分场景处理(拦截日志/正常通话业务扩展)
*/
private void handleCallAfterScreening(String phoneNumber, String callTypeDesc, boolean isNeedBlock) {
if (isNeedBlock) {
// 拦截场景:仅打日志(可扩展:添加拦截记录、本地存储等)
LogUtils.d(TAG, "handleCallAfterScreening: 已拦截通话,类型=" + callTypeDesc + ",号码=" + phoneNumber);
} else {
// 正常通话场景:处理业务逻辑(可扩展:通话记录、广播通知、号码识别等)
int callState = getCallStateByDirection(getCallDirectionFromDesc(callTypeDesc));
handleNormalCallBusiness(phoneNumber, callState);
}
}
// ====================== 业务逻辑方法 ======================
/**
* 处理正常通话的扩展逻辑Java 7 规范写法,添加空指针防护
* 正常通话业务处理(核心业务扩展入口,强化空指针防护
*/
private void handleNormalCall(String phoneNumber, int callType) {
private void handleNormalCallBusiness(String phoneNumber, int callState) {
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
LogUtils.w(TAG, "handleNormalCall: 号码为空,跳过处理");
LogUtils.w(TAG, "handleNormalCallBusiness: 号码为空,跳过业务处理");
return;
}
LogUtils.d(TAG, "处理正常通话:" + phoneNumber + " | 状态:" + getCallStateStr(callType));
// 可在此添加通话记录、广播通知等逻辑
String callStateDesc = getCallStateDesc(callState);
LogUtils.d(TAG, "handleNormalCallBusiness: 处理正常通话业务,号码=" + phoneNumber + ",状态=" + callStateDesc);
// 此处可扩展业务逻辑(示例):
// 1. 保存通话记录到本地
// 2. 发送广播通知其他组件(如通话监听服务)
// 3. 调用号码识别接口,匹配联系人信息
}
// ====================== 工具辅助方法区(统一归类,复用性强) ======================
/**
* 通话方向转文字描述(便于日志查看,快速定位场景)
*/
private String getCallDirectionDesc(int callDirection) {
switch (callDirection) {
case CALL_DIRECTION_INCOMING:
return "来电";
case CALL_DIRECTION_OUTGOING:
return "外拨";
default:
return "未知通话";
}
}
/**
* 辅助方法:将通话状态转为字符串,避免重复代码
* 文字描述转通话方向(配合业务逻辑反向匹配,避免重复判断)
*/
private String getCallStateStr(int callType) {
switch (callType) {
private int getCallDirectionFromDesc(String callTypeDesc) {
if ("来电".equals(callTypeDesc)) {
return CALL_DIRECTION_INCOMING;
} else if ("外拨".equals(callTypeDesc)) {
return CALL_DIRECTION_OUTGOING;
} else {
return -1; // 未知方向
}
}
/**
* 通话方向转 TelephonyManager 状态(统一状态标准,便于业务复用)
*/
private int getCallStateByDirection(int callDirection) {
switch (callDirection) {
case CALL_DIRECTION_INCOMING:
return TelephonyManager.CALL_STATE_RINGING; // 来电=响铃中
case CALL_DIRECTION_OUTGOING:
return TelephonyManager.CALL_STATE_OFFHOOK; // 外拨=通话中
default:
return TelephonyManager.CALL_STATE_IDLE; // 未知=空闲
}
}
/**
* TelephonyManager 状态转文字描述(统一日志格式,提升可读性)
*/
private String getCallStateDesc(int callState) {
switch (callState) {
case TelephonyManager.CALL_STATE_RINGING:
return "响铃中";
case TelephonyManager.CALL_STATE_OFFHOOK:
@@ -149,53 +239,8 @@ public class MyCallScreeningService extends CallScreeningService {
case TelephonyManager.CALL_STATE_IDLE:
return "空闲";
default:
return "未知";
return "未知状态";
}
}
// ====================== 前台服务通知构建方法 ======================
/**
* 创建前台服务通知:严格 Java 7 分步构建,强化异常防护
* @return 可用的 Notification 实例
*/
private Notification createForegroundNotification() {
// 1. 创建 Android 8.0+ 通知渠道,添加空指针防护
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
NotificationChannel channel = new NotificationChannel(
FOREGROUND_CHANNEL_ID,
"来电筛查服务",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("后台处理来电筛查,防止服务被系统回收");
// 关闭通知音效和震动,避免打扰用户
channel.setSound(null, null);
channel.enableVibration(false);
NotificationManager manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (manager != null) {
manager.createNotificationChannel(channel);
LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
} else {
LogUtils.e(TAG, "createForegroundNotification: NotificationManager 获取失败");
}
}
// 2. 构建 NotificationJava 7 分步设置,不使用链式调用
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
builder = new Notification.Builder(mContext, FOREGROUND_CHANNEL_ID);
} else {
builder = new Notification.Builder(mContext);
}
// 关键:确保小图标资源存在,替换为项目实际图标
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentTitle("来电筛查服务运行中");
builder.setContentText("正在监控来电状态");
builder.setPriority(Notification.PRIORITY_LOW);
builder.setOngoing(true); // 前台服务通知不可手动取消
return builder.build();
}
}