Files
WinBoLL/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java
2025-12-12 20:08:21 +08:00

519 lines
24 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cc.winboll.studio.contacts;
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.activities.WinBollActivity;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.contacts.fragments.ContactsFragment;
import cc.winboll.studio.contacts.fragments.LogFragment;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.services.MyCallScreeningService;
import cc.winboll.studio.contacts.utils.PermissionUtils;
import cc.winboll.studio.contacts.views.DunTemperatureView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/08/30 14:32
* @Describe Contacts 主窗口(完全适配 API 30 + Java 7 语法)
*/
public final class MainActivity extends WinBollActivity implements IWinBoLLActivity, ViewPager.OnPageChangeListener, View.OnClickListener {
// ====================== 常量定义区Java 7 硬编码 API 版本,避免高版本依赖) ======================
public static final String TAG = "MainActivity";
public static final int REQUEST_HOME_ACTIVITY = 0;
public static final int REQUEST_ABOUT_ACTIVITY = 1;
public static final int REQUEST_APP_SETTINGS = 2;
public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoLL.ACTION_SOS";
private static final int DIALER_REQUEST_CODE = 1;
private static final int REQUEST_REQUIRED_PERMISSIONS = 1002;
private static final int REQUEST_OVERLAY_PERMISSION = 1003;
private static final int REQUEST_CALL_SCREENING_PERMISSION = 1004;
// API 版本常量硬编码Java 7 兼容,不依赖 Build.VERSION_CODES 高版本字段)
private static final int ANDROID_6_API = 23;
private static final int ANDROID_8_API = 26;
private static final int ANDROID_10_API = 29;
private static final int ANDROID_14_API = 34;
// ====================== 静态成员区 ======================
static MainActivity _MainActivity;
// ====================== 权限常量区(移除废弃权限,直接复用 PermissionUtils 常量) ======================
private final String[] REQUIRED_PERMISSIONS = PermissionUtils.BASE_PERMISSIONS;
// ====================== UI控件成员区 ======================
private ADsBannerView mADsBannerView;
private LogView mLogView;
private Toolbar mToolbar;
private CheckBox cbMainService;
private TabLayout tabLayout;
private ViewPager viewPager;
private List<View> views;
private ImageView[] imageViews;
private LinearLayout linearLayout;
// ====================== 业务逻辑成员区 ======================
private MainServiceBean mMainServiceBean;
private int currentPoint = 0;
private TelephonyManager telephonyManager;
private MyPhoneStateListener phoneStateListener;
private List<Fragment> fragmentList;
private List<String> tabTitleList;
// ====================== 接口实现区 ======================
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
// ====================== 生命周期函数区(优化权限检查逻辑,接入 PermissionUtils 工具类) ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 主Activity开始创建");
_MainActivity = this;
// 权限检查分流:基于 PermissionUtils 工具类,简化逻辑
// if (!PermissionUtils.checkPermissions(this, REQUIRED_PERMISSIONS)) {
// LogUtils.d(TAG, "onCreate: 危险权限未完全授予,发起申请");
// PermissionUtils.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_REQUIRED_PERMISSIONS);
// } else if (!PermissionUtils.isOverlayPermissionGranted(this)) {
// LogUtils.d(TAG, "onCreate: 悬浮窗权限未授予,跳转设置页");
// PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
// } else if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
// LogUtils.d(TAG, "onCreate: 通话筛选权限未授予,跳转设置页");
// PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
// } else {
// LogUtils.d(TAG, "onCreate: 所有权限已授予初始化UI和业务逻辑");
// initUIAndLogic(savedInstanceState);
// }
initUIAndLogic(savedInstanceState);
LogUtils.d(TAG, "onCreate: 主Activity创建流程结束");
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
LogUtils.d(TAG, "onPostCreate: 主Activity创建完成");
}
@Override
protected void onResume() {
super.onResume();
// LogUtils.d(TAG, "onResume: 主Activity进入前台");
// // 权限补全检查:使用工具类统一判断,简化代码
// boolean isAllPermGranted = PermissionUtils.checkPermissions(this, REQUIRED_PERMISSIONS)
// && PermissionUtils.isOverlayPermissionGranted(this);
// boolean isCallScreeningOk = true;
// if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
// isCallScreeningOk = PermissionUtils.isCallScreeningPermissionGranted(this);
// }
// if (isAllPermGranted && isCallScreeningOk && mToolbar == null) {
// LogUtils.d(TAG, "onResume: 权限已补全初始化UI和逻辑");
// initUIAndLogic(null);
// } else if (!PermissionUtils.isOverlayPermissionGranted(this)) {
// LogUtils.w(TAG, "onResume: 悬浮窗权限仍未授予,再次提示申请");
// PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
// } else if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
// LogUtils.w(TAG, "onResume: 通话筛选权限仍未授予,再次提示申请");
// PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
// }
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
LogUtils.d(TAG, "onResume: 广告栏资源已恢复");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 主Activity开始销毁");
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
LogUtils.d(TAG, "onDestroy: 广告栏资源已释放");
}
// 移除电话状态监听避免内存泄漏Java 7 规范)
if (telephonyManager != null && phoneStateListener != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
LogUtils.d(TAG, "onDestroy: 主Activity销毁完成");
}
// ====================== 权限相关函数区(完全接入 PermissionUtils移除冗余代码 ======================
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求结果回调requestCode=" + requestCode);
if (requestCode == REQUEST_REQUIRED_PERMISSIONS) {
// 使用工具类解析被拒绝的权限,简化字符串拼接逻辑
String deniedPerms = PermissionUtils.getDeniedPermissions(this, permissions);
if (deniedPerms.length() == 0) {
LogUtils.d(TAG, "onRequestPermissionsResult: 所有危险权限授予成功");
if (PermissionUtils.isOverlayPermissionGranted(this)) {
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
} else {
initUIAndLogic(null);
}
} else {
PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
}
} else {
LogUtils.e(TAG, "onRequestPermissionsResult: 存在权限被拒绝,弹出提示对话框");
String tip = "应用需要「" + deniedPerms + "」权限才能正常运行,请授予权限后重新打开应用。";
showPermissionDeniedDialogAndExit(tip);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
LogUtils.d(TAG, "onActivityResult: 回调触发requestCode=" + requestCode + "resultCode=" + resultCode);
if (requestCode == DIALER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
LogUtils.d(TAG, "onActivityResult: 设为默认拨号应用成功");
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用", Toast.LENGTH_SHORT).show();
}
} else if (requestCode == REQUEST_APP_SETTINGS) {
LogUtils.d(TAG, "onActivityResult: 从设置页返回重建Activity");
recreate();
} else if (requestCode == REQUEST_OVERLAY_PERMISSION) {
LogUtils.d(TAG, "onActivityResult: 从悬浮窗设置页返回");
if (PermissionUtils.isOverlayPermissionGranted(this)) {
LogUtils.d(TAG, "onActivityResult: 悬浮窗权限申请成功");
if (Build.VERSION.SDK_INT >= ANDROID_10_API && !PermissionUtils.isCallScreeningPermissionGranted(this)) {
PermissionUtils.requestCallScreeningPermission(this, REQUEST_CALL_SCREENING_PERMISSION);
} else {
initUIAndLogic(null);
}
} else {
LogUtils.e(TAG, "onActivityResult: 悬浮窗权限申请失败");
showPermissionDeniedDialogAndExit("应用需要悬浮窗权限才能展示来电弹窗,请授予后重新打开应用。");
}
} else if (requestCode == REQUEST_CALL_SCREENING_PERMISSION) {
LogUtils.d(TAG, "onActivityResult: 从通话筛选设置页返回");
if (PermissionUtils.isCallScreeningPermissionGranted(this)) {
LogUtils.d(TAG, "onActivityResult: 通话筛选权限申请成功");
initUIAndLogic(null);
} else {
LogUtils.e(TAG, "onActivityResult: 通话筛选权限申请失败");
showPermissionDeniedDialogAndExit("应用需要通话筛选权限监听外拨电话,请授予后重新打开应用。");
}
}
}
/**
* 权限拒绝提示对话框(纯 Java 7 匿名内部类,无 Lambda
*/
private void showPermissionDeniedDialogAndExit(String tip) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("权限不足,无法使用");
builder.setMessage(tip);
builder.setCancelable(false);
builder.setNegativeButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户点击设置权限,跳转应用设置页");
PermissionUtils.goAppDetailsSettings(MainActivity.this);
}
});
builder.setPositiveButton("确定退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户点击退出,关闭应用");
finishAndRemoveTask();
}
});
builder.show();
}
// ====================== UI与逻辑初始化区修复服务启动判断优化内存管理 ======================
private void initUIAndLogic(Bundle savedInstanceState) {
if (mToolbar != null) {
LogUtils.d(TAG, "initUIAndLogic: UI已初始化无需重复执行");
return;
}
LogUtils.d(TAG, "initUIAndLogic: 开始初始化UI布局");
setContentView(R.layout.activity_main);
// 工具栏初始化
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
setSupportActionBar(mToolbar);
getSupportActionBar().setSubtitle(TAG);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 显示状态栏
getWindow().getInsetsController().show(WindowInsets.Type.statusBars());
// 设置滑动时状态栏暂显的正常行为
getWindow().getInsetsController().setSystemBarsBehavior(
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);
}
// Tab与ViewPager初始化
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
initViewPagerAndTabs();
tabLayout.setupWithViewPager(viewPager);
LogUtils.d(TAG, "initUIAndLogic: ViewPager与TabLayout初始化完成");
// 广告栏初始化
mADsBannerView = (ADsBannerView) findViewById(R.id.adsbanner);
LogUtils.d(TAG, "initUIAndLogic: 广告栏控件初始化完成");
// 服务启动逻辑
initMainService();
LogUtils.d(TAG, "initUIAndLogic: 主服务初始化完成");
// 电话状态监听初始化(适配 API 30启动通话筛选服务
initPhoneStateListener();
LogUtils.d(TAG, "initUIAndLogic: 电话状态监听器初始化完成");
// 盾值视图初始化Java 7 数组初始化规范)
DunTemperatureView tempView = (DunTemperatureView) findViewById(R.id.dun_temp_view);
tempView.setMaxValue(Rules.getInstance(this).getSettingsModel().getDunTotalCount());
tempView.setCurrentValue(Rules.getInstance(this).getSettingsModel().getDunCurrentCount());
int[] customColors = new int[2];
customColors[0] = Color.parseColor("#FF3366FF");
customColors[1] = Color.parseColor("#FF9900CC");
float[] positions = new float[2];
positions[0] = 0.0f;
positions[1] = 1.0f;
tempView.setGradientColors(customColors, positions);
}
private void initViewPagerAndTabs() {
fragmentList = new ArrayList<Fragment>();
tabTitleList = new ArrayList<String>();
fragmentList.add(CallLogFragment.newInstance(0));
fragmentList.add(ContactsFragment.newInstance(1));
fragmentList.add(LogFragment.newInstance(2));
tabTitleList.add("通话记录");
tabTitleList.add("联系人");
tabTitleList.add("应用日志");
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(2); // 优化保留2个缓存页减少重建
viewPager.addOnPageChangeListener(this);
}
private void initMainService() {
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean == null) {
LogUtils.d(TAG, "initMainService: 服务配置Bean不存在创建新实例并保存");
mMainServiceBean = new MainServiceBean();
MainServiceBean.saveBean(this, mMainServiceBean);
}
// 双重判断:服务是否启用 + 是否已运行,避免重复启动
if (mMainServiceBean.isEnable() && !isServiceRunning(MainService.class)) {
LogUtils.d(TAG, "initMainService: 主服务已启用且未运行延迟1秒启动服务");
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
MainService.startMainService(MainActivity.this);
}
}, 1000);
} else {
LogUtils.d(TAG, "initMainService: 主服务未启用或已运行,跳过启动流程");
}
}
/**
* 初始化通话监听API 30 适配,启动通话筛选服务)
*/
private void initPhoneStateListener() {
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
phoneStateListener = new MyPhoneStateListener();
// 仅监听通话状态,避免冗余监听
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
// API 30+ 启动通话筛选服务,替代 PROCESS_OUTGOING_CALLS 权限
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
Intent screeningIntent = new Intent(this, MyCallScreeningService.class);
// 适配 Android O 以上前台服务启动要求
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
startForegroundService(screeningIntent);
} else {
startService(screeningIntent);
}
LogUtils.d(TAG, "initPhoneStateListener: API 30+ 通话筛选服务已启动");
}
}
// ====================== 菜单相关函数区 ======================
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.item_settings) {
LogUtils.d(TAG, "onOptionsItemSelected: 用户点击设置菜单,跳转设置页面");
startActivity(new Intent(this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
// ====================== 页面回调函数区 ======================
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {
currentPoint = position;
LogUtils.d(TAG, "onPageSelected: 页面切换至索引" + position + ",标题=" + tabTitleList.get(position));
}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onClick(View v) {}
// ====================== 电话相关工具函数区(优化权限检查,接入工具类) ======================
public static void dialPhoneNumber(String phoneNumber) {
if (PermissionUtils.checkPermission(_MainActivity, Manifest.permission.CALL_PHONE)) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + phoneNumber));
LogUtils.d(TAG, "dialPhoneNumber: 发起拨号,号码=" + phoneNumber);
_MainActivity.startActivity(intent);
} else {
LogUtils.e(TAG, "dialPhoneNumber: 拨号权限不足,无法发起拨号");
Toast.makeText(_MainActivity, "拨号权限不足", Toast.LENGTH_SHORT).show();
}
}
public boolean isDefaultPhoneCallApp() {
if (Build.VERSION.SDK_INT >= ANDROID_6_API) {
TelecomManager manager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
if (manager != null && manager.getDefaultDialerPackage() != null) {
boolean isDefault = manager.getDefaultDialerPackage().equals(getPackageName());
LogUtils.d(TAG, "isDefaultPhoneCallApp: 当前应用是否为默认拨号应用=" + isDefault);
return isDefault;
}
}
LogUtils.d(TAG, "isDefaultPhoneCallApp: 系统版本低于M无法判断默认拨号应用");
return false;
}
// ====================== 服务状态工具函数区 ======================
public boolean isServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (manager == null) {
LogUtils.w(TAG, "isServiceRunning: ActivityManager获取失败");
return false;
}
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getName() + "]正在运行");
return true;
}
}
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getName() + "]未运行");
return false;
}
// ====================== 内部类定义区Java 7 规范,无 Lambda ======================
private class MyPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList;
private final List<String> tabTitleList;
public MyPagerAdapter(FragmentManager fm, List<Fragment> fragmentList, List<String> tabTitleList) {
super(fm);
this.fragmentList = fragmentList;
this.tabTitleList = tabTitleList;
LogUtils.d(TAG, "MyPagerAdapter: ViewPager适配器初始化Fragment数量=" + fragmentList.size());
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
}
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
LogUtils.d(TAG, "onCallStateChanged: 电话状态-空闲");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
LogUtils.d(TAG, "onCallStateChanged: 电话状态-通话中");
break;
case TelephonyManager.CALL_STATE_RINGING:
LogUtils.d(TAG, "onCallStateChanged: 电话状态-来电,号码=" + incomingNumber);
break;
default:
LogUtils.d(TAG, "onCallStateChanged: 未知通话状态state=" + state);
break;
}
}
}
}