Compare commits

..

3 Commits

208 changed files with 12850 additions and 2918 deletions

205
README.md
View File

@@ -1,103 +1,104 @@
# WinBoLL 源生态计划项目说明书
## 一、项目概述
### 1. 核心定位
WinBoLL 手机源码计划,旨在通过核心项目 WinBoLL 构建手机端与服务器端的 Android 项目的开发源码生态。实现手机与服务器的源码的联合开发。
### 2. 仓库架构
#### **仓库类型:功能说明**
☆ 基础项目分支 WinBoLL手机端安卓应用开发基础模板。
☆ 应用项目分支 APPBase、AES、PowerBell、Positions**:安卓应用单一管理系列项目。
☆ 源码汇总管理 OriginMaster**:各类分支源码合并存档,不适宜作为开发库使用。
### 3. 源码合并管理推送路线图
⚠️ **注意**:仅仅展示不同应用模块源码的综合管理路线。分支合并操作时,必须具备 Git 管理经验。
★ WinBoLL → APPBase → OriginMaster
★ WinBoLL → AES → OriginMaster
★ WinBoLL → PowerBell → OriginMaster
★ WinBoLL → Positions → OriginMaster
## 二、WinBoLL 项目核心信息
### 1. 项目简介
☆ WinBoLL 项目是为手机端开发Android 项目的需求而设计的项目。
### 2. 官方资源
#### ☆ 官方网站**https://www.winboll.cc/
#### ☆ 源码地址:
★ Giteahttps://gitea.winboll.cc/Studio/WinBoLL.git
★ GitHubhttps://github.com/ZhanGSKen/WinBoLL.git
★ 码云https://gitee.com/zhangsken/winboll.git
## 三、应用编译环境检查问题
### 核心判断条件:
☆ WinBoLL 项目以文件夹 `"/sdcard/WinBoLLStudio/APKs"` 是否存在为判断环境编译输出条件因为编译输出的APK文件需要一个可供保存的环境。
☆ 文件夹"/sdcard/WinBoLLStudio/APKs" 目录条件设置方法:
***Linux 服务器端方面***:建立 `/sdcard/WinBoLLStudio/APKs` 目录即可。
***手机开发端方面***:建立 `"/sdcard/WinBoLLStudio/APKs"` 目录(即 `"/storage/emulated/0/WinBoLLStudio/APKs"` 目录) 即可。
## 四、前置条件
### 1. WinBoLL APP 开发环境配置介绍
#### WinBoLL APK 编译输出内容包括:
☆ "/sdcard/WinBoLLStudio/APKs"` 目录内的所有应用分支的 APK 文件。
☆ "/sdcard/AppProjects/app.apk"文件。
#### WinBoLL APK 源码命名空间规范
☆ WinBoLL 项目使用 "cc.winboll.studio" 作为源码命名空间。在此命名空间下进行源码定义。
## 五、核心需求规划
### 1. WinBoLL 应用安全验证需求
#### ☆ 支持访问 https://console.winboll.cc/ 服务器以校验应用包签名与版本。
### 2. 手机端源码开发管理需求
#### ☆ 支持切换不同 WinBoLL 分支,以开发不同安卓应用。
## 六、编译与使用指南
### 1. 项目初始化(必须)
#### 1. 复制 `settings.gradle-demo` 为 `settings.gradle`。编辑 `settings.gradle` 文件内容,取消对应项目模块注释。
#### 2. 复制 `gradle.properties-androidx-demo` (Android X 项目) 或 `gradle.properties-android-demo` (基本 Android 项目) 为 `gradle.properties`。
#### 3. 复制(可选)`local.properties-demo` 为 `local.properties`,编辑 `local.properties` 文件内容,配置 Android SDK 目录。
#### 4. **签名设置**
☆ **调试编译秘钥制作**:使用 Termux 应用终端cd 进入 GenKeyStore 目录,运行 `bash gen_debug_keystore.sh` 脚本即可生成应用调试秘钥。
☆ **应用秘钥配置方法**:拷贝调试编译秘钥制作生成的 `appkey.jks` 与 `appkey.keystore` 文件到项目根目录即可。
## 七、应用编译命令介绍
### 1类库型模块配置要点
#### 1. **优先修改配置文件**:优先修改应用测试项目(目录为 `"<WinBoLl根目录>/<类库测试应用>/"`)内 `build.properties` 文件,设置对应的类库项目名称:`libraryProject=<类库项目模块名>`。
#### 2. **编译优先启动步骤**:使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishAPKAddTag.sh <类库测试项目模块名>` 命令。运行后可生成测试项目与类库项目的编译参数文件 `build.properties`。生成的 `build.properties` 文件有两份,一份在测试项目模块的文件夹内,一份在类库项目本身的模块文件夹内。
#### 3. **最后类库编译发布步骤**:使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块名>` 命令。运行后可发布至 WinBoLL Nexus Maven 库、本地 maven 目录或者是通用默认的 Gradle Maven 库。
### 2单一应用型模块与类库测试型模块配置要点
#### ☆ APK 编译方法:
使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名>`。
#### ☆ 运行后的 APK 输出路径:
★ 默认路径 (`$ bash gradlew assembleBetaDebug` 任务)APK 在 `/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/debug/` 目录。
★ 默认路径 (`$ bash assembleStageRelease` 任务)APK 在 `/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/` 目录。
★ 额外输出路径:(假设 `winboll.properties` 文件已配置 `ExtraAPKOutputPath` 属性) 输出至 `ExtraAPKOutputPath` 属性配置的目录下。
### 3手机端应用调试命令介绍
#### ☆ Beta 渠道调试命令
$bash gradlew assembleBetaDebug
#### ☆ Stage 渠道调试命令
$bash gradlew assembleStageDebug
### 4服务器端开发命令介绍
##### ☆ Stage 渠道应用发布命令为:
"<WinBoLl根目录>/settings.gradle"文件需要配置编译模块开启参数,拷贝 settings.gradle-demo 为 settings.gradle 文件取消对应的分支配置部分即可。)
$bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
或者是
$bash gradlew assembleStageRelease
WinBoLL 源生态计划项目说明书
## 八、WinBoLL 应用 APK 版本号命名规则
### ☆ Stage 渠道:
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号> 示例 APPBase_15.7.0 
### ☆ Beta 渠道:
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> 示例 APPBase_15.9.6-beta8_5413 
一、项目概述
1. 核心定位
【OriginMaster】WinBoLL 源生态计划,旨在通过核心项目 WinBoLL 联动系列开发库,构建手机端 Android 项目开发与多端编译同步的完整生态,实现手机与电脑的源码同步开发。
2. 仓库架构
仓库类型 包含仓库 功能说明
开发库 WinBoLL、APPBase、AES、PowerBell、Positions 核心开发依赖库,其中 WinBoLL 可作为应用开发的基础继承模板
分支汇总存档库 OriginMaster 仅用于汇总各开发库分支,不适宜作为开发库克隆使用,非应用开发基础库
3. 源码推送路径
- WinBoLL → APPBase → OriginMaster
- WinBoLL → AES → OriginMaster
- WinBoLL → PowerBell → OriginMaster
- WinBoLL → Positions → OriginMaster
二、WinBoLL APP 核心信息
1. 项目简介
WinBoLL Studio Android 应用开源项目,专注于手机端 Android 开发与多端编译同步。
2. 官方资源
- 官方网站https://www.winboll.cc/
- 源码地址:
- Giteahttps://gitea.winboll.cc/Studio/WinBoLL.git
- GitHubhttps://github.com/ZhanGSKen/WinBoLL.git
- 码云https://gitee.com/zhangsken/winboll.git
- 托管类库源码:
- APPBasejitpack.iohttps://github.com/ZhanGSKen/APPBase.git
- AESjitpack.iohttps://github.com/ZhanGSKen/AES.git
三、通用特征文件夹前置(/sdcard
- Linux 系统文件夹直接使用  /sdcard 
- 手机 SD 卡存储( /storage/emulated/0 挂载的别名也可为  /sdcard 
四、前置条件
1. WinBoLL-APP 配置
- APK 编译输出目录: /sdcard/WinBoLLStudio/APKs/ ,以及  /sdcard/AppProjects/ (命名为  app.apk 
- 签名与命名空间:支持应用签名验证定制化,与衍生 APP 共享  cc.winboll.studio  命名空间
五、核心需求规划
1. 主机端需求
- 支持  winboll.cc  域名的用户注册登录服务
- 支持  https://console.winboll.cc/api  访问
2. APP 端需求
- 实现手机端 Android 应用开发与管理功能
六、编译与使用指南
1. 项目初始化(必须)
1. 复制  settings.gradle-demo  settings.gradle 取消对应项目模块注释
2. 复制  gradle.properties-androidx-demo  gradle.properties-android-demo  gradle.properties 
3. (可选)复制  local.properties-demo  local.properties 配置 Android SDK 目录
4. 签名设置:
- 调试编译:进入 GenKeyStore 目录执行  bash gen_debug_keystore.sh 
- 非必须clone keystore 模块,拷贝  appkey.jks  appkey.keystore  到项目根目录
2. 编译命令
1类库型项目
1. 修改测试项目  build.properties 设置  libraryProject=<类库项目模块名> 
2. 编译测试项目 bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
3. 编译类库项目 bash .winboll/bashPublishLIBAddTag.sh <类库项目模块名> (发布至 WinBoLL Nexus Maven 库)
2应用型项目
- 编译命令 bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
3调试编译
- Beta 调试 bash gradlew assembleBetaDebug 
- Stage 调试 bash gradlew assembleStageDebug
 
4发布编译
- Stage 发布bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
或者执行  bash gradlew assembleStageRelease
3. 编译输出路径
- 默认路径(assembleBetaDebug任务) /sdcard/WinBoLLStudio/APKs/<项目根目录名称>/debug/ 
- 默认路径(assembleStageRelease任务) /sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/ 
- 额外路径:若  winboll.properties  配置  ExtraAPKOutputPath APK 同步拷贝至该ExtraAPKOutputPath路径
4. 版本号命名规则
- Stage 渠道 V<应用开发环境编号><应用功能变更号><应用调试阶段号> 示例 APPBase_15.7.0 
- Beta 渠道 V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> 示例 APPBase_15.9.6-beta8_5413 

View File

@@ -1,15 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
mavenLocal {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
@@ -20,6 +11,19 @@ buildscript {
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoLL Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
//println "mavenLocal : ==========="
//println mavenLocal().url
//println "mavenLocal : ==========="
//mavenLocal()
}
dependencies {
// 适配MIUI12
@@ -31,15 +35,6 @@ buildscript {
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
mavenLocal {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
@@ -50,6 +45,19 @@ allprojects {
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoLL Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
//println "mavenLocal : ==========="
//println mavenLocal().url
//println "mavenLocal : ==========="
//mavenLocal()
}
ext {
// 定义全局变量,常用于版本管理

View File

@@ -1,34 +0,0 @@
# Gallery
#### 介绍
云宝相册应用
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh gallery
#### 使用说明
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
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/)
#### 参考文档

View File

@@ -1,119 +0,0 @@
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 {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.gallery"
minSdkVersion 23
// 适配MIUI12
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.0"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
sourceSets {
main {
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
}
}
}
dependencies {
api 'com.google.code.gson:gson:2.10.1'
// 下拉控件
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
// SSH
api 'com.jcraft:jsch:0.1.55'
// Html 解析
api 'org.jsoup:jsoup:1.13.1'
// 二维码类库
api 'com.google.zxing:core:3.4.1'
api 'com.journeyapps:zxing-android-embedded:3.6.0'
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// OkHttp网络请求
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
// FastJSON解析
implementation 'com.alibaba:fastjson:1.2.76'
// 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.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//implementation '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'
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.core:core:1.6.0"
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.google.guava:guava:24.1-jre"
/*
implementation "io.noties.markwon:core:$markwonVersion"
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
implementation "io.noties.markwon:linkify:$markwonVersion"
implementation "io.noties.markwon:recycler:$markwonVersion"
*/
/*implementation 'com.termux:terminal-emulator:0.118.0'
implementation 'com.termux:terminal-view:0.118.0'
implementation 'com.termux:termux-shared:0.118.0'
*/
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.15.9'
api 'cc.winboll.studio:libappbase:15.15.19'
// WinBoLL备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -1,8 +0,0 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Apr 25 06:42:01 HKT 2026
stageCount=3
libraryProject=
baseVersion=15.0
publishVersion=15.0.2
buildCount=0
baseBetaVersion=15.0.3

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
<string name="app_name">Gallery+</string>
</resources>

View File

@@ -1,58 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.gallery">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:resizeableActivity="true"
android:name=".GlobalWinBoLLApplication"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/settings_title"/>
<activity
android:name=".AlbumActivity"
android:label="@string/app_name"/>
<activity
android:name=".ImageViewerActivity"
android:label="@string/app_name"/>
<activity
android:name=".TrashActivity"
android:label="@string/trash"/>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name=".GlobalApplication$CrashActivity"/>
</application>
</manifest>

View File

@@ -1,25 +0,0 @@
package cc.winboll.studio.gallery;
import android.net.Uri;
import cc.winboll.studio.libappbase.LogUtils;
public class Album {
public static final String TAG = "Album";
private String name;
private String path;
private Uri coverUri;
private int imageCount;
public Album(String name, String path, Uri coverUri, int imageCount) {
LogUtils.d(TAG, "Album created: " + name);
this.name = name;
this.path = path;
this.coverUri = coverUri;
this.imageCount = imageCount;
}
public String getName() { return name; }
public String getPath() { return path; }
public Uri getCoverUri() { return coverUri; }
public int getImageCount() { return imageCount; }
}

View File

@@ -1,153 +0,0 @@
package cc.winboll.studio.gallery;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.gallery.ImageAdapter.OnImageClickListener;
public class AlbumActivity extends AppCompatActivity {
public static final String TAG = "AlbumActivity";
private static final int PERMISSION_REQUEST_CODE = 101;
public static final String EXTRA_ALBUM_PATH = "album_path";
public static final String EXTRA_ALBUM_NAME = "album_name";
private RecyclerView recyclerView;
private ImageAdapter adapter;
private String albumPath;
private String albumName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LogUtils.d(TAG, "onCreate");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
albumPath = getIntent().getStringExtra(EXTRA_ALBUM_PATH);
albumName = getIntent().getStringExtra(EXTRA_ALBUM_NAME);
getSupportActionBar().setTitle(albumName);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
adapter = new ImageAdapter();
recyclerView.setAdapter(adapter);
adapter.setOnImageClickListener(new OnImageClickListener() {
@Override
public void onImageClick(int position, ArrayList<Uri> urls, ArrayList<String> paths) {
Intent intent = new Intent(AlbumActivity.this, ImageViewerActivity.class);
intent.putParcelableArrayListExtra(ImageViewerActivity.EXTRA_IMAGE_URLS, urls);
intent.putStringArrayListExtra(ImageViewerActivity.EXTRA_POSITIONS, paths);
intent.putExtra(ImageViewerActivity.EXTRA_POSITION, position);
startActivity(intent);
}
});
if (checkPermission()) {
loadImages();
} else {
requestPermission();
}
}
private boolean checkPermission() {
return ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
}
private void requestPermission() {
ActivityCompat.requestPermissions(this,
new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
loadImages();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
}
}
}
private void loadImages() {
LogUtils.d(TAG, "loadImages");
ArrayList<Uri> imageUrls = new ArrayList<>();
ArrayList<String> imagePaths = new ArrayList<>();
ContentResolver contentResolver = getContentResolver();
Uri collection = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String selection = android.provider.MediaStore.Images.Media.DATA + " LIKE ?";
String[] selectionArgs = new String[]{albumPath + "%"};
String sortOrder = android.provider.MediaStore.Images.Media.DATE_ADDED + " DESC";
try (Cursor cursor = contentResolver.query(collection, null, selection, selectionArgs, sortOrder)) {
if (cursor != null) {
int dataColumn = cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media.DATA);
while (cursor.moveToNext()) {
String path = cursor.getString(dataColumn);
if (path != null && path.startsWith(albumPath + "/")) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Images.Media._ID));
Uri contentUri = Uri.withAppendedPath(collection, String.valueOf(id));
imageUrls.add(contentUri);
imagePaths.add(path);
}
}
}
}
if (imageUrls.isEmpty()) {
Toast.makeText(this, R.string.no_images_found, Toast.LENGTH_SHORT).show();
LogUtils.i(TAG, "No images found");
}
adapter.setData(imageUrls, imagePaths);
LogUtils.d(TAG, "Loaded " + imageUrls.size() + " images");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_refresh) {
if (checkPermission()) {
loadImages();
}
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
if (checkPermission()) {
loadImages();
}
}
}

View File

@@ -1,117 +0,0 @@
package cc.winboll.studio.gallery;
import android.net.Uri;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.io.File;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder> {
public static final String TAG = "AlbumAdapter";
private ArrayList<Album> albums = new ArrayList<>();
private OnAlbumClickListener listener;
public interface OnAlbumClickListener {
void onAlbumClick(Album album);
}
public void setOnAlbumClickListener(OnAlbumClickListener listener) {
this.listener = listener;
}
public void setData(ArrayList<Album> albums) {
this.albums = albums;
LogUtils.d(TAG, "setData: " + albums.size() + " albums");
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_album, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
final Album album = albums.get(position);
LogUtils.d(TAG, "bind: " + album.getName() + ", cover=" + album.getCoverUri());
holder.albumName.setText(album.getName());
holder.imageCount.setText(album.getImageCount() + " photos");
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onAlbumClick(album);
}
}
});
Uri coverUri = album.getCoverUri();
if (coverUri != null) {
String uriString = coverUri.toString();
LogUtils.d(TAG, "uri scheme: " + coverUri.getScheme() + ", path: " + uriString);
// For content:// URIs, try to get the actual file path
if ("content".equals(coverUri.getScheme())) {
try {
android.database.Cursor cursor = holder.coverImage.getContext().getContentResolver()
.query(coverUri, new String[]{MediaStore.Images.Media.DATA}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
String filePath = cursor.getString(dataColumn);
cursor.close();
if (filePath != null) {
File actualFile = new File(filePath);
LogUtils.d(TAG, "actual file: " + actualFile.getAbsolutePath() + ", exists=" + actualFile.exists());
// Use file path instead of content URI for better compatibility
Glide.with(holder.coverImage.getContext())
.load(actualFile)
.centerCrop()
.into(holder.coverImage);
return;
}
}
} catch (Exception e) {
LogUtils.e(TAG, "query error: " + e.getMessage());
}
}
}
// Fallback to content URI
Glide.with(holder.coverImage.getContext())
.load(coverUri)
.centerCrop()
.into(holder.coverImage);
}
@Override
public int getItemCount() {
return albums.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView coverImage;
TextView albumName;
TextView imageCount;
ViewHolder(View itemView) {
super(itemView);
coverImage = itemView.findViewById(R.id.album_cover);
albumName = itemView.findViewById(R.id.album_name);
imageCount = itemView.findViewById(R.id.image_count);
}
}
}

View File

@@ -1,44 +0,0 @@
package cc.winboll.studio.gallery;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/24 15:23
*/
public class GlobalWinBoLLApplication extends GlobalApplication {
public static final String TAG = "GlobalWinBoLLApplication";
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate");
setIsDebugging(BuildConfig.DEBUG);
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.view_toast);
//ToastUtils.setStyle(new WhiteToastStyle());
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
//CrashHandler.getInstance().registerGlobal(this);
//CrashHandler.getInstance().registerPart(this);
}
@Override
public void onTerminate() {
super.onTerminate();
LogUtils.d(TAG, "onTerminate");
ToastUtils.release();
}
}

View File

@@ -1,76 +0,0 @@
package cc.winboll.studio.gallery;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
public static final String TAG = "ImageAdapter";
private ArrayList<Uri> imageUrls = new ArrayList<>();
private ArrayList<String> imagePaths = new ArrayList<>();
private OnImageClickListener listener;
public interface OnImageClickListener {
void onImageClick(int position, ArrayList<Uri> urls, ArrayList<String> paths);
}
public void setOnImageClickListener(OnImageClickListener listener) {
this.listener = listener;
}
public void setData(ArrayList<Uri> urls, ArrayList<String> paths) {
this.imageUrls = urls;
this.imagePaths = paths;
LogUtils.d(TAG, "setData: " + urls.size() + " images");
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_gallery, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
Glide.with(holder.imageView.getContext())
.load(imageUrls.get(position))
.centerCrop()
.into(holder.imageView);
final ArrayList<Uri> urls = imageUrls;
final ArrayList<String> paths = imagePaths;
holder.imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onImageClick(position, urls, paths);
}
}
});
}
@Override
public int getItemCount() {
return imageUrls.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
ViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image);
}
}
}

View File

@@ -1,56 +0,0 @@
package cc.winboll.studio.gallery;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
public class ImagePagerAdapter extends PagerAdapter {
public static final String TAG = "ImagePagerAdapter";
private ArrayList<Uri> imageUrls;
public ImagePagerAdapter(ArrayList<Uri> imageUrls) {
this.imageUrls = imageUrls;
LogUtils.d(TAG, "ImagePagerAdapter created with " + imageUrls.size() + " images");
}
@Override
public int getCount() {
return imageUrls.size();
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
View view = LayoutInflater.from(container.getContext())
.inflate(R.layout.item_image_pager, container, false);
ImageView imageView = view.findViewById(R.id.image);
Glide.with(imageView.getContext())
.load(imageUrls.get(position))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(imageView);
container.addView(view);
return view;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
}

View File

@@ -1,377 +0,0 @@
package cc.winboll.studio.gallery;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.ImageButton;
import androidx.viewpager.widget.ViewPager;
import java.io.File;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
public class ImageViewerActivity extends Activity implements ViewPager.OnPageChangeListener {
public static final String TAG = "ImageViewerActivity";
public static final String EXTRA_IMAGE_URLS = "image_urls";
public static final String EXTRA_POSITIONS = "image_positions";
public static final String EXTRA_POSITION = "position";
private ArrayList<Uri> imageUrls;
private ArrayList<String> imagePaths;
private int currentPosition;
private ViewPager viewPager;
private View toolbar;
private ImageButton btnBack;
private ImageButton btnDelete;
private ImageButton btnShare;
private ImageButton btnInfo;
private GestureDetector gestureDetector;
private TrashManager trashManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_image_viewer);
LogUtils.d(TAG, "onCreate");
imageUrls = getIntent().getParcelableArrayListExtra(EXTRA_IMAGE_URLS);
imagePaths = getIntent().getStringArrayListExtra(EXTRA_POSITIONS);
currentPosition = getIntent().getIntExtra(EXTRA_POSITION, 0);
trashManager = new TrashManager(this);
viewPager = findViewById(R.id.view_pager);
toolbar = findViewById(R.id.toolbar);
btnBack = findViewById(R.id.btn_back);
btnDelete = findViewById(R.id.btn_delete);
btnShare = findViewById(R.id.btn_share);
btnInfo = findViewById(R.id.btn_info);
ImagePagerAdapter adapter = new ImagePagerAdapter(imageUrls);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(currentPosition);
viewPager.addOnPageChangeListener(this);
gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
toggleToolbar();
return true;
}
});
viewPager.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDeleteDialog();
}
});
btnShare.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
shareCurrentImage();
}
});
btnInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showImageInfo();
}
});
}
private void toggleToolbar() {
if (toolbar.getVisibility() == View.VISIBLE) {
toolbar.setVisibility(View.GONE);
} else {
toolbar.setVisibility(View.VISIBLE);
}
}
private void showDeleteDialog() {
new AlertDialog.Builder(this)
.setMessage("Delete to trash?")
.setPositiveButton("Yes", new android.content.DialogInterface.OnClickListener() {
@Override
public void onClick(android.content.DialogInterface dialog, int which) {
moveToTrash();
}
})
.setNegativeButton("No", null)
.show();
}
private void moveToTrash() {
LogUtils.d(TAG, "moveToTrash");
if (currentPosition >= 0 && currentPosition < imageUrls.size()) {
String imagePath = "";
if (imagePaths != null && currentPosition < imagePaths.size()) {
imagePath = imagePaths.get(currentPosition);
} else {
imagePath = getPathFromUri(imageUrls.get(currentPosition));
}
Uri imageUri = imageUrls.get(currentPosition);
long result = -1;
if (!imagePath.isEmpty()) {
result = trashManager.addToTrash(imagePath);
}
if (result > 0) {
try {
getContentResolver().delete(imageUri, null, null);
} catch (Exception e) {
e.printStackTrace();
}
android.widget.Toast.makeText(this, "Moved to trash", android.widget.Toast.LENGTH_SHORT).show();
LogUtils.i(TAG, "Moved to trash");
removeCurrentImage();
} else {
try {
int deleted = getContentResolver().delete(imageUri, null, null);
android.widget.Toast.makeText(this, "Deleted: " + deleted, android.widget.Toast.LENGTH_SHORT).show();
removeCurrentImage();
} catch (Exception e) {
e.printStackTrace();
removeCurrentImage();
}
}
}
}
private void removeCurrentImage() {
imageUrls.remove(currentPosition);
if (imagePaths != null) {
imagePaths.remove(currentPosition);
}
if (imageUrls.isEmpty()) {
finish();
} else {
if (currentPosition >= imageUrls.size()) {
currentPosition = imageUrls.size() - 1;
}
viewPager.setAdapter(new ImagePagerAdapter(imageUrls));
viewPager.setCurrentItem(currentPosition);
}
}
private String getPathFromUri(Uri uri) {
String[] projection = { MediaStore.Images.Media.DATA };
android.database.Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
return cursor.getString(columnIndex);
}
} finally {
cursor.close();
}
}
return uri.getPath();
}
private void shareCurrentImage() {
if (currentPosition >= 0 && currentPosition < imageUrls.size()) {
Uri imageUri = imageUrls.get(currentPosition);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share Image"));
}
}
private void showImageInfo() {
if (currentPosition < 0 || currentPosition >= imageUrls.size()) {
return;
}
String imagePath = "";
if (imagePaths != null && currentPosition < imagePaths.size()) {
imagePath = imagePaths.get(currentPosition);
} else {
imagePath = getPathFromUri(imageUrls.get(currentPosition));
}
File imageFile = new File(imagePath);
if (!imageFile.exists()) {
imageFile = new File(imagePath);
}
android.widget.LinearLayout layout = new android.widget.LinearLayout(this);
layout.setOrientation(android.widget.LinearLayout.VERTICAL);
layout.setPadding(48, 32, 48, 32);
android.widget.TextView labelPath = new android.widget.TextView(this);
labelPath.setText("Path:");
labelPath.setTextColor(getColor(android.R.color.darker_gray));
labelPath.setTextSize(14);
labelPath.setTypeface(null, android.graphics.Typeface.BOLD);
layout.addView(labelPath);
android.widget.TextView valuePath = new android.widget.TextView(this);
valuePath.setText(imagePath);
valuePath.setTextColor(getColor(android.R.color.black));
valuePath.setTextSize(14);
valuePath.setTextIsSelectable(true);
layout.addView(valuePath);
if (imageFile.exists()) {
long sizeBytes = imageFile.length();
String size;
if (sizeBytes < 1024) {
size = sizeBytes + " B";
} else if (sizeBytes < 1024 * 1024) {
size = String.format("%.2f KB", sizeBytes / 1024.0);
} else {
size = String.format("%.2f MB", sizeBytes / (1024.0 * 1024.0));
}
android.widget.TextView labelSize = new android.widget.TextView(this);
labelSize.setText("Size:");
labelSize.setTextColor(getColor(android.R.color.darker_gray));
labelSize.setTextSize(14);
labelSize.setTypeface(null, android.graphics.Typeface.BOLD);
layout.addView(labelSize);
android.widget.TextView valueSize = new android.widget.TextView(this);
valueSize.setText(size);
valueSize.setTextColor(getColor(android.R.color.black));
valueSize.setTextSize(14);
valueSize.setTextIsSelectable(true);
layout.addView(valueSize);
}
try {
android.graphics.BitmapFactory.Options options = new android.graphics.BitmapFactory.Options();
options.inJustDecodeBounds = true;
android.graphics.BitmapFactory.decodeFile(imagePath, options);
if (options.outWidth > 0 && options.outHeight > 0) {
android.widget.TextView labelPixels = new android.widget.TextView(this);
labelPixels.setText("Pixels:");
labelPixels.setTextColor(getColor(android.R.color.darker_gray));
labelPixels.setTextSize(14);
labelPixels.setTypeface(null, android.graphics.Typeface.BOLD);
layout.addView(labelPixels);
android.widget.TextView valuePixels = new android.widget.TextView(this);
valuePixels.setText(options.outWidth + " x " + options.outHeight);
valuePixels.setTextColor(getColor(android.R.color.black));
valuePixels.setTextSize(14);
valuePixels.setTextIsSelectable(true);
layout.addView(valuePixels);
}
} catch (Exception e) {
LogUtils.e(TAG, "get pixels error: " + e.getMessage());
}
try {
String[] projection = {
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.DATE_TAKEN
};
android.database.Cursor cursor = getContentResolver().query(
imageUrls.get(currentPosition), projection, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
int dateAddedCol = cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED);
int dateTakenCol = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN);
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if (dateTakenCol >= 0) {
long dateTaken = cursor.getLong(dateTakenCol);
if (dateTaken > 0) {
android.widget.TextView labelTaken = new android.widget.TextView(this);
labelTaken.setText("Date Taken:");
labelTaken.setTextColor(getColor(android.R.color.darker_gray));
labelTaken.setTextSize(14);
labelTaken.setTypeface(null, android.graphics.Typeface.BOLD);
layout.addView(labelTaken);
android.widget.TextView valueTaken = new android.widget.TextView(this);
valueTaken.setText(sdf.format(new java.util.Date(dateTaken)));
valueTaken.setTextColor(getColor(android.R.color.black));
valueTaken.setTextSize(14);
valueTaken.setTextIsSelectable(true);
layout.addView(valueTaken);
}
}
if (dateAddedCol >= 0) {
long dateAdded = cursor.getLong(dateAddedCol);
if (dateAdded > 0) {
android.widget.TextView labelAdded = new android.widget.TextView(this);
labelAdded.setText("Date Added:");
labelAdded.setTextColor(getColor(android.R.color.darker_gray));
labelAdded.setTextSize(14);
labelAdded.setTypeface(null, android.graphics.Typeface.BOLD);
layout.addView(labelAdded);
android.widget.TextView valueAdded = new android.widget.TextView(this);
valueAdded.setText(sdf.format(new java.util.Date(dateAdded * 1000)));
valueAdded.setTextColor(getColor(android.R.color.black));
valueAdded.setTextSize(14);
valueAdded.setTextIsSelectable(true);
layout.addView(valueAdded);
}
}
}
cursor.close();
}
} catch (Exception e) {
LogUtils.e(TAG, "get date error: " + e.getMessage());
}
new AlertDialog.Builder(this)
.setTitle("Image Info")
.setView(layout)
.setPositiveButton("OK", null)
.show();
}
@Override
public void onPageSelected(int position) {
currentPosition = position;
}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onBackPressed() {
finish();
}
}

View File

@@ -1,290 +0,0 @@
package cc.winboll.studio.gallery;
import android.Manifest;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.gallery.AlbumAdapter.OnAlbumClickListener;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogActivity;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private static final int PERMISSION_REQUEST_CODE = 100;
private static final int MANAGE_PERMISSION_REQUEST_CODE = 101;
private RecyclerView recyclerView;
private AlbumAdapter adapter;
private Preferences prefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LogUtils.d(TAG, "onCreate");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
prefs = new Preferences(this);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(this, 2));
adapter = new AlbumAdapter();
recyclerView.setAdapter(adapter);
adapter.setOnAlbumClickListener(new OnAlbumClickListener() {
@Override
public void onAlbumClick(Album album) {
Intent intent = new Intent(MainActivity.this, AlbumActivity.class);
intent.putExtra(AlbumActivity.EXTRA_ALBUM_PATH, album.getPath());
intent.putExtra(AlbumActivity.EXTRA_ALBUM_NAME, album.getName());
startActivity(intent);
}
});
checkAndRequestPermissions();
}
private void checkAndRequestPermissions() {
LogUtils.i(TAG, "checkAndRequestPermissions");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, MANAGE_PERMISSION_REQUEST_CODE);
} catch (Exception e) {
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, MANAGE_PERMISSION_REQUEST_CODE);
}
return;
}
}
if (checkPermission()) {
loadAlbums();
} else {
requestPermission();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MANAGE_PERMISSION_REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
loadAlbums();
} else {
Toast.makeText(this, "Permission required", Toast.LENGTH_SHORT).show();
}
}
}
}
private boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
}
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
}
private void requestPermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
loadAlbums();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
}
}
}
private void loadAlbums() {
LogUtils.d(TAG, "loadAlbums");
String folderPath = prefs.getFolderPath();
File baseFolder = new File(folderPath);
LogUtils.d(TAG, "baseFolder: " + baseFolder.getAbsolutePath() + ", exists=" + baseFolder.exists());
if (!baseFolder.exists() || !baseFolder.isDirectory()) {
folderPath = Preferences.getDefaultPath();
baseFolder = new File(folderPath);
LogUtils.d(TAG, "try default: " + baseFolder.getAbsolutePath() + ", exists=" + baseFolder.exists());
if (!baseFolder.exists()) {
folderPath = Environment.getExternalStorageDirectory() + "/Pictures";
baseFolder = new File(folderPath);
LogUtils.d(TAG, "try Pictures: " + baseFolder.getAbsolutePath() + ", exists=" + baseFolder.exists());
}
}
ArrayList<Album> albums = new ArrayList<>();
FileFilter directoryFilter = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
};
File[] subfolders = baseFolder.listFiles(directoryFilter);
LogUtils.d(TAG, "subfolders: " + (subfolders != null ? subfolders.length : 0));
if (subfolders != null) {
for (File subfolder : subfolders) {
LogUtils.d(TAG, "scanning folder: " + subfolder.getName());
ArrayList<Uri> images = getImagesInFolder(subfolder.getAbsolutePath());
if (!images.isEmpty()) {
Uri latestImage = images.get(0);
albums.add(new Album(subfolder.getName(), subfolder.getAbsolutePath(), latestImage, images.size()));
LogUtils.d(TAG, "album added: " + subfolder.getName() + ", " + images.size() + " images");
}
}
}
if (albums.isEmpty()) {
Toast.makeText(this, R.string.no_images_found, Toast.LENGTH_SHORT).show();
LogUtils.i(TAG, "No albums found");
}
adapter.setData(albums);
LogUtils.d(TAG, "Loaded " + albums.size() + " albums");
}
private ArrayList<Uri> getImagesInFolder(String folderPath) {
ArrayList<Uri> imageUrls = new ArrayList<>();
ContentResolver contentResolver = getContentResolver();
Uri collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Images.Media.DATA + " LIKE ?";
String[] selectionArgs = new String[]{folderPath + "/%"};
String sortOrder = MediaStore.Images.Media.DATE_ADDED + " DESC";
LogUtils.d(TAG, "getImagesInFolder: " + folderPath);
try (Cursor cursor = contentResolver.query(collection, null, selection, selectionArgs, sortOrder)) {
if (cursor != null) {
LogUtils.d(TAG, "cursor count: " + cursor.getCount());
int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
while (cursor.moveToNext()) {
String path = cursor.getString(dataColumn);
if (path != null) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
Uri contentUri = Uri.withAppendedPath(collection, String.valueOf(id));
LogUtils.d(TAG, "image: id=" + id + ", path=" + path);
imageUrls.add(contentUri);
}
}
}
}
LogUtils.d(TAG, "found " + imageUrls.size() + " images");
return imageUrls;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
startActivity(new Intent(this, SettingsActivity.class));
return true;
} else if (id == R.id.action_trash) {
startActivity(new Intent(this, TrashActivity.class));
return true;
} else if (id == R.id.action_refresh) {
if (checkPermission()) {
loadAlbums();
}
return true;
} else if (id == R.id.action_debug) {
LogActivity.startLogActivity(this);
// Log.d("Gallery", "Debug log message");
// Toast.makeText(this, R.string.debug_message, Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
if (checkPermission()) {
scanMediaStore();
loadAlbums();
}
}
private void scanMediaStore() {
String folderPath = prefs.getFolderPath();
File baseFolder = new File(folderPath);
if (baseFolder.exists() && baseFolder.isDirectory()) {
File[] subfolders = baseFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
if (subfolders != null) {
ArrayList<String> paths = new ArrayList<>();
for (File subfolder : subfolders) {
File[] images = subfolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
String lower = name.toLowerCase();
return lower.endsWith(".jpg") || lower.endsWith(".jpeg")
|| lower.endsWith(".png") || lower.endsWith(".gif")
|| lower.endsWith(".webp") || lower.endsWith(".bmp");
}
});
if (images != null) {
for (File img : images) {
paths.add(img.getAbsolutePath());
}
}
}
if (!paths.isEmpty()) {
LogUtils.d(TAG, "scanning " + paths.size() + " files to MediaStore");
String[] pathArray = paths.toArray(new String[0]);
MediaScannerConnection.scanFile(this, pathArray, null, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
LogUtils.d(TAG, "scanCompleted: " + path + " -> " + uri);
}
});
}
}
}
}
}

View File

@@ -1,33 +0,0 @@
package cc.winboll.studio.gallery;
import android.content.Context;
import android.content.SharedPreferences;
import cc.winboll.studio.libappbase.LogUtils;
public class Preferences {
public static final String TAG = "Preferences";
private static final String PREF_NAME = "gallery_prefs";
private static final String KEY_FOLDER_PATH = "folder_path";
private static final String DEFAULT_PATH = "/storage/emulated/0/Pictures/Gallery/owner";
public static String getDefaultPath() {
return DEFAULT_PATH;
}
private final SharedPreferences prefs;
public Preferences(Context context) {
prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public String getFolderPath() {
String path = prefs.getString(KEY_FOLDER_PATH, DEFAULT_PATH);
LogUtils.d(TAG, "getFolderPath: " + path);
return path;
}
public void setFolderPath(String path) {
LogUtils.d(TAG, "setFolderPath: " + path);
prefs.edit().putString(KEY_FOLDER_PATH, path).apply();
}
}

View File

@@ -1,46 +0,0 @@
package cc.winboll.studio.gallery;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libappbase.LogUtils;
public class SettingsActivity extends AppCompatActivity {
public static final String TAG = "SettingsActivity";
private Preferences prefs;
private EditText editFolderPath;
private TextView textCurrentPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
LogUtils.d(TAG, "onCreate");
prefs = new Preferences(this);
editFolderPath = findViewById(R.id.edit_folder_path);
textCurrentPath = findViewById(R.id.text_current_path);
Button btnSave = findViewById(R.id.btn_save);
String currentPath = prefs.getFolderPath();
editFolderPath.setText(currentPath);
textCurrentPath.setText("Current: " + currentPath);
btnSave.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String newPath = editFolderPath.getText().toString().trim();
if (!newPath.isEmpty()) {
prefs.setFolderPath(newPath);
textCurrentPath.setText("Current: " + newPath);
}
finish();
}
});
}
}

View File

@@ -1,193 +0,0 @@
package cc.winboll.studio.gallery;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
public class TrashActivity extends AppCompatActivity {
public static final String TAG = "TrashActivity";
private static final int PERMISSION_REQUEST_CODE = 102;
private RecyclerView recyclerView;
private TrashAdapter adapter;
private TrashManager trashManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LogUtils.d(TAG, "onCreate");
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle("Trash");
trashManager = new TrashManager(this);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
adapter = new TrashAdapter();
recyclerView.setAdapter(adapter);
adapter.setOnTrashClickListener(new TrashAdapter.OnTrashClickListener() {
@Override
public void onRestoreClick(int position) {
restoreImage(position);
}
@Override
public void onDeleteClick(int position) {
permanentlyDelete(position);
}
});
if (checkPermission()) {
loadTrash();
} else {
requestPermission();
}
}
private boolean checkPermission() {
return ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
}
private void requestPermission() {
ActivityCompat.requestPermissions(this,
new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
loadTrash();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
}
}
}
private void loadTrash() {
LogUtils.d(TAG, "loadTrash");
Cursor cursor = trashManager.getTrashList();
ArrayList<TrashItem> items = new ArrayList<TrashItem>();
ArrayList<Uri> uris = new ArrayList<Uri>();
String trashPath = TrashDbHelper.getTrashPath();
File trashDir = new File(trashPath);
if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
do {
try {
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
String fileName = cursor.getString(cursor.getColumnIndexOrThrow("file_name"));
String originalPath = cursor.getString(cursor.getColumnIndexOrThrow("original_path"));
String originalFolder = cursor.getString(cursor.getColumnIndexOrThrow("original_folder"));
TrashItem item = new TrashItem();
item.id = id;
item.fileName = fileName;
item.originalPath = originalPath;
item.originalFolder = originalFolder;
File trashFile = new File(trashDir, fileName);
if (trashFile.exists()) {
items.add(item);
uris.add(Uri.fromFile(trashFile));
}
} catch (Exception e) {
e.printStackTrace();
}
} while (cursor.moveToNext());
cursor.close();
}
adapter.setData(items, uris);
if (items.isEmpty()) {
Toast.makeText(this, "Trash is empty", Toast.LENGTH_SHORT).show();
}
}
private void restoreImage(int position) {
LogUtils.d(TAG, "restoreImage: " + position);
long id = adapter.getItemId(position);
String fileName = adapter.getFileName(position);
String originalPath = adapter.getOriginalPath(position);
if (trashManager.restore(id, fileName, originalPath)) {
Toast.makeText(this, "Image restored", Toast.LENGTH_SHORT).show();
LogUtils.i(TAG, "Image restored");
loadTrash();
} else {
Toast.makeText(this, "Restore failed", Toast.LENGTH_SHORT).show();
}
}
private void permanentlyDelete(int position) {
long id = adapter.getItemId(position);
String fileName = adapter.getFileName(position);
if (trashManager.deletePermanently(id, fileName)) {
Toast.makeText(this, "Image deleted", Toast.LENGTH_SHORT).show();
loadTrash();
} else {
Toast.makeText(this, "Delete failed", Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_trash, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_clear_trash) {
trashManager.clearTrash();
Toast.makeText(this, "Trash cleared", Toast.LENGTH_SHORT).show();
loadTrash();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
if (checkPermission()) {
loadTrash();
}
}
public static class TrashItem {
public long id;
public String fileName;
public String originalPath;
public String originalFolder;
}
}

View File

@@ -1,111 +0,0 @@
package cc.winboll.studio.gallery;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import cc.winboll.studio.libappbase.LogUtils;
public class TrashAdapter extends RecyclerView.Adapter<TrashAdapter.ViewHolder> {
public static final String TAG = "TrashAdapter";
private ArrayList<TrashActivity.TrashItem> trashItems = new ArrayList<TrashActivity.TrashItem>();
private ArrayList<Uri> imageUrls = new ArrayList<Uri>();
private OnTrashClickListener listener;
public interface OnTrashClickListener {
void onRestoreClick(int position);
void onDeleteClick(int position);
}
public void setOnTrashClickListener(OnTrashClickListener listener) {
this.listener = listener;
}
public void setData(ArrayList<TrashActivity.TrashItem> items, ArrayList<Uri> uris) {
this.trashItems = items;
this.imageUrls = uris;
LogUtils.d(TAG, "setData: " + items.size() + " items");
notifyDataSetChanged();
}
public long getItemId(int position) {
if (position >= 0 && position < trashItems.size()) {
return trashItems.get(position).id;
}
return -1;
}
public String getFileName(int position) {
if (position >= 0 && position < trashItems.size()) {
return trashItems.get(position).fileName;
}
return "";
}
public String getOriginalPath(int position) {
if (position >= 0 && position < trashItems.size()) {
return trashItems.get(position).originalPath;
}
return "";
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_trash, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
if (position < imageUrls.size()) {
Glide.with(holder.imageView.getContext())
.load(imageUrls.get(position))
.centerCrop()
.into(holder.imageView);
}
holder.btnRestore.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onRestoreClick(position);
}
}
});
holder.btnDelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onDeleteClick(position);
}
}
});
}
@Override
public int getItemCount() {
return trashItems.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
ImageView btnRestore;
ImageView btnDelete;
ViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image);
btnRestore = itemView.findViewById(R.id.btn_restore);
btnDelete = itemView.findViewById(R.id.btn_delete);
}
}
}

View File

@@ -1,78 +0,0 @@
package cc.winboll.studio.gallery;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import java.io.File;
import cc.winboll.studio.libappbase.LogUtils;
public class TrashDbHelper extends SQLiteOpenHelper {
public static final String TAG = "TrashDbHelper";
private static final String DB_NAME = "trash.db";
private static final int DB_VERSION = 1;
private static final String TABLE_NAME = "trash_items";
private static final String COL_ID = "_id";
private static final String COL_FILE_NAME = "file_name";
private static final String COL_ORIGINAL_PATH = "original_path";
private static final String COL_ORIGINAL_FOLDER = "original_folder";
private static final String COL_DELETE_TIME = "delete_time";
public TrashDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
LogUtils.d(TAG, "onCreate");
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" +
COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COL_FILE_NAME + " TEXT, " +
COL_ORIGINAL_PATH + " TEXT, " +
COL_ORIGINAL_FOLDER + " TEXT, " +
COL_DELETE_TIME + " INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
LogUtils.i(TAG, "onUpgrade: " + oldVersion + " -> " + newVersion);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
public long insert(String fileName, String originalPath, String originalFolder) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COL_FILE_NAME, fileName);
values.put(COL_ORIGINAL_PATH, originalPath);
values.put(COL_ORIGINAL_FOLDER, originalFolder);
values.put(COL_DELETE_TIME, System.currentTimeMillis());
return db.insert(TABLE_NAME, null, values);
}
public Cursor getAll() {
SQLiteDatabase db = getReadableDatabase();
return db.query(TABLE_NAME, null, null, null, null, null, COL_DELETE_TIME + " DESC");
}
public int delete(long id) {
SQLiteDatabase db = getWritableDatabase();
return db.delete(TABLE_NAME, COL_ID + "=?", new String[]{String.valueOf(id)});
}
public void clear() {
SQLiteDatabase db = getWritableDatabase();
db.delete(TABLE_NAME, null, null);
}
public static String getTrashPath() {
File trashDir = new File(Environment.getExternalStorageDirectory(), ".Trash");
if (!trashDir.exists()) {
trashDir.mkdirs();
}
return trashDir.getAbsolutePath();
}
}

View File

@@ -1,158 +0,0 @@
package cc.winboll.studio.gallery;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import java.io.File;
import java.util.UUID;
import cc.winboll.studio.libappbase.LogUtils;
public class TrashManager {
public static final String TAG = "TrashManager";
private final Context context;
private final TrashDbHelper dbHelper;
public TrashManager(Context context) {
LogUtils.d(TAG, "TrashManager created");
this.context = context;
this.dbHelper = new TrashDbHelper(context);
}
public long addToTrash(String imagePath) {
LogUtils.d(TAG, "addToTrash: " + imagePath);
File sourceFile = new File(imagePath);
if (!sourceFile.exists()) {
return -1;
}
String uniqueId = UUID.randomUUID().toString();
String extension = getExtension(imagePath);
String newFileName = uniqueId + extension;
String trashPath = TrashDbHelper.getTrashPath();
File destFile = new File(trashPath, newFileName);
if (sourceFile.renameTo(destFile)) {
String originalFolder = sourceFile.getParent();
long result = dbHelper.insert(newFileName, imagePath, originalFolder);
LogUtils.i(TAG, "Added to trash: " + newFileName);
return result;
}
LogUtils.e(TAG, "Failed to move to trash");
return -1;
}
public Cursor getTrashList() {
return dbHelper.getAll();
}
public boolean restore(long id, String fileName, String originalPath) {
LogUtils.i(TAG, "restore: " + fileName + " -> " + originalPath);
File trashFile = new File(TrashDbHelper.getTrashPath(), fileName);
LogUtils.d(TAG, "trashFile exists: " + trashFile.exists() + ", path: " + trashFile.getAbsolutePath());
if (!trashFile.exists()) {
LogUtils.e(TAG, "trashFile not exists: " + trashFile.getAbsolutePath());
return false;
}
File originalFolder = new File(originalPath).getParentFile();
LogUtils.d(TAG, "originalFolder: " + originalFolder + ", exists: " + (originalFolder != null && originalFolder.exists()));
if (originalFolder != null && !originalFolder.exists()) {
boolean created = originalFolder.mkdirs();
LogUtils.d(TAG, "mkdirs result: " + created + ", path: " + originalFolder.getAbsolutePath());
}
File originalFile = new File(originalPath);
String restoreName = originalFile.getName();
File restoreFile = new File(originalFolder, restoreName);
LogUtils.d(TAG, "restoreFile: " + restoreFile.getAbsolutePath() + ", exists: " + restoreFile.exists());
boolean renameResult = trashFile.renameTo(restoreFile);
LogUtils.d(TAG, "renameTo result: " + renameResult);
if (renameResult) {
dbHelper.delete(id);
LogUtils.i(TAG, "Restored: " + fileName);
scanMedia(restoreFile.getAbsolutePath());
return true;
}
// Try copy + delete if rename failed
LogUtils.i(TAG, "renameTo failed, trying copy + delete");
try {
java.io.InputStream in = new java.io.FileInputStream(trashFile);
java.io.OutputStream out = new java.io.FileOutputStream(restoreFile);
byte[] buffer = new byte[4096];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
boolean deleted = trashFile.delete();
LogUtils.d(TAG, "copy+delete result: " + deleted);
if (deleted) {
dbHelper.delete(id);
LogUtils.i(TAG, "Restored (copy): " + fileName);
scanMedia(restoreFile.getAbsolutePath());
return true;
}
} catch (Exception e) {
LogUtils.e(TAG, "copy failed: " + e.getMessage());
}
LogUtils.e(TAG, "Failed to restore: " + fileName);
return false;
}
public boolean deletePermanently(long id, String fileName) {
File trashFile = new File(TrashDbHelper.getTrashPath(), fileName);
boolean deleted = trashFile.delete();
if (deleted) {
dbHelper.delete(id);
}
return deleted;
}
public void clearTrash() {
Cursor cursor = getTrashList();
if (cursor != null) {
while (cursor.moveToNext()) {
try {
int colIndex = cursor.getColumnIndexOrThrow("_id");
if (!cursor.isNull(colIndex)) {
String fileName = cursor.getString(cursor.getColumnIndexOrThrow("file_name"));
File trashFile = new File(TrashDbHelper.getTrashPath(), fileName);
trashFile.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
cursor.close();
}
dbHelper.clear();
}
private String getExtension(String path) {
int lastDot = path.lastIndexOf('.');
if (lastDot > 0) {
return path.substring(lastDot);
}
return ".jpg";
}
private void scanMedia(String filePath) {
LogUtils.d(TAG, "scanMedia: " + filePath);
MediaScannerConnection.scanFile(context, new String[]{filePath}, null, new android.media.MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
LogUtils.d(TAG, "scanCompleted: " + path + " -> " + uri);
}
});
}
}

View File

@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,-1H5v2h14V4z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9H1l3.89,3.89 0.07,0.14L9,12H6c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,17.9 10.51,19 13,19c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08V8H12z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -1,65 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:clickable="true"
android:focusable="true">
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"/>
<LinearLayout
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#CC000000"
android:orientation="horizontal"
android:padding="4dp">
<ImageButton
android:id="@+id/btn_back"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
android:contentDescription="Back"/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<ImageButton
android:id="@+id/btn_share"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_share"
android:contentDescription="Share"/>
<ImageButton
android:id="@+id/btn_info"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_info"
android:contentDescription="Info"/>
<ImageButton
android:id="@+id/btn_delete"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_delete"
android:contentDescription="Delete"/>
</LinearLayout>
</FrameLayout>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"/>
</LinearLayout>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/folder_path"
android:textSize="16sp"
android:textStyle="bold"/>
<EditText
android:id="@+id/edit_folder_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/enter_folder_path"
android:inputType="text"/>
<TextView
android:id="@+id/text_current_path"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="12sp"
android:textColor="#888888"/>
<Button
android:id="@+id/btn_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/save"/>
</LinearLayout>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp">
<ImageView
android:id="@+id/album_cover"
android:layout_width="match_parent"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:background="@color/black"/>
<TextView
android:id="@+id/album_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#80000000"
android:padding="4dp"
android:textColor="@color/white"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"/>
<TextView
android:id="@+id/image_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:background="#80000000"
android:padding="4dp"
android:textColor="@color/white"
android:textSize="10sp"/>
</FrameLayout>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:background="@color/black"/>
</FrameLayout>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"/>
</FrameLayout>

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:background="@color/black"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#80000000"
android:orientation="horizontal">
<ImageView
android:id="@+id/btn_restore"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_weight="1"
android:padding="8dp"
android:src="@drawable/ic_restore"
android:contentDescription="Restore"/>
<ImageView
android:id="@+id/btn_delete"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_weight="1"
android:padding="8dp"
android:src="@drawable/ic_delete"
android:contentDescription="Delete"/>
</LinearLayout>
</FrameLayout>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_trash"
android:title="@string/trash"
app:showAsAction="never"/>
<item
android:id="@+id/action_settings"
android:title="@string/settings"
app:showAsAction="never"/>
<item
android:id="@+id/action_refresh"
android:title="@string/refresh"
app:showAsAction="never"/>
<item
android:id="@+id/action_debug"
android:title="@string/debug_log"
app:showAsAction="never"/>
</menu>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_trash"
android:title="@string/trash"
app:showAsAction="never"/>
<item
android:id="@+id/action_clear_trash"
android:title="@string/clear_trash"
app:showAsAction="never"/>
</menu>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
<color name="black">#000000</color>
<color name="white">#FFFFFF</color>
</resources>

View File

@@ -1,19 +0,0 @@
<resources>
<string name="app_name">Gallery</string>
<string name="refresh">Refresh</string>
<string name="settings">Settings</string>
<string name="settings_title">Settings</string>
<string name="folder_path">Folder Path</string>
<string name="enter_folder_path">Enter folder path</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="no_images_found">No images found</string>
<string name="trash">Trash</string>
<string name="clear_trash">Clear Trash</string>
<string name="delete_confirm">Delete to trash?</string>
<string name="restore_confirm">Restore to original folder?</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="debug_log">Debug Log</string>
<string name="debug_message">Debug log message</string>
</resources>

View File

@@ -1,11 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

0
gradlew vendored Executable file → Normal file
View File

1
mymessagemanager/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -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<zhangsken@188.com>)
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

View File

@@ -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'])
}

View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Feb 11 05:29:19 HKT 2026
stageCount=8
libraryProject=
baseVersion=15.12
publishVersion=15.12.7
buildCount=0
baseBetaVersion=15.12.8

View File

@@ -67,6 +67,12 @@
-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

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cc.winboll.studio.mymessagemanager.beta.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider"/>
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">我的短信管家 ☆</string>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">My Message Manager +</string>
</resources>

View File

@@ -0,0 +1,225 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.mymessagemanager">
<!-- 发送短信 -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<!-- 接收讯息(短信) -->
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<!-- 读取短信 -->
<uses-permission android:name="android.permission.READ_SMS"/>
<!-- WRITE_SMS -->
<uses-permission android:name="android.permission.WRITE_SMS"/>
<!-- 接收讯息(彩信) -->
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!-- 开机启动 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- 运行前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- 读取联系人 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!-- 读取您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 修改或删除您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 接收讯息 (WAP) -->
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<!-- MANAGE_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<!-- 此应用可显示在其他应用上方 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE"/>
</intent>
</queries>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:persistent="true"
android:resizeableActivity="true"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".activitys.SMSActivity"/>
<activity android:name=".activitys.SMSReceiveRuleActivity">
</activity>
<activity
android:name=".activitys.SharedJSONReceiveActivity"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.EDIT"/>
<data android:mimeType="application/json"/>
<data android:mimeType="text/x-json"/>
</intent-filter>
</activity>
<activity android:name=".activitys.TTSPlayRuleActivity"/>
<activity android:name=".activitys.AboutActivity"/>
<activity
android:name=".activitys.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activitys.ComposeSMSActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SENDTO"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="sms"/>
<data android:scheme="smsto"/>
<data android:scheme="mms"/>
<data android:scheme="mmsto"/>
</intent-filter>
</activity>
<activity android:name=".activitys.AppSettingsActivity"/>
<service android:name=".services.TTSPlayService"/>
<service android:name=".services.MainService"/>
<service android:name=".services.AssistantService"/>
<service
android:name=".services.DefaultSMSManagerService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="sms"/>
<data android:scheme="smsto"/>
<data android:scheme="mms"/>
<data android:scheme="mmsto"/>
</intent-filter>
</service>
<receiver
android:name=".receivers.MainReceiver"
android:enabled="true"
android:exported="false"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver
android:name=".receivers.SMSRecevier"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter android:priority="1">
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
</intent-filter>
</receiver>
<receiver
android:name=".receivers.MmsReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
<data android:mimeType="application/vnd.wap.mms-message"/>
</intent-filter>
</receiver>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name="cc.winboll.studio.mymessagemanager.activitys.SMSRecycleActivity"/>
<activity android:name="cc.winboll.studio.mymessagemanager.unittest.UnitTestActivity"/>
<activity android:name="cc.winboll.studio.mymessagemanager.activitys.TTSFloatSettingsActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
[
{
"userId": -1,
"ruleData": ".*",
"isEnable": true
}
]

View File

@@ -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
}
]

View File

@@ -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();
}
}

View File

@@ -0,0 +1,92 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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);
}
}

View File

@@ -0,0 +1,155 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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. 创建IntentAction为“打开网页”
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();
}
public void onTTSFloatSettingsActivity(View view) {
Intent intent = new Intent(this, TTSFloatSettingsActivity.class);
startActivity(intent);
}
}

View File

@@ -0,0 +1,375 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @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<Map<String, Object>> mAdapterData = new ArrayList<Map<String, Object>>();
private ListView mlvContracts;
private List<PhoneBean> 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<PhoneBean> allContacts = phoneUtil.getPhoneList();
List<PhoneBean> matchedContacts = new ArrayList<PhoneBean>();
// 遍历所有联系人,匹配包含输入号码的联系人
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<PhoneBean>() : 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<PhoneBean> 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<PhoneBean> 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<String, Object> map = new HashMap<String, Object>();
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();
}
}
}

View File

@@ -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<String> 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中可能这样定义
/*/ <!-- strings.xml -->
<resources>
<string-array name="tab_names">
<item>Tab 1</item>
<item>Tab 2</item>
<item>Tab 3</item>
</string-array>
</resources>
*/
// 现在你可以遍历这个数组来访问每个元素
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);
}
}

View File

@@ -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<SMSActivity> mActivity;
MyHandler(SMSActivity activity) {
mActivity = new WeakReference<SMSActivity>(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());
}
}
}
}

View File

@@ -0,0 +1,244 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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();
}
}

View File

@@ -0,0 +1,104 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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();
}
};
}

View File

@@ -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<SMSAcceptRuleBean> beanList = new ArrayList<SMSAcceptRuleBean>();
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<SMSAcceptRuleBean> beanListShare = new ArrayList<SMSAcceptRuleBean>();
SMSAcceptRuleBean.loadBeanListFromFile(szSrcJSON, beanListShare, SMSAcceptRuleBean.class);
ArrayList<SMSAcceptRuleBean> beanListApp = new ArrayList<SMSAcceptRuleBean>();
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<TTSPlayRuleBean> beanList = new ArrayList<TTSPlayRuleBean>();
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<TTSPlayRuleBean> beanListShare = new ArrayList<TTSPlayRuleBean>();
TTSPlayRuleBean.loadBeanListFromFile(szSrcJSON, beanListShare, TTSPlayRuleBean.class);
ArrayList<TTSPlayRuleBean> beanListApp = new ArrayList<TTSPlayRuleBean>();
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();
}
}));
}
}
}

View File

@@ -0,0 +1,23 @@
package cc.winboll.studio.mymessagemanager.activitys;
import android.app.Activity;
import android.os.Bundle;
import cc.winboll.studio.mymessagemanager.R;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/02/11 03:45
* @Describe TTS悬浮窗设置类使用可拖动自定义控件
*/
public class TTSFloatSettingsActivity extends Activity {
public static final String TAG = "TTSFloatSettingsActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 直接加载包含自定义拖动控件的布局
setContentView(R.layout.activity_ttsfloatsettings);
}
}

View File

@@ -0,0 +1,200 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
};
}

View File

@@ -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);
}
}
};
}

View File

@@ -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<SMSBean> mData;
List<PhoneBean> mlistContacts;
PhoneUtil mPhoneUtil;
public PhoneArrayAdapter(Context context) {
mContext = context;
mData = new ArrayList<SMSBean>();
}
public void loadData() {
ArrayList<SMSBean> 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;
}
}

View File

@@ -0,0 +1,229 @@
package cc.winboll.studio.mymessagemanager.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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<RecyclerView.ViewHolder> {
public static final String TAG = "SMSAcceptRuleArrayAdapter";
Context mContext;
ArrayList<SMSAcceptRuleBean> 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);
}
}
}

View File

@@ -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<SMSBean> mData;
public SMSArrayAdapter(Context context, String szPhone) {
mContext = context;
mszPhone = szPhone;
mData = new ArrayList<SMSBean>();
mData = loadSMSList(context, szPhone);
}
ArrayList<SMSBean> loadSMSList(Context context, String szPhone) {
ArrayList<SMSBean> 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;
}
}

View File

@@ -0,0 +1,290 @@
package cc.winboll.studio.mymessagemanager.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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<RecyclerView.ViewHolder> {
public static final String TAG = "SMSRecycleAdapter";
Context mContext;
ArrayList<SMSRecycleBean> mDataList;
String mszSMSRecycleListDataPath;
AppConfigUtil mAppConfigUtil;
public SMSRecycleAdapter(Context context) {
mContext = context;
mAppConfigUtil = AppConfigUtil.getInstance(mContext);
mszSMSRecycleListDataPath = SMSRecycleUtil.getSMSRecycleListDataPath(mContext);
mDataList = new ArrayList<SMSRecycleBean>();
mDataList = loadSMSRecycleList();
}
public ArrayList<SMSRecycleBean> loadSMSRecycleList() {
ArrayList<SMSRecycleBean> list = new ArrayList<SMSRecycleBean>();
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);
}
}
}

View File

@@ -0,0 +1,193 @@
package cc.winboll.studio.mymessagemanager.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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<RecyclerView.ViewHolder> {
public static final String TAG = "TTSRuleBeanRecyclerViewAdapter";
Context mContext;
ArrayList<TTSPlayRuleBean> 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);
}
}
}

View File

@@ -0,0 +1,184 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,88 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}*/
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,53 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,39 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,121 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,50 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,272 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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<SMSBean> list, boolean isDesc) {
Collections.sort(list, new SortSMSByDateDesc(isDesc));
}
private static class SortSMSByDateDesc implements Comparator<SMSBean> {
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;
}
}
}
}

View File

@@ -0,0 +1,127 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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<SMSRecycleBean> list, boolean isDesc) {
Collections.sort(list, new SortSMSByDeleteDateDesc(isDesc));
}
private static class SortSMSByDeleteDateDesc implements Comparator<SMSRecycleBean> {
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;
}
}
}
}

View File

@@ -0,0 +1,147 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,281 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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<TTSPlayRuleBean_V1> parseBeanList(String beanList) {
try {
StringReader stringReader = new StringReader(beanList);
JsonReader jsonReader = new JsonReader(stringReader);
ArrayList<TTSPlayRuleBean_V1> list = new ArrayList<TTSPlayRuleBean_V1>();
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<TTSPlayRuleBean_V1> 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<TTSPlayRuleBean_V1> loadBeanList(Context context) {
return loadBeanListFromFile(getBeanListJsonFilePath(context));
}*/
public static ArrayList<TTSPlayRuleBean_V1> loadBeanListFromFile(String szFilePath) {
ArrayList<TTSPlayRuleBean_V1> 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<TTSPlayRuleBean_V1> beanList) {
return saveBeanListToFile(getBeanListJsonFilePath(context), beanList);
}
public static boolean saveBeanListToFile(String szFilePath, ArrayList<TTSPlayRuleBean_V1> 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;
}*/
}

View File

@@ -0,0 +1,24 @@
package cc.winboll.studio.mymessagemanager.beans;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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;
}
}

View File

@@ -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&豆包大模型<zhangsken@qq.com>
* @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);
}
}

View File

@@ -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&豆包大模型<zhangsken@qq.com>
* @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;
}
}

View File

@@ -0,0 +1,43 @@
package cc.winboll.studio.mymessagemanager.receivers;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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!");
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,96 @@
package cc.winboll.studio.mymessagemanager.services;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @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();
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More