diff --git a/mymessagemanager/.gitignore b/mymessagemanager/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/mymessagemanager/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mymessagemanager/README.md b/mymessagemanager/README.md new file mode 100644 index 0000000..b17ab1d --- /dev/null +++ b/mymessagemanager/README.md @@ -0,0 +1,45 @@ +# MyMessageManager + +#### 介绍 +用正则表达式方法自定义短信过滤和语音播报的短信应用。 + +#### 软件架构 +软件架构说明 + + +#### 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +#### 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码:ZhanGSKen(ZhanGSKen) +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) + +#### 参考文档 + +使用GitHub Actions实现Android自动打包apk +https://blog.csdn.net/ZZL23333/article/details/115798615?app_version=6.0.0&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22115798615%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app + +Android中assets的使用(用于读取内容) +https://blog.csdn.net/qq_27664947/article/details/103924058?app_version=6.0.0&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22103924058%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app diff --git a/mymessagemanager/app_update_description.txt b/mymessagemanager/app_update_description.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mymessagemanager/app_update_description.txt @@ -0,0 +1 @@ + diff --git a/mymessagemanager/build.gradle b/mymessagemanager/build.gradle new file mode 100644 index 0000000..ee9dbe4 --- /dev/null +++ b/mymessagemanager/build.gradle @@ -0,0 +1,88 @@ +apply plugin: 'com.android.application' +apply from: '../.winboll/winboll_app_build.gradle' +apply from: '../.winboll/winboll_lint_build.gradle' + +def genVersionName(def versionName){ + // 检查编译标志位配置 + assert (winbollBuildProps['stageCount'] != null) + assert (winbollBuildProps['baseVersion'] != null) + // 保存基础版本号 + winbollBuildProps.setProperty("baseVersion", "${versionName}"); + //保存编译标志配置 + FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile) + winbollBuildProps.store(fos, "${winbollBuildPropsDesc}"); + fos.close(); + + // 返回编译版本号 + return "${versionName}." + winbollBuildProps['stageCount'] +} + +android { + + // 关键:改为你已安装的 SDK 32(≥ targetSdkVersion 30,兼容已安装环境) + compileSdkVersion 32 + + // 直接使用已安装的构建工具 33.0.3(无需修改) + buildToolsVersion "33.0.3" + + defaultConfig { + applicationId "cc.winboll.studio.mymessagemanager" + minSdkVersion 23 + targetSdkVersion 30 + versionCode 8 + // versionName 更新后需要手动设置 + // .winboll/winbollBuildProps.properties 文件的 stageCount=0 + // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" + versionName "15.12" + if(true) { + versionName = genVersionName("${versionName}") + } + } + + // 米盟 SDK + packagingOptions { + doNotStrip "*/*/libmimo_1011.so" + } +} + +dependencies { + + // 米盟 + api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk + //注意:以下5个库必须要引入 + //api 'androidx.appcompat:appcompat:1.4.1' + api 'androidx.recyclerview:recyclerview:1.0.0' + api 'com.google.code.gson:gson:2.8.5' + api 'com.github.bumptech.glide:glide:4.9.0' + //annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' + + api 'io.github.medyo:android-about-page:2.0.0' + api 'com.jcraft:jsch:0.1.55' + api 'org.jsoup:jsoup:1.13.1' + api 'com.squareup.okhttp3:okhttp:4.4.1' + + api 'com.belerweb:pinyin4j:2.5.1' + + // 权限请求框架:https://github.com/getActivity/XXPermissions + api 'com.github.getActivity:XXPermissions:18.63' + api 'com.baoyz.pullrefreshlayout:library:1.2.0' + + // AndroidX 类库 + api 'androidx.appcompat:appcompat:1.1.0' + api 'com.google.android.material:material:1.4.0' + //api 'androidx.viewpager:viewpager:1.0.0' + //api 'androidx.vectordrawable:vectordrawable:1.1.0' + //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' + //api 'androidx.fragment:fragment:1.1.0' + api 'com.google.android.material:material:1.0.0' + + // WinBoLL库 nexus.winboll.cc 地址 + api 'cc.winboll.studio:libaes:15.12.12' + api 'cc.winboll.studio:libappbase:15.14.2' + + // WinBoLL备用库 jitpack.io 地址 + //api 'com.github.ZhanGSKen:AES:aes-v15.12.9' + //api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1' + + api fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/mymessagemanager/build.properties b/mymessagemanager/build.properties new file mode 100644 index 0000000..3ec3fb9 --- /dev/null +++ b/mymessagemanager/build.properties @@ -0,0 +1,8 @@ +#Created by .winboll/winboll_app_build.gradle +#Tue Feb 10 19:09:02 GMT 2026 +stageCount=7 +libraryProject= +baseVersion=15.12 +publishVersion=15.12.6 +buildCount=1 +baseBetaVersion=15.12.7 diff --git a/mymessagemanager/proguard-rules.pro b/mymessagemanager/proguard-rules.pro new file mode 100644 index 0000000..855b18a --- /dev/null +++ b/mymessagemanager/proguard-rules.pro @@ -0,0 +1,143 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# ============================== 基础通用规则 ============================== +# 保留系统组件 +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference + +# 保留 WinBoLL 核心包及子类(合并简化规则) +-keep class cc.winboll.studio.** { *; } +-keepclassmembers class cc.winboll.studio.** { *; } + +# 保留所有类中的 public static final String TAG 字段(便于日志定位) +-keepclassmembers class * { + public static final java.lang.String TAG; +} + +# 保留序列化类(避免Parcelable/Gson解析异常) +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} + +# 保留 R 文件(避免资源ID混淆) +-keepclassmembers class **.R$* { + public static ; +} + +# 保留 native 方法(避免JNI调用失败) +-keepclasseswithmembernames class * { + native ; +} + +# 保留注解和泛型(避免反射/序列化异常) +-keepattributes *Annotation* +-keepattributes Signature + +# 屏蔽 Java 8+ 警告(适配 Java 7 语法) +-dontwarn java.lang.invoke.* +-dontwarn android.support.v8.renderscript.* +-dontwarn java.util.function.** + +# ============================== 第三方框架专项规则 ============================== +# OkHttp 4.4.1(米盟广告请求依赖,完善Lambda兼容) +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-keep class okhttp3.internal.** { *; } +-keep class okio.** { *; } +-dontwarn okhttp3.internal.platform.** +-dontwarn okio.** +# ============================== 必要补充规则 ============================== +# OkHttp 4.4.1 补充规则(Java 7 兼容) +-keep class okhttp3.internal.concurrent.** { *; } +-keep class okhttp3.internal.connection.** { *; } +-dontwarn okhttp3.internal.concurrent.TaskRunner +-dontwarn okhttp3.internal.connection.RealCall + +# Glide 4.9.0(米盟广告图片加载依赖) +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType { + **[] $VALUES; + public *; +} +-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule { + (); +} +-dontwarn com.bumptech.glide.** + +# Gson 2.8.5(米盟广告数据序列化依赖) +-keep class com.google.gson.** { *; } +-keep interface com.google.gson.** { *; } +-keepclassmembers class * { + @com.google.gson.annotations.SerializedName ; +} + +# 米盟 SDK(核心广告组件,完整保留避免加载失败) +-keep class com.miui.zeus.** { *; } +-keep interface com.miui.zeus.** { *; } +# 保留米盟日志字段(便于广告加载失败排查) +-keepclassmembers class com.miui.zeus.mimo.sdk.** { + public static final java.lang.String TAG; +} + +# RecyclerView 1.0.0(米盟广告布局渲染依赖) +-keep class androidx.recyclerview.** { *; } +-keep interface androidx.recyclerview.** { *; } +-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter { + public *; +} + +# 其他第三方框架(按引入依赖保留,无则可删除) +# XXPermissions 18.63 +-keep class com.hjq.permissions.** { *; } +-keep interface com.hjq.permissions.** { *; } + +# ZXing 二维码(核心解析组件) +-keep class com.google.zxing.** { *; } +-keep class com.journeyapps.zxing.** { *; } + +# Jsoup HTML解析 +-keep class org.jsoup.** { *; } + +# Pinyin4j 拼音搜索 +-keep class net.sourceforge.pinyin4j.** { *; } + +# JSch SSH组件 +-keep class com.jcraft.jsch.** { *; } + +# AndroidX 基础组件 +-keep class androidx.appcompat.** { *; } +-keep interface androidx.appcompat.** { *; } + +# ============================== 优化与调试配置 ============================== +# 优化级别(平衡混淆效果与性能) +-optimizationpasses 5 +-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* + +# 调试辅助(保留行号便于崩溃定位) +-verbose +-dontpreverify +-dontusemixedcaseclassnames +-keepattributes SourceFile,LineNumberTable + diff --git a/mymessagemanager/src/beta/AndroidManifest.xml b/mymessagemanager/src/beta/AndroidManifest.xml new file mode 100644 index 0000000..522ecec --- /dev/null +++ b/mymessagemanager/src/beta/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/beta/res/values-zh/strings.xml b/mymessagemanager/src/beta/res/values-zh/strings.xml new file mode 100644 index 0000000..ffbc3e5 --- /dev/null +++ b/mymessagemanager/src/beta/res/values-zh/strings.xml @@ -0,0 +1,6 @@ + + + + 我的短信管家 ☆ + + diff --git a/mymessagemanager/src/beta/res/values/strings.xml b/mymessagemanager/src/beta/res/values/strings.xml new file mode 100644 index 0000000..085d3ca --- /dev/null +++ b/mymessagemanager/src/beta/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + My Message Manager + + + diff --git a/mymessagemanager/src/main/AndroidManifest.xml b/mymessagemanager/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7647205 --- /dev/null +++ b/mymessagemanager/src/main/AndroidManifest.xml @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/assets/GlobalApplication/SMSAcceptRuleBean_List.json b/mymessagemanager/src/main/assets/GlobalApplication/SMSAcceptRuleBean_List.json new file mode 100644 index 0000000..b7fdefe --- /dev/null +++ b/mymessagemanager/src/main/assets/GlobalApplication/SMSAcceptRuleBean_List.json @@ -0,0 +1,7 @@ +[ + { + "userId": -1, + "ruleData": ".*", + "isEnable": true + } +] \ No newline at end of file diff --git a/mymessagemanager/src/main/assets/GlobalApplication/TTSPlayRuleBean_List.json b/mymessagemanager/src/main/assets/GlobalApplication/TTSPlayRuleBean_List.json new file mode 100644 index 0000000..8c9f761 --- /dev/null +++ b/mymessagemanager/src/main/assets/GlobalApplication/TTSPlayRuleBean_List.json @@ -0,0 +1,38 @@ +[ + { + "userId": 1, + "ruleName": "规则1", + "demoSMSText": "【短信应用A】验证码123456", + "patternText": "^(【.*】)验证码(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)$", + "ttdRuleText": "$1验证码是($2)($3)($4)($5)($6)($7)。", + "isSimpleView": false, + "isEnable": true + }, + { + "userId": 1, + "ruleName": "规则2", + "demoSMSText": "[短信应用A]验证码123456", + "patternText": "^(\\[.*\\])验证码(\\d)(\\d)(\\d)(\\d)(\\d)(\\d)$", + "ttdRuleText": "$1验证码是($2)($3)($4)($5)($6)($7)。", + "isSimpleView": false, + "isEnable": true + }, + { + "userId": 1, + "ruleName": "规则3", + "demoSMSText": "【短信应用A】验证码123456", + "patternText": ".*(【.+】).*", + "ttdRuleText": "短信来自$1。", + "isSimpleView": false, + "isEnable": true + }, + { + "userId": 1, + "ruleName": "规则4", + "demoSMSText": "[短信应用A]验证码123456", + "patternText": ".*(\\[.*\\]).*", + "ttdRuleText": "短信来自$1。", + "isSimpleView": false, + "isEnable": true + } +] \ No newline at end of file diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/App.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/App.java new file mode 100644 index 0000000..c741d31 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/App.java @@ -0,0 +1,52 @@ +package cc.winboll.studio.mymessagemanager; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2023/07/24 01:46:59 + * @Describe 全局应用类 + */ +import android.view.Gravity; +import cc.winboll.studio.libappbase.GlobalApplication; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import java.io.File; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; + +public class App extends GlobalApplication { + + public static final String TAG = "GlobalApplication"; + + static String _mszAppExternalFilesDir; + static String _mszConfigUtilFileName = "ConfigUtil.json"; + static String _mszConfigUtilPath; + static String _mszSMSReceiveRuleUtilFileName = "SMSReceiveRuleUtil.json"; + static String _mszSMSReceiveRuleUtilPath; + + public static final int USER_ID = -1; + Long mszVersionName = 1L; + Long mszDataVersionName = 1L; + + + @Override + public void onCreate() { + super.onCreate(); + setIsDebugging(BuildConfig.DEBUG); + //setIsDebugging(false); + + // 初始化窗口管理类 + WinBoLLActivityManager.init(this); + + // 初始化 Toast 框架 + ToastUtils.init(this); + + _mszAppExternalFilesDir = getExternalFilesDir(TAG).toString(); + _mszConfigUtilPath = _mszAppExternalFilesDir + File.separator + _mszConfigUtilFileName; + _mszSMSReceiveRuleUtilPath = _mszAppExternalFilesDir + File.separator + _mszSMSReceiveRuleUtilFileName; + } + + @Override + public void onTerminate() { + super.onTerminate(); + ToastUtils.release(); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AboutActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AboutActivity.java new file mode 100644 index 0000000..27a9c2d --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AboutActivity.java @@ -0,0 +1,92 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen + * @Date 2024/07/14 13:20:33 + * @Describe 应用介绍窗口 + */ +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import androidx.appcompat.widget.Toolbar; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.models.APPInfo; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libaes.views.AboutView; +import cc.winboll.studio.mymessagemanager.App; +import cc.winboll.studio.mymessagemanager.R; + +public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "AboutActivity"; + + Context mContext; + Toolbar mToolbar; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + setContentView(R.layout.activity_about); + + mToolbar = findViewById(R.id.toolbar); + setSupportActionBar(mToolbar); + mToolbar.setSubtitle(TAG); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + AboutView aboutView = CreateAboutView(); + // 在 Activity 的 onCreate 或其他生命周期方法中调用 +// LinearLayout layout = new LinearLayout(this); +// layout.setOrientation(LinearLayout.VERTICAL); +// // 创建布局参数(宽度和高度) +// ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( +// ViewGroup.LayoutParams.MATCH_PARENT, +// ViewGroup.LayoutParams.MATCH_PARENT +// ); +// addContentView(aboutView, params); + + LinearLayout layout = findViewById(R.id.aboutviewroot_ll); + // 创建布局参数(宽度和高度) + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ); + layout.addView(aboutView, params); + + WinBoLLActivityManager.getInstance().add(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + WinBoLLActivityManager.getInstance().registeRemove(this); + } + + public AboutView CreateAboutView() { + String szBranchName = "mymessagemanager"; + APPInfo appInfo = new APPInfo(); + appInfo.setAppName(getString(R.string.app_name)); + appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll); + appInfo.setAppDescription(getString(R.string.app_description)); + appInfo.setAppGitName("APPBase"); + appInfo.setAppGitOwner("Studio"); + appInfo.setAppGitAPPBranch(szBranchName); + appInfo.setAppGitAPPSubProjectFolder(szBranchName); + appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=5&extra=page%3D1"); + appInfo.setAppAPKName("MyMessageManager"); + appInfo.setAppAPKFolderName("MyMessageManager"); + return new AboutView(mContext, appInfo); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java new file mode 100644 index 0000000..69d8426 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java @@ -0,0 +1,150 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen + * @Date 2024/05/12 20:03:42 + * @Describe 应用设置窗口 + */ +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; +import android.widget.EditText; +import android.widget.Switch; +import android.widget.Toast; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; +import cc.winboll.studio.libaes.views.AToolbar; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.dialogs.CharsetRefuseEditDialog; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; +import cc.winboll.studio.mymessagemanager.utils.PermissionUtil; + +public class AppSettingsActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "AppSettingsActivity"; + + // 讯飞语记官网下载页链接 + private static final String XUNFEI_YUJI_DOWNLOAD_URL = "https://iflynote.com/h/share-download-app.html"; + + AppConfigUtil mAppConfigUtil; + AToolbar mAToolbar; + AOHPCTCSeekBar mAOHPCTCSeekBar; + EditText metTTSPlayDelayTimes; + EditText metPhoneMergePrefix; + Switch mswMergePrefixPhone; + Switch mswSMSRecycleProtectMode; + //EditText metProtectModerRefuseChars; + EditText metProtectModerReplaceChars; + String mszProtectModerRefuseChars = ""; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_appsettings); + + // 初始化属性 + mAppConfigUtil = AppConfigUtil.getInstance(this); + int nTtsPlayDelayTimes = mAppConfigUtil.mAppConfigBean.getTtsPlayDelayTimes(); + metTTSPlayDelayTimes = findViewById(R.id.activityappsettingsEditText1); + metTTSPlayDelayTimes.setText(Integer.toString(nTtsPlayDelayTimes / 1000)); + + // 初始化标题栏 + mAToolbar = findViewById(R.id.activityappsettingsAToolbar1); + mAToolbar.setSubtitle(getString(R.string.activity_name_appsettings)); + setActionBar(mAToolbar); + + metPhoneMergePrefix = findViewById(R.id.activityappsettingsEditText2); + metPhoneMergePrefix.setText(mAppConfigUtil.mAppConfigBean.getCountryCode()); + + mswMergePrefixPhone = findViewById(R.id.activityappsettingsSwitch1); + mswMergePrefixPhone.setChecked(mAppConfigUtil.mAppConfigBean.isMergeCountryCodePrefix()); + + mswSMSRecycleProtectMode = findViewById(R.id.activityappsettingsSwitch3); + mswSMSRecycleProtectMode.setChecked(mAppConfigUtil.mAppConfigBean.isSMSRecycleProtectMode()); + + //metProtectModerRefuseChars = findViewById(R.id.activityappsettingsEditText3); + //metProtectModerRefuseChars.setText(mAppConfigUtil.mAppConfigBean.getProtectModerRefuseChars()); + mszProtectModerRefuseChars = mAppConfigUtil.mAppConfigBean.getProtectModerRefuseChars(); + + metProtectModerReplaceChars = findViewById(R.id.activityappsettingsEditText4); + metProtectModerReplaceChars.setText(mAppConfigUtil.mAppConfigBean.getProtectModerReplaceChars()); + + mAOHPCTCSeekBar = findViewById(R.id.activityappsettingsAOHPCTCSeekBar1); + mAOHPCTCSeekBar.setThumb(getDrawable(R.drawable.cursor_pointer)); + mAOHPCTCSeekBar.setThumbOffset(0); + mAOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener(){ + + @Override + public void onOHPCommit() { + mAppConfigUtil.reLoadConfig(); + mAppConfigUtil.mAppConfigBean.setIsSMSRecycleProtectMode(mswSMSRecycleProtectMode.isChecked()); + //mAppConfigUtil.mAppConfigBean.setProtectModerRefuseChars(metProtectModerRefuseChars.getText().toString()); + mAppConfigUtil.mAppConfigBean.setProtectModerRefuseChars(mszProtectModerRefuseChars); + mAppConfigUtil.mAppConfigBean.setProtectModerReplaceChars(metProtectModerReplaceChars.getText().toString()); + mAppConfigUtil.mAppConfigBean.setCountryCode(metPhoneMergePrefix.getText().toString()); + mAppConfigUtil.mAppConfigBean.setIsMergeCountryCodePrefix(mswMergePrefixPhone.isChecked()); + int nTtsPlayDelayTimes = 1000 * Integer.parseInt(metTTSPlayDelayTimes.getText().toString()); + mAppConfigUtil.mAppConfigBean.setTtsPlayDelayTimes(nTtsPlayDelayTimes); + mAppConfigUtil.saveConfig(); + Toast.makeText(getApplication(), "App config data is saved.", Toast.LENGTH_SHORT).show(); + //LogUtils.d(TAG, "TTS Play Delay Times is setting to : " + Integer.toString(mAppConfigData.getTtsPlayDelayTimes()));Toast.makeText(getApplication(), "onOHPCommit", Toast.LENGTH_SHORT).show(); + } + }); + }; + + public void onOpenSystemDefaultAppSettings(View view) { + Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS); + startActivity(intent); + } + + public void onCheckAndGetAppPermission(View view) { + //LogUtils.d(TAG, "onCheckAndGetAppPermission"); + if (PermissionUtil.checkAndGetAppPermission(this)) { + Toast.makeText(getApplication(), "应用已获得所需权限。", Toast.LENGTH_SHORT).show(); + } + } + + public void onAddTTSSupport(View view) { + try { + // 1. 创建Intent,Action为“打开网页” + Intent intent = new Intent(Intent.ACTION_VIEW); + // 2. 设置要跳转的URL + intent.setData(Uri.parse(XUNFEI_YUJI_DOWNLOAD_URL)); + // 3. 确保Intent可被解析(避免无浏览器时崩溃) + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); // 跳转至浏览器打开下载页 + } else { + // 无浏览器时的提示 + Toast.makeText(this, "未找到浏览器应用,请安装后重试", Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(this, "无法打开下载页面,请稍后再试", Toast.LENGTH_SHORT).show(); + } + } + + public void onCharsetRefuseEditDialog(View view) { + CharsetRefuseEditDialog dlg = new CharsetRefuseEditDialog(this, new CharsetRefuseEditDialog.OnTextConfirmListener(){ + @Override + public void onTextConfirmed(String editText) { + //ToastUtils.show(editText); + mszProtectModerRefuseChars = editText; + } + }, mszProtectModerRefuseChars); + dlg.show(); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/ComposeSMSActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/ComposeSMSActivity.java new file mode 100644 index 0000000..ea4acfe --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/ComposeSMSActivity.java @@ -0,0 +1,375 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/08/30 14:32 + * @Describe 联系人查询与短信发送窗口 + */ +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.SimpleAdapter; +import android.widget.TextView; +import android.widget.Toolbar; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.beans.PhoneBean; +import cc.winboll.studio.mymessagemanager.utils.PhoneUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import android.app.Activity; + +public class ComposeSMSActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static String TAG = "ComposeSMSActivity"; + public static String EXTRA_SMSBODY = "sms_body"; + private static final String MAP_NAME = "NAME"; + private static final String MAP_PHONE = "PHONE"; + + private String mszSMSBody; + private String mszScheme; + private String mszPhoneTo; + private TextView mtvTOName; + private EditText metTONameSearch; + private EditText metTO; + private EditText metSMSBody; + private SimpleAdapter mSimpleAdapter; + private List> mAdapterData = new ArrayList>(); + private ListView mlvContracts; + private List mListPhoneBeanContracts; + private Toolbar mToolbar; + private AOHPCTCSeekBar mAOHPCTCSeekBar; + private RelativeLayout mrlContracts; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + LogUtils.d(TAG, "onCreate"); + setContentView(R.layout.activity_composesms); + + // 初始化Intent数据(增加空判断,避免NullPointerException) + Intent intent = getIntent(); + if (intent != null) { + mszSMSBody = intent.getStringExtra(EXTRA_SMSBODY); + if (intent.getData() != null) { + mszScheme = intent.getData().getScheme(); + mszPhoneTo = intent.getData().getSchemeSpecificPart(); + } + } + + // 校验启动方式,非smsto则退出 + if (mszScheme == null || !"smsto".equals(mszScheme)) { + ToastUtils.show("不支持的启动方式"); + finish(); + return; + } + + initView(); + initAdapter(null); // 初始加载所有联系人 + setListViewPrePositionByPhone(); + } + + private void initView() { + // 初始化标题栏 + mToolbar = (Toolbar) findViewById(R.id.activitycomposesmsASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.activity_name_composesms)); + setActionBar(mToolbar); + + // 初始化联系人姓名显示和搜索栏 + mtvTOName = (TextView) findViewById(R.id.activitycomposesmsTextView2); + mrlContracts = (RelativeLayout) findViewById(R.id.activitycomposesmsRelativeLayout1); + metTONameSearch = (EditText) findViewById(R.id.activitycomposesmsEditText2); + + // 姓名搜索框文本变化监听 + metTONameSearch.addTextChangedListener(new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + metTO.setText(""); // 清空号码输入框,避免冲突 + String input = s == null ? "" : s.toString().trim(); + if (input.isEmpty()) { + initAdapter(null); // 空搜索时显示所有联系人 + } else { + setListViewPrePositionByName(); // 按姓名搜索 + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // 无操作 + } + + @Override + public void afterTextChanged(Editable s) { + // 无操作 + } + }); + + // 初始化联系人列表(关键:设置单选模式,确保选中状态生效) + mlvContracts = (ListView) findViewById(R.id.activitycomposesmsListView1); + mlvContracts.setChoiceMode(ListView.CHOICE_MODE_SINGLE); // 开启单选,与布局中一致 + + // 初始化号码输入框(核心:优化文本变化监听逻辑) + metTO = (EditText) findViewById(R.id.activitycomposesmsEditText1); + if (mszPhoneTo != null) { + metTO.setText(mszPhoneTo); + } + metTO.addTextChangedListener(new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + mtvTOName.setText(""); // 清空姓名显示 + String inputPhone = s == null ? "" : s.toString().trim(); + + if (inputPhone.isEmpty()) { + // 输入为空时,显示所有联系人 + initAdapter(null); + } else { + // 输入非空时,按号码搜索并更新列表(无结果则清空) + filterListByPhone(inputPhone); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // 无操作 + } + + @Override + public void afterTextChanged(Editable s) { + // 无操作 + } + }); + + // 初始化发送控件 + mAOHPCTCSeekBar = (AOHPCTCSeekBar) findViewById(R.id.viewsmssendpart1AOHPCTCSeekBar1); + Drawable thumbDrawable = getResources().getDrawable(R.drawable.ic_message); // Java 7兼容写法 + mAOHPCTCSeekBar.setThumb(thumbDrawable); + mAOHPCTCSeekBar.setThumbOffset(20); + mAOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() { + @Override + public void onOHPCommit() { + sendSMS(); + } + }); + + // 初始化短信内容输入框 + TextView tvAOHPCTCSeekBarMSG = (TextView) findViewById(R.id.viewsmssendpart1TextView1); + tvAOHPCTCSeekBarMSG.setText(R.string.msg_100sendmsg); + metSMSBody = (EditText) findViewById(R.id.viewsmssendpart1EditText1); + if (mszSMSBody != null) { + metSMSBody.setText(mszSMSBody); + } + } + + // 核心优化:根据输入号码筛选列表(无结果则显示空列表,优化选中逻辑) + private void filterListByPhone(String inputPhone) { + PhoneUtil phoneUtil = new PhoneUtil(this); + List allContacts = phoneUtil.getPhoneList(); + List matchedContacts = new ArrayList(); + + // 遍历所有联系人,匹配包含输入号码的联系人 + for (PhoneBean contact : allContacts) { + if (contact.getTelPhone().contains(inputPhone) + || phoneUtil.isTheSamePhoneNumber(contact.getTelPhone(), inputPhone)) { + matchedContacts.add(contact); + } + } + + LogUtils.d(TAG, "号码搜索:输入'" + inputPhone + "', 匹配" + matchedContacts.size() + "个结果"); + + // 用筛选结果更新列表(无结果则传入空列表) + initAdapter(matchedContacts.isEmpty() ? new ArrayList() : matchedContacts); + + // 定位并选中匹配项(如果有) + if (!matchedContacts.isEmpty()) { + boolean isFound = false; + for (int i = 0; i < matchedContacts.size(); i++) { + PhoneBean item = matchedContacts.get(i); + // 精确匹配号码(兼容区域码格式) + if (phoneUtil.isTheSamePhoneNumber(item.getTelPhone(), inputPhone)) { + mtvTOName.setText(item.getName()); + // 关键:先滚动到目标位置,再设置选中状态 + mlvContracts.setSelection(i); + // 主动设置选中(确保样式生效,兼容部分系统) + mlvContracts.setItemChecked(i, true); + LogUtils.d(TAG, String.format("%s 匹配 %s,选中位置:%d", inputPhone, item.getTelPhone(), i)); + isFound = true; + break; + } + } + // 若未精确匹配,选中第一个结果 + /*if (!isFound) { + mlvContracts.setSelection(0); + mlvContracts.setItemChecked(0, true); + mtvTOName.setText(matchedContacts.get(0).getName()); + }*/ + } else { + mtvTOName.setText(""); // 无结果时清空姓名显示 + } + } + + // 根据姓名搜索联系人 + private void setListViewPrePositionByName() { + String searchName = metTONameSearch.getText().toString().trim(); + PhoneUtil phoneUtil = new PhoneUtil(this); + List matchedContacts = phoneUtil.getPhonesByName(searchName); + initAdapter(matchedContacts); + if (!matchedContacts.isEmpty()) { + // 选中第一个结果并设置样式 + mlvContracts.setSelection(0); + mlvContracts.setItemChecked(0, true); + } + } + + // 初始定位号码对应的联系人 + private void setListViewPrePositionByPhone() { + String inputPhone = metTO.getText().toString().trim(); + if (inputPhone.isEmpty()) { + return; + } + filterListByPhone(inputPhone); // 复用筛选逻辑 + } + + // 获取号码匹配的位置(兼容旧逻辑) + private int getContractsDataPrePositionByPhone(String szPhone) { + if (mListPhoneBeanContracts == null || mListPhoneBeanContracts.isEmpty()) { + return 0; + } + for (int i = 0; i < mListPhoneBeanContracts.size(); i++) { + PhoneBean bean = mListPhoneBeanContracts.get(i); + if (bean.getTelPhone().compareTo(szPhone) >= 0) { + return i; + } + } + return 0; + } + + // 获取姓名匹配的位置(兼容旧逻辑) + private int getContractsDataPrePositionByName(String szName) { + if (mListPhoneBeanContracts == null || mListPhoneBeanContracts.isEmpty()) { + return 0; + } + for (int i = 0; i < mListPhoneBeanContracts.size(); i++) { + if (mListPhoneBeanContracts.get(i).getName().startsWith(szName)) { + return i; + } + } + return 0; + } + + // 初始化或更新列表适配器 + private void initAdapter(List initData) { + mAdapterData.clear(); // 清空旧数据 + final PhoneUtil phoneUtil = new PhoneUtil(this); + + // 确定数据源:传入的筛选数据或所有联系人 + if (initData != null) { + mListPhoneBeanContracts = initData; + } else { + mListPhoneBeanContracts = phoneUtil.getPhoneList(); + } + + // 转换数据为SimpleAdapter所需格式 + if (mListPhoneBeanContracts != null) { + for (PhoneBean bean : mListPhoneBeanContracts) { + Map map = new HashMap(); + map.put(MAP_NAME, bean.getName()); + map.put(MAP_PHONE, bean.getTelPhone()); + mAdapterData.add(map); + } + } + + // 初始化或更新适配器 + if (mSimpleAdapter == null) { + mSimpleAdapter = new SimpleAdapter( + ComposeSMSActivity.this, + mAdapterData, + R.layout.listview_contracts, + new String[]{MAP_NAME, MAP_PHONE}, + new int[]{R.id.listviewcontractsTextView1, R.id.listviewcontractsTextView2} + ); + mSimpleAdapter.setDropDownViewResource(R.layout.listview_contracts); + mlvContracts.setAdapter(mSimpleAdapter); + + // 列表项点击事件:点击时主动设置选中状态,确保样式突显 + mlvContracts.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (position < mAdapterData.size()) { + // 1. 主动设置当前项为选中状态 + mlvContracts.setItemChecked(position, true); + // 2. 更新号码输入框和姓名显示 + String phone = mAdapterData.get(position).get(MAP_PHONE).toString(); + metTO.setText(phone); + mtvTOName.setText(phoneUtil.getNameByPhone(phone)); + // 3. 滚动到点击位置(确保可见) + mlvContracts.setSelection(position); + } + } + }); + + // 列表项选中状态变化监听(可选,增强选中反馈) + mlvContracts.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + // 选中时可添加额外反馈(如改变文本颜色,可选) + if (view != null) { + TextView tvName = (TextView) view.findViewById(R.id.listviewcontractsTextView1); + TextView tvPhone = (TextView) view.findViewById(R.id.listviewcontractsTextView2); + if (tvName != null) tvName.setTextColor(getResources().getColor(R.color.white)); + if (tvPhone != null) tvPhone.setTextColor(getResources().getColor(R.color.white)); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // 未选中时无操作 + } + }); + } else { + // 数据更新时,先取消所有旧选中状态,再通知适配器刷新 + mlvContracts.clearChoices(); + mSimpleAdapter.notifyDataSetChanged(); + } + } + + // 发送短信逻辑 + private void sendSMS() { + String phoneTo = metTO.getText().toString().trim(); + if (phoneTo.isEmpty()) { + ToastUtils.show("没有设置接收号码。"); + return; + } + String smsBody = metSMSBody.getText().toString().trim(); + if (smsBody.isEmpty()) { + ToastUtils.show("没有消息内容可发送。"); + return; + } + if (SMSUtil.sendMessageByInterface2(ComposeSMSActivity.this, phoneTo, smsBody)) { + finish(); + } + } +} + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/MainActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/MainActivity.java new file mode 100644 index 0000000..49df3e0 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/MainActivity.java @@ -0,0 +1,340 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +import android.Manifest; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ScrollView; +import androidx.appcompat.widget.Toolbar; +import cc.winboll.studio.libaes.utils.AESThemeUtil; +import cc.winboll.studio.libaes.utils.DevelopUtils; +import cc.winboll.studio.libaes.utils.WinBoLLActivityManager; +import cc.winboll.studio.libaes.views.ADsBannerView; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.App; +import cc.winboll.studio.mymessagemanager.BuildConfig; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.MainActivity; +import cc.winboll.studio.mymessagemanager.adapters.PhoneArrayAdapter; +import cc.winboll.studio.mymessagemanager.services.MainService; +import cc.winboll.studio.mymessagemanager.unittest.UnitTestActivity; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; +import cc.winboll.studio.mymessagemanager.utils.AppGoToSettingsUtil; +import cc.winboll.studio.mymessagemanager.utils.PermissionUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import cc.winboll.studio.mymessagemanager.utils.ViewUtil; +import cc.winboll.studio.mymessagemanager.views.ConfirmSwitchView; +import cc.winboll.studio.mymessagemanager.views.PhoneListViewForScrollView; +import com.baoyz.widget.PullRefreshLayout; +import java.util.ArrayList; + +public class MainActivity extends WinBoLLActivity { + + public final static String TAG = "MainActivity"; + + public static final int ACTIVITY_RESULT_APP_SETTINGS = -1; + + public final static int MSG_RELOADSMS = 0; + + public static final int PERMISSION_SETTING_FOR_RESULT = 0; + public static final int MY_PERMISSIONS_REQUEST = 0; + + static MainActivity _mMainActivity; + ADsBannerView mADsBannerView; + + //LogView mLogView; + AppConfigUtil mAppConfigUtil; + ConfirmSwitchView msvEnableService; + ConfirmSwitchView msvOnlyReceiveContacts; + ConfirmSwitchView msvEnableTTS; + ConfirmSwitchView msvEnableTTSRuleMode; + PhoneListViewForScrollView mListViewPhone; + Toolbar mToolbar; + PhoneArrayAdapter mPhoneArrayAdapter; + AppGoToSettingsUtil mAppGoToSettingsUtil; + String[] mPermissionList = {Manifest.permission.READ_CONTACTS, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.READ_SMS}; + ArrayList listPerms; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + _mMainActivity = MainActivity.this; + + // 米盟广告栏 + mADsBannerView = findViewById(R.id.adsbanner); + + mAppConfigUtil = AppConfigUtil.getInstance(this); + initView(); + + // 调用调试检查函数 + onOnceAndroidStory(null); + } + + // + // 这是一个测试函数, + // 用于调试读取 string.xml string-array使用。 + // + public void onOnceAndroidStory(View view) { + if (BuildConfig.DEBUG) { + // 获取strings.xml文件中的tab_names数组 + String[] tab_names = getResources().getStringArray(R.array.strings_OnceAndroidStory); + // 这里R.array.tab_names是你在XML文件中定义的数组资源ID + // 例如,在strings.xml中可能这样定义: + /*/ + + + Tab 1 + Tab 2 + Tab 3 + + + */ + // 现在你可以遍历这个数组来访问每个元素 + for (int i = 0; i < tab_names.length; i++) { + // 创建Random实例并传入任意非负种子(这里是1) + java.util.Random r = new java.util.Random(1); + // 调用nextInt(6),范围是0到5(包括0和5),加1后得到1到5 + int randomNum = r.nextInt(6) + 1; + System.out.println("Random number between 1 and 5: " + randomNum); + LogUtils.d("OnceAndroidStory", tab_names[i]); + } + } + } + + void scrollScrollView() { + ScrollView sv = findViewById(R.id.activitymainScrollView1); + ViewUtil.scrollScrollView(sv); + } + + void genTestData() { + for (int i = 0; i < 2; i++) { + SMSUtil.saveReceiveSms(this, "13172887736", "调试阶段生成的短信" + Integer.toString(i), "0", -1, "inbox"); + } + } + + // + // 初始化视图控件 + // + void initView() { + // 设置调试日志 +// mLogView = findViewById(R.id.logview); +// mLogView.start(); + + // 设置消息处理函数 + setOnActivityMessageReceived(mIOnActivityMessageReceived); + + // 设置标题栏 + mToolbar = findViewById(R.id.activitymainASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.activity_name_main)); + setSupportActionBar(mToolbar); + + boolean isEnableService = mAppConfigUtil.mAppConfigBean.isEnableService(); + msvEnableService = findViewById(R.id.activitymainSwitchView1); + msvEnableService.setChecked(isEnableService); + msvEnableService.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isEnable = ((ConfirmSwitchView)v).isChecked(); + mAppConfigUtil.reLoadConfig(); + mAppConfigUtil.mAppConfigBean.setIsEnableService(isEnable); + mAppConfigUtil.saveConfig(); + initService(isEnable); + } + }); + + boolean isOnlyReceiveContacts = mAppConfigUtil.mAppConfigBean.isEnableOnlyReceiveContacts(); + msvOnlyReceiveContacts = findViewById(R.id.activitymainSwitchView2); + msvOnlyReceiveContacts.setChecked(isOnlyReceiveContacts); + msvOnlyReceiveContacts.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isEnable = ((ConfirmSwitchView)v).isChecked(); + mAppConfigUtil.reLoadConfig(); + mAppConfigUtil.mAppConfigBean.setIsEnableOnlyReceiveContacts(isEnable); + mAppConfigUtil.saveConfig(); + } + }); + + boolean isEnableTTS = mAppConfigUtil.mAppConfigBean.isEnableTTS(); + msvEnableTTS = findViewById(R.id.activitymainSwitchView3); + msvEnableTTS.setChecked(isEnableTTS); + msvEnableTTS.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isEnable = ((ConfirmSwitchView)v).isChecked(); + mAppConfigUtil.reLoadConfig(); + mAppConfigUtil.mAppConfigBean.setIsEnableTTS(isEnable); + mAppConfigUtil.saveConfig(); + } + }); + + boolean isEnableTTSRuleMode = mAppConfigUtil.mAppConfigBean.isEnableTTSRuleMode(); + msvEnableTTSRuleMode = findViewById(R.id.activitymainSwitchView4); + msvEnableTTSRuleMode.setChecked(isEnableTTSRuleMode); + msvEnableTTSRuleMode.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isEnable = ((ConfirmSwitchView)v).isChecked(); + mAppConfigUtil.reLoadConfig(); + mAppConfigUtil.mAppConfigBean.setIsEnableTTSRuleMode(isEnable); + mAppConfigUtil.saveConfig(); + } + }); + + initService(isEnableService); + + // 短信发送窗口按钮 + Button btnSendSMS = findViewById(R.id.activitymainButton1); + btnSendSMS.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + Uri uri = Uri.parse("smsto:"); + Intent it = new Intent(Intent.ACTION_SENDTO, uri); + it.putExtra("sms_body", ""); + startActivity(it); + } + }); + + mListViewPhone = (PhoneListViewForScrollView) findViewById(R.id.activitymainListView1); + //准备数据 + mPhoneArrayAdapter = new PhoneArrayAdapter(MainActivity.this); + + final PullRefreshLayout layout = (PullRefreshLayout) findViewById(R.id.activitymainPullRefreshLayout1); + //将适配器加载到控件中 + mListViewPhone.setAdapter(mPhoneArrayAdapter); + + // listen refresh event + layout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + // start refresh + reloadSMS(); + layout.setRefreshing(false); + } + }); + } + + void initService(boolean isEnableService) { + if (isEnableService) { + Intent service = new Intent(this, MainService.class); + startService(service); + } else { + Intent service = new Intent(this, MainService.class); + stopService(service); + } + } + + // + // 定义应用内消息处理函数 + // + IOnActivityMessageReceived mIOnActivityMessageReceived = new IOnActivityMessageReceived(){ + + @Override + public void onActivityMessageReceived(Message msg) { + switch (msg.arg1) { + case MSG_RELOADSMS : { + LogUtils.d(TAG, "MSG_RELOADSMS"); + if (PermissionUtil.checkAppPermission(MainActivity.this)) { + mPhoneArrayAdapter.loadData(); + mPhoneArrayAdapter.notifyDataSetChanged(); + } else { + LogUtils.i(TAG, "遇到应用权限问题,请打开应用设置检查一下应用权限。"); + } + break; + } + } + } + }; + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + return super.onCreatePanelMenu(featureId, menu); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mADsBannerView != null) { + mADsBannerView.releaseAdResources(); + } + } + + @Override + protected void onResume() { + super.onResume(); + reloadSMS(); + if (mADsBannerView != null) { + mADsBannerView.resumeADs(MainActivity.this); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar_main_first, menu); + // 主题菜单 + AESThemeUtil.inflateMenu(this, menu); + // 调试工具菜单 + if (App.isDebugging()) { + DevelopUtils.inflateMenu(this, menu); + getMenuInflater().inflate(R.menu.toolbar_main_debug, menu); + } + getMenuInflater().inflate(R.menu.toolbar_main_last, menu); + return true; + } + + public static void reloadSMS() { + if (_mMainActivity != null) { + Message msg = new Message(); + msg.arg1 = MSG_RELOADSMS; + _mMainActivity.sendActivityMessage(msg); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int nItemId = item.getItemId(); + int menuItemId = item.getItemId(); + if (AESThemeUtil.onAppThemeItemSelected(this, item)) { + recreate(); + } if (DevelopUtils.onDevelopItemSelected(this, item)) { + LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId())); + } else if (nItemId == R.id.app_ttsrule) { + Intent i = new Intent(MainActivity.this, TTSPlayRuleActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } else if (nItemId == R.id.app_smsrule) { + Intent i = new Intent(MainActivity.this, SMSReceiveRuleActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } else if (nItemId == R.id.app_appsettings) { + Intent i = new Intent(MainActivity.this, AppSettingsActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } else if (nItemId == R.id.app_unittest) { + Intent i = new Intent(MainActivity.this, UnitTestActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } else if (nItemId == R.id.app_about) { + Intent i = new Intent(MainActivity.this, AboutActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } else if (nItemId == R.id.app_smsrecycle) { + Intent i = new Intent(MainActivity.this, SMSRecycleActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } + + return super.onOptionsItemSelected(item); + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java new file mode 100644 index 0000000..6bc9542 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSActivity.java @@ -0,0 +1,281 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.AbsListView; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.Toolbar; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.adapters.SMSArrayAdapter; +import cc.winboll.studio.mymessagemanager.utils.AddressUtils; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import cc.winboll.studio.mymessagemanager.utils.ViewUtil; +import cc.winboll.studio.mymessagemanager.views.BottomPositionFixedScrollView; +import cc.winboll.studio.mymessagemanager.views.SMSListViewForScrollView; +import android.app.Activity; + +public class SMSActivity extends WinBoLLActivity implements IWinBoLLActivity { + public static String TAG = "SMSActivity"; + public static final String ACTION_NOTIFY_SMS_CHANGED = "cc.winboll.studio.mymessagemanager.activitys.SMSActivity.ACTION_NOTIFY_SMS_CHANGED"; + public static final String EXTRA_PHONE = "Phone"; + final static int MSG_SET_FOCUS = 0; + + SMSListViewForScrollView mlvSMS; + Toolbar mToolbar; + String mszPhoneTo; + SMSArrayAdapter mSMSArrayAdapter; + BottomPositionFixedScrollView mScrollView1; + EditText metSMSBody; + SMSActivityBroadcastReceiver mSMSActivityBroadcastReceiver; + Handler mSetFocusHandler; + private boolean isImeVisible = false; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sms); + + initView(); + scrollScrollView(); + setupImeStatusListener(); + + // 新增:监听窗口加载完成,触发mScrollView1滚动到底部 + setupScrollToBottomAfterWindowLoaded(); + } + + // 新增:窗口加载完成后让mScrollView1滚动到底部 + private void setupScrollToBottomAfterWindowLoaded() { + final View rootView = findViewById(android.R.id.content); + // 监听根布局绘制完成(窗口加载完成的标志) + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // 滚动到底部 + mScrollView1.post(new Runnable() { + @Override + public void run() { + mScrollView1.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + + // 移除监听,避免重复触发 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + } + }); + } + + private void setupImeStatusListener() { + final View rootView = findViewById(android.R.id.content); + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + int rootViewHeight = rootView.getHeight(); + int screenHeight = getResources().getDisplayMetrics().heightPixels; + int imeThreshold = dp2px(200); + + boolean currentImeVisible = (screenHeight - rootViewHeight) > imeThreshold; + + if (currentImeVisible != isImeVisible) { + isImeVisible = currentImeVisible; + setupScrollView1Height(); + if (!isImeVisible) { + metSMSBody.clearFocus(); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + setupImeStatusListener(); + } + }); + } + + private int dp2px(int dp) { + return (int) (dp * getResources().getDisplayMetrics().density + 0.5f); + } + + /*static class MyHandler extends Handler { + WeakReference mActivity; + MyHandler(SMSActivity activity) { + mActivity = new WeakReference(activity); + } + public void handleMessage(Message msg) { + SMSActivity theActivity = mActivity.get(); + switch (msg.what) { + case MSG_SET_FOCUS: + theActivity.metSMSBody.setFocusable(true); + theActivity.metSMSBody.requestFocus(); + theActivity.setupScrollView1Height(); + break; + default: + break; + } + super.handleMessage(msg); + } + }*/ + + @Override + protected void onDestroy() { + super.onDestroy(); + LocalBroadcastManager.getInstance(this).unregisterReceiver(mSMSActivityBroadcastReceiver); + } + + void initView() { + mszPhoneTo = getIntent().getStringExtra(EXTRA_PHONE); + if (mszPhoneTo == null || mszPhoneTo.trim().equals("")) { + finish(); + } + + mToolbar = (Toolbar) findViewById(R.id.activitysmsASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.activity_name_smsinphone) + " < Phone : " + AddressUtils.getFormattedAddress(mszPhoneTo) + " >"); + setActionBar(mToolbar); + + mScrollView1 = (BottomPositionFixedScrollView) findViewById(R.id.activitysmsScrollView1); + + metSMSBody = (EditText) findViewById(R.id.viewsmssendpart1EditText1); + metSMSBody.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setupScrollView1Height(); + } + }); + metSMSBody.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + setupScrollView1Height(); + } + }); + + final AOHPCTCSeekBar aOHPCTCSeekBar = (AOHPCTCSeekBar) findViewById(R.id.viewsmssendpart1AOHPCTCSeekBar1); + aOHPCTCSeekBar.setThumb(getDrawable(R.drawable.ic_message)); + aOHPCTCSeekBar.setThumbOffset(20); + aOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() { + @Override + public void onOHPCommit() { + sendSMS(); + } + }); + + TextView tvAOHPCTCSeekBarMSG = (TextView) findViewById(R.id.viewsmssendpart1TextView1); + tvAOHPCTCSeekBarMSG.setText(R.string.msg_100sendmsg); + + mlvSMS = (SMSListViewForScrollView) findViewById(R.id.activitysmsSMSListViewForScrollView1); + mSMSArrayAdapter = new SMSArrayAdapter(SMSActivity.this, mszPhoneTo); + mlvSMS.setAdapter(mSMSArrayAdapter); + + mlvSMS.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) {} + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) { + mSMSArrayAdapter.cancelMessageNotification(); + } + } + }); + + mSMSActivityBroadcastReceiver = new SMSActivityBroadcastReceiver(); + IntentFilter intentFilter = new IntentFilter(ACTION_NOTIFY_SMS_CHANGED); + LocalBroadcastManager.getInstance(this).registerReceiver(mSMSActivityBroadcastReceiver, intentFilter); + } + + private void setupScrollView1Height() { + mScrollView1.postDelayed(new Runnable() { + @Override + public void run() { + final ScrollView scrollView2 = (ScrollView) findViewById(R.id.activitysmsScrollView2); + final BottomPositionFixedScrollView scrollView1 = (BottomPositionFixedScrollView) findViewById(R.id.activitysmsScrollView1); + final View includeView = findViewById(R.id.activitysmsinclude1); + + scrollView2.post(new Runnable() { + @Override + public void run() { + int scrollView2Height = scrollView2.getHeight(); + int includeHeight = includeView.getHeight(); + int targetHeight = Math.max(scrollView2Height - includeHeight, 0); + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) scrollView1.getLayoutParams(); + params.height = targetHeight; + scrollView1.setLayoutParams(params); + } + }); + } + }, 100); + } + + public void updateSMSView() { + mSMSArrayAdapter.reLoadSMSList(SMSActivity.this, mszPhoneTo); + mSMSArrayAdapter.notifyDataSetChanged(); + } + + void scrollScrollView() { + ViewUtil.scrollScrollView(mScrollView1); + } + + void sendSMS() { + String szSMSBody = metSMSBody.getText().toString(); + if (szSMSBody.equals("")) { + Toast.makeText(getApplication(), "没有消息内容可发送。", Toast.LENGTH_SHORT).show(); + return; + } + + if (SMSUtil.sendMessageByInterface2(this, mszPhoneTo, szSMSBody)) { + metSMSBody.setText(""); + metSMSBody.clearFocus(); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + updateSMSView(); + ViewUtil.scrollScrollView(mScrollView1); + } + }, 1000); + } + } + + class SMSActivityBroadcastReceiver extends BroadcastReceiver { + public SMSActivityBroadcastReceiver() {} + + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_NOTIFY_SMS_CHANGED.equals(intent.getAction())) { + updateSMSView(); + ViewUtil.scrollScrollView(mScrollView1); + } else { + throw new IllegalStateException("Unexpected value: " + intent.getAction()); + } + } + } +} + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSReceiveRuleActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSReceiveRuleActivity.java new file mode 100644 index 0000000..e1a2b25 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSReceiveRuleActivity.java @@ -0,0 +1,244 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 12:50:52 + * @Describe 短信匹配过滤规则设置窗口 + */ +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.Toast; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.mymessagemanager.App; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.SMSReceiveRuleActivity; +import cc.winboll.studio.mymessagemanager.adapters.SMSAcceptRuleArrayAdapter; +import cc.winboll.studio.mymessagemanager.beans.SMSAcceptRuleBean; +import cc.winboll.studio.mymessagemanager.utils.FileUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSReceiveRuleUtil; +import com.baoyz.widget.PullRefreshLayout; +import android.app.Activity; + +public class SMSReceiveRuleActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "SMSReceiveRuleActivity"; + + Context mContext; + RecyclerView mRecyclerView; + Toolbar mToolbar; + RadioButton mrbAccept; + RadioButton mrbRefuse; + CheckBox mcbEnable; + SMSAcceptRuleBean mSMSAcceptRuleBeanAdd; + SMSAcceptRuleArrayAdapter mSMSAcceptRuleArrayAdapter; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_smsacceptrulesetting); + mContext = SMSReceiveRuleActivity.this; + initSMSAcceptRuleBeanAdd(); + // 初始化视图 + initView(); + } + + // + // 初始化视图 + // + public void initView() { + // 初始化标题栏 + mToolbar = findViewById(R.id.activitysmsacceptrulesettingASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.text_smsrule)); + setSupportActionBar(mToolbar); + + mrbAccept = findViewById(R.id.activitysmsacceptrulesettingRadioButton1); + mrbRefuse = findViewById(R.id.activitysmsacceptrulesettingRadioButton2); + mcbEnable = findViewById(R.id.activitysmsacceptrulesettingCheckBox1); + if (mSMSAcceptRuleBeanAdd.getRuleType() == SMSAcceptRuleBean.RuleType.ACCEPT) { + mrbAccept.setChecked(true); + mrbRefuse.setChecked(false); + } + if (mSMSAcceptRuleBeanAdd.getRuleType() == SMSAcceptRuleBean.RuleType.REFUSE) { + mrbAccept.setChecked(false); + mrbRefuse.setChecked(true); + } + mcbEnable.setChecked(mSMSAcceptRuleBeanAdd.isEnable()); + + Button btnAddSMSAcceptRule = findViewById(R.id.activitysmsacceptrulesettingButton1); + btnAddSMSAcceptRule.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + EditText et = findViewById(R.id.activitysmsacceptrulesettingEditText1); + String szRule = et.getText().toString().trim(); + if (szRule.equals("")) { + Toast.makeText(getApplication(), "空字符串规则不能添加", Toast.LENGTH_SHORT).show(); + } else { + mSMSAcceptRuleBeanAdd.setRuleData(et.getText().toString()); + mSMSAcceptRuleBeanAdd.setIsEnable(mcbEnable.isChecked()); + mSMSAcceptRuleBeanAdd.setRuleType(mrbRefuse.isChecked() ?SMSAcceptRuleBean.RuleType.REFUSE: SMSAcceptRuleBean.RuleType.ACCEPT); + mSMSAcceptRuleArrayAdapter.addSMSAcceptRule(mSMSAcceptRuleBeanAdd); + initSMSAcceptRuleBeanAdd(); + et.setText(""); + Toast.makeText(getApplication(), "已添加规则 : " + szRule, Toast.LENGTH_SHORT).show(); + } + } + }); + + // 绑定控件 + mRecyclerView = findViewById(R.id.activitysmsacceptrulesettingRecyclerView1); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); + mRecyclerView.setLayoutManager(layoutManager); + mSMSAcceptRuleArrayAdapter = new SMSAcceptRuleArrayAdapter(this); + mRecyclerView.setAdapter(mSMSAcceptRuleArrayAdapter); + + final PullRefreshLayout pullRefreshLayout = findViewById(R.id.activitysmsacceptrulesettingPullRefreshLayout1); + pullRefreshLayout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener(){ + @Override + public void onRefresh() { + pullRefreshLayout.setRefreshing(false); + mSMSAcceptRuleArrayAdapter.loadConfigData(); + mSMSAcceptRuleArrayAdapter.notifyDataSetChanged(); + } + }); + } + + void initSMSAcceptRuleBeanAdd() { + mSMSAcceptRuleBeanAdd = new SMSAcceptRuleBean(App.USER_ID, "", true, SMSAcceptRuleBean.RuleType.REFUSE, true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //return super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.toolbar_rule, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int nItemId = item.getItemId(); + if (nItemId == R.id.item_rule_share) { + //SMSReceiveRuleUtil smsAcceptRuleConfig = SMSReceiveRuleUtil.getInstance(this, false); + SMSAcceptRuleBean beanTemp = new SMSAcceptRuleBean(); + String szConfigPath = beanTemp.getBeanListJsonFilePath(mContext); + FileUtil.shareJSONFile(SMSReceiveRuleActivity.this, szConfigPath); + } else if (nItemId == R.id.item_rule_reset) { + showResetConfigDialog(); + } else if (nItemId == R.id.item_rule_clean) { + showCleanConfigDialog(); + } + return true; + } + + // + // 短信匹配过滤规则数据重置对话框 + // + void showResetConfigDialog() { + Dialog alertDialog = new AlertDialog.Builder(this). + setTitle("确定重置?"). + setMessage("您确定重置短信接收规则为默认设置吗?"). + setIcon(R.drawable.ic_launcher). + setPositiveButton("确定", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO Auto-generated method stub + SMSReceiveRuleUtil smsAcceptRuleConfig = SMSReceiveRuleUtil.getInstance(getApplicationContext(), false); + smsAcceptRuleConfig.resetConfig(); + mSMSAcceptRuleArrayAdapter.notifyDataSetChanged(); + Toast.makeText(getApplication(), "Rules Reset", Toast.LENGTH_SHORT).show(); + } + }). + setNegativeButton("取消", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO Auto-generated method stub + + } + }). + /*setNeutralButton("查看详情", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO Auto-generated method stub + } + }).*/ + create(); + alertDialog.show(); + } + + // + // 短信匹配过滤规则数据清空对话框 + // + void showCleanConfigDialog() { + Dialog alertDialog = new AlertDialog.Builder(this). + setTitle("确定清理"). + setMessage("您确定清理所有短信接收规则吗?"). + setIcon(R.drawable.ic_launcher). + setPositiveButton("确定", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO Auto-generated method stub + SMSReceiveRuleUtil smsAcceptRuleConfig = SMSReceiveRuleUtil.getInstance(getApplicationContext(), false); + smsAcceptRuleConfig.cleanConfig(); + mSMSAcceptRuleArrayAdapter.notifyDataSetChanged(); + Toast.makeText(getApplication(), "Rules Cleaned.", Toast.LENGTH_SHORT).show(); + } + }). + setNegativeButton("取消", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO Auto-generated method stub + + } + }). + /*setNeutralButton("查看详情", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // TODO Auto-generated method stub + } + }).*/ + create(); + alertDialog.show(); + } + + public void onAcceptRuleType(View view) { + mrbRefuse.setChecked(false); + } + + public void onRefuseRuleType(View view) { + mrbAccept.setChecked(false); + } + + @Override + protected void onResume() { + super.onResume(); + mSMSAcceptRuleArrayAdapter.loadConfigData(); + mSMSAcceptRuleArrayAdapter.notifyDataSetChanged(); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSRecycleActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSRecycleActivity.java new file mode 100644 index 0000000..c8ce545 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SMSRecycleActivity.java @@ -0,0 +1,104 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 16:56:18 + * @Describe 短信回收站 + */ +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.SMSRecycleActivity; +import cc.winboll.studio.mymessagemanager.adapters.SMSRecycleAdapter; +import cc.winboll.studio.mymessagemanager.utils.SMSRecycleUtil; +import com.baoyz.widget.PullRefreshLayout; +import java.io.File; + +public class SMSRecycleActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "SMSRecycleActivity"; + + Toolbar mToolbar; + RecyclerView mRecyclerView; + SMSRecycleAdapter mSMSRecycleAdapter; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_smsrecycle); + // 初始化标题栏 + mToolbar = findViewById(R.id.activitysmsrecycleASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.activity_name_about)); + setSupportActionBar(mToolbar); + initView(); + } + + void initView() { + // 绑定控件 + mRecyclerView = findViewById(R.id.activitysmsrecycleRecyclerView1); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); + mRecyclerView.setLayoutManager(layoutManager); + + mSMSRecycleAdapter = new SMSRecycleAdapter(this); + mRecyclerView.setAdapter(mSMSRecycleAdapter); + + final PullRefreshLayout pullRefreshLayout = findViewById(R.id.activitysmsrecyclePullRefreshLayout1); + pullRefreshLayout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + mSMSRecycleAdapter.loadSMSRecycleList(); + mSMSRecycleAdapter.notifyDataSetChanged(); + pullRefreshLayout.setRefreshing(false); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //return super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.toolbar_smsrecycle, menu); + return true; + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int nItemId = item.getItemId(); + if (nItemId == R.id.item_cleansmsrecycle) { + YesNoAlertDialog.show(this, "回收站清空确认", "是否清空回收站", mDeleteListener); + } + return true; + } + + YesNoAlertDialog.OnDialogResultListener mDeleteListener = new YesNoAlertDialog.OnDialogResultListener() { + + @Override + public void onNo() { + } + + @Override + public void onYes() { + File file = new File(SMSRecycleUtil.getSMSRecycleListDataPath(SMSRecycleActivity.this)); + file.delete(); + mSMSRecycleAdapter.loadSMSRecycleList(); + mSMSRecycleAdapter.notifyDataSetChanged(); + } + }; +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SharedJSONReceiveActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SharedJSONReceiveActivity.java new file mode 100644 index 0000000..9f215b9 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/SharedJSONReceiveActivity.java @@ -0,0 +1,154 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.Toolbar; +import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.SMSReceiveRuleActivity; +import cc.winboll.studio.mymessagemanager.activitys.SharedJSONReceiveActivity; +import cc.winboll.studio.mymessagemanager.activitys.TTSPlayRuleActivity; +import cc.winboll.studio.mymessagemanager.beans.SMSAcceptRuleBean; +import cc.winboll.studio.mymessagemanager.beans.TTSPlayRuleBean; +import cc.winboll.studio.mymessagemanager.utils.UriUtil; +import java.util.ArrayList; +import android.app.Activity; + +public class SharedJSONReceiveActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "SharedJSONReceive"; + + Toolbar mToolbar; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sharedjsonreceive); + + StringBuilder sb = new StringBuilder(); + // 接收分享数据 + Intent intent = getIntent(); + String action = intent.getAction();//action + String type = intent.getType();//类型 + //LogUtils.d(TAG, "action is " + action); + //LogUtils.d(TAG, "type is " + type); + if ((Intent.ACTION_SEND.equals(action) || Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action)) + && type != null && (("application/json".equals(type)) || ("text/x-json".equals(type)))) { + + //取出文件uri + Uri uri = intent.getData(); + if (uri == null) { + uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + } + //获取文件真实地址 + String szSrcJSON = UriUtil.getFileFromUri(getApplication(), uri); + if (TextUtils.isEmpty(szSrcJSON)) { + return; + } + + String szCheck = TTSPlayRuleBean.checkIsTheSameBeanListAndFile(szSrcJSON, TTSPlayRuleBean.class); + if (szCheck.equals("")) { + importTTSPlayRuleBean(szSrcJSON); + } else { + sb.append("\n语音规则数据检测结果\n"); + sb.append(szCheck); + } + //LogUtils.d(TAG, "szCheck is " + szCheck); + + szCheck = SMSAcceptRuleBean.checkIsTheSameBeanListAndFile(szSrcJSON, SMSAcceptRuleBean.class); + if (szCheck.equals("")) { + importSMSAcceptRuleBean(szSrcJSON); + } else { + sb.append("\n短信接收规则数据检测结果\n"); + sb.append(szCheck); + } + //LogUtils.d(TAG, "szCheck is " + szCheck); + } else { + sb.append("Not supported action."); + } + + mToolbar = findViewById(R.id.activitysharedjsonreceiveASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.activity_name_sharedjsonreceive)); + setActionBar(mToolbar); + + TextView tvMessage = findViewById(R.id.activitysharedjsonreceiveTextView1); + tvMessage.setText(sb.toString()); + } + + void importSMSAcceptRuleBean(final String szSrcJSON) { + ArrayList beanList = new ArrayList(); + boolean bCheck = SMSAcceptRuleBean.loadBeanListFromFile(szSrcJSON, beanList, SMSAcceptRuleBean.class); + if (bCheck && beanList.size() > 0) { + YesNoAlertDialog.show(SharedJSONReceiveActivity.this, + "短信接收规则共享提示", + "已收到短信接收规则" + Integer.toString(beanList.size()) + "个,\n是否导入应用?" + , (new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + ArrayList beanListShare = new ArrayList(); + SMSAcceptRuleBean.loadBeanListFromFile(szSrcJSON, beanListShare, SMSAcceptRuleBean.class); + ArrayList beanListApp = new ArrayList(); + SMSAcceptRuleBean.loadBeanList(SharedJSONReceiveActivity.this, beanListApp, SMSAcceptRuleBean.class); + beanListApp.addAll(0, beanListShare); + SMSAcceptRuleBean.saveBeanList(SharedJSONReceiveActivity.this, beanListApp, SMSAcceptRuleBean.class); + Toast.makeText(getApplication(), "已导入" + Integer.toString(beanListShare.size()) + "个数据。", Toast.LENGTH_SHORT).show(); + finish(); + Intent intent = new Intent(SharedJSONReceiveActivity.this, SMSReceiveRuleActivity.class); + startActivity(intent); + } + + @Override + public void onNo() { + finish(); + } + })); + } + } + + void importTTSPlayRuleBean(final String szSrcJSON) { + ArrayList beanList = new ArrayList(); + boolean bCheck = TTSPlayRuleBean.loadBeanListFromFile(szSrcJSON, beanList, TTSPlayRuleBean.class); + if (bCheck && beanList.size() > 0) { + YesNoAlertDialog.show(SharedJSONReceiveActivity.this, + "语音规则共享提示", + "已收到语音规则" + Integer.toString(beanList.size()) + "个,\n是否导入应用?" + , (new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + ArrayList beanListShare = new ArrayList(); + TTSPlayRuleBean.loadBeanListFromFile(szSrcJSON, beanListShare, TTSPlayRuleBean.class); + ArrayList beanListApp = new ArrayList(); + TTSPlayRuleBean.loadBeanList(SharedJSONReceiveActivity.this, beanListApp, TTSPlayRuleBean.class); + beanListApp.addAll(0, beanListShare); + TTSPlayRuleBean.saveBeanList(SharedJSONReceiveActivity.this, beanListApp, TTSPlayRuleBean.class); + Toast.makeText(getApplication(), "已导入" + Integer.toString(beanListShare.size()) + "个数据。", Toast.LENGTH_SHORT).show(); + finish(); + Intent intent = new Intent(SharedJSONReceiveActivity.this, TTSPlayRuleActivity.class); + startActivity(intent); + } + + @Override + public void onNo() { + finish(); + } + })); + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/TTSPlayRuleActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/TTSPlayRuleActivity.java new file mode 100644 index 0000000..3423b10 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/TTSPlayRuleActivity.java @@ -0,0 +1,200 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 12:50:52 + * @Describe TTS 语音播放规则规则设置窗口 + */ +import android.os.Bundle; +import android.os.Message; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.adapters.TTSRuleBeanRecyclerViewAdapter; +import cc.winboll.studio.mymessagemanager.beans.TTSPlayRuleBean; +import cc.winboll.studio.mymessagemanager.utils.FileUtil; +import cc.winboll.studio.mymessagemanager.utils.TTSPlayRuleUtil; +import android.app.Activity; + +public class TTSPlayRuleActivity extends WinBoLLActivity implements IWinBoLLActivity { + + public static final String TAG = "TTSPlayRuleActivity"; + + public static final int MSG_RELOAD = 0; + + public static final String EXTRA_TTSDEMOTEXT = "EXTRA_TTSDEMOTEXT"; + + Toolbar mToolbar; + TTSRuleBeanRecyclerViewAdapter mTTSRuleBeanRecyclerViewAdapter; + TTSPlayRuleUtil mTTSPlayRuleUtil; + TTSPlayRuleBean mTTSRuleBeanCurrent; + RecyclerView mRecyclerView; + EditText metCurrentDemoSMSText; + EditText metPatternText; + EditText metCurrentTTSRuleText; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ttsplayrule); + + mTTSPlayRuleUtil = TTSPlayRuleUtil.getInstance(TTSPlayRuleActivity.this); + + initView(); + + // 设置窗口消息处理 + setOnActivityMessageReceived(new IOnActivityMessageReceived(){ + @Override + public void onActivityMessageReceived(Message msg) { + switch (msg.what) { + case MSG_RELOAD : { + //Toast.makeText(getApplication(), "MSG_RELOAD", Toast.LENGTH_SHORT).show(); + mTTSRuleBeanRecyclerViewAdapter.reloadConfigData(); + break; + } + } + } + }); + } + + void initView() { + // 初始化标题栏 + mToolbar = findViewById(R.id.activityttsplayruleASupportToolbar1); + mToolbar.setSubtitle(getString(R.string.text_ttsrule)); + setSupportActionBar(mToolbar); + + metCurrentDemoSMSText = findViewById(R.id.activityttsplayruleEditText1); + metPatternText = findViewById(R.id.activityttsplayruleEditText2); + metCurrentTTSRuleText = findViewById(R.id.activityttsplayruleEditText3); + + Button btnTestTTSRule = findViewById(R.id.activityttsplayruleButton1); + btnTestTTSRule.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TTSPlayRuleBean ttsRuleBean = new TTSPlayRuleBean(); + ttsRuleBean.setDemoSMSText(metCurrentDemoSMSText.getText().toString()); + ttsRuleBean.setPatternText(metPatternText.getText().toString()); + ttsRuleBean.setTtsRuleText(metCurrentTTSRuleText.getText().toString()); + + String sz = mTTSPlayRuleUtil.testTTSAnalyzeModeReply(ttsRuleBean); + Toast.makeText(getApplication(), sz, Toast.LENGTH_SHORT).show(); + } + }); + + Button btnAcceptTTSRule = findViewById(R.id.activityttsplayruleButton2); + btnAcceptTTSRule.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mTTSRuleBeanCurrent != null) { + mTTSRuleBeanCurrent.setDemoSMSText(metCurrentDemoSMSText.getText().toString()); + mTTSRuleBeanCurrent.setPatternText(metPatternText.getText().toString()); + mTTSRuleBeanCurrent.setTtsRuleText(metCurrentTTSRuleText.getText().toString()); + mTTSRuleBeanRecyclerViewAdapter.saveConfigData(); + } else { + if (!metCurrentDemoSMSText.getText().toString().equals("")) { + mTTSRuleBeanCurrent = new TTSPlayRuleBean(); + mTTSRuleBeanCurrent.setDemoSMSText(metCurrentDemoSMSText.getText().toString()); + mTTSRuleBeanCurrent.setPatternText(metPatternText.getText().toString()); + mTTSRuleBeanCurrent.setTtsRuleText(metCurrentTTSRuleText.getText().toString()); + mTTSRuleBeanRecyclerViewAdapter.addNewTTSRuleBean(mTTSRuleBeanCurrent); + LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager(); + layoutManager.scrollToPositionWithOffset(0, 0); + } + } + } + }); + + // 绑定控件 + mRecyclerView = findViewById(R.id.activityttsplayruleRecyclerView1); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); + mRecyclerView.setLayoutManager(layoutManager); + + mTTSRuleBeanRecyclerViewAdapter = new TTSRuleBeanRecyclerViewAdapter(TTSPlayRuleActivity.this, mOnTTSRuleChangeListener); + mRecyclerView.setAdapter(mTTSRuleBeanRecyclerViewAdapter); + + // 处理传入的窗口启动参数 + // + String szNewDemoText = getIntent().getStringExtra(EXTRA_TTSDEMOTEXT); + metCurrentDemoSMSText.setText(szNewDemoText); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //return super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.toolbar_rule, menu); + return true; + } + + public void onScrollToDemoSMSTextMatchingRule(View view) { + int rowIndex = mTTSPlayRuleUtil.speakTTSAnalyzeModeText(metCurrentDemoSMSText.getText().toString()); + + LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager(); + layoutManager.scrollToPositionWithOffset(rowIndex, 0); + Toast.makeText(getApplication(), "当前文本匹配的规则序号为 " + Integer.toString(rowIndex + 1), Toast.LENGTH_SHORT).show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int nItemId = item.getItemId(); + if (nItemId == R.id.item_rule_share) { + TTSPlayRuleBean bean = new TTSPlayRuleBean(); + FileUtil.shareJSONFile(this, bean.getBeanListJsonFilePath(TTSPlayRuleActivity.this)); + } else if (nItemId == R.id.item_rule_reset) { + showResetConfigDialog(); + } else if (nItemId == R.id.item_rule_clean) { + showCleanConfigDialog(); + } + + return true; + } + + // + // 规则数据重置对话框 + // + void showResetConfigDialog() { + mTTSPlayRuleUtil.resetConfig(); + } + + // + // 规则数据重置对话框 + // + void showCleanConfigDialog() { + mTTSPlayRuleUtil.cleanConfig(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + // + // 规则项选择事件监听类 + // + TTSRuleBeanRecyclerViewAdapter.OnTTSRuleChangeListener mOnTTSRuleChangeListener = new TTSRuleBeanRecyclerViewAdapter.OnTTSRuleChangeListener() { + @Override + public void onTTSRuleChange(TTSPlayRuleBean bean) { + metCurrentDemoSMSText.setText(bean.getDemoSMSText()); + metPatternText.setText(bean.getPatternText()); + metCurrentTTSRuleText.setText(bean.getTtsRuleText()); + mTTSRuleBeanCurrent = bean; + } + }; +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/WinBoLLActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/WinBoLLActivity.java new file mode 100644 index 0000000..dd65f3b --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/WinBoLLActivity.java @@ -0,0 +1,82 @@ +package cc.winboll.studio.mymessagemanager.activitys; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/31 01:31:17 + * @Describe 应用活动窗口基类 + */ +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.MenuItem; +import androidx.appcompat.app.AppCompatActivity; +import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity; +import cc.winboll.studio.mymessagemanager.enums.ThemeStyleEnum; + +public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity { + + public static final String TAG = "WinBoLLActivity"; + + IOnActivityMessageReceived mIOnActivityMessageReceived; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + // 1. 优先读取SP中保存的主题(必须在setContentView前调用!) + ThemeStyleEnum savedTheme = ThemeStyleEnum.getThemeFromSP(this); + // 2. 设置主题 + setTheme(savedTheme.getStyleId()); + super.onCreate(savedInstanceState); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int selectedMenuId = item.getItemId(); + // 1. 根据菜单ID获取对应的主题枚举 + ThemeStyleEnum selectedTheme = ThemeStyleEnum.getThemeByMenuId(selectedMenuId); + + if (selectedTheme != null) { + // 2. 调用枚举自带方法保存主题到SP(替代AESThemeUtil) + ThemeStyleEnum.saveThemeToSP(this, selectedTheme); + recreate(); // 重建Activity生效主题 + } else if (selectedMenuId == android.R.id.home) { + finish(); + } else { + return super.onOptionsItemSelected(item); + } + return true; + } + + protected interface IOnActivityMessageReceived { + void onActivityMessageReceived(Message msg); + } + + public void sendActivityMessage(Message msg) { + mHandler.sendMessage(msg); + } + + protected void setOnActivityMessageReceived(IOnActivityMessageReceived iOnActivityMessageReceived) { + mIOnActivityMessageReceived = iOnActivityMessageReceived; + } + + Handler mHandler = new Handler(){ + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (mIOnActivityMessageReceived != null) { + mIOnActivityMessageReceived.onActivityMessageReceived(msg); + } + } + }; +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/PhoneArrayAdapter.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/PhoneArrayAdapter.java new file mode 100644 index 0000000..12381bc --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/PhoneArrayAdapter.java @@ -0,0 +1,115 @@ +package cc.winboll.studio.mymessagemanager.adapters; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.SMSActivity; +import cc.winboll.studio.mymessagemanager.beans.PhoneBean; +import cc.winboll.studio.mymessagemanager.beans.SMSBean; +import cc.winboll.studio.mymessagemanager.utils.AddressUtils; +import cc.winboll.studio.mymessagemanager.utils.PhoneUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import java.util.ArrayList; +import java.util.List; + +public class PhoneArrayAdapter extends BaseAdapter { + + public final static String TAG = "PhoneArrayAdapter"; + + Context mContext; + ArrayList mData; + List mlistContacts; + PhoneUtil mPhoneUtil; + + public PhoneArrayAdapter(Context context) { + mContext = context; + mData = new ArrayList(); + } + + public void loadData() { + ArrayList listTemp = SMSUtil.getAllSMSList(mContext); + mData.clear(); + mData.addAll(listTemp); + mPhoneUtil = new PhoneUtil(mContext); + mlistContacts = mPhoneUtil.getPhoneList(); + LogUtils.i(TAG, "SMS List Reload."); + } + + @Override + public int getCount() { + return mData.size(); + } + + @Override + public Object getItem(int p1) { + return mData.get(p1); + } + + @Override + public long getItemId(int p1) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final ViewHolder viewHolder; + if (convertView == null) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(mContext).inflate(R.layout.listview_phone, parent, false); + + //分别获取 image view 和 textview 的实例 + viewHolder.tvAddress = convertView.findViewById(R.id.listviewphoneTextView1); + viewHolder.tvName = convertView.findViewById(R.id.listviewphoneTextView2); + viewHolder.ll = convertView.findViewById(R.id.listviewphoneLinearLayout1); + + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + + final String szAddress = ((SMSBean)getItem(position)).getAddress(); + + viewHolder.tvAddress.setText(AddressUtils.getFormattedAddress(szAddress)); + viewHolder.tvName.setText(getName(szAddress)); + + //Drawable drawableFrame = AppCompatResources.getDrawable(mContext, R.drawable.bg_frame); + //viewHolder.ll.setBackground(drawableFrame); + viewHolder.ll.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View arg0) { + + //Toast.makeText(mContext, tv.getText(), Toast.LENGTH_SHORT).show(); + Intent intent = new Intent(mContext, SMSActivity.class); + intent.putExtra(SMSActivity.EXTRA_PHONE, szAddress); + mContext.startActivity(intent); + } + + }); + return convertView; + + } + + String getName(String szAddress) { + for (int i = 0; i < mlistContacts.size(); i++) { + if (mlistContacts.get(i).getTelPhone().equals(szAddress)) { + return mlistContacts.get(i).getName(); + } + } + return mContext.getString(R.string.text_notincontacts); + } + + class ViewHolder { + TextView tvAddress; + TextView tvName; + LinearLayout ll; + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSAcceptRuleArrayAdapter.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSAcceptRuleArrayAdapter.java new file mode 100644 index 0000000..99522a1 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSAcceptRuleArrayAdapter.java @@ -0,0 +1,229 @@ +package cc.winboll.studio.mymessagemanager.adapters; + +/** + * @Author ZhanGSKen + * @Date 2024/07/20 12:27:34 + * @Describe 短信过滤规则数据适配器 + */ +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.beans.SMSAcceptRuleBean; +import cc.winboll.studio.mymessagemanager.utils.SMSReceiveRuleUtil; +import java.util.ArrayList; + +public class SMSAcceptRuleArrayAdapter extends RecyclerView.Adapter { + + public static final String TAG = "SMSAcceptRuleArrayAdapter"; + + Context mContext; + ArrayList mDataList; + SMSReceiveRuleUtil mSMSReceiveRuleUtil; + + public SMSAcceptRuleArrayAdapter(Context context) { + mContext = context; + mSMSReceiveRuleUtil = SMSReceiveRuleUtil.getInstance(mContext, true); + loadConfigData(); + } + + public void addSMSAcceptRule(SMSAcceptRuleBean bean) { + mSMSReceiveRuleUtil.addRule(bean); + notifyDataSetChanged(); + } + + public void loadConfigData() { + mDataList = mSMSReceiveRuleUtil.loadConfigData(); + for (int i = 0; i < mDataList.size(); i++) { + mDataList.get(i).setIsSimpleView(true); + //LogUtils.d(TAG, "loadConfigData isEnable : " + Boolean.toString(mDataList.get(i).isEnable())); + } + } + + void deleteItem(int position) { + mDataList.remove(position); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + if (mDataList.get(position).isSimpleView()) { + return 0; + } else { + return 1; + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == 0) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_smsacceptrule_simple, parent, false); + return new SimpleViewHolder(view); + } else { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_smsacceptrule, parent, false); + return new ComplexViewHolder(view); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { + final SMSAcceptRuleBean item = mDataList.get(position); + if (holder.getItemViewType() == 0) { + final SimpleViewHolder viewHolder = (SimpleViewHolder) holder; + viewHolder.mtvContent.setText(item.getRuleData()); + viewHolder.mcbEnable.setChecked(item.isEnable()); + viewHolder.mcbEnable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View p1) { + item.setIsEnable(viewHolder.mcbEnable.isChecked()); + item.setIsSimpleView(true); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + }); + viewHolder.mtvRuleType.setText(item.getRuleType().toString()); + viewHolder.mbtnEdit.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v) { + for (int i = 0; i < mDataList.size(); i++) { + mDataList.get(i).setIsSimpleView(true); + } + item.setIsSimpleView(false); + notifyDataSetChanged(); + //ToastUtils.show("setIsSimpleView"); + } + }); + } else { + final ComplexViewHolder viewHolder = (ComplexViewHolder) holder; + if (item != null) { + //Drawable drawableFrame = AppCompatResources.getDrawable(mContext, R.drawable.bg_frame); + viewHolder.metContent.setText(item.getRuleData()); + if (item.getRuleType() == SMSAcceptRuleBean.RuleType.ACCEPT) { + viewHolder.mrbAccept.setChecked(true); + viewHolder.mrbRefuse.setChecked(false); + } + if (item.getRuleType() == SMSAcceptRuleBean.RuleType.REFUSE) { + viewHolder.mrbAccept.setChecked(false); + viewHolder.mrbRefuse.setChecked(true); + } + viewHolder.mrbAccept.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View view) { + viewHolder.mrbRefuse.setChecked(false); + item.setRuleType(SMSAcceptRuleBean.RuleType.ACCEPT); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + }); + viewHolder.mrbRefuse.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View view) { + viewHolder.mrbAccept.setChecked(false); + item.setRuleType(SMSAcceptRuleBean.RuleType.REFUSE); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + }); + viewHolder.mbtnUp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View p1) { + if (position > 0) { + mDataList.add(position-1, mDataList.get(position)); + mDataList.remove(position+1); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + } + }); + viewHolder.mbtnDown.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View p1) { + if (position < mDataList.size() - 1) { + //ToastUtils.show("mbtnDown"); + ToastUtils.show("position " + Integer.toString(position)); + mDataList.add(position+2, mDataList.get(position)); + mDataList.remove(position); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + } + }); + viewHolder.mbtnOK.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View p1) { + item.setRuleData(viewHolder.metContent.getText().toString()); + item.setRuleType(viewHolder.mrbAccept.isChecked() ?SMSAcceptRuleBean.RuleType.ACCEPT: SMSAcceptRuleBean.RuleType.REFUSE); + item.setIsEnable(viewHolder.mcbEnable.isChecked()); + item.setIsSimpleView(true); + mSMSReceiveRuleUtil.saveConfigData(); + notifyDataSetChanged(); + } + }); + viewHolder.mbtnDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View p1) { + deleteItem(position); + } + }); + viewHolder.mcbEnable.setChecked(item.isEnable()); + } + } + } + + @Override + public int getItemCount() { + return mDataList.size(); + } + + @Override + public long getItemId(int posttion) { + return 0; + } + + private static class SimpleViewHolder extends RecyclerView.ViewHolder { + TextView mtvContent; + CheckBox mcbEnable; + TextView mtvRuleType; + Button mbtnEdit; + + SimpleViewHolder(View itemView) { + super(itemView); + mtvContent = itemView.findViewById(R.id.listviewsmsacceptrulesimpleTextView1); + mcbEnable = itemView.findViewById(R.id.listviewsmsacceptrulesimpleCheckBox1); + mtvRuleType = itemView.findViewById(R.id.listviewsmsacceptrulesimpleTextView2); + mbtnEdit = itemView.findViewById(R.id.listviewsmsacceptrulesimpleButton1); + } + } + + private static class ComplexViewHolder extends RecyclerView.ViewHolder { + EditText metContent; + RadioButton mrbAccept; + RadioButton mrbRefuse; + CheckBox mcbEnable; + Button mbtnUp; + Button mbtnDown; + Button mbtnOK; + Button mbtnDelete; + + ComplexViewHolder(View itemView) { + super(itemView); + metContent = itemView.findViewById(R.id.listviewsmsacceptruleEditText1); + mrbAccept = itemView.findViewById(R.id.listviewsmsacceptruleRadioButton1); + mrbRefuse = itemView.findViewById(R.id.listviewsmsacceptruleRadioButton2); + mcbEnable = itemView.findViewById(R.id.listviewsmsacceptruleCheckBox1); + mbtnUp = itemView.findViewById(R.id.listviewsmsacceptruleButton3); + mbtnDown = itemView.findViewById(R.id.listviewsmsacceptruleButton4); + mbtnOK = itemView.findViewById(R.id.listviewsmsacceptruleButton1); + mbtnDelete = itemView.findViewById(R.id.listviewsmsacceptruleButton2); + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSArrayAdapter.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSArrayAdapter.java new file mode 100644 index 0000000..513a0eb --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSArrayAdapter.java @@ -0,0 +1,214 @@ +package cc.winboll.studio.mymessagemanager.adapters; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; +import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.TTSPlayRuleActivity; +import cc.winboll.studio.mymessagemanager.beans.SMSAcceptRuleBean; +import cc.winboll.studio.mymessagemanager.beans.SMSBean; +import cc.winboll.studio.mymessagemanager.utils.NotificationHelper; +import cc.winboll.studio.mymessagemanager.utils.SMSReceiveRuleUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSRecycleUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import cc.winboll.studio.mymessagemanager.utils.TTSPlayRuleUtil; +import cc.winboll.studio.mymessagemanager.views.DateAgoTextView; +import cc.winboll.studio.mymessagemanager.views.SMSView; +import java.util.ArrayList; + +public class SMSArrayAdapter extends BaseAdapter { + + public static String TAG = "SMSArrayAdapter"; + + Context mContext; + String mszPhone; + ArrayList mData; + + public SMSArrayAdapter(Context context, String szPhone) { + mContext = context; + mszPhone = szPhone; + mData = new ArrayList(); + mData = loadSMSList(context, szPhone); + } + + ArrayList loadSMSList(Context context, String szPhone) { + ArrayList data = SMSUtil.getSMSListByPhone(context, szPhone); + SMSBean.sortSMSByDateDesc(data, false); + mData.clear(); + mData.addAll(data); + return mData; + } + + public void cancelMessageNotification() { + for (SMSBean bean : mData) { + NotificationHelper notificationHelper = new NotificationHelper(mContext); + notificationHelper.cancelNotification(bean.getId()); + } + } + + void deleteSMSById(final int position) { + YesNoAlertDialog.show(mContext, + "短信删除提示", + "请确认删除动作!" + , (new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + SMSRecycleUtil.addSMSRecycleItem(mContext, (SMSBean)getItem(position)); + SMSUtil.deleteSMSById(mContext, ((SMSBean)getItem(position)).getId()); + mData.remove(position); + notifyDataSetChanged(); + Toast.makeText(mContext, "SMS delete.", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNo() { + + } + })); + } + + public void reLoadSMSList(Context context, String szPhone) { + mData = loadSMSList(context, szPhone); + } + + @Override + public int getCount() { + return mData.size(); + } + + @Override + public Object getItem(int p1) { + return mData.get(p1); + } + + @Override + public long getItemId(int p1) { + return 0; + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + final ViewHolder viewHolder; + if (convertView == null) { + viewHolder = new ViewHolder(); + convertView = LayoutInflater.from(mContext).inflate(R.layout.listview_sms, parent, false); + viewHolder.mSMSView = convertView.findViewById(R.id.listviewsmsSMSView1); + + viewHolder.mllMain = convertView.findViewById(R.id.listviewsmspart1LinearLayout1); + viewHolder.mllContent = convertView.findViewById(R.id.listviewsmspart1LinearLayout2); + viewHolder.mvMenu = convertView.findViewById(R.id.listviewsmspart1View1); + viewHolder.mtvBody = (TextView) convertView + .findViewById(R.id.listviewsmspart1TextView1); + viewHolder.mdatvDate = convertView.findViewById(R.id.listviewsmspart1DateAgoTextView1); + + viewHolder.mvLeft = convertView.findViewById(R.id.listviewsmsView1); + viewHolder.mvRight = convertView.findViewById(R.id.listviewsmsView2); + + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + } + + final SMSBean item = (SMSBean) getItem(position); + if (item != null) { + if (item.getType() == SMSBean.Type.INBOX) { + viewHolder.mvLeft.setVisibility(View.GONE); + viewHolder.mvRight.setVisibility(View.VISIBLE); + + viewHolder.mSMSView.setSMSType(SMSView.SMSType.INBOX); + viewHolder.mllMain.setGravity(Gravity.LEFT); + } else { + viewHolder.mvLeft.setVisibility(View.VISIBLE); + viewHolder.mvRight.setVisibility(View.GONE); + viewHolder.mSMSView.setSMSType(SMSView.SMSType.SEND); + } + + //Drawable drawableFrame = AppCompatResources.getDrawable(mContext, R.drawable.bg_frame); + //viewHolder.mllContent.setBackground(drawableFrame); + + viewHolder.mtvBody.setText(item.getBody()); + viewHolder.mdatvDate.setDate(item.getDate()); + //viewHolder.mtvType.setText(" [" + item.getType().name() + "] "); + + viewHolder.mSMSView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View p1) { + // 弹出复制菜单 + PopupMenu menu = new PopupMenu(mContext, viewHolder.mvMenu); + //加载菜单资源 + menu.getMenuInflater().inflate(R.menu.toolbar_item_sms, menu.getMenu()); + //设置点击事件的响应 + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + int nItemId = menuItem.getItemId(); + if (nItemId == R.id.copy) { + // Gets a handle to the clipboard service. + ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); + // Creates a new text clip to put on the clipboard + ClipData clip = ClipData.newPlainText("simple text", item.getBody()); + // Set the clipboard's primary clip. + clipboard.setPrimaryClip(clip); + Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show(); + } else if (nItemId == R.id.delete) { + deleteSMSById(position); + } else if (nItemId == R.id.addttsrule) { + Intent intent = new Intent(mContext, TTSPlayRuleActivity.class); + intent.putExtra(TTSPlayRuleActivity.EXTRA_TTSDEMOTEXT, viewHolder.mtvBody.getText().toString()); + mContext.startActivity(intent); + } else if (nItemId == R.id.testtts) { + //Toast.makeText(mContext, "Testing TTS.", Toast.LENGTH_SHORT).show(); + TTSPlayRuleUtil ttsPlayRuleUtil = TTSPlayRuleUtil.getInstance(mContext); + ttsPlayRuleUtil.speakTTSAnalyzeModeText(viewHolder.mtvBody.getText().toString()); + } else if (nItemId == R.id.testreceivetule) { + //Toast.makeText(mContext, "Testing Receive Rule.", Toast.LENGTH_SHORT).show(); + SMSReceiveRuleUtil smsReceiveRuleUtil = SMSReceiveRuleUtil.getInstance(mContext, true); + SMSReceiveRuleUtil.MatchResult matchResult = smsReceiveRuleUtil.getReceiveRuleMatchResult(mContext, viewHolder.mtvBody.getText().toString()); + if (matchResult.matchPositionInRules == SMSReceiveRuleUtil.VALID_MATCHRESULT_POSITION + || matchResult.matchRuleType == SMSAcceptRuleBean.RuleType.REGEXPPIUTILS_ISPPIOK_FALSE) { + //ToastUtils.show("Test"); + ToastUtils.show("Not Receive Rule is Matched.\nResult is : " + matchResult.matchRuleType); + } else { + ToastUtils.show("MatchResult : " + matchResult.matchRuleType + "\nReceiveRule Match Position : " + Integer.toString(matchResult.matchPositionInRules + 1)); + } + } + + return true; + } + }); + //一定要调用show()来显示弹出式菜单 + menu.show(); + + return true; + } + }); + } + + return convertView; + } + + class ViewHolder { + SMSView mSMSView; + LinearLayout mllMain; + LinearLayout mllContent; + TextView mtvBody; + View mvMenu; + DateAgoTextView mdatvDate; + View mvLeft; + View mvRight; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSRecycleAdapter.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSRecycleAdapter.java new file mode 100644 index 0000000..2c622c5 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/SMSRecycleAdapter.java @@ -0,0 +1,290 @@ +package cc.winboll.studio.mymessagemanager.adapters; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 17:07:34 + * @Describe 短信回收站短信数据适配器 + */ +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.TTSPlayRuleActivity; +import cc.winboll.studio.mymessagemanager.beans.SMSBean; +import cc.winboll.studio.mymessagemanager.beans.SMSRecycleBean; +import cc.winboll.studio.mymessagemanager.utils.AddressUtils; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSRecycleUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import cc.winboll.studio.mymessagemanager.utils.TTSPlayRuleUtil; +import cc.winboll.studio.mymessagemanager.utils.UserVisionSystemProtectModeUtil; +import cc.winboll.studio.mymessagemanager.views.DateAgoTextView; +import cc.winboll.studio.mymessagemanager.views.SMSView; +import java.util.ArrayList; + +public class SMSRecycleAdapter extends RecyclerView.Adapter { + + public static final String TAG = "SMSRecycleAdapter"; + + Context mContext; + ArrayList mDataList; + String mszSMSRecycleListDataPath; + AppConfigUtil mAppConfigUtil; + + public SMSRecycleAdapter(Context context) { + mContext = context; + mAppConfigUtil = AppConfigUtil.getInstance(mContext); + mszSMSRecycleListDataPath = SMSRecycleUtil.getSMSRecycleListDataPath(mContext); + mDataList = new ArrayList(); + mDataList = loadSMSRecycleList(); + } + + public ArrayList loadSMSRecycleList() { + ArrayList list = new ArrayList(); + SMSRecycleBean.loadBeanListFromFile(mszSMSRecycleListDataPath, list, SMSRecycleBean.class); + SMSRecycleBean.sortSMSByDeleteDateDesc(list, true); + mDataList.clear(); + mDataList.addAll(list); + for (int i = 0; i < mDataList.size(); i++) { + mDataList.get(i).setIsSimpleView(true); + } + //ToastUtils.show("mDataList.size() : " + Integer.toString(mDataList.size())); + return mDataList; + } + + public void saveSMSRecycleList() { + SMSBean.saveBeanListToFile(mszSMSRecycleListDataPath, mDataList); + } + + void restoreSMSRecycleItem(final int position) { + YesNoAlertDialog.show(mContext, + "短信恢复提示", + "是否恢复该短信!" + , (new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + SMSBean item = mDataList.get(position); + long nResultId = 0; + //LogUtils.d(TAG, "item.getType() : " + item.getType()); + if (item.getType() == SMSBean.Type.INBOX) { + nResultId = SMSUtil.saveReceiveSms(mContext, item.getAddress(), item.getBody(), + (item.getReadStatus() == SMSBean.ReadStatus.READ) ?"1": "0", + item.getDate(), "inbox"); + } else if (item.getType() == SMSBean.Type.SENT) { + nResultId = SMSUtil.saveOldSendedSMS(mContext, item); + } + if (nResultId == 0) { + ToastUtils.show("SMS Restored Failed!\nPlease confirm that the application has the SMS management authority."); + } else { + mDataList.remove(position); + SMSBean.saveBeanListToFile(mszSMSRecycleListDataPath, mDataList); + notifyDataSetChanged(); + ToastUtils.show("SMS Restored. ID : " + Long.toString(nResultId)); + } + } + + @Override + public void onNo() { + + } + })); + } + + void deleteSMSRecycleItem(final int position) { + YesNoAlertDialog.show(mContext, + "短信删除提示", + "请确认删除动作!" + , (new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + mDataList.remove(position); + SMSBean.saveBeanListToFile(mszSMSRecycleListDataPath, mDataList); + notifyDataSetChanged(); + Toast.makeText(mContext, "SMS delete.", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNo() { + + } + })); + } + + public void reLoadSMSList(Context context, String szPhone) { + mDataList = loadSMSRecycleList(); + } + + @Override + public int getItemViewType(int position) { + if (mDataList.get(position).isSimpleView()) { + return 0; + } else { + return 1; + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == 0) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_smsrecycle_simple, parent, false); + return new SimpleViewHolder(view); + } else { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_smsrecycle, parent, false); + return new ComplexViewHolder(view); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { + final SMSRecycleBean item = mDataList.get(position); + if (holder.getItemViewType() == 0) { + SimpleViewHolder viewHolder = (SimpleViewHolder) holder; + viewHolder.mtvAddress.setText(AddressUtils.getFormattedAddress(item.getAddress())); + viewHolder.mbtnViewBody.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v) { + for (int i = 0; i < mDataList.size(); i++) { + mDataList.get(i).setIsSimpleView(true); + } + item.setIsSimpleView(false); + notifyDataSetChanged(); + //ToastUtils.show("setIsSimpleView"); + } + }); + } else { + final ComplexViewHolder viewHolder = (ComplexViewHolder) holder; + if (item.getType() == SMSBean.Type.INBOX) { + viewHolder.mvLeft.setVisibility(View.GONE); + viewHolder.mvRight.setVisibility(View.VISIBLE); + + viewHolder.mSMSView.setSMSType(SMSView.SMSType.INBOX); + viewHolder.mllMain.setGravity(Gravity.LEFT); + } else { + viewHolder.mvLeft.setVisibility(View.VISIBLE); + viewHolder.mvRight.setVisibility(View.GONE); + viewHolder.mSMSView.setSMSType(SMSView.SMSType.SEND); + } + viewHolder.mtvAddress.setText(AddressUtils.getFormattedAddress(item.getAddress())); + viewHolder.mdatvDeleteDate.setDate(item.getDeleteDate()); + viewHolder.mdatvDate.setDate(item.getDate()); + if(mAppConfigUtil.mAppConfigBean.isSMSRecycleProtectMode()) { + viewHolder.mtvBody.setText("ProtectMode : " + UserVisionSystemProtectModeUtil.PreviewShuffleSMS(item.getBody(), mAppConfigUtil.mAppConfigBean.getProtectModerRefuseChars(), mAppConfigUtil.mAppConfigBean.getProtectModerReplaceChars())); + } else { + viewHolder.mtvBody.setText(item.getBody()); + } + /*viewHolder.mTagsAdapter = new TagsAdapter(mContext, item); + RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(mContext); + viewHolder.mTagsRecyclerView.setLayoutManager(layoutManager); + viewHolder.mTagsRecyclerView.setAdapter(viewHolder.mTagsAdapter); + // 这个设置可以解决嵌套listvew的内部listview拉动问题。 + viewHolder.mTagsRecyclerView.setParentScrollView(viewHolder.mScrollView);*/ + viewHolder.mllMain.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View p1) { + // 弹出复制菜单 + PopupMenu menu = new PopupMenu(mContext, viewHolder.mvMenu); + //加载菜单资源 + menu.getMenuInflater().inflate(R.menu.toolbar_item_smsrecycle, menu.getMenu()); + menu.getMenuInflater().inflate(R.menu.toolbar_item_sms, menu.getMenu()); + //设置点击事件的响应 + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + int nItemId = menuItem.getItemId(); + if (nItemId == R.id.item_restoresms) { + restoreSMSRecycleItem(position); + } else if (nItemId == R.id.copy) { + // Gets a handle to the clipboard service. + ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE); + // Creates a new text clip to put on the clipboard + ClipData clip = ClipData.newPlainText("simple text", item.getBody()); + // Set the clipboard's primary clip. + clipboard.setPrimaryClip(clip); + Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show(); + } else if (nItemId == R.id.delete) { + deleteSMSRecycleItem(position); + /*loadSMSRecycleList(); + mDataList.remove(item); + saveSMSRecycleList();*/ + notifyDataSetChanged(); + } else if (nItemId == R.id.addttsrule) { + Intent intent = new Intent(mContext, TTSPlayRuleActivity.class); + intent.putExtra(TTSPlayRuleActivity.EXTRA_TTSDEMOTEXT, viewHolder.mtvBody.getText().toString()); + mContext.startActivity(intent); + } else if (nItemId == R.id.testtts) { + //Toast.makeText(mContext, "Testing TTS.", Toast.LENGTH_SHORT).show(); + TTSPlayRuleUtil ttsPlayRuleUtil = TTSPlayRuleUtil.getInstance(mContext); + ttsPlayRuleUtil.speakTTSAnalyzeModeText(viewHolder.mtvBody.getText().toString()); + } + + return true; + } + }); + //一定要调用show()来显示弹出式菜单 + menu.show(); + + return true; + } + }); + } + } + + @Override + public int getItemCount() { + return mDataList.size(); + } + + private static class SimpleViewHolder extends RecyclerView.ViewHolder { + TextView mtvAddress; + Button mbtnViewBody; + + SimpleViewHolder(View itemView) { + super(itemView); + mtvAddress = itemView.findViewById(R.id.listviewsmsrecyclesimpleTextView1); + mbtnViewBody = itemView.findViewById(R.id.listviewsmsrecyclesimpleButton1); + } + } + + private static class ComplexViewHolder extends RecyclerView.ViewHolder { + TextView mtvAddress; + DateAgoTextView mdatvDeleteDate; + SMSView mSMSView; + LinearLayout mllMain; + LinearLayout mllContent; + TextView mtvBody; + View mvMenu; + DateAgoTextView mdatvDate; + View mvLeft; + View mvRight; + + ComplexViewHolder(View itemView) { + super(itemView); + mtvAddress = itemView.findViewById(R.id.listviewsmsrecycleTextView1); + mdatvDeleteDate = itemView.findViewById(R.id.listviewsmsrecycleDateAgoTextView1); + mSMSView = itemView.findViewById(R.id.listviewsmsrecycleSMSView1); + mllMain = itemView.findViewById(R.id.listviewsmspart1LinearLayout1); + mllContent = itemView.findViewById(R.id.listviewsmspart1LinearLayout2); + mvMenu = itemView.findViewById(R.id.listviewsmsrecycleView1); + mtvBody = itemView.findViewById(R.id.listviewsmspart1TextView1); + mdatvDate = itemView.findViewById(R.id.listviewsmspart1DateAgoTextView1); + mvLeft = itemView.findViewById(R.id.listviewsmsrecycleView1); + mvRight = itemView.findViewById(R.id.listviewsmsrecycleView2); + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/TTSRuleBeanRecyclerViewAdapter.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/TTSRuleBeanRecyclerViewAdapter.java new file mode 100644 index 0000000..c424a2f --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/adapters/TTSRuleBeanRecyclerViewAdapter.java @@ -0,0 +1,193 @@ +package cc.winboll.studio.mymessagemanager.adapters; + +/** + * @Author ZhanGSKen + * @Date 2024/05/18 16:08:20 + * @Describe TTSRuleBean RecyclerView Adapter + */ +import android.content.Context; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.PopupMenu; +import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.TTSPlayRuleActivity; +import cc.winboll.studio.mymessagemanager.beans.TTSPlayRuleBean; +import cc.winboll.studio.mymessagemanager.utils.TTSPlayRuleUtil; +import cc.winboll.studio.mymessagemanager.views.TTSRuleView; +import java.util.ArrayList; + +public class TTSRuleBeanRecyclerViewAdapter extends RecyclerView.Adapter { + + public static final String TAG = "TTSRuleBeanRecyclerViewAdapter"; + + Context mContext; + ArrayList mDataList; + OnTTSRuleChangeListener mOnTTSRuleChangeListener; + TTSPlayRuleUtil mTTSPlayRuleUtil; + + public TTSRuleBeanRecyclerViewAdapter(TTSPlayRuleActivity ttsPlayRuleActivity, OnTTSRuleChangeListener onTTSRuleChangeListener) { + mContext = ttsPlayRuleActivity; + mOnTTSRuleChangeListener = onTTSRuleChangeListener; + + mTTSPlayRuleUtil = TTSPlayRuleUtil.getInstance(ttsPlayRuleActivity); + mTTSPlayRuleUtil.initTTSPlayRuleActivity(ttsPlayRuleActivity); + mDataList = mTTSPlayRuleUtil.loadConfigData(); + } + + public void addNewTTSRuleBean(TTSPlayRuleBean bean) { + mTTSPlayRuleUtil.addNewTTSRuleBean(bean); + //notifyDataSetChanged(); + } + + public void saveConfigData() { + mTTSPlayRuleUtil.saveConfigData(); + //notifyDataSetChanged(); + } + + public void reloadConfigData() { + mDataList = mTTSPlayRuleUtil.loadConfigData(); + notifyDataSetChanged(); + } + + public interface OnTTSRuleChangeListener { + abstract void onTTSRuleChange(TTSPlayRuleBean bean); + } + + @Override + public int getItemViewType(int position) { + if (mDataList.get(position).isSimpleView()) { + return 0; + } else { + return 1; + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == 0) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_ttsplayrule_simple, parent, false); + return new SimpleViewHolder(view); + } else { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listview_ttsplayrule, parent, false); + return new ComplexViewHolder(view); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { + final TTSPlayRuleBean item = mDataList.get(position); + if (holder.getItemViewType() == 0) { + SimpleViewHolder viewHolder = (SimpleViewHolder) holder; + viewHolder.mSortNumber.setText(Integer.toString(position + 1)); + viewHolder.mtvDemoSMSText.setText(item.getDemoSMSText()); + } else { + final ComplexViewHolder viewHolder = (ComplexViewHolder) holder; + viewHolder.mSortNumber.setText(Integer.toString(position + 1)); + viewHolder.mtvDemoSMSText.setText(item.getDemoSMSText()); + viewHolder.mTTSRuleView.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v) { + mOnTTSRuleChangeListener.onTTSRuleChange(item); + } + }); + viewHolder.mbtnUp.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v) { + //Toast.makeText(mContext, Integer.toString(position), Toast.LENGTH_SHORT).show(); + mTTSPlayRuleUtil.changeBeanPosition(position, true); + //notifyDataSetChanged(); + } + }); + viewHolder.mbtnDown.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v) { + //Toast.makeText(mContext, Integer.toString(position), Toast.LENGTH_SHORT).show(); + mTTSPlayRuleUtil.changeBeanPosition(position, false); + //notifyDataSetChanged(); + } + }); + viewHolder.mchbEnable.setChecked(item.isEnable()); + viewHolder.mchbEnable.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v) { + mTTSPlayRuleUtil.setBeanEnable(position, ((CheckBox)v).isChecked()); + //notifyDataSetChanged(); + } + }); + viewHolder.mTTSRuleView.setOnLongClickListener(new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View p1) { + // 弹出复制菜单 + PopupMenu menu = new PopupMenu(mContext, viewHolder.mSortNumber); + //加载菜单资源 + menu.getMenuInflater().inflate(R.menu.toolbar_ttsrule, menu.getMenu()); + //设置点击事件的响应 + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + int nItemId = menuItem.getItemId(); + if (nItemId == R.id.deletettsrule) { + mTTSPlayRuleUtil.deleteTTSRuleBean(position); + //notifyDataSetChanged(); + } + + return true; + } + }); + //一定要调用show()来显示弹出式菜单 + menu.show(); + + return true; + } + }); + } + } + + @Override + public int getItemCount() { + return mDataList.size(); + } + + private static class SimpleViewHolder extends RecyclerView.ViewHolder { + TextView mSortNumber; + TextView mtvDemoSMSText; + + + SimpleViewHolder(View itemView) { + super(itemView); + mSortNumber = itemView.findViewById(R.id.itemttsplayrulesimpleTextView2); + mtvDemoSMSText = itemView.findViewById(R.id.itemttsplayrulesimpleTextView1); + + } + } + + private static class ComplexViewHolder extends RecyclerView.ViewHolder { + TextView mSortNumber; + TTSRuleView mTTSRuleView; + LinearLayout mllMain; + TextView mtvDemoSMSText; + Button mbtnUp; + Button mbtnDown; + CheckBox mchbEnable; + + ComplexViewHolder(View itemView) { + super(itemView); + mSortNumber = itemView.findViewById(R.id.itemttsplayruleTextView2); + mTTSRuleView = itemView.findViewById(R.id.listviewttsplayruleTTSRuleView1); + mllMain = itemView.findViewById(R.id.itemttsplayruleLinearLayout1); + mtvDemoSMSText = itemView.findViewById(R.id.itemttsplayruleTextView1); + mbtnUp = itemView.findViewById(R.id.itemttsplayruleButton1); + mbtnDown = itemView.findViewById(R.id.itemttsplayruleButton2); + mchbEnable = itemView.findViewById(R.id.itemttsplayruleCheckBox1); + + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/AppConfigBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/AppConfigBean.java new file mode 100644 index 0000000..7ba266d --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/AppConfigBean.java @@ -0,0 +1,184 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/06/02 20:07:44 + * @Describe 应用配置数据类 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class AppConfigBean extends BaseBean { + + public static final String TAG = "AppConfigBean"; + + // 当前国家代码(如+8612345678901代码就是86.) + String countryCode = "86"; + // 是否合并的手机号码前缀 + boolean isMergeCountryCodePrefix = true; + // TT语音延时播放毫秒数 + int ttsPlayDelayTimes = 3000; + boolean isEnableService = false; + boolean isEnableOnlyReceiveContacts = false; + boolean isEnableTTS = false; + boolean isEnableTTSRuleMode = false; + boolean isSMSRecycleProtectMode = false; + // 保护式预览拒绝显示的字符集 + String protectModerRefuseChars = "设定被和谐的字符"; + // 保护式预览拒绝显示的字符集的替代字符 + String protectModerReplaceChars = "当前替代显示字符"; + //int appThemeID = ThemeUtil.getThemeID(ThemeUtil.BaseTheme.DEFAULT); + + public void setProtectModerRefuseChars(String protectModerRefuseChars) { + this.protectModerRefuseChars = protectModerRefuseChars; + } + + public String getProtectModerRefuseChars() { + return protectModerRefuseChars; + } + + public void setProtectModerReplaceChars(String protectModerReplaceChars) { + this.protectModerReplaceChars = protectModerReplaceChars; + } + + public String getProtectModerReplaceChars() { + return protectModerReplaceChars; + } + + public void setIsSMSRecycleProtectMode(boolean isSMSRecycleProtectMode) { + this.isSMSRecycleProtectMode = isSMSRecycleProtectMode; + } + + public boolean isSMSRecycleProtectMode() { + return isSMSRecycleProtectMode; + } + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } + + public String getCountryCode() { + return countryCode; + } + + public void setIsMergeCountryCodePrefix(boolean isMergeCountryCodePrefix) { + this.isMergeCountryCodePrefix = isMergeCountryCodePrefix; + } + + public boolean isMergeCountryCodePrefix() { + return isMergeCountryCodePrefix; + } + + public void setTtsPlayDelayTimes(int ttsPlayDelayTimes) { + this.ttsPlayDelayTimes = ttsPlayDelayTimes; + } + + public int getTtsPlayDelayTimes() { + return ttsPlayDelayTimes; + } + + public void setIsEnableService(boolean isEnableService) { + this.isEnableService = isEnableService; + } + + public boolean isEnableService() { + return isEnableService; + } + + public void setIsEnableOnlyReceiveContacts(boolean isEnableOnlyReceiveContacts) { + this.isEnableOnlyReceiveContacts = isEnableOnlyReceiveContacts; + } + + public boolean isEnableOnlyReceiveContacts() { + return isEnableOnlyReceiveContacts; + } + + public void setIsEnableTTS(boolean isEnableTTS) { + this.isEnableTTS = isEnableTTS; + } + + public boolean isEnableTTS() { + return isEnableTTS; + } + + public void setIsEnableTTSRuleMode(boolean isEnableTTSRuleMode) { + this.isEnableTTSRuleMode = isEnableTTSRuleMode; + } + + public boolean isEnableTTSRuleMode() { + return isEnableTTSRuleMode; + } + + /*public void setAppThemeID(int appThemeID) { + this.appThemeID = appThemeID; + } + + public int getAppThemeID() { + return appThemeID; + } + + public void setAppTheme(ThemeUtil.BaseTheme baseTheme) { + setAppThemeID(ThemeUtil.getThemeID(baseTheme)); + }*/ + + @Override + public String getName() { + return AppConfigBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + AppConfigBean bean = this; + jsonWriter.name("countryCode").value(bean.getCountryCode()); + jsonWriter.name("isMergeCountryCodePrefix").value(bean.isMergeCountryCodePrefix()); + jsonWriter.name("ttsPlayDelayTimes").value(bean.getTtsPlayDelayTimes()); + jsonWriter.name("isEnableService").value(bean.isEnableService()); + jsonWriter.name("isEnableOnlyReceiveContacts").value(bean.isEnableOnlyReceiveContacts()); + jsonWriter.name("isEnableTTS").value(bean.isEnableTTS()); + jsonWriter.name("isEnableTTSRuleMode").value(bean.isEnableTTSRuleMode()); + jsonWriter.name("isSMSRecycleProtectMode").value(bean.isSMSRecycleProtectMode()); + jsonWriter.name("protectModerRefuseChars").value(bean.getProtectModerRefuseChars()); + jsonWriter.name("protectModerReplaceChars").value(bean.getProtectModerReplaceChars()); + //jsonWriter.name("appThemeID").value(bean.getAppThemeID()); + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + AppConfigBean bean = new AppConfigBean(); + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (name.equals("countryCode")) { + bean.setCountryCode(jsonReader.nextString()); + } else if (name.equals("isMergeCountryCodePrefix")) { + bean.setIsMergeCountryCodePrefix(jsonReader.nextBoolean()); + } else if (name.equals("ttsPlayDelayTimes")) { + bean.setTtsPlayDelayTimes(jsonReader.nextInt()); + } else if (name.equals("isEnableService")) { + bean.setIsEnableService(jsonReader.nextBoolean()); + } else if (name.equals("isEnableOnlyReceiveContacts")) { + bean.setIsEnableOnlyReceiveContacts(jsonReader.nextBoolean()); + } else if (name.equals("isEnableTTS")) { + bean.setIsEnableTTS(jsonReader.nextBoolean()); + } else if (name.equals("isEnableTTSRuleMode")) { + bean.setIsEnableTTSRuleMode(jsonReader.nextBoolean()); + } else if (name.equals("isSMSRecycleProtectMode")) { + bean.setIsSMSRecycleProtectMode(jsonReader.nextBoolean()); + } else if (name.equals("protectModerRefuseChars")) { + bean.setProtectModerRefuseChars(jsonReader.nextString()); + } else if (name.equals("protectModerReplaceChars")) { + bean.setProtectModerReplaceChars(jsonReader.nextString()); + } /*else if (name.equals("appThemeID")) { + bean.setAppThemeID(jsonReader.nextInt()); + }*/ else { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return bean; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/AppConfigBean_V1.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/AppConfigBean_V1.java new file mode 100644 index 0000000..4c1b789 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/AppConfigBean_V1.java @@ -0,0 +1,88 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2023/06/30 23:21:27 + * @Describe 应用配置数据类,V1 旧版。 + */ +import cc.winboll.studio.mymessagemanager.utils.ThemeUtil; + +public class AppConfigBean_V1 { + + // 当前国家代码(如+8612345678901代码就是86.) + String countryCode = "86"; + // 是否合并的手机号码前缀 + boolean isMergeCountryCodePrefix = true; + // TT语音延时播放毫秒数 + int ttsPlayDelayTimes = 3000; + boolean enableService = false; + boolean enableOnlyReceiveContacts = false; + boolean enableTTS = false; + boolean enableTTSRuleMode = false; + //int appThemeID = ThemeUtil.getThemeID(ThemeUtil.BaseTheme.DEFAULT); + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } + + public String getCountryCode() { + return countryCode; + } + + public void setIsMergeCountryCodePrefix(boolean isMergeCountryCodePrefix) { + this.isMergeCountryCodePrefix = isMergeCountryCodePrefix; + } + + public boolean isMergeCountryCodePrefix() { + return isMergeCountryCodePrefix; + } + + public void setTtsPlayDelayTimes(int ttsPlayDelayTimes) { + this.ttsPlayDelayTimes = ttsPlayDelayTimes; + } + + public int getTtsPlayDelayTimes() { + return ttsPlayDelayTimes; + } + + public void setEnableService(boolean enableService) { + this.enableService = enableService; + } + + public boolean isEnableService() { + return enableService; + } + + public void setEnableOnlyReceiveContacts(boolean enableOnlyReceiveContacts) { + this.enableOnlyReceiveContacts = enableOnlyReceiveContacts; + } + + public boolean isEnableOnlyReceiveContacts() { + return enableOnlyReceiveContacts; + } + + public void setEnableTTS(boolean enableTTS) { + this.enableTTS = enableTTS; + } + + public boolean isEnableTTS() { + return enableTTS; + } + + public void setEnableTTSRuleMode(boolean enableTTSRuleMode) { + this.enableTTSRuleMode = enableTTSRuleMode; + } + + public boolean isEnableTTSRuleMode() { + return enableTTSRuleMode; + } + + /*public void setAppThemeID(int appThemeID) { + this.appThemeID = appThemeID; + } + + public int getAppThemeID() { + return appThemeID; + }*/ + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/ContractsBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/ContractsBean.java new file mode 100644 index 0000000..0a1cf81 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/ContractsBean.java @@ -0,0 +1,29 @@ +package cc.winboll.studio.mymessagemanager.beans; + +public class ContractsBean { + + private String mszName; + private String mszTelPhone; + + public ContractsBean(String szName, String szTelPhone) { + this.mszName = szName; + this.mszTelPhone = szTelPhone; + } + + public void setName(String szName) { + this.mszName = szName; + } + + public String getName() { + return mszName; + } + + public void setTelPhone(String szTelPhone) { + this.mszTelPhone = szTelPhone; + } + + public String getTelPhone() { + return mszTelPhone; + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/MessageNotificationBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/MessageNotificationBean.java new file mode 100644 index 0000000..338e215 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/MessageNotificationBean.java @@ -0,0 +1,53 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 13:10:44 + * @Describe 短信通知栏消息结构 + */ +public class MessageNotificationBean { + + private int messageId; + private String mszPhone; + private String mszTitle; + private String mszContent; + + public MessageNotificationBean(int messageId, String mszPhone, String mszTitle, String mszContent) { + this.messageId = messageId; + this.mszPhone = mszPhone; + this.mszTitle = mszTitle; + this.mszContent = mszContent; + } + + public void setMessageId(int messageId) { + this.messageId = messageId; + } + + public int getMessageId() { + return messageId; + } + + public void setPhone(String szPhone) { + this.mszPhone = szPhone; + } + + public String getPhone() { + return mszPhone; + } + + public void setTitle(String szTitle) { + this.mszTitle = szTitle; + } + + public String getTitle() { + return mszTitle; + } + + public void setContent(String szContent) { + this.mszContent = szContent; + } + + public String getContent() { + return mszContent; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/PhoneBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/PhoneBean.java new file mode 100644 index 0000000..b1e92ff --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/PhoneBean.java @@ -0,0 +1,39 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 13:10:44 + * @Describe 联系人信息类 + */ +public class PhoneBean { + + //联系人姓名 + private String mszName; + //电话号码 + private String mszTelPhone; + + public String getName() { + return mszName; + } + + public void setName(String szName) { + this.mszName = szName; + } + + public String getTelPhone() { + return mszTelPhone; + } + + public void setTelPhone(String szTelPhone) { + this.mszTelPhone = szTelPhone; + } + + public PhoneBean() { + } + + public PhoneBean(String szName, String szTelPhone) { + this.mszName = szName; + this.mszTelPhone = szTelPhone; + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSAcceptRuleBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSAcceptRuleBean.java new file mode 100644 index 0000000..377d27c --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSAcceptRuleBean.java @@ -0,0 +1,121 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/05/30 10:57:14 + * @Describe 短信接收规则类 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class SMSAcceptRuleBean extends BaseBean { + + public static final String TAG = "SMSAcceptRuleBean"; + + // 规则类型枚举 + public enum RuleType { ACCEPT, REFUSE, REGEXPPIUTILS_ISPPIOK_FALSE } + + // 用户ID + int userId = -1; + // 规则数据 + String ruleData = ""; + // 是否启用 + boolean isEnable = false; + // 规则类型 + RuleType ruleType = RuleType.REFUSE; + // 是否简单视图 + boolean isSimpleView = false; + + public SMSAcceptRuleBean() {} + + public SMSAcceptRuleBean(int userId, String ruleData, boolean isEnable, RuleType ruleType, boolean isSimpleView) { + this.userId = userId; + this.ruleData = ruleData; + this.isEnable = isEnable; + this.ruleType = ruleType; + this.isSimpleView = isSimpleView; + } + + public void setRuleType(RuleType ruleType) { + this.ruleType = ruleType; + } + + public RuleType getRuleType() { + return ruleType; + } + + public void setIsSimpleView(boolean isSimpleView) { + this.isSimpleView = isSimpleView; + } + + public boolean isSimpleView() { + return isSimpleView; + } + + public void setUserId(int userID) { + this.userId = userID; + } + + public int getUserId() { + return userId; + } + + public void setRuleData(String ruleData) { + this.ruleData = ruleData; + } + + public String getRuleData() { + return ruleData; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return SMSAcceptRuleBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + SMSAcceptRuleBean bean = this; + jsonWriter.name("userId").value(bean.getUserId()); + jsonWriter.name("ruleData").value(bean.getRuleData()); + jsonWriter.name("isEnable").value(bean.isEnable()); + jsonWriter.name("ruleType").value(bean.getRuleType().ordinal()); + jsonWriter.name("isSimpleView").value(bean.isSimpleView()); + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + SMSAcceptRuleBean bean = new SMSAcceptRuleBean(); + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (name.equals("userId")) { + bean.setUserId(jsonReader.nextInt()); + } else if (name.equals("ruleData")) { + bean.setRuleData(jsonReader.nextString()); + } else if (name.equals("isEnable")) { + bean.setIsEnable(jsonReader.nextBoolean()); + } else if (name.equals("ruleType")) { + bean.setRuleType(RuleType.values()[jsonReader.nextInt()]); + } else if (name.equals("isSimpleView")) { + bean.setIsSimpleView(jsonReader.nextBoolean()); + } else { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return bean; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSAcceptRuleBean_V1.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSAcceptRuleBean_V1.java new file mode 100644 index 0000000..4ac5797 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSAcceptRuleBean_V1.java @@ -0,0 +1,50 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/05/30 10:57:14 + * @Describe 短信接收规则类,V1 旧版。 + */ +public class SMSAcceptRuleBean_V1 { + + public static final String TAG = "SMSAcceptRuleBean_V1"; + + // 用户ID + String userID = ""; + // 规则数据 + String ruleData = ""; + // 是否启用 + boolean enable = false; + + public SMSAcceptRuleBean_V1() {} + + public SMSAcceptRuleBean_V1(String userID, String ruleData, boolean enable) { + this.userID = userID; + this.ruleData = ruleData; + this.enable = enable; + } + + public void setUserID(String userID) { + this.userID = userID; + } + + public String getUserID() { + return userID; + } + + public void setRuleData(String ruleData) { + this.ruleData = ruleData; + } + + public String getRuleData() { + return ruleData; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public boolean isEnable() { + return enable; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSBean.java new file mode 100644 index 0000000..c76df2f --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSBean.java @@ -0,0 +1,272 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/05/30 10:57:14 + * @Describe 短信信息类 + 参考资料: + https://blog.csdn.net/freeking101/article/details/121575985 + + 获取短信只需要得到 ContentResolver 就行了,它的 URI 主要有: + content://sms/ 所有短信 + content://sms/inbox 收件箱 + content://sms/sent 已发送 + content://sms/draft 草稿 + content://sms/outbox 发件箱 + content://sms/failed 发送失败 + content://sms/queued 待发送列表 + SMS 数据库中的字段如下: + _id 一个自增字段,从1开始 + thread_id 序号,同一发信人的id相同 + address 发件人手机号码 + person 联系人列表里的序号,陌生人为null + date 发件日期 + protocol 协议,分为: 0 SMS_RPOTO, 1 MMS_PROTO + read 是否阅读 0未读, 1已读 + status 状态 -1接收,0 complete, 64 pending, 128 failed + type ALL = 0;INBOX = 1;SENT = 2;DRAFT = 3;OUTBOX = 4;FAILED = 5; QUEUED = 6; + body 短信内容 + service_center 短信服务中心号码编号。如+8613800755500 + subject 短信的主题 + reply_path_present TP-Reply-Path + locked + */ +import android.content.ContentValues; +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class SMSBean extends BaseBean { + + //public enum Type { ALL(8), INBOX(0), SENT, DRAFT, OUTBOX, FAILED, QUEUED, TRASH } + public enum Type { ALL(0), INBOX(1), SENT(2), DRAFT(3), OUTBOX(4), FAILED(5), QUEUED(6), TRASH(7); + static String[] _mlistName = { "所有短信", "接收", "发送", "草稿", "发件箱", "发送失败", "待发送列表", "回收站" }; + private int value = 0; + private Type(int value) { //必须是private的,否则编译错误 + this.value = value; + } + } + + public enum ReadStatus { UNREAD, READ } + + transient private static String _ContentValuesName_address = "address"; + transient private static String _ContentValuesName_body = "body"; + transient private static String _ContentValuesName_read = "read"; + transient private static String _ContentValuesName_date = "date"; + + // 短信标识 + protected int id; + // 发件人手机号码 + protected String mszAddress; + // 短信内容 + protected String mszBody; + // 发件日期 + protected long mnDate; + // 短息归类 + protected Type mType; + // 是否阅读 + protected ReadStatus mReadStatus; + // 联系人列表里的序号,陌生人为null + protected int mnPerson; + + public SMSBean() { + this.id = -1; + this.mszAddress = ""; + this.mszBody = ""; + this.mnDate = 0; + this.mType = Type.INBOX; + this.mReadStatus = ReadStatus.UNREAD; + this.mnPerson = 0; + } + + public SMSBean(int id, String mszAddress, String mszBody, long mnDate, Type mType, ReadStatus mReadStatus, int mnPerson) { + this.id = id; + this.mszAddress = mszAddress; + this.mszBody = mszBody; + this.mnDate = mnDate; + this.mType = mType; + this.mReadStatus = mReadStatus; + this.mnPerson = mnPerson; + } + + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setAddress(String szAddress) { + this.mszAddress = szAddress; + } + + public String getAddress() { + return mszAddress; + } + + public void setBody(String szBody) { + this.mszBody = szBody; + } + + public String getBody() { + return mszBody; + } + + public void setDate(long date) { + this.mnDate = date; + } + + public long getDate() { + return mnDate; + } + + public void setType(Type type) { + this.mType = type; + } + + public Type getType() { + return mType; + } + + public void setReadStatus(ReadStatus readStatus) { + this.mReadStatus = readStatus; + } + + public ReadStatus getReadStatus() { + return mReadStatus; + } + + public void setPerson(int person) { + this.mnPerson = person; + } + + public int getPerson() { + return mnPerson; + } + + @Override + public String getName() { + return SMSBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + SMSBean bean = this; + jsonWriter.name("id").value(bean.getId()); + jsonWriter.name("mszAddress").value(bean.getAddress()); + jsonWriter.name("mszBody").value(bean.getBody()); + jsonWriter.name("mnDate").value(bean.getDate()); + jsonWriter.name("mType").value(bean.getType().ordinal()); + jsonWriter.name("mReadStatus").value(bean.getReadStatus().ordinal()); + jsonWriter.name("mnPerson").value(bean.getPerson()); + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if(super.initObjectsFromJsonReader(jsonReader, name)) { return true; } + else{ + if (name.equals("id")) { + setId(jsonReader.nextInt()); + } else if (name.equals("mszAddress")) { + setAddress(jsonReader.nextString()); + } else if (name.equals("mszBody")) { + setBody(jsonReader.nextString()); + } else if (name.equals("mnDate")) { + setDate(jsonReader.nextLong()); + } else if (name.equals("mType")) { + setType(Type.values()[jsonReader.nextInt()]); + } else if (name.equals("mReadStatus")) { + setReadStatus(ReadStatus.values()[jsonReader.nextInt()]); + } else if (name.equals("mnPerson")) { + setPerson(jsonReader.nextInt()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if(!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } + + public static ContentValues createOldSendedSMSContentValues(SMSBean smsBean) { + ContentValues result = new ContentValues(); + result.put(_ContentValuesName_address, smsBean.mszAddress); + result.put(_ContentValuesName_body, smsBean.mszBody); + result.put(_ContentValuesName_read, smsBean.mReadStatus.toString()); //"0" for have not read sms and "1" for have read sms + result.put(_ContentValuesName_date, Long.toString(smsBean.getDate())); + + return result; + } + + public static ContentValues createSendedSMSContentValues(SMSBean smsBean) { + ContentValues result = new ContentValues(); + result.put(_ContentValuesName_address, smsBean.mszAddress); + result.put(_ContentValuesName_body, smsBean.mszBody); + result.put(_ContentValuesName_read, smsBean.mReadStatus.toString()); //"0" for have not read sms and "1" for have read sms + result.put(_ContentValuesName_date, Long.toString(System.currentTimeMillis())); + + return result; + } + + public static String getTypeName(Type type) { + return Type._mlistName[type.ordinal()]; + } + + @Override + public String toString() { + String szResult = "\n"; + szResult += "mszAddress is (" + mszAddress + ")\n"; + szResult += "mszBody is (" + mszBody + ")\n"; + szResult += "mnDate is (" + Long.toString(mnDate) + ")\n"; + szResult += "mType is (" + mType.name() + ")\n"; + if (mReadStatus != null) { + szResult += "mReadStatus is (" + mReadStatus.name() + ")\n"; + } + szResult += "mnPerson is (" + Integer.toString(mnPerson) + ")\n"; + + + return szResult; + } + + public static void sortSMSByDateDesc(ArrayList list, boolean isDesc) { + Collections.sort(list, new SortSMSByDateDesc(isDesc)); + + } + + private static class SortSMSByDateDesc implements Comparator { + private boolean mIsDesc = true; + // isDesc 是否降序排列 + public SortSMSByDateDesc(boolean isDesc) { + mIsDesc = isDesc; + } + Collator cmp = Collator.getInstance(java.util.Locale.CHINA); + @Override + public int compare(SMSBean o1, SMSBean o2) { + boolean b0_1 = (o1.getDate() < o2.getDate()); + if (mIsDesc) { + return b0_1 ?1: -1; + } else { + return b0_1 ?-1: 1; + } + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSRecycleBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSRecycleBean.java new file mode 100644 index 0000000..aaecccc --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/SMSRecycleBean.java @@ -0,0 +1,127 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/07/20 01:51:44 + * @Describe 回收站短信存储类 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class SMSRecycleBean extends SMSBean { + + public static final String TAG = "SMSRecycleBean"; + + // 短信删除日期 + long deleteDate; + // 当前是否是简单视图 + boolean isSimpleView; + + public void setDeleteDate(long deleteDate) { + this.deleteDate = deleteDate; + } + + public long getDeleteDate() { + return deleteDate; + } + + public void setIsSimpleView(boolean isSimpleView) { + this.isSimpleView = isSimpleView; + } + + public boolean isSimpleView() { + return isSimpleView; + } + + public SMSRecycleBean() { + + } + + public SMSRecycleBean(SMSBean smsBean, long deleteDate) { + super.id = smsBean.getId(); + super.mszAddress = smsBean.getAddress(); + super.mszBody = smsBean.getBody(); + super.mnDate = smsBean.getDate(); + super.mType = smsBean.getType(); + super.mReadStatus = smsBean.getReadStatus(); + super.mnPerson = smsBean.getPerson(); + this.deleteDate = deleteDate; + this.isSimpleView = true; + } + + @Override + public String getName() { + return SMSRecycleBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + SMSRecycleBean bean = this; + jsonWriter.name("deleteDate").value(bean.getDeleteDate()); + jsonWriter.name("isSimpleView").value(bean.isSimpleView()); + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + /*SMSRecycleBean bean = new SMSRecycleBean((SMSBean)super.readBeanFromJsonReader(jsonReader), 0); + // 只有在读取完成后,才能获取整个JSON字符串 + String completeJson = jsonReader.toString(); + JsonReader newJsonReader = new JsonReader(new StringReader(completeJson)); + newJsonReader.setLenient(true); + LogUtils.d(TAG, completeJson);*/ + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if(super.initObjectsFromJsonReader(jsonReader, name)) { return true; } + else{ + if (name.equals("deleteDate")) { + setDeleteDate(jsonReader.nextLong()); + } else if (name.equals("isSimpleView")) { + setIsSimpleView(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + public static void sortSMSByDeleteDateDesc(ArrayList list, boolean isDesc) { + Collections.sort(list, new SortSMSByDeleteDateDesc(isDesc)); + } + + private static class SortSMSByDeleteDateDesc implements Comparator { + private boolean mIsDesc = true; + // isDesc 是否降序排列 + public SortSMSByDeleteDateDesc(boolean isDesc) { + mIsDesc = isDesc; + } + Collator cmp = Collator.getInstance(java.util.Locale.CHINA); + @Override + public int compare(SMSRecycleBean o1, SMSRecycleBean o2) { + boolean b0_1 = (o1.getDeleteDate() < o2.getDeleteDate()); + if (mIsDesc) { + return b0_1 ?1: -1; + } else { + return b0_1 ?-1: 1; + } + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSPlayRuleBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSPlayRuleBean.java new file mode 100644 index 0000000..1669a5f --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSPlayRuleBean.java @@ -0,0 +1,147 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/05/28 20:22:12 + * @Describe TTS 语音播放规则类 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class TTSPlayRuleBean extends BaseBean { + + public static final String TAG = "TTSPlayRuleBean"; + + // 用户ID + int userId = -1; + // TTS语音规则名称 + String ruleName = ""; + // 短信测试文本 + String demoSMSText = ""; + // 短信内容查询正则文本 + String patternText = ""; + // TTS语音播报正则文本 + String ttsRuleText = ""; + // 是否启用简单视图 + boolean isSimpleView = false; + // 是否启用规则 + boolean isEnable = false; + + public TTSPlayRuleBean() {} + + public TTSPlayRuleBean(int userId, String ruleName, String demoSMSText, String patternText, String ttsRuleText, boolean isSimpleView, boolean isEnable) { + this.userId = userId; + this.ruleName = ruleName; + this.demoSMSText = demoSMSText; + this.patternText = patternText; + this.ttsRuleText = ttsRuleText; + this.isSimpleView = isSimpleView; + this.isEnable = isEnable; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public int getUserId() { + return userId; + } + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public String getRuleName() { + return ruleName; + } + + public void setDemoSMSText(String demoSMSText) { + this.demoSMSText = demoSMSText; + } + + public String getDemoSMSText() { + return demoSMSText; + } + + public void setPatternText(String patternText) { + this.patternText = patternText; + } + + public String getPatternText() { + return patternText; + } + + public void setTtsRuleText(String ttsRuleText) { + this.ttsRuleText = ttsRuleText; + } + + public String getTtsRuleText() { + return ttsRuleText; + } + + public void setIsSimpleView(boolean isSimpleView) { + this.isSimpleView = isSimpleView; + } + + public boolean isSimpleView() { + return isSimpleView; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + + @Override + public String getName() { + return TTSPlayRuleBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + TTSPlayRuleBean bean = this; + jsonWriter.name("userId").value(bean.userId); + jsonWriter.name("ruleName").value(bean.ruleName); + jsonWriter.name("demoSMSText").value(bean.demoSMSText); + jsonWriter.name("patternText").value(bean.patternText); + jsonWriter.name("ttdRuleText").value(bean.ttsRuleText); + jsonWriter.name("isSimpleView").value(bean.isSimpleView); + jsonWriter.name("isEnable").value(bean.isEnable); + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + TTSPlayRuleBean bean = new TTSPlayRuleBean(); + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (name.equals("userId")) { + bean.setUserId(jsonReader.nextInt()); + } else if (name.equals("ruleName")) { + bean.setRuleName(jsonReader.nextString()); + } else if (name.equals("demoSMSText")) { + bean.setDemoSMSText(jsonReader.nextString()); + } else if (name.equals("patternText")) { + bean.setPatternText(jsonReader.nextString()); + } else if (name.equals("ttdRuleText")) { + bean.setTtsRuleText(jsonReader.nextString()); + } else if (name.equals("isSimpleView")) { + bean.setIsSimpleView(jsonReader.nextBoolean()); + } else if (name.equals("isEnable")) { + bean.setIsEnable(jsonReader.nextBoolean()); + } else { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return bean; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSPlayRuleBean_V1.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSPlayRuleBean_V1.java new file mode 100644 index 0000000..c6953de --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSPlayRuleBean_V1.java @@ -0,0 +1,281 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/05/28 20:22:12 + * @Describe TTS 语音播放规则类,V1 旧版。 + */ +import android.content.Context; +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.utils.FileUtil; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; + +public class TTSPlayRuleBean_V1 { + + public static final String TAG = "TTSPlayRuleBean2"; + + // 用户ID + int userId = -1; + // TTS语音规则名称 + String ruleName = ""; + // 短信测试文本 + String demoSMSText = ""; + // 短信内容查询正则文本 + String patternText = ""; + // TTS语音播报正则文本 + String ttsRuleText = ""; + // 是否启用简单视图 + boolean isSimpleView = false; + // 是否启用规则 + boolean isEnable = false; + + public TTSPlayRuleBean_V1(int userId, String ruleName, String demoSMSText, String patternText, String ttsRuleText, boolean isSimpleView, boolean isEnable) { + this.userId = userId; + this.ruleName = ruleName; + this.demoSMSText = demoSMSText; + this.patternText = patternText; + this.ttsRuleText = ttsRuleText; + this.isSimpleView = isSimpleView; + this.isEnable = isEnable; + } + + public TTSPlayRuleBean_V1() {} + + public void setRuleName(String ruleName) { + this.ruleName = ruleName; + } + + public String getRuleName() { + return ruleName; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public int getUserId() { + return userId; + } + + public void setDemoSMSText(String demoSMSText) { + this.demoSMSText = demoSMSText; + } + + public String getDemoSMSText() { + return demoSMSText; + } + + public void setPatternText(String patternText) { + this.patternText = patternText; + } + + public String getPatternText() { + return patternText; + } + + public void setTtsRuleText(String ttsRuleText) { + this.ttsRuleText = ttsRuleText; + } + + public String getTtsRuleText() { + return ttsRuleText; + } + + public void setIsSimpleView(boolean isSimpleView) { + this.isSimpleView = isSimpleView; + } + + public boolean isSimpleView() { + return isSimpleView; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + static String getBeanJsonFilePath(Context context) { + return context.getExternalFilesDir(TAG) + "/" + TAG + ".json"; + } + + static String getBeanListJsonFilePath(Context context) { + return context.getExternalFilesDir(TAG) + "/" + TAG + "_List.json"; + } + + static void writeBean(JsonWriter writer, TTSPlayRuleBean_V1 bean) throws IOException { + // 开始 JSON 对象 + writer.beginObject(); + // 写入键值对 + writer.name("userId").value(bean.userId); + writer.name("ruleName").value(bean.ruleName); + writer.name("demoSMSText").value(bean.demoSMSText); + writer.name("patternText").value(bean.patternText); + writer.name("ttdRuleText").value(bean.ttsRuleText); + writer.name("isSimpleView").value(bean.isSimpleView); + writer.name("isEnable").value(bean.isEnable); + // 结束 JSON 对象 + writer.endObject(); + } + + static TTSPlayRuleBean_V1 parseBean(JsonReader jsonReader) { + try { + TTSPlayRuleBean_V1 bean = new TTSPlayRuleBean_V1(); + // 开始 JSON 对象 + jsonReader.beginObject(); + // 写入键值对 + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (name.equals("ruleName")) { + bean.setRuleName(jsonReader.nextString()); + } else if (name.equals("userId")) { + bean.setUserId(jsonReader.nextInt()); + } else if (name.equals("demoSMSText")) { + bean.setDemoSMSText(jsonReader.nextString()); + } else if (name.equals("patternText")) { + bean.setPatternText(jsonReader.nextString()); + } else if (name.equals("ttdRuleText")) { + bean.setTtsRuleText(jsonReader.nextString()); + } else if (name.equals("isSimpleView")) { + bean.setIsSimpleView(jsonReader.nextBoolean()); + } else if (name.equals("isEnable")) { + bean.setIsEnable(jsonReader.nextBoolean()); + } else { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return bean; + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return null; + } + + static ArrayList parseBeanList(String beanList) { + try { + StringReader stringReader = new StringReader(beanList); + JsonReader jsonReader = new JsonReader(stringReader); + ArrayList list = new ArrayList(); + jsonReader.beginArray(); + while (jsonReader.hasNext()) { + TTSPlayRuleBean_V1 bean = parseBean(jsonReader); + if (bean != null) { + list.add(bean); + } + } + jsonReader.endArray(); + return list; + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return null; + } + + @Override + public String toString() { + // 创建 JsonWriter 对象 + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.setIndent(" "); + try { + writeBean(jsonWriter, this); + return stringWriter.toString(); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + // 获取 JSON 字符串 + return ""; + } + + public static String toStringByBeanList(ArrayList beanList) { + try { + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.setIndent(" "); + writer.beginArray(); + for (TTSPlayRuleBean_V1 bean : beanList) { + writeBean(writer, bean); + } + writer.endArray(); + writer.close(); + return stringWriter.toString(); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return ""; + } + + public static TTSPlayRuleBean_V1 parseBean(String szBean) { + // 创建 JsonWriter 对象 + StringReader stringReader = new StringReader(szBean); + JsonReader jsonReader = new JsonReader(stringReader); + return parseBean(jsonReader); + } + + /*public static TTSPlayRuleBean_V1 loadBean(Context context) { + return loadBeanFromFile(getBeanJsonFilePath(context)); + } + + public static TTSPlayRuleBean_V1 loadBeanFromFile(String szFilePath) { + TTSPlayRuleBean_V1 bean = null; + try { + String szJson = FileUtil.readFile(szFilePath); + bean = TTSPlayRuleBean_V1.parseBean(szJson); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return bean; + } + + public static void saveBean(Context context, TTSPlayRuleBean_V1 bean) { + saveBeanToFile(getBeanJsonFilePath(context), bean); + } + + public static void saveBeanToFile(String szFilePath, TTSPlayRuleBean_V1 bean) { + try { + String szJson = bean.toString(); + FileUtil.writeFile(szFilePath, szJson); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + } + + public static ArrayList loadBeanList(Context context) { + return loadBeanListFromFile(getBeanListJsonFilePath(context)); + }*/ + + public static ArrayList loadBeanListFromFile(String szFilePath) { + ArrayList beanList = null; + try { + String szListJson = FileUtil.readFile(szFilePath); + beanList = TTSPlayRuleBean_V1.parseBeanList(szListJson); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return beanList; + } + + /*public static boolean saveBeanList(Context context, ArrayList beanList) { + return saveBeanListToFile(getBeanListJsonFilePath(context), beanList); + } + + public static boolean saveBeanListToFile(String szFilePath, ArrayList beanList) { + try { + String szJson = TTSPlayRuleBean_V1.toStringByBeanList(beanList); + FileUtil.writeFile(szFilePath, szJson); + return true; + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return false; + }*/ +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSSpeakTextBean.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSSpeakTextBean.java new file mode 100644 index 0000000..bd57042 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/beans/TTSSpeakTextBean.java @@ -0,0 +1,24 @@ +package cc.winboll.studio.mymessagemanager.beans; + +/** + * @Author ZhanGSKen + * @Date 2024/05/28 20:22:12 + * @Describe TTS 语音播放文本内容类 + */ +import java.io.Serializable; + +public class TTSSpeakTextBean implements Serializable { + + transient public static final String TAG = "TTSSpeakTextBean"; + + // 延迟播放 + public int mnDelay = 0; + // 语音播放内容 + public String mszSpeakContent = ""; + + public TTSSpeakTextBean(int nDelay, String szSpeakContent) { + this.mnDelay = nDelay; + this.mszSpeakContent = szSpeakContent; + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/dialogs/CharsetRefuseEditDialog.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/dialogs/CharsetRefuseEditDialog.java new file mode 100644 index 0000000..d93fc15 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/dialogs/CharsetRefuseEditDialog.java @@ -0,0 +1,148 @@ +package cc.winboll.studio.mymessagemanager.dialogs; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/13 17:04 + * @Describe 字符集编辑拒绝对话框 + * 包含标题、300dp×300dp多行编辑框、确定/取消按钮(确定在右,取消在左) + * 支持预制文本初始化、编辑内容回传 + */ +public class CharsetRefuseEditDialog extends Dialog { + public static final String TAG = "CharsetRefuseEditDialog"; + // 文本回传接口 + public interface OnTextConfirmListener { + void onTextConfirmed(String editText); // 确定按钮点击时回传编辑后的文本 + } + + private final OnTextConfirmListener mListener; // 外部传入的回传接口 + private final String mPreText; // 预制文本框的内容 + private EditText mEditText; // 多行文本编辑框 + + /** + * 构造函数 + * @param context 上下文 + * @param listener 文本回传接口(外部实现) + * @param preText 预制的编辑框初始文本 + */ + public CharsetRefuseEditDialog(Context context, OnTextConfirmListener listener, String preText) { + super(context); + this.mListener = listener; + this.mPreText = preText; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 禁用对话框默认标题,使用自定义标题 + requestWindowFeature(Window.FEATURE_NO_TITLE); + // 初始化对话框布局 + initView(); + } + + /** + * 初始化对话框布局(标题+编辑框+按钮) + */ + private void initView() { + // 根布局:垂直线性布局 + LinearLayout rootLayout = new LinearLayout(getContext()); + rootLayout.setOrientation(LinearLayout.VERTICAL); + rootLayout.setPadding(20, 20, 20, 20); + rootLayout.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + + // 1. 添加标题栏 + TextView titleTv = new TextView(getContext()); + titleTv.setText("拒绝显示字符集编辑"); + titleTv.setTextSize(18); + titleTv.setTextColor(Color.BLACK); + titleTv.setGravity(Gravity.CENTER); + titleTv.setPadding(0, 0, 0, 20); // 标题与编辑框间距 + rootLayout.addView(titleTv); + + // 2. 添加多行编辑框(300dp×300dp) + mEditText = new EditText(getContext()); + // 转换dp为px(适配不同屏幕密度) + int dp300 = dp2px(getContext(), 300); + LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(dp300, dp300); + mEditText.setLayoutParams(editParams); + mEditText.setLines(5); // 多行显示 + mEditText.setHint("请输入内容"); + mEditText.setText(mPreText); // 设置预制文本 + mEditText.setSelection(mPreText.length()); // 光标定位到文本末尾 + rootLayout.addView(mEditText); + + // 3. 添加按钮栏(水平布局,确定在右、取消在左) + LinearLayout btnLayout = new LinearLayout(getContext()); + btnLayout.setOrientation(LinearLayout.HORIZONTAL); + btnLayout.setGravity(Gravity.RIGHT); // 整体右对齐,实现确定在右、取消在左 + btnLayout.setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + btnLayout.setPadding(0, 20, 0, 0); // 按钮栏与编辑框间距 + rootLayout.addView(btnLayout); + + // 3.1 取消按钮(左侧) + TextView cancelBtn = new TextView(getContext()); + cancelBtn.setText("取消"); + cancelBtn.setTextSize(16); + cancelBtn.setTextColor(Color.parseColor("#666666")); + cancelBtn.setPadding(20, 10, 20, 10); + cancelBtn.setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View view) { + dismiss(); + } + }); // 关闭对话框 + btnLayout.addView(cancelBtn); + + // 3.2 确定按钮(右侧) + TextView confirmBtn = new TextView(getContext()); + confirmBtn.setText("确定"); + confirmBtn.setTextSize(16); + confirmBtn.setTextColor(Color.parseColor("#0066CC")); + confirmBtn.setPadding(20, 10, 20, 10); + confirmBtn.setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View view) { + if (mListener != null) { + // 回传编辑后的文本 + mListener.onTextConfirmed(mEditText.getText().toString().trim()); + } + dismiss(); // 关闭对话框 + } + }); + btnLayout.addView(confirmBtn); + + // 设置对话框内容布局 + setContentView(rootLayout); + } + + /** + * dp转px工具方法 + * @param context 上下文 + * @param dpValue dp值 + * @return 对应的px值 + */ + private int dp2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } +} + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/enums/ThemeStyleEnum.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/enums/ThemeStyleEnum.java new file mode 100644 index 0000000..417174f --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/enums/ThemeStyleEnum.java @@ -0,0 +1,93 @@ +package cc.winboll.studio.mymessagemanager.enums; + +import android.content.Context; +import android.content.SharedPreferences; +import androidx.annotation.StyleRes; +import cc.winboll.studio.mymessagemanager.R; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/09 14:15 + * @Describe 主题风格枚举类 - 含SharedPreferences存取方法(主题持久化) + */ +public enum ThemeStyleEnum { + // 主题枚举项(与原逻辑完全对应) + DEPTH_THEME(R.id.item_depththeme, R.style.MyDepthAESTheme), + SKY_THEME(R.id.item_skytheme, R.style.MySkyAESTheme), + GOLDEN_THEME(R.id.item_goldentheme, R.style.MyGoldenAESTheme), + MEMOR_THEME(R.id.item_memortheme, R.style.MyMemorAESTheme), + TAO_THEME(R.id.item_taotheme, R.style.MyTaoAESTheme), + DEFAULT_THEME(R.id.item_defaulttheme, R.style.MyAppTheme); + + // ---------------------- 基础字段(原逻辑保留) ---------------------- + private final int menuId; + @StyleRes + private final int styleId; + + ThemeStyleEnum(int menuId, @StyleRes int styleId) { + this.menuId = menuId; + this.styleId = styleId; + } + + public int getMenuId() { + return menuId; + } + + @StyleRes + public int getStyleId() { + return styleId; + } + + // ---------------------- SharedPreferences 配置(新增) ---------------------- + // SP文件名:主题配置(建议与枚举类关联,便于查找) + private static final String SP_THEME_NAME = "sp_theme_config"; + // SP存储键:当前选中的主题Style ID + private static final String KEY_CURRENT_THEME_STYLE_ID = "current_theme_style_id"; + + // ---------------------- 核心方法:保存主题到SP(新增) ---------------------- + /** + * 保存当前选中的主题Style ID到SharedPreferences + * @param context 上下文(Activity/Application均可) + * @param theme 要保存的主题枚举项(如ThemeStyleEnum.DEFAULT_THEME) + */ + public static void saveThemeToSP(Context context, ThemeStyleEnum theme) { + if (context == null || theme == null) return; + // 获取SP实例(私有模式,仅当前App可访问) + SharedPreferences sp = context.getSharedPreferences(SP_THEME_NAME, Context.MODE_PRIVATE); + // 存入主题对应的Style ID + sp.edit().putInt(KEY_CURRENT_THEME_STYLE_ID, theme.getStyleId()).apply(); + } + + // ---------------------- 核心方法:从SP读取主题(新增) ---------------------- + /** + * 从SharedPreferences读取保存的主题,无存储时返回默认主题 + * @param context 上下文 + * @return 保存的主题枚举项(默认返回DEFAULT_THEME) + */ + public static ThemeStyleEnum getThemeFromSP(Context context) { + if (context == null) return DEFAULT_THEME; + // 读取SP中保存的Style ID + SharedPreferences sp = context.getSharedPreferences(SP_THEME_NAME, Context.MODE_PRIVATE); + int savedStyleId = sp.getInt(KEY_CURRENT_THEME_STYLE_ID, DEFAULT_THEME.getStyleId()); + + // 根据保存的Style ID匹配对应的枚举项 + for (ThemeStyleEnum theme : values()) { + if (theme.getStyleId() == savedStyleId) { + return theme; + } + } + // 无匹配时返回默认主题(防止异常) + return DEFAULT_THEME; + } + + // ---------------------- 辅助方法:根据菜单ID获取主题(原逻辑保留并优化) ---------------------- + public static ThemeStyleEnum getThemeByMenuId(int menuId) { + for (ThemeStyleEnum theme : values()) { + if (theme.getMenuId() == menuId) { + return theme; + } + } + return null; + } +} + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/receivers/MainReceiver.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/receivers/MainReceiver.java new file mode 100644 index 0000000..1ae08e0 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/receivers/MainReceiver.java @@ -0,0 +1,43 @@ +package cc.winboll.studio.mymessagemanager.receivers; + +/** + * @Author ZhanGSKen + * @Date 2024/05/28 20:22:12 + * @Describe 在文件 AndroidManifest.xml 注册监听的广播接收类, + * 用于接收系统启动完毕的广播消息。 + */ +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.services.MainService; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; + +public class MainReceiver extends BroadcastReceiver { + + public static String TAG = "ManagerReceiver"; + + static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + + AppConfigUtil mConfigUtil; + + @Override + public void onReceive(Context context, Intent intent) { + String szAction = intent.getAction(); + if (szAction.equals(ACTION_BOOT_COMPLETED)) { + mConfigUtil = AppConfigUtil.getInstance(context); + if (mConfigUtil.mAppConfigBean.isEnableService()) { + Intent intentService = new Intent(context, MainService.class); + if (Build.VERSION.SDK_INT >= 26) { + context.startForegroundService(intentService); + } else { + context.startService(intentService); + } + LogUtils.i(TAG, "System Boot And Start ManagerService Completed!"); + } + } + + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/receivers/SMSRecevier.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/receivers/SMSRecevier.java new file mode 100644 index 0000000..fed3ba1 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/receivers/SMSRecevier.java @@ -0,0 +1,109 @@ +package cc.winboll.studio.mymessagemanager.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.App; +import cc.winboll.studio.mymessagemanager.activitys.SMSActivity; +import cc.winboll.studio.mymessagemanager.beans.SMSBean; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; +import cc.winboll.studio.mymessagemanager.utils.PhoneUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSReceiveRuleUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSRecycleUtil; +import cc.winboll.studio.mymessagemanager.utils.SMSUtil; +import cc.winboll.studio.mymessagemanager.utils.TTSPlayRuleUtil; +import cc.winboll.studio.mymessagemanager.utils.NotificationHelper; + +public class SMSRecevier extends BroadcastReceiver { + + public static String TAG = "SMSRecevier"; + + public static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; + + public SMSRecevier() { + super(); + //LogUtils.d(TAG, "SMSRecevier()"); + } + + /*public void init(ManagerService.SMSListener smsListener) { + mSMSListener = smsListener; + }*/ + + @Override + public void onReceive(Context context, Intent intent) { + + String szAction = intent.getAction(); + if (szAction.equals(ACTION_SMS_RECEIVED)) { + LogUtils.d(TAG, "ACTION_SMS_RECEIVED"); + String szSmsBody = SMSUtil.getSmsBody(intent); + String szSmsAddress = SMSUtil.getSmsAddress(intent); + AppConfigUtil configUtil = AppConfigUtil.getInstance(context); + boolean isEnableTTS = configUtil.mAppConfigBean.isEnableTTS(); + boolean isEnableTTSAnalyzeMode = configUtil.mAppConfigBean.isEnableTTSRuleMode(); + + if (checkIsSMSOK(context, szSmsBody, szSmsAddress)) { + int nResultId = SMSUtil.saveReceiveSms(context, szSmsAddress, szSmsBody, "0", System.currentTimeMillis(), "inbox"); + if (nResultId >= 0) { + NotificationHelper notificationHelper = new NotificationHelper(context); + notificationHelper.sendSMSReceivedMessage(nResultId, szSmsAddress, szSmsBody); + LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(SMSActivity.ACTION_NOTIFY_SMS_CHANGED)); + LogUtils.d(TAG, "<" + szSmsAddress + "> : ( " + szSmsBody + " ) [SAVED]"); + if (isEnableTTS) { + if (isEnableTTSAnalyzeMode) { + TTSPlayRuleUtil ttsPlayRuleUtil = TTSPlayRuleUtil.getInstance(context); + ttsPlayRuleUtil.speakTTSAnalyzeModeText(szSmsBody, configUtil.mAppConfigBean.getTtsPlayDelayTimes()); + } else { + TTSPlayRuleUtil.speakText(context, szSmsBody, configUtil.mAppConfigBean.getTtsPlayDelayTimes(), 0); + } + + } + } + + abortBroadcast(); + } else { + SMSBean bean = new SMSBean(-1, szSmsAddress, szSmsBody, System.currentTimeMillis(), SMSBean.Type.INBOX, SMSBean.ReadStatus.UNREAD, 0); + SMSRecycleUtil.addSMSRecycleItem(context, bean); + } + } + } + + // + // 检查短信是否在接收设定规则内 + // + public static boolean checkIsSMSOK(Context context, String szSmsBody, String szSmsAddress) { + PhoneUtil phoneUtil = new PhoneUtil(context); + boolean isPhoneInContacts = phoneUtil.isPhoneInContacts(szSmsAddress); + LogUtils.d(TAG, String.format("isPhoneInContacts %s", isPhoneInContacts)); + + boolean isPhoneByDigit = phoneUtil.isPhoneByDigit(szSmsAddress); + LogUtils.d(TAG, String.format("isPhoneByDigit %s", isPhoneByDigit)); + + AppConfigUtil configUtil = AppConfigUtil.getInstance(context); + boolean isOnlyReceiveContacts = configUtil.mAppConfigBean.isEnableOnlyReceiveContacts(); + LogUtils.d(TAG, String.format("isOnlyReceiveContacts %s", isOnlyReceiveContacts)); + + boolean isInSMSAcceptRule = SMSReceiveRuleUtil.getInstance(context, false).checkIsSMSAcceptInRule(context, szSmsBody); + LogUtils.d(TAG, String.format("isInSMSAcceptRule %s", isInSMSAcceptRule)); + + // 启用了只接受通讯录,通讯录里有记录 + if (isOnlyReceiveContacts && isPhoneInContacts) { + return true; + } + // 如果不是数字通讯地址,但是在通讯录内 + if (!isPhoneByDigit && isPhoneInContacts) { + return true; + } + // 通讯地址是数字,并且在短信接收规则内。 + if (isPhoneByDigit && isInSMSAcceptRule) { + return true; + } + + return false; + } +} + + + + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/AssistantService.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/AssistantService.java new file mode 100644 index 0000000..c4ef6c8 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/AssistantService.java @@ -0,0 +1,96 @@ +package cc.winboll.studio.mymessagemanager.services; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用主要服务组件类守护进程服务组件类 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; +import cc.winboll.studio.mymessagemanager.utils.ServiceUtil; + +public class AssistantService extends Service { + + public final static String TAG = "AssistantService"; + + MyServiceConnection mMyServiceConnection; + volatile boolean mIsServiceRunning; + AppConfigUtil mConfigUtil; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + mConfigUtil = AppConfigUtil.getInstance(this); + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + // 设置运行参数 + mIsServiceRunning = false; + run(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + run(); + return START_STICKY; + } + + @Override + public void onDestroy() { + mIsServiceRunning = false; + super.onDestroy(); + } + + // + // 运行服务内容 + // + void run() { + mConfigUtil.reLoadConfig(); + if (mConfigUtil.mAppConfigBean.isEnableService()) { + if (mIsServiceRunning == false) { + // 设置运行状态 + mIsServiceRunning = true; + // 唤醒和绑定主进程 + wakeupAndBindMain(); + } + } + } + + // + // 唤醒和绑定主进程 + // + void wakeupAndBindMain() { + if (ServiceUtil.isServiceAlive(getApplicationContext(), MainService.class.getName()) == false) { + startForegroundService(new Intent(AssistantService.this, MainService.class)); + } + + bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + + // + // 主进程与守护进程连接时需要用到此类 + // + class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mConfigUtil.reLoadConfig(); + if (mConfigUtil.mAppConfigBean.isEnableService()) { + wakeupAndBindMain(); + } + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/DefaultSMSManagerService.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/DefaultSMSManagerService.java new file mode 100644 index 0000000..db5f207 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/DefaultSMSManagerService.java @@ -0,0 +1,21 @@ +package cc.winboll.studio.mymessagemanager.services; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:48:01 + * @Describe 默认短信应用服务组件类 + * 注册安卓系统默认短信应用使用。 + */ +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class DefaultSMSManagerService extends Service { + + public static final String TAG = "DefaultSMSManagerService"; + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/MainService.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/MainService.java new file mode 100644 index 0000000..6e2f656 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/MainService.java @@ -0,0 +1,149 @@ +package cc.winboll.studio.mymessagemanager.services; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用主要服务组件类 + */ +import android.app.Notification; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.IBinder; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.MainActivity; +import cc.winboll.studio.mymessagemanager.receivers.SMSRecevier; +import cc.winboll.studio.mymessagemanager.services.MainService; +import cc.winboll.studio.mymessagemanager.utils.AppConfigUtil; +import cc.winboll.studio.mymessagemanager.utils.NotificationHelper; +import cc.winboll.studio.mymessagemanager.utils.ServiceUtil; + +public class MainService extends Service { + + public static String TAG = "ManagerService"; + + // 前台服务通知工具 + NotificationHelper mNotificationHelper; + Notification notification; + AppConfigUtil mConfigUtil; + //MyBinder mMyBinder; + MyServiceConnection mMyServiceConnection; + volatile static boolean _mIsServiceAlive; + SMSRecevier mSMSRecevier; + + @Override + public IBinder onBind(Intent intent) { + //return mMyBinder; + return null; + } + + @Override + public void onCreate() { + LogUtils.d(TAG, "onCreate"); + super.onCreate(); + _mIsServiceAlive = false; + mConfigUtil = AppConfigUtil.getInstance(this); + + //mMyBinder = new MyBinder(); + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + + // 运行服务内容 + run(); + + } + + private void run() { + //LogUtils.d(TAG, "run"); + mConfigUtil.reLoadConfig(); + if (mConfigUtil.mAppConfigBean.isEnableService()) { + if (_mIsServiceAlive == false) { + // 设置运行状态 + _mIsServiceAlive = true; + //LogUtils.d(TAG, "_mIsServiceAlive set to true."); + + // 唤醒守护进程 + wakeupAndBindAssistant(); + + // 运行其它服务内容 + IntentFilter localIntentFilter = new IntentFilter(SMSRecevier.ACTION_SMS_RECEIVED); + localIntentFilter.setPriority(1); + mSMSRecevier = new SMSRecevier(); + registerReceiver(mSMSRecevier, localIntentFilter); + + // 显示前台通知栏 + NotificationHelper helper = new NotificationHelper(this); + Intent intent = new Intent(this, MainActivity.class); + notification = helper.showForegroundNotification(intent, getString(R.string.app_name), getString(R.string.text_aboutservernotification)); + startForeground(NotificationHelper.FOREGROUND_NOTIFICATION_ID, notification); + + ToastUtils.show("Service is start."); + LogUtils.i(TAG, "Service is start."); + } + } + } + + public interface SMSListener { + void speakMessage(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(mSMSRecevier); + + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + //return super.onStartCommand(intent, flags, startId); + run(); + mConfigUtil.reLoadConfig(); + return mConfigUtil.mAppConfigBean.isEnableService() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId); + } + + /*private class MyBinder extends IMyAidlInterface.Stub { + @Override + public String getServiceName() { + return MainService.class.getSimpleName(); + } + }*/ + + // 主进程与守护进程连接时需要用到此类 + // + private class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + //LogUtils.d(TAG, "call onServiceConnected(...)"); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + //LogUtils.d(TAG, "call onServiceConnected(...)"); + mConfigUtil.reLoadConfig(); + if (mConfigUtil.mAppConfigBean.isEnableService()) { + // 唤醒守护进程 + wakeupAndBindAssistant(); + } + } + } + + // 唤醒和绑定守护进程 + // + void wakeupAndBindAssistant() { + if (ServiceUtil.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) { + startService(new Intent(MainService.this, AssistantService.class)); + //LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService"); + bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + } + + + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/TTSPlayService.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/TTSPlayService.java new file mode 100644 index 0000000..c2f9bdd --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/services/TTSPlayService.java @@ -0,0 +1,42 @@ +package cc.winboll.studio.mymessagemanager.services; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe TTS 语音播放服务组件类 + */ +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import cc.winboll.studio.mymessagemanager.beans.TTSSpeakTextBean; +import cc.winboll.studio.mymessagemanager.utils.TextToSpeechUtil; +import java.util.ArrayList; + +public class TTSPlayService extends Service { + + public static final String TAG = "TTSService"; + + public static final String EXTRA_SPEAKDATA = "EXTRA_SPEAKDATA"; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + ArrayList listTTSSpeakTextBean = (ArrayList)intent.getSerializableExtra(EXTRA_SPEAKDATA); + if (listTTSSpeakTextBean != null) { + + TextToSpeechUtil.getInstance(this).speekTTSList(listTTSSpeakTextBean); + + //Toast.makeText(getApplication(), "onStartCommand", Toast.LENGTH_SHORT).show(); + //TTSThread ttsThread = new TTSThread(TTSService.this, listTTSSpeakTextBean); + //ttsThread.start(); + } + } + + return super.onStartCommand(intent, flags, startId); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/AddressUtils_Test.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/AddressUtils_Test.java new file mode 100644 index 0000000..e310a82 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/AddressUtils_Test.java @@ -0,0 +1,28 @@ +package cc.winboll.studio.mymessagemanager.unittest; +import android.content.Context; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.utils.AddressUtils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/01 13:07:32 + * @Describe AddressUtils Test + */ +public class AddressUtils_Test { + + public static final String TAG = "AddressUtils_Test"; + + public static void main(Context context) { + String szSmsBody = "无影无迹"; + String szSmsAddress = "无名小辈"; + LogUtils.d(TAG, String.format("szSmsAddress %s\n getFormattedAddress : %s", szSmsAddress, AddressUtils.getFormattedAddress(szSmsAddress))); + szSmsAddress = "13172887736"; + LogUtils.d(TAG, String.format("szSmsAddress %s\n getFormattedAddress : %s", szSmsAddress, AddressUtils.getFormattedAddress(szSmsAddress))); + szSmsAddress = "+8613172887736"; + LogUtils.d(TAG, String.format("szSmsAddress %s\n getFormattedAddress : %s", szSmsAddress, AddressUtils.getFormattedAddress(szSmsAddress))); + szSmsAddress = "8613172887736"; + LogUtils.d(TAG, String.format("szSmsAddress %s\n getFormattedAddress : %s", szSmsAddress, AddressUtils.getFormattedAddress(szSmsAddress))); + + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/SMSRecevier_Test.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/SMSRecevier_Test.java new file mode 100644 index 0000000..0d9257f --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/SMSRecevier_Test.java @@ -0,0 +1,56 @@ +package cc.winboll.studio.mymessagemanager.unittest; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/25 19:02:15 + * @Describe SMSRecevier 测试类 + */ +import android.content.Context; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.receivers.SMSRecevier; + +public class SMSRecevier_Test { + + public static final String TAG = "SMSRecevier_Test"; + + public static void main(Context context) { + String szSmsBody = "无影无迹"; + String szSmsAddress = "无名小辈"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "无影无迹"; + szSmsAddress = "淘宝物流"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "无影无迹"; + szSmsAddress = "1?0"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "无影无迹"; + szSmsAddress = "10000"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "【UC】无影无迹"; + szSmsAddress = "无名小辈"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "【UC】无影无迹"; + szSmsAddress = "10000"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "【UC】无影无迹"; + szSmsAddress = "13172887736"; + test1(context, szSmsBody, szSmsAddress); + + szSmsBody = "【UC】无影无迹"; + szSmsAddress = "+8613172887736"; + test1(context, szSmsBody, szSmsAddress); + + } + + public static void test1(Context context, String szSmsBody, String szSmsAddress) { + + boolean isSMSOK = SMSRecevier.checkIsSMSOK(context, szSmsBody, szSmsAddress); + LogUtils.d(TAG, String.format("szSmsBody : %s\nszSmsAddress : %s\nisSMSOK : %s", szSmsBody, szSmsAddress, isSMSOK)); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/UnitTestActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/UnitTestActivity.java new file mode 100644 index 0000000..d34fd81 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/unittest/UnitTestActivity.java @@ -0,0 +1,36 @@ +package cc.winboll.studio.mymessagemanager.unittest; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/25 19:00:10 + * @Describe 应用单元测试窗口 + */ +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.LogView; +import cc.winboll.studio.mymessagemanager.R; + +public class UnitTestActivity extends Activity { + + public static final String TAG = "UnitTestActivity"; + + LogView mLogView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_unittest); + + mLogView = findViewById(R.id.logview); + mLogView.start(); + } + + public void onMain(View view) { + LogUtils.d(TAG, "SMSRecevier_Test"); + SMSRecevier_Test.main(this); + LogUtils.d(TAG, "AddressUtils_Test"); + AddressUtils_Test.main(this); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AddressUtils.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AddressUtils.java new file mode 100644 index 0000000..fd1566d --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AddressUtils.java @@ -0,0 +1,20 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/01 13:03:16 + * @Describe 通信录地址工具 + */ +public class AddressUtils { + + public static final String TAG = "AddressUtils"; + + public static String getFormattedAddress(String address) { + if (address != null && address.matches("[+]?\\d+")) { + return address; + } else { + return "【" + address + "】"; + } + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppConfigUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppConfigUtil.java new file mode 100644 index 0000000..140f6d8 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppConfigUtil.java @@ -0,0 +1,55 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/06/02 21:43:52 + * @Describe 应用配置工具类 + */ +import android.content.Context; +import cc.winboll.studio.mymessagemanager.beans.AppConfigBean; + +public class AppConfigUtil { + + public static final String TAG = "AppConfigUtil"; + + static AppConfigUtil _mConfigUtil; + Context mContext; + public AppConfigBean mAppConfigBean; + + AppConfigUtil(Context context) { + mContext = context; + mAppConfigBean = AppConfigBean.loadBean(context, AppConfigBean.class); + if(mAppConfigBean == null) { + mAppConfigBean = new AppConfigBean(); + AppConfigBean.saveBean(context, mAppConfigBean); + } + } + + public static AppConfigUtil getInstance(Context context) { + if (_mConfigUtil == null) { + _mConfigUtil = new AppConfigUtil(context); + } + return _mConfigUtil; + } + + public void reLoadConfig() { + mAppConfigBean = AppConfigBean.loadBean(mContext, AppConfigBean.class); + } + + public void saveConfig() { + AppConfigBean.saveBean(mContext, mAppConfigBean); + } + + public String getPhoneReplaceString() { + //String phoneNumber = "+86 123 4567 8901"; // 带有国家代码和空格的手机号码 + //String filteredNumber = phoneNumber.replaceAll("^\\+86|\\s", ""); // 过滤国家代码和空格 + //LogUtils.d(TAG, filteredNumber); + + String szReplace = "\\s"; + if (mAppConfigBean.isMergeCountryCodePrefix()) { + szReplace = "^\\+" + mAppConfigBean.getCountryCode() + "|\\s"; + } + //LogUtils.d(TAG, "szReplace is : " + szReplace); + return szReplace; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppConfigUtil_V1.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppConfigUtil_V1.java new file mode 100644 index 0000000..3ba2132 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppConfigUtil_V1.java @@ -0,0 +1,170 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用配置工具类,V1 旧版。 + */ +import android.util.JsonReader; +import cc.winboll.studio.mymessagemanager.beans.AppConfigBean_V1; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; + +public class AppConfigUtil_V1 { + + public final static String TAG = "ConfigUtil"; + + static AppConfigUtil_V1 _mConfigUtil; + //AppConfigBean_V1 mAppConfigBean_V1; + + AppConfigUtil_V1() {} + + public static AppConfigUtil_V1 getInstance() { + if (_mConfigUtil == null) { + _mConfigUtil = new AppConfigUtil_V1(); + } + return _mConfigUtil; + } + + /*public void setAppTheme(ThemeUtil.BaseTheme baseTheme) { + loadConfigData(); + mAppConfigBean.setAppThemeID(ThemeUtil.getThemeID(baseTheme)); + saveConfigData(); + }*/ + + // + // 加载应用配置数据 + // + /*void loadConfigData() { + File fJson = new File(GlobalApplication._mszConfigUtilPath); + ArrayList listTemp = null; + try { + if (fJson.exists()) { + listTemp = readJsonStream(new FileInputStream(fJson)); + if (listTemp != null) { + mAppConfigBean = listTemp.get(0); + } + } else { + mAppConfigBean = new AppConfigBean(); + saveConfigData(); + } + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + }*/ + + // + // 读取 Json 文件 + // + public ArrayList readJsonStream(InputStream in) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + return readJsonArrayList(reader); + } + + // + // 读取 Json 文件的每一 Json 项 + // + public ArrayList readJsonArrayList(JsonReader reader) throws IOException { + ArrayList list = new ArrayList(); + reader.beginArray(); + while (reader.hasNext()) { + list.add(readBeanItem(reader)); + } + reader.endArray(); + return list; + } + + // + // 读取 Json 文件的某一 Json 项 + // + public AppConfigBean_V1 readBeanItem(JsonReader reader) throws IOException { + AppConfigBean_V1 bean = new AppConfigBean_V1(); + int nReaderCount = 0; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("countryCode")) { + bean.setCountryCode(reader.nextString()); + nReaderCount++; + } else if (name.equals("isMergeCountryCodePrefix")) { + bean.setIsMergeCountryCodePrefix(reader.nextBoolean()); + nReaderCount++; + } else if (name.equals("ttsPlayDelayTimes")) { + bean.setTtsPlayDelayTimes(reader.nextInt()); + nReaderCount++; + } else if (name.equals("enableService")) { + bean.setEnableService(reader.nextBoolean()); + nReaderCount++; + } else if (name.equals("enableOnlyReceiveContacts")) { + bean.setEnableOnlyReceiveContacts(reader.nextBoolean()); + nReaderCount++; + } else if (name.equals("enableTTS")) { + bean.setEnableTTS(reader.nextBoolean()); + nReaderCount++; + } else if (name.equals("enableTTSRuleMode")) { + bean.setEnableTTSRuleMode(reader.nextBoolean()); + nReaderCount++; + } /*else if (name.equals("appThemeID")) { + bean.setAppThemeID(reader.nextInt()); + nReaderCount++; + }*/ else { + reader.skipValue(); + } + } + reader.endObject(); + return nReaderCount > 0 ? bean : null; + } + + // + // 写入 Json 文件的某一 Json 项 + // + /*public void writeBeanItem(JsonWriter writer, AppConfigBean bean) throws IOException { + writer.beginObject(); + writer.name("countryCode").value(bean.getCountryCode()); + writer.name("isMergeCountryCodePrefix").value(bean.isMergeCountryCodePrefix()); + writer.name("ttsPlayDelayTimes").value(bean.getTtsPlayDelayTimes()); + writer.name("enableService").value(bean.isEnableService()); + writer.name("enableOnlyReceiveContacts").value(bean.isEnableOnlyReceiveContacts()); + writer.name("enableTTS").value(bean.isEnableTTS()); + writer.name("enableTTSRuleMode").value(bean.isEnableTTSRuleMode()); + writer.name("appThemeID").value(bean.getAppThemeID()); + writer.endObject(); + } + + // + // 保存应用配置数据 + // + public void saveConfigData() { + try { + File fJson = new File(GlobalApplication._mszConfigUtilPath); + ArrayList list = new ArrayList(); + list.add(mAppConfigBean); + writeJsonStream(new FileOutputStream(fJson, false), list); + } catch (IOException e) { + LogUtils.d(TAG, "IOException : " + e.getMessage()); + } + } + + // + // 写入 Json 文件 + // + public void writeJsonStream(OutputStream out, ArrayList beanList) throws IOException { + JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); + writer.setIndent(" "); + writeJsonArrayList(writer, beanList); + writer.close(); + } + + // + // 记录 Json 文件的某一 Json 项 + // + public void writeJsonArrayList(JsonWriter writer, ArrayList beanList) throws IOException { + writer.beginArray(); + for (AppConfigBean bean : beanList) { + writeBeanItem(writer, bean); + } + writer.endArray(); + }*/ +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppGoToSettingsUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppGoToSettingsUtil.java new file mode 100644 index 0000000..2759063 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/AppGoToSettingsUtil.java @@ -0,0 +1,270 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/05/31 17:58:27 + * @Describe 调用应用属性设置页工具类 + * 来源:https://blog.csdn.net/zhuhai__yizhi/article/details/78737593 + * Created by zyy on 2018/3/12. + * 直接跳转到权限后返回,可以监控权限授权情况,但是,跳转到应用详情页,无法监测权限情况 + * 是否要加以区分,若是应用详情页,则跳转回来后,onRestart检测所求权限,如果授权,则收回提示,如果没授权,则继续提示 + */ +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import cc.winboll.studio.mymessagemanager.activitys.MainActivity; + +public class AppGoToSettingsUtil { + + public static final String TAG = "AppGoToSettingsUtil"; + + public static final int ACTIVITY_RESULT_APP_SETTINGS = MainActivity.ACTIVITY_RESULT_APP_SETTINGS; + + /** + * Build.MANUFACTURER判断各大手机厂商品牌 + */ + private static final String MANUFACTURER_HUAWEI = "Huawei";//华为 + private static final String MANUFACTURER_MEIZU = "Meizu";//魅族 + private static final String MANUFACTURER_XIAOMI = "Xiaomi";//小米 + private static final String MANUFACTURER_SONY = "Sony";//索尼 + private static final String MANUFACTURER_OPPO = "OPPO"; + private static final String MANUFACTURER_LG = "LG"; + private static final String MANUFACTURER_VIVO = "vivo"; + private static final String MANUFACTURER_SAMSUNG = "samsung";//三星 + private static final String MANUFACTURER_LETV = "Letv";//乐视 + private static final String MANUFACTURER_ZTE = "ZTE";//中兴 + private static final String MANUFACTURER_YULONG = "YuLong";//酷派 + private static final String MANUFACTURER_LENOVO = "LENOVO";//联想 + + public static boolean isAppSettingOpen=false; + /** + * 跳转到相应品牌手机系统权限设置页,如果跳转不成功,则跳转到应用详情页 + * 这里需要改造成返回true或者false,应用详情页:true,应用权限页:false + * @param activity + */ + public static void GoToSetting(Activity activity) { + switch (Build.MANUFACTURER) { + case MANUFACTURER_HUAWEI://华为 + Huawei(activity); + break; + case MANUFACTURER_MEIZU://魅族 + Meizu(activity); + break; + case MANUFACTURER_XIAOMI://小米 + Xiaomi(activity); + break; + case MANUFACTURER_SONY://索尼 + Sony(activity); + break; + case MANUFACTURER_OPPO://oppo + OPPO(activity); + break; + case MANUFACTURER_LG://lg + LG(activity); + break; + case MANUFACTURER_LETV://乐视 + Letv(activity); + break; + default://其他 + try {//防止应用详情页也找不到,捕获异常后跳转到设置,这里跳转最好是两级,太多用户也会觉得麻烦,还不如不跳 + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } catch (Exception e) { + SystemConfig(activity); + } + break; + } + } + + /** + * 华为跳转权限设置页 + * @param activity + */ + public static void Huawei(Activity activity) { + try { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", activity.getPackageName()); + ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity"); + intent.setComponent(comp); + activity.startActivityForResult(intent, ACTIVITY_RESULT_APP_SETTINGS); + isAppSettingOpen = false; + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + + /** + * 魅族跳转权限设置页,测试时,点击无反应,具体原因不明 + * @param activity + */ + public static void Meizu(Activity activity) { + try { + Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra("packageName", activity.getPackageName()); + activity.startActivity(intent); + isAppSettingOpen = false; + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + + /** + * 小米,功能正常 + * @param activity + */ + public static void Xiaomi(Activity activity) { + try { //MIUI 8 9 + Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); + localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity"); + localIntent.putExtra("extra_pkgname", activity.getPackageName()); + activity.startActivityForResult(localIntent, ACTIVITY_RESULT_APP_SETTINGS); + isAppSettingOpen = false; + //activity.startActivity(localIntent); + } catch (Exception e) { + try { //MIUI 5/6/7 + Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR"); + localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); + localIntent.putExtra("extra_pkgname", activity.getPackageName()); + activity.startActivityForResult(localIntent, ACTIVITY_RESULT_APP_SETTINGS); + isAppSettingOpen = false; + //activity.startActivity(localIntent); + } catch (Exception e1) { //否则跳转到应用详情 + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + //这里有个问题,进入活动后需要再跳一级活动,就检测不到返回结果 + //activity.startActivity(getAppDetailSettingIntent()); + } + } + } + + /** + * 索尼,6.0以上的手机非常少,基本没看见 + * @param activity + */ + public static void Sony(Activity activity) { + try { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", activity.getPackageName()); + ComponentName comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity"); + intent.setComponent(comp); + activity.startActivity(intent); + isAppSettingOpen = false; + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + + /** + * OPPO + * @param activity + */ + public static void OPPO(Activity activity) { + try { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", activity.getPackageName()); + ComponentName comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity"); + intent.setComponent(comp); + activity.startActivity(intent); + isAppSettingOpen = false; + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + + /** + * LG经过测试,正常使用 + * @param activity + */ + public static void LG(Activity activity) { + try { + Intent intent = new Intent("android.intent.action.MAIN"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", activity.getPackageName()); + ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity"); + intent.setComponent(comp); + activity.startActivity(intent); + isAppSettingOpen = false; + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + + /** + * 乐视6.0以上很少,基本都可以忽略了,现在乐视手机不多 + * @param activity + */ + public static void Letv(Activity activity) { + try { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", activity.getPackageName()); + ComponentName comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps"); + intent.setComponent(comp); + activity.startActivity(intent); + isAppSettingOpen = false; + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + + /** + * 只能打开到自带安全软件 + * @param activity + */ + public static void _360(Activity activity) { + try { + Intent intent = new Intent("android.intent.action.MAIN"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("packageName", activity.getPackageName()); + ComponentName comp = new ComponentName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity"); + intent.setComponent(comp); + activity.startActivity(intent); + } catch (Exception e) { + openAppDetailSetting(activity); + //activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT); + } + } + /** + * 系统设置界面 + * @param activity + */ + public static void SystemConfig(Activity activity) { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + activity.startActivity(intent); + } + /** + * 获取应用详情页面 + * @return + */ + private static Intent getAppDetailSettingIntent(Activity activity) { + Intent localIntent = new Intent(); + localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //if (Build.VERSION.SDK_INT >= 9) { + localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); + localIntent.setData(Uri.fromParts("package", activity.getPackageName(), null)); + /*} else if (Build.VERSION.SDK_INT <= 8) { + localIntent.setAction(Intent.ACTION_VIEW); + localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); + localIntent.putExtra("com.android.settings.ApplicationPkgName", activity.getPackageName()); + }*/ + return localIntent; + } + + public static void openAppDetailSetting(Activity activity) { + activity.startActivityForResult(getAppDetailSettingIntent(activity), ACTIVITY_RESULT_APP_SETTINGS); + isAppSettingOpen = true; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/FileUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/FileUtil.java new file mode 100644 index 0000000..a3fd304 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/FileUtil.java @@ -0,0 +1,113 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 文件工具类 + */ +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.net.Uri; +import androidx.core.content.FileProvider; +import cc.winboll.studio.libappbase.LogUtils; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; + +public class FileUtil { + + public static final String TAG = "FileUtil"; + + public static void shareJSONFile(Context context, String szConfigFile) { + Uri uri; + File file = new File(szConfigFile); + uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); + /*if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上 + uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); + } else { + uri = Uri.fromFile(file); + }*/ + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + shareIntent.setType("application/json"); + + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + /*if (Build.VERSION.SDK_INT >= 24) { + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + }*/ + + // 设置分享的标题 + context.startActivity(Intent.createChooser(shareIntent, "SHARE JSON")); + } + + // + // 把字符串写入文件,指定 UTF-8 编码 + // + public static void writeFile(String filePath, String content) throws IOException { + File file = new File(filePath); + FileOutputStream outputStream = new FileOutputStream(file); + OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); + writer.write(content); + writer.close(); + } + + // + // 读取文件到字符串,指定 UTF-8 编码 + // + public static String readFile(String filePath) throws IOException { + File file = new File(filePath); + FileInputStream inputStream = new FileInputStream(file); + InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + StringBuilder content = new StringBuilder(); + int character; + while ((character = reader.read()) != -1) { + content.append((char) character); + } + reader.close(); + return content.toString(); + } + + public static void copyAssetsToSD(Context context, String szSrcAssets, String szDstSD) { + LogUtils.d(TAG, "copyAssetsToSD [" + szSrcAssets + "] to [" + szDstSD + "]"); + AssetManager assetManager = context.getAssets(); + InputStream inputStream = null; + OutputStream outputStream = null; + try { + inputStream = assetManager.open(szSrcAssets); + File outputFile = new File(szDstSD); + outputStream = new FileOutputStream(outputFile); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + outputStream.flush(); + LogUtils.d(TAG, "copyAssetsToSD done."); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + } + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationHelper.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationHelper.java new file mode 100644 index 0000000..b086dd7 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationHelper.java @@ -0,0 +1,202 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/04/01 14:10:35 + * @Describe 应用通知工具类 + */ +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.os.Build; +import android.widget.RemoteViews; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.SMSActivity; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class NotificationHelper { + public static final String TAG = "NotificationHelper"; + + // 渠道ID和名称 + private static final String CHANNEL_ID_FOREGROUND = "foreground_channel"; + private static final String CHANNEL_NAME_FOREGROUND = "Foreground Service"; + private static final String CHANNEL_ID_TEMPORARY = "temporary_channel"; + private static final String CHANNEL_NAME_TEMPORARY = "Temporary Notifications"; + + // 通知ID + public static final int FOREGROUND_NOTIFICATION_ID = 1001; + public static final int TEMPORARY_NOTIFICATION_ID = 2001; + + private final Context mContext; + private final NotificationManager mNotificationManager; + + // 示例:维护当前使用的渠道ID列表 + // 键:渠道ID,值:渠道重要性级别 + Map activeChannelConfigs = new HashMap<>(); + + public NotificationHelper(Context context) { + mContext = context; + mNotificationManager = context.getSystemService(NotificationManager.class); + + // 初始化配置 + activeChannelConfigs.put( + CHANNEL_ID_FOREGROUND, + NotificationManager.IMPORTANCE_HIGH + ); + activeChannelConfigs.put( + CHANNEL_ID_TEMPORARY, + NotificationManager.IMPORTANCE_DEFAULT + ); + + createNotificationChannels(); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private void createNotificationChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createForegroundChannel(); + createTemporaryChannel(); + } + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private void createForegroundChannel() { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID_FOREGROUND, + CHANNEL_NAME_FOREGROUND, + NotificationManager.IMPORTANCE_LOW + ); + channel.setDescription("Persistent service notifications"); + channel.setSound(null, null); + channel.enableVibration(false); + mNotificationManager.createNotificationChannel(channel); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private void createTemporaryChannel() { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID_TEMPORARY, + CHANNEL_NAME_TEMPORARY, + NotificationManager.IMPORTANCE_HIGH + ); + channel.setDescription("Temporary alert notifications"); + channel.setSound(null, null); + channel.enableVibration(true); + channel.setVibrationPattern(new long[]{100, 200, 300, 400}); + channel.setBypassDnd(true); + mNotificationManager.createNotificationChannel(channel); + } + + // 显示常驻通知(通常用于前台服务) + public Notification showForegroundNotification(Intent intent, String title, String content) { + PendingIntent pendingIntent = createPendingIntent(intent); + + Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_FOREGROUND) + .setSmallIcon(R.drawable.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) + //.setContentTitle(title) + .setContentTitle(content) + //.setContentText(content) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .build(); + + mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification); + return notification; + } + + // 显示临时通知(自动消失) + public void showTemporaryNotification(Intent intent, String title, String content) { + showTemporaryNotification(intent, TEMPORARY_NOTIFICATION_ID, title, content); + } + + // 显示临时通知(自动消失) + public void showTemporaryNotification(Intent intent, int notificationID, String title, String content) { + PendingIntent pendingIntent = createPendingIntent(intent); + + Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) + .setSmallIcon(R.drawable.ic_launcher) + .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) + .setContentTitle(title) + .setContentText(content) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .setVibrate(new long[]{100, 200, 300, 400}) + .build(); + + mNotificationManager.notify(notificationID, notification); + } + + // 创建自定义布局通知(可扩展) + public void showCustomNotification(Intent intent, RemoteViews contentView, RemoteViews bigContentView) { + PendingIntent pendingIntent = createPendingIntent(intent); + + Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMPORARY) + .setSmallIcon(R.drawable.ic_launcher) + .setContentIntent(pendingIntent) + .setContent(contentView) + .setCustomBigContentView(bigContentView) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .build(); + + mNotificationManager.notify(TEMPORARY_NOTIFICATION_ID + 1, notification); + } + + // 取消所有通知 + public void cancelAllNotifications() { + mNotificationManager.cancelAll(); + } + + // 取消指定通知 + public void cancelNotification(int notificationID) { + mNotificationManager.cancel(notificationID); + } + + // 创建PendingIntent(兼容不同API版本) + private PendingIntent createPendingIntent(Intent intent) { + int flags = PendingIntent.FLAG_UPDATE_CURRENT; +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { +// flags |= PendingIntent.FLAG_IMMUTABLE; +// } + return PendingIntent.getActivity( + mContext, + 0, + intent, + flags + ); + } + + public void sendSMSReceivedMessage(int notificationID, String szPhone, String szBody) { + Intent intent = new Intent(mContext, SMSActivity.class); + intent.putExtra(SMSActivity.EXTRA_PHONE, szPhone); + String szTitle = mContext.getString(R.string.text_smsfrom) + "<" + szPhone + ">"; + String szContent = "[ " + szBody + " ]"; + showTemporaryNotification(intent, notificationID, szTitle, szContent); + } + + public void cleanOldChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + List allChannels = mNotificationManager.getNotificationChannels(); + for (NotificationChannel channel : allChannels) { + LogUtils.d(TAG, "Clean channel : " + channel.getId()); + if (!activeChannelConfigs.containsKey(channel.getId())) { + // 安全删除渠道 + mNotificationManager.deleteNotificationChannel(channel.getId()); + LogUtils.d(TAG, String.format("Deleted Channel %s", channel.getId())); + } + } + } + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationUtil.java new file mode 100644 index 0000000..0a2deba --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationUtil.java @@ -0,0 +1,168 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用通知栏工具类 + */ +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.media.RingtoneManager; +import android.widget.RemoteViews; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.MainActivity; +import cc.winboll.studio.mymessagemanager.activitys.SMSActivity; +import cc.winboll.studio.mymessagemanager.beans.MessageNotificationBean; +import cc.winboll.studio.mymessagemanager.services.MainService; + +public class NotificationUtil { + + public static final String TAG = "NotificationUtil"; + public static final int ID_MSG_SERVICE = 10000; + + static final String szSMSChannelID = "1"; + + static final String szServiceChannelID = "0"; + + + //static int mNumSendForegroundNotification = 10000; + //static int mNumSendSMSNotification = 20000; + + public NotificationManager createServiceNotificationChannel(Context context) { + //创建通知渠道ID + String channelId = szServiceChannelID; + //创建通知渠道名称 + String channelName = "Service Message"; + //创建通知渠道重要性 + int importance = NotificationManager.IMPORTANCE_MIN; + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + channel.setSound(null, null); + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel(channel); + return notificationManager; + } + + public NotificationManager createSMSNotificationChannel(Context context) { + //创建通知渠道ID + String channelId = szSMSChannelID; + //创建通知渠道名称 + String channelName = "SMS Message"; + //创建通知渠道重要性 + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), Notification.AUDIO_ATTRIBUTES_DEFAULT); + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel(channel); + return notificationManager; + } + + // 创建通知 + // + public void sendForegroundNotification(MainService service, MessageNotificationBean nessageNotificationBean) { + //创建Notification,传入Context和channelId + Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 + intent.setClass(service, MainActivity.class); + + //这里放一个count用来区分每一个通知 + //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 + + //参数1:context 上下文对象 + //参数2:发送者私有的请求码(Private request code for the sender) + //参数3:intent 意图对象 + //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 + PendingIntent mForegroundPendingIntent = PendingIntent.getActivity(service, nessageNotificationBean.getMessageId(), intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT); + + Notification mForegroundNotification = new Notification.Builder(service, szServiceChannelID) + .setAutoCancel(true) + .setContentTitle(nessageNotificationBean.getTitle()) + .setContentText(nessageNotificationBean.getContent()) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.drawable.ic_launcher) + //设置红色 + .setColor(Color.parseColor("#F00606")) + .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) + .setContentIntent(mForegroundPendingIntent) + .build(); + + + RemoteViews mrvForegroundNotificationView = new RemoteViews(service.getPackageName(), R.layout.remoteview); + mrvForegroundNotificationView.setTextViewText(R.id.remoteviewTextView1, nessageNotificationBean.getTitle()); + mrvForegroundNotificationView.setTextViewText(R.id.remoteviewTextView2, nessageNotificationBean.getContent()); + mrvForegroundNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); + mForegroundNotification.contentView = mrvForegroundNotificationView; + mForegroundNotification.bigContentView = mrvForegroundNotificationView; + + service.startForeground(nessageNotificationBean.getMessageId(), mForegroundNotification); + + } + + public void sendSMSNotification(Context context, MessageNotificationBean messageNotificationBean) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + /*NotificationManager notificationManager = createSMSNotificationChannel(context); + if (notificationManager == null) { + LogUtils.d(TAG, "createSMSNotificationChannel failed."); + return; + }*/ + + //创建Notification,传入Context和channelId + Intent intent = new Intent(context, SMSActivity.class); + intent.putExtra(SMSActivity.EXTRA_PHONE, messageNotificationBean.getPhone()); + LogUtils.d(TAG, "sendSMSNotification(...) message.getPhone() is : " + messageNotificationBean.getPhone()); + //Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 + //intent.setClass(context, MainActivity.class); + //这里放一个count用来区分每一个通知 + //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 + + //参数1:context 上下文对象 + //参数2:发送者私有的请求码(Private request code for the sender) + //参数3:intent 意图对象 + //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 + PendingIntent mRemindPendingIntent = PendingIntent.getActivity(context, messageNotificationBean.getMessageId(), intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT); + Notification mSMSNotification = new Notification.Builder(context, szSMSChannelID) + .setAutoCancel(true) + .setContentTitle(messageNotificationBean.getTitle()) + .setContentText(messageNotificationBean.getContent()) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.drawable.ic_launcher) + //设置红色 + .setColor(Color.parseColor("#F00606")) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher)) + .setContentIntent(mRemindPendingIntent) + .build(); + + RemoteViews mrvSMSNotificationView = new RemoteViews(context.getPackageName(), R.layout.remoteview); + mrvSMSNotificationView.setTextViewText(R.id.remoteviewTextView1, messageNotificationBean.getTitle()); + mrvSMSNotificationView.setTextViewText(R.id.remoteviewTextView2, messageNotificationBean.getContent()); + mrvSMSNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); + mSMSNotification.contentView = mrvSMSNotificationView; + mSMSNotification.bigContentView = mrvSMSNotificationView; + notificationManager.notify(messageNotificationBean.getMessageId(), mSMSNotification); + LogUtils.d(TAG, "getMessageId is : " + Integer.toString(messageNotificationBean.getMessageId())); + + } + + public void sendSMSReceivedMessage(Context context, int nMessageId, String szPhone, String szBody) { + String szTitle = context.getString(R.string.text_smsfrom) + "<" + szPhone + ">"; + String szContent = "[ " + szBody + " ]"; + sendSMSNotification(context, new MessageNotificationBean(nMessageId, szPhone, szTitle, szContent)); + } + + public static void cancelNotification(Context context, int notificationId) { + // 获取 NotificationManager 实例 + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + // 撤回指定 ID 的通知栏消息 + notificationManager.cancel(notificationId); + } +} + + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationUtil_Bck.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationUtil_Bck.java new file mode 100644 index 0000000..730af52 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/NotificationUtil_Bck.java @@ -0,0 +1,168 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/07/19 14:30:57 + * @Describe 应用通知栏工具类 + */ +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.media.RingtoneManager; +import android.widget.RemoteViews; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.MainActivity; +import cc.winboll.studio.mymessagemanager.activitys.SMSActivity; +import cc.winboll.studio.mymessagemanager.beans.MessageNotificationBean; +import cc.winboll.studio.mymessagemanager.services.MainService; + +public class NotificationUtil_Bck { + + public static final String TAG = "NotificationUtil"; + public static final int ID_MSG_SERVICE = 10000; + + static final String szSMSChannelID = "1"; + + static final String szServiceChannelID = "0"; + + + //static int mNumSendForegroundNotification = 10000; + //static int mNumSendSMSNotification = 20000; + + public NotificationManager createServiceNotificationChannel(Context context) { + //创建通知渠道ID + String channelId = szServiceChannelID; + //创建通知渠道名称 + String channelName = "Service Message"; + //创建通知渠道重要性 + int importance = NotificationManager.IMPORTANCE_MIN; + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + channel.setSound(null, null); + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel(channel); + return notificationManager; + } + + public NotificationManager createSMSNotificationChannel(Context context) { + //创建通知渠道ID + String channelId = szSMSChannelID; + //创建通知渠道名称 + String channelName = "SMS Message"; + //创建通知渠道重要性 + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), Notification.AUDIO_ATTRIBUTES_DEFAULT); + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + notificationManager.createNotificationChannel(channel); + return notificationManager; + } + + // 创建通知 + // + public void sendForegroundNotification(MainService service, MessageNotificationBean nessageNotificationBean) { + //创建Notification,传入Context和channelId + Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 + intent.setClass(service, MainActivity.class); + + //这里放一个count用来区分每一个通知 + //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 + + //参数1:context 上下文对象 + //参数2:发送者私有的请求码(Private request code for the sender) + //参数3:intent 意图对象 + //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 + PendingIntent mForegroundPendingIntent = PendingIntent.getActivity(service, nessageNotificationBean.getMessageId(), intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT); + + Notification mForegroundNotification = new Notification.Builder(service, szServiceChannelID) + .setAutoCancel(true) + .setContentTitle(nessageNotificationBean.getTitle()) + .setContentText(nessageNotificationBean.getContent()) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.drawable.ic_launcher) + //设置红色 + .setColor(Color.parseColor("#F00606")) + .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher)) + .setContentIntent(mForegroundPendingIntent) + .build(); + + + RemoteViews mrvForegroundNotificationView = new RemoteViews(service.getPackageName(), R.layout.remoteview); + mrvForegroundNotificationView.setTextViewText(R.id.remoteviewTextView1, nessageNotificationBean.getTitle()); + mrvForegroundNotificationView.setTextViewText(R.id.remoteviewTextView2, nessageNotificationBean.getContent()); + mrvForegroundNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); + mForegroundNotification.contentView = mrvForegroundNotificationView; + mForegroundNotification.bigContentView = mrvForegroundNotificationView; + + service.startForeground(nessageNotificationBean.getMessageId(), mForegroundNotification); + + } + + public void sendSMSNotification(Context context, MessageNotificationBean messageNotificationBean) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + /*NotificationManager notificationManager = createSMSNotificationChannel(context); + if (notificationManager == null) { + LogUtils.d(TAG, "createSMSNotificationChannel failed."); + return; + }*/ + + //创建Notification,传入Context和channelId + Intent intent = new Intent(context, SMSActivity.class); + intent.putExtra(SMSActivity.EXTRA_PHONE, messageNotificationBean.getPhone()); + LogUtils.d(TAG, "sendSMSNotification(...) message.getPhone() is : " + messageNotificationBean.getPhone()); + //Intent intent = new Intent();//这个intent会传给目标,可以使用getIntent来获取 + //intent.setClass(context, MainActivity.class); + //这里放一个count用来区分每一个通知 + //intent.putExtra("intent", "intent--->" + count);//这里设置一个数据,带过去 + + //参数1:context 上下文对象 + //参数2:发送者私有的请求码(Private request code for the sender) + //参数3:intent 意图对象 + //参数4:必须为FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT,FLAG_UPDATE_CURRENT,中的一个 + PendingIntent mRemindPendingIntent = PendingIntent.getActivity(context, messageNotificationBean.getMessageId(), intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT); + Notification mSMSNotification = new Notification.Builder(context, szSMSChannelID) + .setAutoCancel(true) + .setContentTitle(messageNotificationBean.getTitle()) + .setContentText(messageNotificationBean.getContent()) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.drawable.ic_launcher) + //设置红色 + .setColor(Color.parseColor("#F00606")) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher)) + .setContentIntent(mRemindPendingIntent) + .build(); + + RemoteViews mrvSMSNotificationView = new RemoteViews(context.getPackageName(), R.layout.remoteview); + mrvSMSNotificationView.setTextViewText(R.id.remoteviewTextView1, messageNotificationBean.getTitle()); + mrvSMSNotificationView.setTextViewText(R.id.remoteviewTextView2, messageNotificationBean.getContent()); + mrvSMSNotificationView.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher); + mSMSNotification.contentView = mrvSMSNotificationView; + mSMSNotification.bigContentView = mrvSMSNotificationView; + notificationManager.notify(messageNotificationBean.getMessageId(), mSMSNotification); + LogUtils.d(TAG, "getMessageId is : " + Integer.toString(messageNotificationBean.getMessageId())); + + } + + public void sendSMSReceivedMessage(Context context, int nMessageId, String szPhone, String szBody) { + String szTitle = context.getString(R.string.text_smsfrom) + "<" + szPhone + ">"; + String szContent = "[ " + szBody + " ]"; + sendSMSNotification(context, new MessageNotificationBean(nMessageId, szPhone, szTitle, szContent)); + } + + public static void cancelNotification(Context context, int notificationId) { + // 获取 NotificationManager 实例 + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + // 撤回指定 ID 的通知栏消息 + notificationManager.cancel(notificationId); + } +} + + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/PermissionUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/PermissionUtil.java new file mode 100644 index 0000000..4ab5071 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/PermissionUtil.java @@ -0,0 +1,204 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/06/01 13:02:30 + * @Describe 应用权限申请工具类 + */ +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.WinBoLLActivity; +import com.hjq.permissions.OnPermissionCallback; +import com.hjq.permissions.Permission; +import com.hjq.permissions.XXPermissions; +import java.util.List; + +public class PermissionUtil { + + public static final String TAG = "PermissionUtil"; + + public static boolean checkAppPermission(Context context) { + if (!XXPermissions.isGranted(context, Permission.READ_CONTACTS)) { + LogUtils.i(TAG, "Permission.READ_CONTACTS error."); + return false; + } + if (!XXPermissions.isGranted(context, Permission.Group.STORAGE)) { + LogUtils.i(TAG, "Permission.Group.STORAGE error."); + return false; + } + if (!XXPermissions.isGranted(context, Permission.READ_SMS)) { + LogUtils.i(TAG, "Permission.READ_SMS error."); + return false; + } + if (!XXPermissions.isGranted(context, Permission.RECEIVE_SMS)) { + LogUtils.i(TAG, "Permission.RECEIVE_SMS error."); + return false; + } + + return true; + } + + public static boolean checkAndGetAppPermission(Context context) { + if (!XXPermissions.isGranted(context, Permission.READ_CONTACTS)) { + getAppPermission(context, Permission.READ_CONTACTS); + return false; + } + if (!XXPermissions.isGranted(context, Permission.Group.STORAGE)) { + getAppPermissionsList(context, Permission.Group.STORAGE); + return false; + } + if (!XXPermissions.isGranted(context, Permission.READ_SMS)) { + getAppPermission(context, Permission.READ_SMS); + return false; + } + if (!XXPermissions.isGranted(context, Permission.RECEIVE_SMS)) { + getAppPermission(context, Permission.RECEIVE_SMS); + return false; + } + return true; + } + + // + // 申请多个权限 + // + static void getAppPermissionsList(final Context context, final String[] szPermissionList) { + XXPermissions.with(context) + // 申请多个权限 + .permission(szPermissionList) + // 设置权限请求拦截器(局部设置) + //.interceptor(new PermissionInterceptor()) + // 设置不触发错误检测机制(局部设置) + //.unchecked() + .request(new OnPermissionCallback() { + + @Override + public void onGranted(List permissions, boolean allGranted) { + StringBuilder sb = new StringBuilder(); + for (String sz : permissions) { + sb.append(sz); + } + if (!allGranted) { + + showPermissionDialog(context, sb.toString()); + LogUtils.d(TAG, "获取部分权限成功,但部分权限未正常授予"); + return; + } + checkAndGetAppPermission(context); + LogUtils.d(TAG, "获取权限成功:" + sb.toString()); + } + + @Override + public void onDenied(List permissions, boolean doNotAskAgain) { + StringBuilder sb = new StringBuilder(); + for (String sz : permissions) { + sb.append(sz); + } + if (doNotAskAgain) { + LogUtils.d(TAG, "被永久拒绝授权,请手动授予应用权限:" + sb.toString()); + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + //XXPermissions.startPermissionActivity(context, permissions); + showPermissionDialog(context, sb.toString()); + + } else { + LogUtils.d(TAG, "获取应用权限失败:" + sb.toString()); + } + } + }); + } + + // + // 申请单个权限 + // + static void getAppPermission(final Context context, final String szPermission) { + XXPermissions.with(context) + // 申请单个权限 + .permission(szPermission) + // 设置权限请求拦截器(局部设置) + //.interceptor(new PermissionInterceptor()) + // 设置不触发错误检测机制(局部设置) + //.unchecked() + .request(new OnPermissionCallback() { + + @Override + public void onGranted(List permissions, boolean allGranted) { + StringBuilder sb = new StringBuilder(); + for (String sz : permissions) { + sb.append(sz); + } + if (!allGranted) { + showPermissionDialog(context, sb.toString()); + LogUtils.d(TAG, "获取部分权限成功,但部分权限未正常授予"); + return; + } + checkAndGetAppPermission(context); + LogUtils.d(TAG, "获取权限成功:" + sb.toString()); + } + + @Override + public void onDenied(List permissions, boolean doNotAskAgain) { + StringBuilder sb = new StringBuilder(); + for (String sz : permissions) { + sb.append(sz); + } + if (doNotAskAgain) { + LogUtils.d(TAG, "被永久拒绝授权,请手动授予应用权限"); + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + //XXPermissions.startPermissionActivity(context, permissions); + showPermissionDialog(context, sb.toString()); + + } else { + LogUtils.d(TAG, "获取应用权限失败:" + sb.toString()); + } + } + }); + } + + + // + // 打开应用属性设置页 + // + static void openSettingIntent(Context context) { + Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setData(Uri.parse("package:" + context.getPackageName())); + context.startActivity(intent); + } + + static void showPermissionDialog(final Context context, String szPermissionMessage) { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + // set title + alertDialogBuilder.setTitle(context.getString(R.string.app_permission_require_info)); + + // set dialog message + alertDialogBuilder + .setMessage(szPermissionMessage) + .setCancelable(false) + .setPositiveButton("Open Permission Dialog", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // if this button is clicked, close + // current activity + //MainActivity.this.finish(); + AppGoToSettingsUtil appGoToSettingsUtil = new AppGoToSettingsUtil(); + appGoToSettingsUtil.GoToSetting((WinBoLLActivity)context); + } + }) + .setNegativeButton("Exit", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // if this button is clicked, just close + // the dialog box and do nothing + dialog.cancel(); + } + }); + + // create alert dialog + AlertDialog alertDialog = alertDialogBuilder.create(); + + // show it + alertDialog.show(); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/PhoneUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/PhoneUtil.java new file mode 100644 index 0000000..a3b36b5 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/PhoneUtil.java @@ -0,0 +1,239 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/08/30 14:32 + * @Describe 手机联系人工具类 + */ +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.beans.PhoneBean; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; + +public class PhoneUtil { + + public static String TAG = "PhoneUtil"; + + // 号码 + public final static String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER; + // 联系人姓名 + public final static String DISPLAY_NAME = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME; + + // 上下文对象 + Context mContext; + // 联系人提供者的Uri + Uri mUriPhoneContent = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; + + public PhoneUtil(Context context) { + mContext = context; + } + + // 读取所有联系人 + public List getPhoneList() { + List listPhoneBean = new ArrayList<>(); + ContentResolver cr = mContext.getContentResolver(); + Cursor cursor = cr.query(mUriPhoneContent, new String[]{NUMBER, DISPLAY_NAME}, null, null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + // 去除号码中的空格 + String phone = cursor.getString(0).replaceAll("\\s", ""); + String name = cursor.getString(1); + PhoneBean phoneBean = new PhoneBean(name, phone); + listPhoneBean.add(phoneBean); + } + cursor.close(); + } + + // 按电话号码排序 + Collections.sort(listPhoneBean, new Comparator() { + @Override + public int compare(PhoneBean o1, PhoneBean o2) { + return o1.getTelPhone().compareTo(o2.getTelPhone()); + } + }); + + return listPhoneBean; + } + + /** + * 根据联系人名称查询号码(兼容拼音查询) + * @param keyword 搜索关键词(支持汉字、拼音、拼音首字母) + * @return 匹配的联系人列表(包含姓名和号码) + */ + public List getPhonesByName(String keyword) { + List result = new ArrayList<>(); + if (keyword == null || keyword.trim().isEmpty()) { + return result; // 关键词为空,返回空列表 + } + + // 获取所有联系人 + List allContacts = getPhoneList(); + // 统一转为小写,忽略大小写 + String keywordLower = keyword.trim().toLowerCase(); + + for (PhoneBean contact : allContacts) { + String name = contact.getName(); + if (name == null || name.isEmpty()) { + continue; + } + + // 1. 直接匹配姓名(包含关键词) + if (name.toLowerCase().contains(keywordLower)) { + result.add(contact); + continue; + } + + // 2. 匹配姓名的全拼(包含关键词) + String namePinyin = getPinyin(name).toLowerCase(); + if (namePinyin.contains(keywordLower)) { + result.add(contact); + continue; + } + + // 3. 匹配姓名的拼音首字母(包含关键词) + String namePinyinFirstLetter = getPinyinFirstLetter(name).toLowerCase(); + if (namePinyinFirstLetter.contains(keywordLower)) { + result.add(contact); + continue; + } + } + + return result; + } + + /** + * 将汉字转为全拼(不带声调,小写) + * 例如:"张三" → "zhangsan" + */ + private String getPinyin(String chinese) { + StringBuilder pinyin = new StringBuilder(); + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.LOWERCASE); // 小写 + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); // 不带声调 + + char[] chars = chinese.toCharArray(); + for (char c : chars) { + // 如果是汉字,转换为拼音;否则直接拼接(如字母、数字、符号) + if (Character.toString(c).matches("[\\u4e00-\\u9fa5]")) { + try { + String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c, format); + if (pinyinArray != null && pinyinArray.length > 0) { + pinyin.append(pinyinArray[0]); // 取第一个拼音(多音字默认取第一个) + } + } catch (BadHanyuPinyinOutputFormatCombination e) { + LogUtils.e(TAG, "拼音转换失败:" + e.getMessage()); + } + } else { + pinyin.append(c); + } + } + return pinyin.toString(); + } + + /** + * 将汉字转为拼音首字母(小写) + * 例如:"张三" → "zs" + */ + private String getPinyinFirstLetter(String chinese) { + StringBuilder firstLetters = new StringBuilder(); + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.LOWERCASE); + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + + char[] chars = chinese.toCharArray(); + for (char c : chars) { + if (Character.toString(c).matches("[\\u4e00-\\u9fa5]")) { + try { + String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c, format); + if (pinyinArray != null && pinyinArray.length > 0) { + // 取拼音首字母(如"zhang" → "z") + firstLetters.append(pinyinArray[0].charAt(0)); + } + } catch (BadHanyuPinyinOutputFormatCombination e) { + LogUtils.e(TAG, "拼音首字母转换失败:" + e.getMessage()); + } + } else { + // 非汉字直接拼接首字符(如"李3" → "l3") + firstLetters.append(c); + } + } + return firstLetters.toString(); + } + + public boolean isPhoneInContacts(String szPhone) { + List listPhoneDto = getPhoneList(); + LogUtils.d(TAG, String.format("isPhoneInContacts(...) listPhoneDto.size() %d", listPhoneDto.size())); + for (int i = 0; i < listPhoneDto.size(); i++) { + if (isTheSamePhoneNumber(listPhoneDto.get(i).getTelPhone(), szPhone)) { + return true; + } + } + return false; + } + + public String getNameByPhone(String szPhone) { + if (szPhone == null || szPhone.equals("")) { + return ""; + } + + List listPhoneDto = getPhoneList(); + LogUtils.d(TAG, String.format("getNameByPhone(...) listPhoneDto.size() %d", listPhoneDto.size())); + for (int i = 0; i < listPhoneDto.size(); i++) { + if (isTheSamePhoneNumber(listPhoneDto.get(i).getTelPhone(), szPhone)) { + return listPhoneDto.get(i).getName(); + } + } + return ""; + } + + public boolean isTheSamePhoneNumber(String szNum1, String szNum2) { + if (szNum1.equals(szNum2)) { + LogUtils.d(TAG, "szNum1.equals(szNum2)"); + return true; + } + + if (UnitAreaUtils.getInstance(mContext).isCurrentUnitAreaNumber(szNum1)) { + if (szNum1.equals(UnitAreaUtils.getInstance(mContext).genCurrentUnitAreaNumber(szNum2))) { + LogUtils.d(TAG, "szNum1.equals(UnitAreaUtils.genCurrentUnitAreaNumber(szNum2))"); + return true; + } + } + + if (UnitAreaUtils.getInstance(mContext).isCurrentUnitAreaNumber(szNum2)) { + if (szNum2.equals(UnitAreaUtils.getInstance(mContext).genCurrentUnitAreaNumber(szNum1))) { + LogUtils.d(TAG, "szNum2.equals(UnitAreaUtils.genCurrentUnitAreaNumber(szNum1))"); + return true; + } + } + + LogUtils.d(TAG, "isTheSamePhoneNumber(...) return false;"); + return false; + } + + // 检验电话号码是否是数字 + public static boolean isPhoneByDigit(String szPhone) { + if (!RegexPPiUtils.isPPiOK(szPhone)) { + return false; + } + String regex = "[+]?\\d+"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(szPhone); + LogUtils.d(TAG, String.format("matcher.matches() : %s", matcher.matches())); + return matcher.matches(); + } +} + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/RegexPPiUtils.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/RegexPPiUtils.java new file mode 100644 index 0000000..111579d --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/RegexPPiUtils.java @@ -0,0 +1,32 @@ +package cc.winboll.studio.mymessagemanager.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/09 11:10 + * @Describe .* 前置预防针 + * regex pointer preventive injection + * 简称 RegexPPi + */ +public class RegexPPiUtils { + + public static final String TAG = "RegexPPiUtils"; + + // + // 检验文本是否满足适合正则表达式模式计算 + // + public static boolean isPPiOK(String text) { + //String text = "这里是一些任意的文本内容"; + String regex = ".*"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(text); + /*if (matcher.matches()) { + System.out.println("文本满足该正则表达式模式"); + } else { + System.out.println("文本不满足该正则表达式模式"); + }*/ + return matcher.matches(); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSReceiveRuleUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSReceiveRuleUtil.java new file mode 100644 index 0000000..58801a1 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSReceiveRuleUtil.java @@ -0,0 +1,303 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2023/07/01 05:59:25 + * @Describe 短信接收过滤规则工具类 + */ +import android.content.Context; +import android.util.JsonReader; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.beans.SMSAcceptRuleBean; +import cc.winboll.studio.mymessagemanager.beans.SMSAcceptRuleBean_V1; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class SMSReceiveRuleUtil { + + public static final String TAG = "SMSReceiveRuleUtil"; + + public static final int VALID_MATCHRESULT_POSITION = -1; + + + // 单例模式的实例变量 + static SMSReceiveRuleUtil _mInstance; + // 类实例运行的上下文环境 + Context mContext; + // 当前实例的配置操作数据 + ArrayList mDataList; + + // + // ReceiveRule 规则数组匹配结果数据类 + // + public class MatchResult { + // 当前匹配规则的运算结果 + public SMSAcceptRuleBean.RuleType matchRuleType; + // 在规则数组中匹配的位置 + public int matchPositionInRules; + + MatchResult() { + matchRuleType = SMSAcceptRuleBean.RuleType.ACCEPT; + // 在规则数组中匹配的位置 + matchPositionInRules = VALID_MATCHRESULT_POSITION; + } + } + + // + // 私有的隐藏的实例构造函数 + // + private SMSReceiveRuleUtil(Context context) { + // 保存当前类实例的运行环境 + mContext = context; + //mszConfigPath = context.getExternalFilesDir(TAG) + File.separator + mszConfigFileName; + // 从存储设备加载应用数据 + mDataList = new ArrayList(); + loadConfigData(); + } + + // + // 为了解决各个窗口数据同步问题, + // 设置数据重加载开关 + // @isReload : 每次取数据实例都从存储设备读取数据 + // + public static SMSReceiveRuleUtil getInstance(Context context, boolean isReload) { + if (_mInstance == null) { + _mInstance = new SMSReceiveRuleUtil(context); + } else if (isReload) { + _mInstance = new SMSReceiveRuleUtil(context); + } + + return _mInstance; + } + + // + // 从存储设备读取数据,添加默认配置,并保存到存储设备。 + // + public void resetConfig() { + String szAssetsFilePath = "GlobalApplication/SMSAcceptRuleBean_List.json"; + SMSAcceptRuleBean beanTemp = new SMSAcceptRuleBean(); + String szConfigFilePath = beanTemp.getBeanListJsonFilePath(mContext); + /*File fConfigFilePath = new File(szConfigFilePath); + if(fConfigFilePath.exists()) { + fConfigFilePath.delete(); + }*/ + FileUtil.copyAssetsToSD(mContext, szAssetsFilePath, szConfigFilePath); + loadConfigData(); + } + + public void cleanConfig() { + mDataList.clear(); + saveConfigData(); + } + + // + // Rule数据排序 + // @isDesc : 是否降序排列 + // + public static void sortListByRuleData(List list, boolean isDesc) { + Collections.sort(list, new SortListByRuleData(isDesc)); + } + + // + // Rule数据排序比较类定义 + // + static class SortListByRuleData implements Comparator { + private boolean mIsDesc = true; + // isDesc 是否降序排列 + public SortListByRuleData(boolean isDesc) { + mIsDesc = isDesc; + } + Collator cmp = Collator.getInstance(java.util.Locale.CHINA); + @Override + public int compare(SMSAcceptRuleBean o1, SMSAcceptRuleBean o2) { + int b0_1 = cmp.compare(o1.getRuleData(), o2.getRuleData()); + if (mIsDesc) { + return b0_1 > 0 ? -1 : 1; + } else { + return b0_1 > 0 ? 1 : -1; + } + } + } + + // + // 添加Rule数据现 + // @szRule : Rule数据内容 + // @isEnable : Rule数据启用开关项 + // @@ 返回 :Rule数据添加结果 + // + public boolean addRule(SMSAcceptRuleBean bean) { + loadConfigData(); + mDataList.add(bean); + saveConfigData(); + return true; + } + + // + // 校验短信是否在规则表里 + // + public boolean checkIsSMSAcceptInRule(Context context, String szSMS) { + /*ArrayList configData = loadConfigData(); + for (int i = 0; i < configData.size(); i++) { + SMSAcceptRuleBean bean = configData.get(i); + if (bean.isEnable()) { + String regex = bean.getRuleData(); + if (szSMS.matches(regex)) { + if (bean.getRuleType() == SMSAcceptRuleBean.RuleType.REFUSE) { + return false; + } else if (bean.getRuleType() == SMSAcceptRuleBean.RuleType.ACCEPT) { + return true; + } + } + } + + }*/ + MatchResult matchResult = getReceiveRuleMatchResult(context, szSMS); + return matchResult.matchRuleType == SMSAcceptRuleBean.RuleType.ACCEPT; + } + + // + // 校验短信是否在规则表里 + // + public MatchResult getReceiveRuleMatchResult(Context context, String szSMS) { + + MatchResult matchResult = new MatchResult(); + matchResult.matchRuleType = !RegexPPiUtils.isPPiOK(szSMS)? SMSAcceptRuleBean.RuleType.REGEXPPIUTILS_ISPPIOK_FALSE : matchResult.matchRuleType; + //LogUtils.d(TAG, "RegexPPiUtils.isPPiOK(szSMS) " + Boolean.toString(RegexPPiUtils.isPPiOK(szSMS))); + + ArrayList configData = loadConfigData(); + for (int i = 0; i < configData.size(); i++) { + SMSAcceptRuleBean bean = configData.get(i); + if (bean.isEnable()) { + String regex = bean.getRuleData(); + if (szSMS.matches(regex)) { + matchResult.matchRuleType = bean.getRuleType(); + matchResult.matchPositionInRules = i; + LogUtils.d(TAG, "matchPositionInRules " + Integer.toString(i)); + return matchResult; + } + } + + } + return matchResult; + } + + // + // 加载应用配置数据 + // + public ArrayList loadConfigData() { + ArrayList list = new ArrayList(); + SMSAcceptRuleBean.loadBeanList(mContext, list, SMSAcceptRuleBean.class); +// for (int i = 0; i < list.size(); i++) { +// LogUtils.d(TAG, "loadConfigData isEnable : " + Boolean.toString(list.get(i).isEnable())); +// } + mDataList.clear(); + mDataList.addAll(list); + return mDataList; + } + + /*ArrayList loadDataFromPath(String szPath) { + File fJson = new File(szPath); + ArrayList listTemp = null; + try { + listTemp = readJsonStream(new FileInputStream(fJson)); + } catch (IOException e) { + LogUtils.d(TAG, "IOException : " + e.getMessage()); + } + if (listTemp == null) { + listTemp = new ArrayList(); + } + return listTemp; + }*/ + + // + // 读取 Json 文件 + // + public ArrayList readJsonStream_V1(InputStream in) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + return readJsonArrayList_V1(reader); + } + + // + // 读取 Json 文件的每一 Json 项 + // + public ArrayList readJsonArrayList_V1(JsonReader reader) throws IOException { + ArrayList list = new ArrayList(); + reader.beginArray(); + while (reader.hasNext()) { + list.add(readBeanItem_V1(reader)); + } + reader.endArray(); + return list; + } + + // + // 读取 Json 文件的某一 Json 项 + // + public SMSAcceptRuleBean_V1 readBeanItem_V1(JsonReader reader) throws IOException { + SMSAcceptRuleBean_V1 bean = new SMSAcceptRuleBean_V1(); + int nReaderCount = 0; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("userID")) { + bean.setUserID(reader.nextString()); + nReaderCount++; + } else if (name.equals("ruleData")) { + bean.setRuleData(reader.nextString()); + nReaderCount++; + } else if (name.equals("enable")) { + bean.setEnable(reader.nextBoolean()); + nReaderCount++; + } else { + reader.skipValue(); + } + } + reader.endObject(); + return nReaderCount > 0 ? bean : null; + } + + // + // 写入 Json 文件的某一 Json 项 + // + /*public void writeBeanItem(JsonWriter writer, SMSAcceptRuleBean_V1 bean) throws IOException { + writer.beginObject(); + writer.name("userID").value(bean.getUserID()); + writer.name("ruleData").value(bean.getRuleData()); + writer.name("enable").value(bean.isEnable()); + writer.endObject(); + }*/ + + // + // 保存应用配置数据 + // + public void saveConfigData() { + SMSAcceptRuleBean.saveBeanList(mContext, mDataList, SMSAcceptRuleBean.class); + } + + /*// + // 写入 Json 文件 + // + public void writeJsonStream(OutputStream out, ArrayList beanList) throws IOException { + JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); + writer.setIndent(" "); + writeJsonArrayList(writer, beanList); + writer.close(); + } + + // + // 记录 Json 文件的某一 Json 项 + // + public void writeJsonArrayList(JsonWriter writer, ArrayList beanList) throws IOException { + writer.beginArray(); + for (SMSAcceptRuleBean_V1 bean : beanList) { + writeBeanItem(writer, bean); + } + writer.endArray(); + }*/ +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSRecycleUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSRecycleUtil.java new file mode 100644 index 0000000..466084b --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSRecycleUtil.java @@ -0,0 +1,33 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 19:29:59 + * @Describe 短信回收站工具类 + */ +import android.content.Context; +import cc.winboll.studio.mymessagemanager.App; +import cc.winboll.studio.mymessagemanager.beans.SMSBean; +import cc.winboll.studio.mymessagemanager.beans.SMSRecycleBean; +import java.util.ArrayList; + +public class SMSRecycleUtil { + + public static final String TAG = "SMSRecycleUtil"; + + public static String getSMSRecycleListDataPath(Context context) { + if(cc.winboll.studio.mymessagemanager.BuildConfig.DEBUG) { + return context.getExternalFilesDir(TAG) + "/mSMSRecycleList.json"; + } else { + return context.getDataDir() + "/home/" + TAG + "/mSMSRecycleList.json"; + } + } + + public static void addSMSRecycleItem(Context context, SMSBean bean) { + ArrayList list = new ArrayList(); + SMSRecycleBean.loadBeanListFromFile(getSMSRecycleListDataPath(context), list, SMSRecycleBean.class); + SMSRecycleBean smsRecycleBean = new SMSRecycleBean(bean, System.currentTimeMillis()); + list.add(smsRecycleBean); + SMSRecycleBean.saveBeanListToFile(getSMSRecycleListDataPath(context), list); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSUtil.java new file mode 100644 index 0000000..2ead9a3 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/SMSUtil.java @@ -0,0 +1,387 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用短信管理工具类 + */ +import android.app.PendingIntent; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Telephony; +import android.telephony.gsm.SmsManager; +import android.telephony.gsm.SmsMessage; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.mymessagemanager.beans.SMSBean; +import java.util.ArrayList; + +public class SMSUtil { + + public static String TAG = "SMSUtil"; + public static String SENT_SMS_ACTION = SMSUtil.class.getName() + ".SENT_SMS_ACTION"; + public static String DELIVERED_SMS_ACTION = SMSUtil.class.getName() + ".SENT_SMS_ACTION"; + + final static String SMS_URI_ALL = "content://sms/"; // 所有短信 + final static String SMS_URI_INBOX = "content://sms/inbox"; // 收件箱 + final static String SMS_URI_SEND = "content://sms/sent"; // 已发送 + final static String SMS_URI_DRAFT = "content://sms/draft"; // 草稿 + final static String SMS_URI_OUTBOX = "content://sms/outbox"; // 发件箱 + final static String SMS_URI_FAILED = "content://sms/failed"; // 发送失败 + final static String SMS_URI_QUEUED = "content://sms/queued"; // 待发送列表 + + // + // 获得短信内容 + // + public static String getSmsBody(Intent intent) { + + String tempString = ""; + Bundle bundle = intent.getExtras(); + Object messages[] = (Object[]) bundle.get("pdus"); + SmsMessage[] smsMessage = new SmsMessage[messages.length]; + for (int n = 0; n < messages.length; n++) { + smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]); + // 短信有可能因为使用了回车而导致分为多条,所以要加起来接受 + tempString += smsMessage[n].getDisplayMessageBody(); + } + return tempString; + + } + + public static int deleteSMSById(Context context, int nDeleteId) { + //LogUtils.d(TAG, "nDeleteId is " + Integer.toString(nDeleteId)); + int nResult = 0; + //int nTotal = 0; + //Uri uri = Uri.parse(SMS_URI_ALL); + //String[] projection = new String[] { "_id", "address", "person", + // "body", "date", "type", }; + /*Cursor cursor = context.getContentResolver().query(uri, projection, + "_id = ? ", + new String[] {Integer.toString(nDeleteId)}, + "");*/ + nResult = context.getContentResolver().delete(Uri.parse("content://sms/" + Integer.toString(nDeleteId)), null, null); + /*if (cursor.moveToFirst()) { + do { + //nTotal++; + //int nSMSId = cursor.getInt(0); + //String szType = (SMSBean.Type.values()[cursor.getInt(5)]).toString(); + //LogUtils.d(TAG, "nSMSId is : " + Integer.toString(nSMSId)); + //LogUtils.d(TAG, "szType is : " + szType); + //if (nSMSId == nDeleteId) { + //LogUtils.d(TAG, "nSMSId == nDeleteId"); + //int threadId = cursor.getInt(cursor.getColumnIndex("_id")); + //nResult = context.getContentResolver().delete(Uri.parse("content://sms/" + nSMSId), null, null); + //LogUtils.d(TAG, "getContentResolver delete"); + //} + } while (cursor.moveToNext()); + }*/ + + /*if (!cursor.isClosed()) { + cursor.close(); + cursor = null; + }*/ + + //LogUtils.d(TAG, "nTotal is : " + Integer.toString(nTotal)); + //LogUtils.d(TAG, "nResult is : " + Integer.toString(nResult)); + return nResult; + } + + public static ArrayList getAllSMSList(Context context) { + ArrayList returnList = new ArrayList(); + try { + String szReplaceString = AppConfigUtil.getInstance(context).getPhoneReplaceString(); + + Uri uri = Uri.parse(SMS_URI_ALL); + String[] projection = new String[] { "_id", "address", "person", + "body", "date", "type", }; + Cursor cur = context.getContentResolver().query(uri, projection, + "", + null, + ""); + // 获取短信中最新的未读短信 + // Cursor cur = getContentResolver().query(uri, projection, + // "read = ?", new String[]{"0"}, "date desc"); + if (cur.moveToFirst()) { + do { + SMSBean smsBean = new SMSBean(); + smsBean.setId(cur.getInt(0)); + smsBean.setAddress(cur.getString(1).replaceAll(szReplaceString, "")); + smsBean.setPerson(cur.getInt(2)); + smsBean.setBody(cur.getString(3)); + smsBean.setDate(cur.getLong(4)); + smsBean.setType(SMSBean.Type.values()[cur.getInt(5)]); + returnList.add(smsBean); + } while (cur.moveToNext()); + + // 按时间降序排列 + SMSBean.sortSMSByDateDesc(returnList, true); + + // 去除重复 mszAddress 的数据; + for (int i = returnList.size() - 1; i > -1; i--) { + String szCheckAddress =returnList.get(i).getAddress(); + if (szCheckAddress != null) { + for (int j = i - 1; j > -1; j--) { + if ((returnList.get(j).getAddress() != null) + && (szCheckAddress.equals(returnList.get(j).getAddress()))) { + returnList.remove(i); + break; + } + } + } + + } + + if (!cur.isClosed()) { + cur.close(); + cur = null; + } + } + } catch (SQLiteException e) { + LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); + } + return returnList; + } + + public static ArrayList getSMSListByPhone(Context context, String szPhone) { + ArrayList returnList = new ArrayList(); + try { + + + Uri uri = Uri.parse(SMS_URI_ALL); + String[] projection = new String[] { "_id", "address", "person", + "body", "date", "type", }; + Cursor cursor; + AppConfigUtil configUtil = AppConfigUtil.getInstance(context); + String szPhoneCountryCodePrefix = "\\s"; + if (configUtil.mAppConfigBean.isMergeCountryCodePrefix()) { + szPhoneCountryCodePrefix = "+" + configUtil.mAppConfigBean.getCountryCode() + szPhone; + cursor = context.getContentResolver().query(uri, projection, + "address = ? or address = ? ", + new String[] {szPhone, szPhoneCountryCodePrefix}, + ""); + } else { + cursor = context.getContentResolver().query(uri, projection, + "address = ? ", + new String[] {szPhone}, + ""); + } + // 获取短信中最新的未读短信 + // Cursor cur = getContentResolver().query(uri, projection, + // "read = ?", new String[]{"0"}, "date desc"); + if (cursor.moveToFirst()) { + do { + /*int nSMSId = cursor.getInt(0); + String szType = (SMSBean.Type.values()[cursor.getInt(5)]).toString(); + LogUtils.d(TAG, "nSMSId is : " + Integer.toString(nSMSId)); + LogUtils.d(TAG, "szType is : " + szType); + */ + + SMSBean smsBean = new SMSBean(); + smsBean.setId(cursor.getInt(0)); + smsBean.setAddress(cursor.getString(1)); + smsBean.setPerson(cursor.getInt(2)); + smsBean.setBody(cursor.getString(3)); + smsBean.setDate(cursor.getLong(4)); + smsBean.setType(SMSBean.Type.values()[cursor.getInt(5)]); + /*long nDateDefault = Date.parse("2022/01/01"); + if (smsBean.mnDate < nDateDefault) { + LogUtils.d(TAG, "smsBean >>> " + smsBean.toString()); + smsBean.mnDate = nDateDefault; + }*/ + + returnList.add(smsBean); + } while (cursor.moveToNext()); + + if (!cursor.isClosed()) { + cursor.close(); + cursor = null; + } + } + } catch (SQLiteException e) { + LogUtils.d("SQLiteException : ", e.getMessage()); + } + return returnList; + } + + + // + // 获得短信地址 + // + public static String getSmsAddress(Intent intent) { + + Bundle bundle = intent.getExtras(); + Object messages[] = (Object[]) bundle.get("pdus"); + return SmsMessage.createFromPdu((byte[]) messages[0]) + .getDisplayOriginatingAddress(); + } + + public static int saveReceiveSms(Context context, String phoneNumber, String message, String readState, long time, String folderName) { + int nResultId = -1; + try { + ContentValues values = new ContentValues(); + values.put("address", phoneNumber); + values.put("body", message); + values.put("read", readState); //"0" for have not read sms and "1" for have read sms + values.put("date", Long.toString(time)); + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Uri uri = Telephony.Sms.Sent.CONTENT_URI; + if (folderName.equals("inbox")) { + uri = Telephony.Sms.Inbox.CONTENT_URI; + } + // 插入数据 + Uri uriReturn = context.getContentResolver().insert(uri, values); + // 读取插入记录的 id + nResultId = (int)ContentUris.parseId(uriReturn); + /*} else { + // folderName could be inbox or sent + context.getContentResolver().insert(Uri.parse("content://sms/" + folderName), values); + }*/ + //ToastUtils.show("saveReceiveSms done."); + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + return nResultId; + } + + public static long saveSendedSMS(Context context, String phoneNumber, String message) { + SMSBean smsBean = new SMSBean(); + smsBean.setAddress(phoneNumber); + smsBean.setBody(message); + smsBean.setReadStatus(SMSBean.ReadStatus.UNREAD); + smsBean.setDate(System.currentTimeMillis()); + + return saveSendedSMS(context, smsBean); + } + + + public static long saveOldSendedSMS(Context context, SMSBean smsBean) { + long nResultId = 0; + try { + ContentValues values = SMSBean.createOldSendedSMSContentValues(smsBean); + LogUtils.d(TAG, "ContentValues created."); + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Uri uri = Telephony.Sms.Sent.CONTENT_URI; + Uri uriReturn = context.getContentResolver().insert(uri, values); + LogUtils.d(TAG, "inserted"); + // 读取插入记录的 id + nResultId = ContentUris.parseId(uriReturn); + /*} else { + // folderName could be inbox or sent + context.getContentResolver().insert(Uri.parse(SMS_URI_SEND), values); + }*/ + LogUtils.d(TAG, "nResultId : " + Long.toString(nResultId)); + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + return nResultId; + } + + public static long saveSendedSMS(Context context, SMSBean smsBean) { + long nResultId = 0; + try { + ContentValues values = SMSBean.createSendedSMSContentValues(smsBean); + + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Uri uri = Telephony.Sms.Sent.CONTENT_URI; + Uri uriReturn = context.getContentResolver().insert(uri, values); + // 读取插入记录的 id + nResultId = ContentUris.parseId(uriReturn); + /*} else { + // folderName could be inbox or sent + context.getContentResolver().insert(Uri.parse(SMS_URI_SEND), values); + }*/ + //LogUtils.d(TAG, "nResultId : " + Integer.toString(nResultId)); + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + return nResultId; + } + + // 发送短文本短信 + // phoneNumber:接收号码 + // message:发送内容 + // + /*public static boolean sendSMS(Context context, String phoneNumber, String message) { + //Getting intent and PendingIntent instance + PendingIntent pi = PendingIntent. + getBroadcast(context, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE); + //Get the SmsManager instance and call the sendTextMessage method to send message + SmsManager sms=SmsManager.getDefault(); + sms.sendTextMessage(phoneNumber, null, message, pi, null); + if (SMSUtil.saveSendedSMS(context, phoneNumber, message) != 0) { + Toast.makeText(context, "TO : <" + phoneNumber + "> Sended.", Toast.LENGTH_SHORT).show(); + return true; + } + return false; + }*/ + + // + // 发送长文本短信,第一种方法发送短信,是将短信分割成多条短信,分别发给接收方。 + // phoneNumber:接收号码 + // message:发送内容 + // + /*public static boolean sendMessageByInterface(Context context, String phoneNumber, String message) { + SmsManager smsManager = SmsManager.getDefault(); + //String message = "这是一条很长的短信,需要分割"; + //int maxLength = SmsManager.getMaxMessageLength(); // 获取最大长度 + int maxLength = 70; + //int remainingChars = maxLength; + List messages = new ArrayList<>(); + for (int i = 0; i < message.length(); i += maxLength) { + if (i + maxLength >= message.length()) { // 如果到达结尾 + messages.add(message.substring(i)); + break; + } else { + messages.add(message.substring(i, i + maxLength)); + } + } + + for (String msg : messages) { + smsManager.sendTextMessage(phoneNumber, null, msg, null, null); + } + ToastUtils.show("TO : <" + phoneNumber + "> Sended."); + if (SMSUtil.saveSendedSMS(context, phoneNumber, message) != 0) { + return true; + } + return false; + }*/ + + // + // 发送长文本短信,第二种方法是将短信内容一次性发给接收方。在接收方的短信列表中,显示的是一条短信,但是实际上还是按多条短信计费。 + // phoneNumber:接收号码 + // message:发送内容 + // + public static boolean sendMessageByInterface2(Context context, String phoneNumber, String message) { + SmsManager sms = SmsManager.getDefault(); + + Intent sentIntent = new Intent(SENT_SMS_ACTION); + PendingIntent sentPI = PendingIntent.getBroadcast(context, 0, sentIntent, 0); + + Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION); + PendingIntent deliverPI = PendingIntent.getBroadcast(context, 0, deliverIntent, 0); + + if (message.length() > 70) { + ArrayList msgs = sms.divideMessage(message); + ArrayList sentIntents = new ArrayList(); + for (int i = 0;i < msgs.size();i++) { + sentIntents.add(sentPI); + } + sms.sendMultipartTextMessage(phoneNumber, null, msgs, sentIntents, null); + LogUtils.d(TAG, "Long SMS TO : <" + phoneNumber + "> Sended."); + } else { + sms.sendTextMessage(phoneNumber, null, message, sentPI, deliverPI); + LogUtils.d(TAG, "SMS TO : <" + phoneNumber + "> Sended."); + } + if (SMSUtil.saveSendedSMS(context, phoneNumber, message) != 0) { + ToastUtils.show("SMS TO : <" + phoneNumber + "> Sended."); + return true; + } + return false; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ServiceUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ServiceUtil.java new file mode 100644 index 0000000..3bc83b0 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ServiceUtil.java @@ -0,0 +1,34 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用服务组件工具类 + */ +import android.app.ActivityManager; +import android.content.Context; +import java.util.List; + +public class ServiceUtil { + public final static String TAG = "ServiceUtil"; + + public static boolean isServiceAlive(Context context, String szServiceName) { + // 获取Activity管理者对象 + ActivityManager manager = (ActivityManager) context + .getSystemService(Context.ACTIVITY_SERVICE); + // 获取正在运行的服务(此处设置最多取1000个) + List runningServices = manager + .getRunningServices(1000); + if (runningServices.size() <= 0) { + return false; + } + // 遍历,若存在名字和传入的serviceName的一致则说明存在 + for (ActivityManager.RunningServiceInfo runningServiceInfo : runningServices) { + if (runningServiceInfo.service.getClassName().equals(szServiceName)) { + return true; + } + } + + return false; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TTSPlayRuleUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TTSPlayRuleUtil.java new file mode 100644 index 0000000..e4d27b3 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TTSPlayRuleUtil.java @@ -0,0 +1,430 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe TTS 语音播放工具类 + */ +import android.content.Context; +import android.content.Intent; +import android.os.Message; +import android.util.JsonReader; +import android.widget.Toast; +import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.activitys.TTSPlayRuleActivity; +import cc.winboll.studio.mymessagemanager.beans.TTSPlayRuleBean; +import cc.winboll.studio.mymessagemanager.beans.TTSPlayRuleBean_V1; +import cc.winboll.studio.mymessagemanager.beans.TTSSpeakTextBean; +import cc.winboll.studio.mymessagemanager.services.TTSPlayService; +import cc.winboll.studio.mymessagemanager.utils.FileUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TTSPlayRuleUtil { + public static final String TAG = "TTSPlayRuleUtil"; + + TTSPlayRuleActivity mTTSPlayRuleActivity; + static TTSPlayRuleUtil _mTTSPlayRuleUtil; + Context mContext; + ArrayList mConfigData; + public static String mszConfigFileName = TAG + ".json"; + String mszConfigPath_V1 = null; + + TTSPlayRuleUtil(Context context) { + mContext = context; + mszConfigPath_V1 = context.getExternalFilesDir(TAG) + File.separator + mszConfigFileName; + mConfigData = new ArrayList(); + mConfigData = loadConfigData(); + } + + public static TTSPlayRuleUtil getInstance(Context context) { + if (_mTTSPlayRuleUtil == null) { + _mTTSPlayRuleUtil = new TTSPlayRuleUtil(context); + } + return _mTTSPlayRuleUtil; + } + + public void initTTSPlayRuleActivity(TTSPlayRuleActivity activity) { + mTTSPlayRuleActivity = activity; + } + + // + // 用 TTS 播放语音 + // @szSpeak : 语音内容 + // @nRepeat : 重复次数 + // + public static void speakText(Context context, String szSpeak, int nRepeat) { + speakText(context, szSpeak, 0, nRepeat); + } + + + // + // 用 TTS 播放语音 + // @szSpeak : 语音内容 + // @nTtsPlayDelayTimes : 延迟初始播放时间毫秒数 + // @nRepeat : 重复次数 + // + public static void speakText(Context context, String szSpeak, int nTtsPlayDelayTimes, int nRepeat) { + // 初始化语音数据 + ArrayList ttsData = new ArrayList(); + ttsData.add(new TTSSpeakTextBean(nTtsPlayDelayTimes, szSpeak)); + for (int i = 0; i < nRepeat - 1; i++) { + ttsData.add(new TTSSpeakTextBean(3000, szSpeak)); + } + // 调用TTS语音服务 + Intent intent = new Intent(context, TTSPlayService.class); + intent.putExtra(TTSPlayService.EXTRA_SPEAKDATA, ttsData); + context.startService(intent); + } + + // + // 短信解析模式播放短信的 TTS 语音 + // @context : 上下文 + // @szSMS : 要解析的短信 + // + public int speakTTSAnalyzeModeText(String szSMS) { + return speakTTSAnalyzeModeText(szSMS, 0); + } + + // + // 短信解析模式播放短信的 TTS 语音 + // @context : 上下文 + // @szSMS : 要解析的短信 + // @nTtsPlayDelayTimes : 延迟初始播放时间毫秒数 + // + public int speakTTSAnalyzeModeText(String szSMS, int nTtsPlayDelayTimes) { + // 初始化语音数据 + ArrayList ttsData = new ArrayList(); + int reault = addTTSAnalyzeModeReply(szSMS, ttsData, nTtsPlayDelayTimes); + + // 调用TTS语音服务 + Intent intent = new Intent(mContext, TTSPlayService.class); + intent.putExtra(TTSPlayService.EXTRA_SPEAKDATA, ttsData); + mContext.startService(intent); + + return reault; + } + + public void deleteTTSRuleBean(int position) { + mConfigData.remove(position); + saveConfigData(); + } + + // + // 添加语音回复数据 + // @context : 上下文 + // @szSMS : 提供校验的短信 + // @ttsData : 语音数据规则表 + // @nTtsPlayDelayTimes : 延迟初始播放时间毫秒数 + // + int addTTSAnalyzeModeReply(String szSMS, ArrayList ttsData, int nTtsPlayDelayTimes) { + for (int i = 0; i < mConfigData.size(); i++) { + if (mConfigData.get(i).isEnable()) { + String szResult = getRewriteRegExpResult(szSMS, mConfigData.get(i).getPatternText(), mConfigData.get(i).getTtsRuleText()); + if (!szResult.equals("")) { + ttsData.add(new TTSSpeakTextBean(nTtsPlayDelayTimes, szResult)); + return i; + } + } + } + return -1; + } + + // + // 添加语音回复数据 + // @context : 上下文 + // @szSMS : 提供校验的短信 + // @ttsData : 语音数据规则表 + // + void addTTSAnalyzeModeReply(String szSMS, ArrayList ttsData) { + addTTSAnalyzeModeReply(szSMS, ttsData, 0); + } + + // + // 测试语音回复数据 + // @context : 上下文 + // @szSMS : 示例短信 + // @ttsData : 语音数据规则 + // + public String testTTSAnalyzeModeReply(TTSPlayRuleBean bean) { + + String szResult = getRewriteRegExpResult(bean.getDemoSMSText(), bean.getPatternText(), bean.getTtsRuleText()); + if (szResult.equals("")) { + szResult += "\nDemoSMSText : " + bean.getDemoSMSText(); + szResult += "\nPatternText : " + bean.getPatternText(); + szResult += "\nTTSRuleText : " + bean.getTtsRuleText(); + } else { + // 初始化语音数据 + ArrayList ttsData = new ArrayList(); + ttsData.add(new TTSSpeakTextBean(0, szResult)); + + // 调用TTS语音服务 + Intent intent = new Intent(mContext, TTSPlayService.class); + intent.putExtra(TTSPlayService.EXTRA_SPEAKDATA, ttsData); + mContext.startService(intent); + } + return szResult; + } + + // + // 正则替换函数 + // @szMatchText : 操作字符串 + // @szPattern : 查找模板 + // @szRewrite : 替换模板 + // + String getRewriteRegExpResult(String szMatchText, String szPattern, String szRewrite) { + String szReturn = ""; + if (!szPattern.equals("") && !szRewrite.equals("")) { + try { + Pattern pattern = Pattern.compile(szPattern, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(szMatchText); + LogUtils.d(TAG, "szMatchText : " + szMatchText); + while (matcher.find()) { + szReturn += matcher.replaceAll(szRewrite); + } + } catch (java.lang.IndexOutOfBoundsException ex) { + LogUtils.d(TAG, "getRewriteRegExpResult(...) IndexOutOfBoundsException : " + ex.getMessage()); + } + } + + return szReturn; + } + + public void addNewTTSRuleBean(TTSPlayRuleBean bean) { + mConfigData.add(0, bean); + saveConfigData(); + } + + public void changeBeanPosition(int currentPosition, boolean isUp) { + if (isUp && currentPosition > 0) { + //LogUtils.d(TAG, "Up"); + mConfigData.add(currentPosition - 1, mConfigData.get(currentPosition)); + mConfigData.remove(currentPosition + 1); + saveConfigData(); + return; + } + + if (!isUp && currentPosition < mConfigData.size() - 1) { + //LogUtils.d(TAG, "Down"); + mConfigData.add(currentPosition + 2, mConfigData.get(currentPosition)); + mConfigData.remove(currentPosition); + saveConfigData(); + return; + } + } + + public void setBeanEnable(int position, boolean isEnable) { + mConfigData.get(position).setIsEnable(isEnable); + saveConfigData(); + } + + /*public String getConfigPath() { + return mszConfigPath_V1; + }*/ + + // + // 从指定路径的文件读取数据,添加并保存到现有的数据文件。 + // @szPath : 要读取的文件指定的路径 + // @@ 返回 :添加的数据条数 + // + /*public int importConfig(String szPath) { + ArrayList listBeanImport = loadDataFromPath(szPath); + // 添加更新表到现有操作表 + if (mConfigData == null) { + mConfigData = new ArrayList(); + } + ArrayList configData = loadConfigData(); + configData.addAll(listBeanImport); + // 保存操作表数据 + saveConfigData(configData); + return listBeanImport.size(); + }*/ + + /*ArrayList loadDataFromPath(String szPath) { + File fJson = new File(szPath); + ArrayList listTemp = null; + try { + listTemp = readJsonStream(new FileInputStream(fJson)); + } catch (IOException e) { + LogUtils.d(TAG, "IOException : " + e.getMessage()); + } + if (listTemp == null) { + listTemp = new ArrayList(); + } + return listTemp; + }*/ + + // + // 加载 TTS 配置数据 + // + public ArrayList loadConfigData() { + TTSPlayRuleBean.loadBeanList(mContext, mConfigData, TTSPlayRuleBean.class); + return mConfigData; + } + + // + // 保存 TTS 配置数据 + // + public void saveConfigData() { + // 设定只能在规则编辑窗口改变规则 + if (mTTSPlayRuleActivity == null) { + LogUtils.i(TAG, "Please edit rules in TTSPlayRuleActivity."); + return; + } + + YesNoAlertDialog.show(mTTSPlayRuleActivity, mContext.getString(R.string.text_ttsrule), "是否更新语音规则?", new YesNoAlertDialog.OnDialogResultListener(){ + @Override + public void onYes() { + TTSPlayRuleBean.saveBeanList(mContext, mConfigData, TTSPlayRuleBean.class); + Toast.makeText(mTTSPlayRuleActivity, "语音数据已更改。", Toast.LENGTH_SHORT).show(); + + Message message = new Message(); + message.what = TTSPlayRuleActivity.MSG_RELOAD; + mTTSPlayRuleActivity.sendActivityMessage(message); + } + + @Override + public void onNo() { + Message message = new Message(); + message.what = TTSPlayRuleActivity.MSG_RELOAD; + mTTSPlayRuleActivity.sendActivityMessage(message); + } + }); + } + + // + // 清空 TTS 配置数据 + // + public void cleanConfig() { + YesNoAlertDialog.show(mTTSPlayRuleActivity, "确定清理", "您确定清理所有语音规则吗?", new YesNoAlertDialog.OnDialogResultListener(){ + @Override + public void onYes() { + mConfigData.clear(); + TTSPlayRuleBean.saveBeanList(mContext, mConfigData, TTSPlayRuleBean.class); + Toast.makeText(mTTSPlayRuleActivity, "语音数据已更改。", Toast.LENGTH_SHORT).show(); + + Message message = new Message(); + message.what = TTSPlayRuleActivity.MSG_RELOAD; + mTTSPlayRuleActivity.sendActivityMessage(message); + } + @Override + public void onNo() { + } + }); + } + + // + // 重置默认 TTS 配置数据 + // + public void resetConfig() { + YesNoAlertDialog.show(mTTSPlayRuleActivity, "确定重置", "您确定重置语音规则为默认设置吗?", new YesNoAlertDialog.OnDialogResultListener(){ + @Override + public void onYes() { + String szAssetsFilePath = "GlobalApplication/TTSPlayRuleBean_List.json"; + TTSPlayRuleBean beanTemp = new TTSPlayRuleBean(); + String szConfigFilePath = beanTemp.getBeanListJsonFilePath(mContext); + FileUtil.copyAssetsToSD(mContext, szAssetsFilePath, szConfigFilePath); + Toast.makeText(mTTSPlayRuleActivity, "语音数据已更改。", Toast.LENGTH_SHORT).show(); + + loadConfigData(); + Message message = new Message(); + message.what = TTSPlayRuleActivity.MSG_RELOAD; + mTTSPlayRuleActivity.sendActivityMessage(message); + } + @Override + public void onNo() { + } + }); + } + + // + // 读取 Json 文件 + // + public ArrayList readJsonStream(InputStream in) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + return readJsonArrayList(reader); + } + + // + // 读取 Json 文件的每一 Json 项 + // + public ArrayList readJsonArrayList(JsonReader reader) throws IOException { + ArrayList list = new ArrayList(); + reader.beginArray(); + while (reader.hasNext()) { + list.add(readBeanItem(reader)); + } + reader.endArray(); + return list; + } + + // + // 读取 Json 文件的某一 Json 项 + // + public TTSPlayRuleBean_V1 readBeanItem(JsonReader reader) throws IOException { + TTSPlayRuleBean_V1 bean = new TTSPlayRuleBean_V1(); + int nReaderCount = 0; + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("DemoSMSText")) { + bean.setDemoSMSText(reader.nextString()); + nReaderCount++; + } else if (name.equals("PatternText")) { + bean.setPatternText(reader.nextString()); + nReaderCount++; + } else if (name.equals("TTSRuleText")) { + bean.setTtsRuleText(reader.nextString()); + nReaderCount++; + } else if (name.equals("Enable")) { + bean.setIsEnable(reader.nextBoolean()); + nReaderCount++; + } else { + reader.skipValue(); + } + } + reader.endObject(); + return nReaderCount > 0 ? bean : null; + } + + // + // 写入 Json 文件的某一 Json 项 + // + /*public void writeBeanItem(JsonWriter writer, TTSPlayRuleBean bean) throws IOException { + writer.beginObject(); + writer.name("Name").value(bean.getRuleName()); + writer.name("DemoSMSText").value(bean.getDemoSMSText()); + writer.name("PatternText").value(bean.getPatternText()); + writer.name("TTSRuleText").value(bean.getTtsRuleText()); + writer.name("Enable").value(bean.isEnable()); + writer.endObject(); + }*/ + + // + // 写入 Json 文件 + // + /*public void writeJsonStream(OutputStream out, ArrayList beanList) throws IOException { + JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); + writer.setIndent(" "); + writeJsonArrayList(writer, beanList); + writer.close(); + } + + // + // 记录 Json 文件的某一 Json 项 + // + public void writeJsonArrayList(JsonWriter writer, ArrayList beanList) throws IOException { + writer.beginArray(); + for (TTSPlayRuleBean bean : beanList) { + writeBeanItem(writer, bean); + } + writer.endArray(); + }*/ +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java new file mode 100644 index 0000000..70f6938 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java @@ -0,0 +1,186 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/03 10:27:46 + * @Describe TTS语音播放工具类 + */ +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Build; +import android.speech.tts.TextToSpeech; +import android.speech.tts.UtteranceProgressListener; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.widget.LinearLayout; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.R; +import cc.winboll.studio.mymessagemanager.beans.TTSSpeakTextBean; +import java.util.ArrayList; + +public class TextToSpeechUtil { + + public static final String TAG = "TextToSpeechUtil"; + + public static final String UNIQUE_ID = "UNIQUE_ID"; + + static TextToSpeechUtil _mTextToSpeechUtil; + + View mView; + WindowManager mWindowManager; + TextToSpeech mTextToSpeech; + Context mContext; + volatile boolean isExist = false; + + TextToSpeechUtil(Context context) { + mContext = context; + // 获取WindowManager + mWindowManager = (WindowManager) mContext.getSystemService(mContext.WINDOW_SERVICE); + } + + public static TextToSpeechUtil getInstance(Context context) { + if (_mTextToSpeechUtil == null) { + _mTextToSpeechUtil = new TextToSpeechUtil(context); + } + return _mTextToSpeechUtil; + } + + // + // 播放 TTS 语音队列 + // + public void speekTTSList(final ArrayList listTTSSpeakTextBean) { + // 重置播放退出标志位 + isExist = false; + + // 开始播放 + if (mTextToSpeech == null) { + //ToastUtils.show("mTextToSpeech == null"); + // 创建TextToSpeech实例 + mTextToSpeech = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() { + @Override + public void onInit(int i) { + if (i == TextToSpeech.SUCCESS) { + speekTTSList(listTTSSpeakTextBean); + } else { + LogUtils.d(TAG, "TTS init failed : " + Integer.toString(i) + ". The app [https://play.google.com/store/apps/details?id=com.google.android.tts] maybe fix this TTS probrem. "); + } + } + }); + mTextToSpeech.setOnUtteranceProgressListener(mUtteranceProgressListener); + } else { + if (mTextToSpeech != null && listTTSSpeakTextBean != null && listTTSSpeakTextBean.size() > 0) { + // 清理过期的悬浮窗 + if (mWindowManager != null && mView != null) { + try { + mWindowManager.removeView(mView); + mView = null; + } catch(Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + // 显示悬浮窗 + initWindow(); + + // 播放 TTS 语音 + // + //ToastUtils.show("initWindow done."); + // 设置延迟间隔 + int nDelay = listTTSSpeakTextBean.get(0).mnDelay; + try { + Thread.sleep(nDelay); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + //ToastUtils.show("Delay done."); + for (int speakPosition = 0; speakPosition < listTTSSpeakTextBean.size() && !isExist; speakPosition++) { + // 播放语音 + String szSpeakContent = listTTSSpeakTextBean.get(speakPosition).mszSpeakContent; + isExist = (listTTSSpeakTextBean.size() - 2 < speakPosition); + //ToastUtils.show("for isExist is : " + Boolean.toString(isExist)); + if (speakPosition == 0) { + mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_FLUSH, null, UNIQUE_ID); + } else { + mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_ADD, null, UNIQUE_ID); + } + //ToastUtils.show("mTextToSpeech.speak"); + } + } + } + } + + UtteranceProgressListener mUtteranceProgressListener = new UtteranceProgressListener() { + @Override + public void onStart(String utteranceId) { + LogUtils.d(TAG, "播放开始"); + } + + @Override + public void onDone(String utteranceId) { + LogUtils.d(TAG, "播放结束"); + //ToastUtils.show("isExist is : " + Boolean.toString(isExist)); + // 关闭悬浮窗 + if (isExist && mWindowManager != null && mView != null) { + LogUtils.d(TAG, "关闭悬浮窗"); + mWindowManager.removeView(mView); + } + } + + @Override + public void onError(String utteranceId) { + LogUtils.d(TAG, "播放出错"); + } + }; + + + // + // 初始化 TTS 悬浮窗 + // + private void initWindow() { + //ToastUtils.show("initWindow"); + // 创建布局参数 + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + //这里需要进行不同的设置 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } else { + params.type = WindowManager.LayoutParams.TYPE_PHONE; + } + //设置透明度 + params.alpha = 0.9f; + //设置内部视图对齐方式 + params.gravity = Gravity.RIGHT | Gravity.BOTTOM; + //窗口的右上角角坐标 + params.x = 20; + params.y = 20; + //是指定窗口的像素格式为 RGBA_8888。 + //使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。 + params.format = PixelFormat.RGBA_8888; + //设置窗口的宽高,这里为自动 + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + //这段非常重要,是后续是否穿透点击的关键 + params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。 + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。 + //这里的引入布局文件的方式,也可以动态添加控件 + mView = View.inflate(mContext, R.layout.view_tts_back, null); + LinearLayout llMain = mView.findViewById(R.id.viewttsbackLinearLayout1); + llMain.setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View view) { + //ToastUtils.show("onClick"); + isExist = true; + if (mTextToSpeech != null) { + mTextToSpeech.stop(); + } + if (mWindowManager != null && mView != null) { + mWindowManager.removeView(mView); + mView = null; + } + } + }); + mWindowManager.addView(mView, params); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ThemeUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ThemeUtil.java new file mode 100644 index 0000000..10c6ac7 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ThemeUtil.java @@ -0,0 +1,47 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe 应用主题工具类 + */ +import cc.winboll.studio.mymessagemanager.R; + +public class ThemeUtil { +/* + public static final String SZ_THEME_TYPE = "SZ_THEME_TYPE"; + public static final String THEME_PREFERENCES = "THEME_PREFERENCES"; + + public enum BaseTheme { DEFAULT(0), SKY(1), GOLDEN(2); + static String[] _mlistName = { "默认主题", "天空主题", "辉煌主题" }; + private int value; + private BaseTheme(int value) { + this.value = value; + } + } + + public static int getThemeID(BaseTheme baseTheme) { + int themeId; + if (baseTheme == BaseTheme.DEFAULT) { + themeId = R.style.AppTheme_Default; + } else if (baseTheme == BaseTheme.SKY) { + themeId = R.style.AppTheme_Sky; + } else if (baseTheme == BaseTheme.GOLDEN) { + themeId = R.style.AppTheme_Golden; + } else { + themeId = R.style.AppTheme_Default; + } + return themeId; + } + + public static BaseTheme getTheme(int nThemeID) { + if (nThemeID == R.style.AppTheme_Sky) { + return BaseTheme.SKY; + } else if (nThemeID == R.style.AppTheme_Golden) { + return BaseTheme.GOLDEN; + } else { + return BaseTheme.DEFAULT; + } + } + */ +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UnitAreaUtils.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UnitAreaUtils.java new file mode 100644 index 0000000..8d531b8 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UnitAreaUtils.java @@ -0,0 +1,53 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/04/14 15:55:36 + * @Describe 电话号码区域管理辅助类 + */ +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.mymessagemanager.beans.AppConfigBean; +import android.content.Context; + +public class UnitAreaUtils { + + public static final String TAG = "UnitAreaUtils"; + + static UnitAreaUtils _UnitAreaUtils; + Context mContext; + + UnitAreaUtils(Context context) { + mContext = context; + } + + public static UnitAreaUtils getInstance(Context context) { + if (_UnitAreaUtils == null) { + _UnitAreaUtils = new UnitAreaUtils(context); + } + return _UnitAreaUtils; + } + + public boolean isCurrentUnitAreaNumber(String szPhoneNumer) { + String szUnitArea = getUnitArea(); + try { + String szPhoneNumerUnitArea = szPhoneNumer.substring(1, 3); + LogUtils.d(TAG, String.format("szPhoneNumerUnitArea %s", szPhoneNumerUnitArea)); + return szPhoneNumerUnitArea.equals(szUnitArea); + } catch (StringIndexOutOfBoundsException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + return false; + } + + public String genCurrentUnitAreaNumber(String szPhoneNumer) { + String szUnitArea = getUnitArea(); + LogUtils.d(TAG, String.format("szUnitArea %s", szUnitArea)); + return "+" + szUnitArea + szPhoneNumer; + } + + String getUnitArea() { + String szUnitArea = AppConfigUtil.getInstance(mContext).mAppConfigBean.getCountryCode(); + LogUtils.d(TAG, String.format("szUnitArea %s", szUnitArea)); + return szUnitArea; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UriUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UriUtil.java new file mode 100644 index 0000000..c22e06a --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UriUtil.java @@ -0,0 +1,131 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe Uri 资源管理工具类 + */ +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class UriUtil { + + public static final String TAG = "UriUtil"; + + // + // 获取真实路径 + // + // @param context + // + public static String getFileFromUri(Context context, Uri uri) { + if (uri == null) { + return null; + } + switch (uri.getScheme()) { + case ContentResolver.SCHEME_CONTENT: + //Android7.0之后的uri content:// URI + return getFilePathFromContentUri(context, uri); + case ContentResolver.SCHEME_FILE: + default: + //Android7.0之前的uri file:// + return new File(uri.getPath()).getAbsolutePath(); + } + } + + // + // 从uri获取path + // + // @param uri content://media/external/file/109009 + //

+ // FileProvider适配 + // content://com.tencent.mobileqq.fileprovider/external_files/storage/emulated/0/Tencent/QQfile_recv/ + // content://com.tencent.mm.external.fileprovider/external/tencent/MicroMsg/Download/ + // + private static String getFilePathFromContentUri(Context context, Uri uri) { + if (null == uri) return null; + String data = null; + + String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME}; + Cursor cursor = context.getContentResolver().query(uri, filePathColumn, null, null, null); + if (null != cursor) { + if (cursor.moveToFirst()) { + int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); + if (index > -1) { + data = cursor.getString(index); + } else { + int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); + String fileName = cursor.getString(nameIndex); + data = getPathFromInputStreamUri(context, uri, fileName); + } + } + cursor.close(); + } + return data; + } + + // + // 用流拷贝文件一份到自己APP私有目录下 + // + // @param context + // @param uri + // @param fileName + // + private static String getPathFromInputStreamUri(Context context, Uri uri, String fileName) { + InputStream inputStream = null; + String filePath = null; + + if (uri.getAuthority() != null) { + try { + inputStream = context.getContentResolver().openInputStream(uri); + File file = createTemporalFileFrom(context, inputStream, fileName); + filePath = file.getPath(); + + } catch (Exception e) { + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (Exception e) { + } + } + } + + return filePath; + } + + private static File createTemporalFileFrom(Context context, InputStream inputStream, String fileName) + throws IOException { + File targetFile = null; + if (inputStream != null) { + int read; + byte[] buffer = new byte[8 * 1024]; + //自己定义拷贝文件路径 + targetFile = new File(context.getExternalCacheDir(), fileName); + if (targetFile.exists()) { + targetFile.delete(); + } + OutputStream outputStream = new FileOutputStream(targetFile); + + while ((read = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, read); + } + outputStream.flush(); + + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return targetFile; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UserVisionSystemProtectModeUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UserVisionSystemProtectModeUtil.java new file mode 100644 index 0000000..e91ea2f --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/UserVisionSystemProtectModeUtil.java @@ -0,0 +1,53 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/24 16:20:13 + * @Describe 用户视觉系统保护模式工具集 + */ +import java.util.Random; + +public class UserVisionSystemProtectModeUtil { + + public static final String TAG = "UserVisionSystemProtectModeUtil"; + + public static final String PreviewShuffleSMS(String szSMSText, String szRefuseChars, String szReplaceChars) { + String szPreview; + // 将字符串转换为字符数组 + char[] charArray = szSMSText.toCharArray(); + // 打乱字符数组 + shuffleArray(charArray); + // 构建新的字符串并打印 + szPreview = new String(charArray); + szPreview = useProtectedCharsRule(szPreview, szRefuseChars, szReplaceChars); + return szPreview; + } + + // + // 打乱字符数组的方法 + // + // @param array 要被打乱的字符数组 + // + public static void shuffleArray(char[] array) { + // 创建随机数生成器 + Random random = new Random(); + for (int i = array.length - 1; i > 0; i--) { + // 生成一个随机索引j,范围在0到i之间(包括i) + int j = random.nextInt(i + 1); + + // 交换array[i]和array[j]的位置 + char temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + } + + public static String useProtectedCharsRule(String szContent, String szRefuseChars, String szReplaceChars) { + // 正则表达式模式 (寻找 szProtectChars 其中一个字符) + String pattern = "[" + szRefuseChars + "]"; + + // 替换模式后的字符串 + String szProtectedContent = szContent.replaceAll(pattern, szReplaceChars); + return szProtectedContent; + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ViewUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ViewUtil.java new file mode 100644 index 0000000..72f8d00 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/ViewUtil.java @@ -0,0 +1,20 @@ +package cc.winboll.studio.mymessagemanager.utils; + +/** + * @Author ZhanGSKen + * @Date 2024/07/19 14:30:57 + * @Describe Uri 视图元素工具类 + */ +import android.widget.ScrollView; + +public class ViewUtil { + + public static void scrollScrollView(final ScrollView scrollView) { + scrollView.post(new Runnable() { + @Override + public void run() { + scrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/BottomPositionFixedScrollView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/BottomPositionFixedScrollView.java new file mode 100644 index 0000000..56ae783 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/BottomPositionFixedScrollView.java @@ -0,0 +1,125 @@ +package cc.winboll.studio.mymessagemanager.views; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/08/23 00:39 + * @Describe 多级拉动响应自定义控件 + */ +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewTreeObserver; +import android.widget.ScrollView; + +public class BottomPositionFixedScrollView extends ScrollView { + public static final String TAG = "BottomPositionFixedScrollView"; + // 记录底部对应的内容绝对位置(即底部位置在内容中的y坐标,该位置需始终保持在视图底部) + private int mBottomContentY = 0; + // 标记是否是首次布局(避免初始加载误触发) + private boolean isFirstLayout = true; + + public BottomPositionFixedScrollView(Context context) { + super(context); + init(); + } + + public BottomPositionFixedScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BottomPositionFixedScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + // 监听布局变化(高度改变时触发) + getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (isFirstLayout) { + isFirstLayout = false; + return; + } + // 布局变化后,恢复底部位置 + restoreBottomPosition(); + } + }); + } + + /** + * 重写滚动事件,记录“底部对应的内容绝对位置” + * (即当前视图底部边缘对应的内容y坐标,该坐标需始终保持在视图底部) + */ + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (getChildCount() == 0) { + mBottomContentY = 0; + return; + } + + // 内容总高度 + int contentHeight = getChildAt(0).getMeasuredHeight(); + // 视图可视高度(自身高度) + int scrollViewHeight = getMeasuredHeight(); + // 当前视图底部边缘对应的内容y坐标 = 顶部滚动距离(t) + 可视高度 + // (该坐标就是“底部内容的绝对位置”,需始终保持在视图底部) + mBottomContentY = t + scrollViewHeight; + + // 避免超过内容总高度(比如内容不足一屏时,底部最多到内容底部) + if (mBottomContentY > contentHeight) { + mBottomContentY = contentHeight; + } + } + + /** + * 恢复底部位置:让原记录的“底部内容绝对位置”仍保持在视图底部 + */ + private void restoreBottomPosition() { + if (getChildCount() == 0) { + return; + } + + // 新的内容总高度 + int newContentHeight = getChildAt(0).getMeasuredHeight(); + // 新的视图可视高度 + int newScrollViewHeight = getMeasuredHeight(); + + // 目标:让原mBottomContentY(底部内容绝对位置)仍位于视图底部 + // 此时需要的顶部滚动距离 = mBottomContentY - 新的可视高度 + int targetScrollY = mBottomContentY - newScrollViewHeight; + + // 边界修正: + // 1. 不能小于0(避免滚动到负数位置) + // 2. 不能大于“最大可滚动距离”(内容高度 - 可视高度,避免超出内容范围) + int maxScrollY = Math.max(newContentHeight - newScrollViewHeight, 0); + targetScrollY = Math.max(targetScrollY, 0); + targetScrollY = Math.min(targetScrollY, maxScrollY); + + // 滚动到目标位置,保持底部内容位置不变 + smoothScrollTo(0, targetScrollY); + } + + /** + * 外部手动设置底部内容绝对位置(可选) + */ + public void setBottomContentY(int bottomContentY) { + if (getChildCount() == 0) { + mBottomContentY = bottomContentY; + return; + } + // 限制不超过内容总高度 + int contentHeight = getChildAt(0).getMeasuredHeight(); + mBottomContentY = Math.min(bottomContentY, contentHeight); + restoreBottomPosition(); + } + + /** + * 获取当前底部内容绝对位置(可选) + */ + public int getBottomContentY() { + return mBottomContentY; + } +} + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/ConfirmSwitchView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/ConfirmSwitchView.java new file mode 100644 index 0000000..4eb835c --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/ConfirmSwitchView.java @@ -0,0 +1,76 @@ +package cc.winboll.studio.mymessagemanager.views; + +/** + * @Author ZhanGSKen + * @Date 2023/07/25 13:37:55 + */ +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Switch; +import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libappbase.LogUtils; + +public class ConfirmSwitchView extends Switch { + + public static final String TAG = "SwitchView"; + + Context mContext; + + public ConfirmSwitchView(Context context) { + super(context); + initView(context); + } + + public ConfirmSwitchView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public ConfirmSwitchView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context); + } + + public ConfirmSwitchView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initView(context); + } + + void initView(Context context) { + mContext = context; + /*TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SMSView, 0, 0); + colorInbox = a.getColor(R.styleable.SMSView_attrSMSViewInboxColor, 0); + colorSend = a.getColor(R.styleable.SMSView_attrSMSViewSendColor, 0); + a.recycle();*/ + } + + @Override + public void setOnClickListener(final View.OnClickListener l) { + super.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final boolean isChecked = isChecked(); + StringBuilder sbMessage = new StringBuilder("请确定["); + sbMessage.append(isChecked ?"开启": "关闭"); + sbMessage.append("]操作。"); + // 在这里添加您的点击事件响应逻辑 + YesNoAlertDialog.show(mContext, getText().toString(), sbMessage.toString(), new YesNoAlertDialog.OnDialogResultListener(){ + @Override + public void onYes() { + LogUtils.d(TAG, "onYes"); + setChecked(isChecked); + } + @Override + public void onNo() { + LogUtils.d(TAG, "onNo"); + setChecked(!isChecked); + } + }); + //Toast.makeText(getContext(), "Switch clicked", Toast.LENGTH_SHORT).show(); + // 确保调用了父类的onClick()方法 + l.onClick(v); + } + }); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/DateAgoTextView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/DateAgoTextView.java new file mode 100644 index 0000000..e813885 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/DateAgoTextView.java @@ -0,0 +1,32 @@ +package cc.winboll.studio.mymessagemanager.views; + +import android.content.Context; +import android.util.AttributeSet; +import androidx.appcompat.widget.AppCompatTextView; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class DateAgoTextView extends AppCompatTextView { + long mnDate; + + public DateAgoTextView(Context context) { + super(context); + } + + public DateAgoTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public DateAgoTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setDate(long nDate) { + SimpleDateFormat dateFormat = new SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + Date d = new Date(nDate); + + setText(dateFormat.format(d)); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/PhoneListViewForScrollView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/PhoneListViewForScrollView.java new file mode 100644 index 0000000..b7d5b43 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/PhoneListViewForScrollView.java @@ -0,0 +1,65 @@ +package cc.winboll.studio.mymessagemanager.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ListView; + +public class PhoneListViewForScrollView extends ListView { + + public PhoneListViewForScrollView(Context context) { + super(context); + } + + public PhoneListViewForScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PhoneListViewForScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + /** + * 重写onMeasure方法,重新计算高度,达到使ListView适应ScrollView的效果 + * + * @param widthMeasureSpec 宽度测量规则 + * @param heightMeasureSpec 高度测量规则 + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + /* + // 子控件TextView不换行显示 + ListAdapter listAdapter = this.getAdapter(); + if (listAdapter == null) { + return; + } + int maxWidth = 0; + for (int i = 0; i < listAdapter.getCount(); i++) { + View listItem = listAdapter.getView(i, null, this); + View cb = listItem.findViewById(R.id.listviewfiledataCheckBox1); + View iv = listItem.findViewById(R.id.listviewfiledataImageView1); + View tv = listItem.findViewById(R.id.listviewfiledataTextView1); + + cb.measure(0,0); + iv.measure(0,0); + tv.measure(0,0); + + //listItem.measure(0, 0); + //int width = listItem.getMeasuredWidth(); + int width = cb.getMeasuredWidth() + iv.getMeasuredWidth()+ tv.getMeasuredWidth(); + if(width>maxWidth) maxWidth = width; + } + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST); + super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec); + */ + + // 子控件TextView换行显示 + //Integer.MAX_VALUE:表示int类型能够表示的最大值,值为2的31次方-1 + //>>2:右移N位相当于除以2的N的幂 + //MeasureSpec.AT_MOST:子布局可以根据自己的大小选择任意大小的模式 + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + //int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST); + + super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSAcceptRuleListViewForScrollView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSAcceptRuleListViewForScrollView.java new file mode 100644 index 0000000..d60d8f6 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSAcceptRuleListViewForScrollView.java @@ -0,0 +1,33 @@ +package cc.winboll.studio.mymessagemanager.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ListView; + +public class SMSAcceptRuleListViewForScrollView extends ListView { + + public SMSAcceptRuleListViewForScrollView(Context context) { + super(context); + } + + public SMSAcceptRuleListViewForScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SMSAcceptRuleListViewForScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + /** + * 重写onMeasure方法,重新计算高度,达到使ListView适应ScrollView的效果 + * + * @param widthMeasureSpec 宽度测量规则 + * @param heightMeasureSpec 高度测量规则 + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSListViewForScrollView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSListViewForScrollView.java new file mode 100644 index 0000000..296882e --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSListViewForScrollView.java @@ -0,0 +1,73 @@ +package cc.winboll.studio.mymessagemanager.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ListView; + +public class SMSListViewForScrollView extends ListView { + static int nMaxWidth = 0; + + public SMSListViewForScrollView(Context context) { + super(context); + } + + public SMSListViewForScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + + } + + public SMSListViewForScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + /** + * 重写onMeasure方法,重新计算高度,达到使ListView适应ScrollView的效果 + * + * @param widthMeasureSpec 宽度测量规则 + * @param heightMeasureSpec 高度测量规则 + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + /* + // 子控件TextView不换行显示 + ListAdapter listAdapter = this.getAdapter(); + if (listAdapter == null) { + return; + } + int maxWidth = 0; + for (int i = 0; i < listAdapter.getCount(); i++) { + View listItem = listAdapter.getView(i, null, this); + View cb = listItem.findViewById(R.id.listviewfiledataCheckBox1); + View iv = listItem.findViewById(R.id.listviewfiledataImageView1); + View tv = listItem.findViewById(R.id.listviewfiledataTextView1); + + cb.measure(0,0); + iv.measure(0,0); + tv.measure(0,0); + + //listItem.measure(0, 0); + //int width = listItem.getMeasuredWidth(); + int width = cb.getMeasuredWidth() + iv.getMeasuredWidth()+ tv.getMeasuredWidth(); + if(width>maxWidth) maxWidth = width; + } + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST); + super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec); + */ + + // 子控件TextView换行显示 + //Integer.MAX_VALUE:表示int类型能够表示的最大值,值为2的31次方-1 + //>>2:右移N位相当于除以2的N的幂 + //MeasureSpec.AT_MOST:子布局可以根据自己的大小选择任意大小的模式 + + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + + //int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST); + + //int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.AT_MOST); + + super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); + } + +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSView.java new file mode 100644 index 0000000..9ce0bbe --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/SMSView.java @@ -0,0 +1,78 @@ +package cc.winboll.studio.mymessagemanager.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.util.AttributeSet; +import androidx.cardview.widget.CardView; +import cc.winboll.studio.mymessagemanager.R; + +public class SMSView extends CardView { + + public static final String TAG = "SMSView"; + + Context mContext; + int colorInbox; + int colorSend; + int colorItem; + public enum SMSType { INBOX, SEND } + SMSType mSMSType = SMSType.INBOX; + + public void setSMSType(SMSType smsType) { + this.mSMSType = smsType; + updateViewBackgroundColor(); + } + + public SMSType getCardType() { + return mSMSType; + } + + void updateViewBackgroundColor() { + if (mSMSType == SMSType.INBOX) { + setCardBackgroundColor(colorInbox); + } else if (mSMSType == SMSType.SEND) { + setCardBackgroundColor(colorSend); + } + } + + public SMSView(Context context) { + super(context); + mContext = context; + } + + public SMSView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + + TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SMSView, 0, 0); + String szSMSType = a.getString(R.styleable.SMSView_attrSMSType); + if((szSMSType == null)||szSMSType.equals("")) { + mSMSType = SMSType.SEND; + } else { + mSMSType = SMSType.valueOf(szSMSType); + } + colorInbox = a.getColor(R.styleable.SMSView_attrSMSViewInboxColor, 0); + colorSend = a.getColor(R.styleable.SMSView_attrSMSViewSendColor, 0); + a.recycle(); + } + + public SMSView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mContext = context; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // 子控件TextView换行显示 + //Integer.MAX_VALUE:表示int类型能够表示的最大值,值为2的31次方-1 + //>>2:右移N位相当于除以2的N的幂 + //MeasureSpec.AT_MOST:子布局可以根据自己的大小选择任意大小的模式 + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); + } +} diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/TTSRuleListViewForScrollView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/TTSRuleListViewForScrollView.java new file mode 100644 index 0000000..be676cb --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/TTSRuleListViewForScrollView.java @@ -0,0 +1,82 @@ +package cc.winboll.studio.mymessagemanager.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ListView; + +public class TTSRuleListViewForScrollView extends ListView { + static int nMaxWidth = 0; + + public TTSRuleListViewForScrollView(Context context) { + super(context); + } + + public TTSRuleListViewForScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + + } + + public TTSRuleListViewForScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void setSelection(int position) { + super.setSelection(position); + } + + /** + * 重写onMeasure方法,重新计算高度,达到使ListView适应ScrollView的效果 + * + * @param widthMeasureSpec 宽度测量规则 + * @param heightMeasureSpec 高度测量规则 + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + /* + // 子控件TextView不换行显示 + ListAdapter listAdapter = this.getAdapter(); + if (listAdapter == null) { + return; + } + int maxWidth = 0; + for (int i = 0; i < listAdapter.getCount(); i++) { + View listItem = listAdapter.getView(i, null, this); + View cb = listItem.findViewById(R.id.listviewfiledataCheckBox1); + View iv = listItem.findViewById(R.id.listviewfiledataImageView1); + View tv = listItem.findViewById(R.id.listviewfiledataTextView1); + + cb.measure(0,0); + iv.measure(0,0); + tv.measure(0,0); + + //listItem.measure(0, 0); + //int width = listItem.getMeasuredWidth(); + int width = cb.getMeasuredWidth() + iv.getMeasuredWidth()+ tv.getMeasuredWidth(); + if(width>maxWidth) maxWidth = width; + } + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST); + super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec); + */ + + // 子控件TextView换行显示 + //Integer.MAX_VALUE:表示int类型能够表示的最大值,值为2的31次方-1 + //>>2:右移N位相当于除以2的N的幂 + //MeasureSpec.AT_MOST:子布局可以根据自己的大小选择任意大小的模式 + + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + + //int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST); + + //int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.AT_MOST); + + super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); + } + +} + + + + diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/TTSRuleView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/TTSRuleView.java new file mode 100644 index 0000000..73ed2a3 --- /dev/null +++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/TTSRuleView.java @@ -0,0 +1,48 @@ +package cc.winboll.studio.mymessagemanager.views; + +/** + * @Author ZhanGSKen + * @Date 2023/07/24 15:08:31 + * @Describe TTS语音规则视图 + */ +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import androidx.cardview.widget.CardView; +import cc.winboll.studio.mymessagemanager.R; + +public class TTSRuleView extends CardView { + + public static final String TAG = "TTSRuleView"; + + Context mContext; + + public TTSRuleView(Context context) { + super(context); + mContext = context; + } + + public TTSRuleView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TTSRuleView, 0, 0); + int colorBackground = a.getColor(R.styleable.TTSRuleView_attrTTSRuleViewBackgroundColor, 0); + a.recycle(); + setCardBackgroundColor(colorBackground); + } + + public TTSRuleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mContext = context; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //Integer.MAX_VALUE:表示int类型能够表示的最大值,值为2的31次方-1 + //>>2:右移N位相当于除以2的N的幂 + //MeasureSpec.AT_MOST:子布局可以根据自己的大小选择任意大小的模式 + int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, newHeightMeasureSpec); + } + +} diff --git a/mymessagemanager/src/main/res/anim/slow_fade_in.xml b/mymessagemanager/src/main/res/anim/slow_fade_in.xml new file mode 100644 index 0000000..fa73170 --- /dev/null +++ b/mymessagemanager/src/main/res/anim/slow_fade_in.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mymessagemanager/src/main/res/drawable/bg_frame.xml b/mymessagemanager/src/main/res/drawable/bg_frame.xml new file mode 100644 index 0000000..75b2b94 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/bg_frame.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/bg_frame_black.xml b/mymessagemanager/src/main/res/drawable/bg_frame_black.xml new file mode 100644 index 0000000..cfe4379 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/bg_frame_black.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/bg_frame_white.xml b/mymessagemanager/src/main/res/drawable/bg_frame_white.xml new file mode 100644 index 0000000..002ffe1 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/bg_frame_white.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/cursor_pointer.xml b/mymessagemanager/src/main/res/drawable/cursor_pointer.xml new file mode 100644 index 0000000..e828063 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/cursor_pointer.xml @@ -0,0 +1,20 @@ + + + + diff --git a/mymessagemanager/src/main/res/drawable/ic_launcher.xml b/mymessagemanager/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000..d4d1eaf --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/mymessagemanager/src/main/res/drawable/ic_launcher_background.xml b/mymessagemanager/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..a4f78de --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/ic_launcher_background_golden.xml b/mymessagemanager/src/main/res/drawable/ic_launcher_background_golden.xml new file mode 100644 index 0000000..804236c --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/ic_launcher_background_golden.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/ic_launcher_background_sky.xml b/mymessagemanager/src/main/res/drawable/ic_launcher_background_sky.xml new file mode 100644 index 0000000..1ff84d4 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/ic_launcher_background_sky.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/ic_launcher_foreground.xml b/mymessagemanager/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..872b04e --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,10 @@ + + + + diff --git a/mymessagemanager/src/main/res/drawable/ic_message.xml b/mymessagemanager/src/main/res/drawable/ic_message.xml new file mode 100644 index 0000000..70f1497 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/ic_message.xml @@ -0,0 +1,20 @@ + + + + diff --git a/mymessagemanager/src/main/res/drawable/listview_item_selector.xml b/mymessagemanager/src/main/res/drawable/listview_item_selector.xml new file mode 100644 index 0000000..8fcab31 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/listview_item_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/shape_gradient.xml b/mymessagemanager/src/main/res/drawable/shape_gradient.xml new file mode 100644 index 0000000..c164fe9 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/shape_gradient.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/mymessagemanager/src/main/res/drawable/speaker.xml b/mymessagemanager/src/main/res/drawable/speaker.xml new file mode 100644 index 0000000..53e00c1 --- /dev/null +++ b/mymessagemanager/src/main/res/drawable/speaker.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/mymessagemanager/src/main/res/layout/activity_about.xml b/mymessagemanager/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..787ec5b --- /dev/null +++ b/mymessagemanager/src/main/res/layout/activity_about.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/mymessagemanager/src/main/res/layout/activity_appsettings.xml b/mymessagemanager/src/main/res/layout/activity_appsettings.xml new file mode 100644 index 0000000..98029b7 --- /dev/null +++ b/mymessagemanager/src/main/res/layout/activity_appsettings.xml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + +