Compare commits

...

126 Commits

Author SHA1 Message Date
ZhanGSKen
348edc8aaf Merge remote-tracking branch 'gitee/timestamp' into appbase 2025-09-10 01:58:15 +08:00
ZhanGSKen
5970ae33c8 Merge remote-tracking branch 'gitee/powerbell' into appbase 2025-09-10 01:58:08 +08:00
ZhanGSKen
91a44f48ef Merge remote-tracking branch 'gitee/numtable' into appbase 2025-09-10 01:58:01 +08:00
ZhanGSKen
c66e9a090b Merge remote-tracking branch 'gitee/mymessagemanager' into appbase 2025-09-10 01:57:54 +08:00
ZhanGSKen
7a14b55247 Merge remote-tracking branch 'gitee/contacts' into appbase 2025-09-10 01:57:47 +08:00
ZhanGSKen
fab68f16c8 Merge remote-tracking branch 'gitee/autoinstaller' into appbase 2025-09-10 01:57:40 +08:00
ZhanGSKen
3a97c6135f Merge remote-tracking branch 'gitee/apputils' into appbase 2025-09-10 01:57:35 +08:00
ZhanGSKen
cdad017d8c Merge remote-tracking branch 'gitee/androidxdemo' into appbase 2025-09-10 01:57:24 +08:00
ZhanGSKen
bf84382963 Merge remote-tracking branch 'gitee/androiddemo' into appbase 2025-09-10 01:57:17 +08:00
ZhanGSKen
34601fc5b1 Merge remote-tracking branch 'gitee/aes' into appbase 2025-09-10 01:57:11 +08:00
ZhanGSKen
ea65810e7d <libaes>Library Release 15.9.3 2025-08-31 23:40:22 +08:00
ZhanGSKen
ad991e3da2 <libapputils>Library Release 15.8.5 2025-08-31 23:39:23 +08:00
ZhanGSKen
80201e8370 <powerbell>APK 15.4.11 release Publish. 2025-08-31 06:21:48 +08:00
ZhanGSKen
ea0473606a 更新类库 2025-08-31 06:17:41 +08:00
ZhanGSKen
870e9a94fb <mymessagemanager>APK 15.3.6 release Publish. 2025-08-31 06:13:45 +08:00
ZhanGSKen
2421ecb943 更新类库 2025-08-31 06:12:49 +08:00
ZhanGSKen
687fff7216 <mymessagemanager>Start New Stage Version. 2025-08-31 06:10:17 +08:00
ZhanGSKen
50d4cd830b <contacts>APK 15.3.16 release Publish. 2025-08-31 06:05:42 +08:00
ZhanGSKen
2079822c00 更新类库 2025-08-31 06:04:54 +08:00
ZhanGSKen
297c76f328 <contacts>Start New Stage Version. 2025-08-31 06:02:02 +08:00
ZhanGSKen
43b18ee662 <contacts>Start New Stage Version. 2025-08-31 06:01:23 +08:00
ZhanGSKen
3ec3a4cfc2 <androidxdemo>APK 15.1.1 release Publish. 2025-08-31 05:42:50 +08:00
ZhanGSKen
2c10a9f38c 设置初始版本号 2025-08-31 05:42:19 +08:00
ZhanGSKen
e62888636e <androidxdemo>APK 15.0.0 release Publish. 2025-08-31 05:39:39 +08:00
ZhanGSKen
364980dd02 更新类库 2025-08-31 05:38:28 +08:00
ZhanGSKen
0e155e4f3a <androidxdemo>Start New Stage Version. 2025-08-31 05:29:49 +08:00
ZhanGSKen
89febba5a9 <androidxdemo>Start New Stage Version. 2025-08-31 05:29:33 +08:00
ZhanGSKen
653330f8e1 <androidxdemo>Start New Stage Version. 2025-08-31 05:26:36 +08:00
ZhanGSKen
386c73effc <androidxdemo>Start New Stage Version. 2025-08-31 05:19:41 +08:00
ZhanGSKen
b385583c5a <androiddemo>Start New Stage Version. 2025-08-31 05:11:29 +08:00
ZhanGSKen
e7a9be2f56 <androiddemo>APK 15.0.0 release Publish. 2025-08-31 05:11:26 +08:00
ZhanGSKen
b27f7b0080 更新类库 2025-08-31 05:06:06 +08:00
ZhanGSKen
0a440419ff <libaes>Library Release 15.9.3 2025-08-31 05:01:04 +08:00
ZhanGSKen
9e189ed5ac <aes>APK 15.9.3 release Publish. 2025-08-31 05:00:43 +08:00
ZhanGSKen
0fb6aadc72 更新类库版本 2025-08-31 04:55:30 +08:00
ZhanGSKen
46f3315b02 Merge remote-tracking branch 'gitee/appbase' into aes 2025-08-31 04:54:49 +08:00
ZhanGSKen
c0ff228845 <libapputils>Library Release 15.8.5 2025-08-31 04:53:26 +08:00
ZhanGSKen
a0fe8f17a8 <apputils>APK 15.8.5 release Publish. 2025-08-31 04:53:04 +08:00
ZhanGSKen
16bd40fc59 Merge remote-tracking branch 'gitee/appbase' into apputils 2025-08-31 04:51:48 +08:00
ZhanGSKen
ffaf683c54 <libapputils>Library Release 15.8.4 2025-08-31 04:46:19 +08:00
ZhanGSKen
26f5f8d3db 编译参数修复 2025-08-31 04:45:24 +08:00
ZhanGSKen
917e25cdc8 更新基础类库版本 2025-08-31 04:41:42 +08:00
ZhanGSKen
3795cf8631 <mymessagemanager>APK 15.3.5 release Publish. 2025-08-31 01:10:33 +08:00
ZhanGSKen
b374f3117a 更新联系人查询与短信发送框,在搜索到空数据时的显示逻辑。 2025-08-30 21:15:01 +08:00
ZhanGSKen
d581cd9842 <contacts>APK 15.3.15 release Publish. 2025-08-30 15:04:01 +08:00
ZhanGSKen
cef50d087d 联系人搜索框添加拼音首字母简单搜索功能,如“周星星”输入“zxx”即可搜索到。 2025-08-30 15:02:37 +08:00
ZhanGSKen
6d9adc124e <mymessagemanager>APK 15.3.4 release Publish. 2025-08-30 11:42:36 +08:00
ZhanGSKen
52f738b45b 修正选项开关提示 2025-08-30 11:40:55 +08:00
ZhanGSKen
9ece6778b7 <mymessagemanager>APK 15.3.3 release Publish. 2025-08-28 21:13:38 +08:00
ZhanGSKen
b7f8b76ace <libapputils>Library Release 15.8.4 2025-08-28 21:11:05 +08:00
ZhanGSKen
326e5fa68e Merge remote-tracking branch 'studio/aes' into aes 2025-08-28 21:08:37 +08:00
ZhanGSKen
e9c8f9029e <appbase>APK 15.9.1 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
c00bfa1292 <appbase>APK 15.9.0 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
11ee4dcf27 提升版本 2025-08-28 21:04:59 +08:00
ZhanGSKen
8974e24dce <appbase>Start New Stage Version. 2025-08-28 21:04:59 +08:00
ZhanGSKen
89142e379c 编译测试 2025-08-28 21:04:59 +08:00
ZhanGSKen
dabc671c27 <contacts>APK 15.3.11 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
ed849e92d1 区分防御层数量级差异,区分量级给出相应提示。 2025-08-28 21:04:59 +08:00
ZhanGSKen
42d2522927 <contacts>APK 15.3.10 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
6a52b2a8c3 添加应用效果提示 2025-08-28 21:04:59 +08:00
ZhanGSKen
02ed5bd5d1 <contacts>APK 15.3.9 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
b685665d0c 命名重构 2025-08-28 21:04:59 +08:00
ZhanGSKen
1b030a2855 联系人号码添加复制功能 2025-08-28 21:04:59 +08:00
ZhanGSKen
dd577f1765 通话记录号码添加复制功能 2025-08-28 21:04:59 +08:00
ZhanGSKen
0f8350600d <contacts>APK 15.3.8 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
1777ebb8dc 添加清空 BoBullToon 数据功能,更新默认 BoBullToon 数据地址。 2025-08-28 21:04:59 +08:00
ZhanGSKen
2b99f707e7 <contacts>APK 15.3.7 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
068c94e749 规则编辑列表显示优化 2025-08-28 21:04:59 +08:00
ZhanGSKen
0b3bc7e296 <contacts>APK 15.3.6 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
6ef747bcf8 <contacts/>Start New Stage Version. 2025-08-28 21:04:59 +08:00
ZhanGSKen
a8e843c388 更新应用描述 2025-08-28 21:04:59 +08:00
ZhanGSKen
d3fd593cb0 更新应用介绍页 2025-08-28 21:04:59 +08:00
ZhanGSKen
3aec176b8b 更新按钮文字描述 2025-08-28 21:04:59 +08:00
ZhanGSKen
5960f76238 添加应用使用方法提示 2025-08-28 21:04:59 +08:00
ZhanGSKen
1a118da827 更新说明书 2025-08-28 21:04:59 +08:00
ZhanGSKen
f73cad6f3e <contacts>APK 15.3.5 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
7130ecf023 <contacts>APK 15.3.4 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
953c8f08cd 检验拨不通号码群排在查询通讯录联系人前面 2025-08-28 21:04:59 +08:00
ZhanGSKen
87b7557f72 <contacts>APK 15.3.3 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
77f5a156f8 编译调试 2025-08-28 21:04:59 +08:00
ZhanGSKen
b34ea40536 <mymessagemanager>APK 15.3.1 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
d202a3443d <mymessagemanager>APK 15.3.0 release Publish. 2025-08-28 21:04:59 +08:00
ZhanGSKen
8c532c885f 介于UI界面有调整,版本号升级以区分。 2025-08-28 21:04:59 +08:00
ZhanGSKen
5fc4cb5f74 应用介绍页链接更新 2025-08-28 21:04:59 +08:00
ZhanGSKen
2a590a99fb 菜单功能BugFix 2025-08-28 21:04:59 +08:00
ZhanGSKen
c6ad707ca2 应用菜单排列调整 2025-08-28 21:04:59 +08:00
ZhanGSKen
ee13a43fb6 <libaes>Library Release 15.9.2 2025-08-28 21:04:18 +08:00
ZhanGSKen
5fbe1d8f71 <libaes>Library Release 15.9.2 2025-08-28 20:57:31 +08:00
ZhanGSKen
e13c8e7af0 <mymessagemanager>Start New Stage Version. 2025-08-28 16:03:47 +08:00
ZhanGSKen
a4988b5b68 联系人查询发送窗口添加拼音查询功能。 2025-08-28 16:02:41 +08:00
ZhanGSKen
04df902b6b <contacts>APK 15.3.14 release Publish. 2025-08-26 01:37:58 +08:00
ZhanGSKen
33c71ea868 添加手机端BoBullToon数据文件夹查找模块。 2025-08-25 20:47:49 +08:00
ZhanGSKen
5507126f6b <mymessagemanager>APK 15.3.2 release Publish. 2025-08-23 13:40:21 +08:00
ZhanGSKen
d381c29452 短信发送窗口UI优化 2025-08-23 13:38:20 +08:00
ZhanGSKen
ba861d910e <contacts>APK 15.3.13 release Publish. 2025-08-20 20:19:22 +08:00
ZhanGSKen
f5d9aafe43 更新默认BoBullToon数据源URL 2025-08-20 20:17:53 +08:00
ZhanGSKen
eb61eb7306 <powerbell>APK 15.4.10 release Publish. 2025-08-13 02:46:25 +08:00
ZhanGSKen
31ad66685c 修复信息提示错误问题。 2025-08-13 02:45:05 +08:00
ZhanGSKen
beb561ad6a <powerbell>APK 15.4.9 release Publish. 2025-08-13 02:37:14 +08:00
ZhanGSKen
8869265d60 优化电量清理活动窗口UI显示。 2025-08-13 02:35:36 +08:00
ZhanGSKen
2739627aff <timestamp>APK 15.1.3 release Publish. 2025-07-28 11:36:51 +08:00
ZhanGSKen
58e0be9cf4 添加开机启动功能。 2025-07-28 11:35:30 +08:00
ZhanGSKen
9e9402f84e <contacts>APK 15.3.12 release Publish. 2025-07-27 15:39:49 +08:00
ZhanGSKen
ec18330022 窗口回显时刷新到最新数据。 2025-07-27 15:38:16 +08:00
ZhanGSKen
8bb80ef575 <timestamp>APK 15.1.2 release Publish. 2025-07-24 15:25:52 +08:00
ZhanGSKen
c1e6e32809 在主界面添加时间戳截取按钮 2025-07-24 15:22:18 +08:00
ZhanGSKen
3e7722e2c0 <numtable>APK 15.1.1 release Publish. 2025-07-24 10:16:27 +08:00
ZhanGSKen
a1707e73b5 添加页面方向指示图标创建功能 2025-07-24 10:12:30 +08:00
ZhanGSKen
9dcbaa0d75 添加桌面快捷方式创建功能 2025-07-24 09:00:19 +08:00
ZhanGSKen
07b5e66875 <powerbell>APK 15.4.8 release Publish. 2025-07-06 12:53:58 +08:00
ZhanGSKen
91f5cf9051 更新应用介绍页 2025-07-06 12:51:50 +08:00
ZhanGSKen
8730f434dd 更新说明书 2025-06-28 01:18:20 +08:00
ZhanGSKen
f8944490f8 <powerbell>APK 15.4.7 release Publish. 2025-06-23 20:07:50 +08:00
ZhanGSKen
733af004f6 编译参数修复 2025-06-23 20:07:12 +08:00
ZhanGSKen
c03568e1f5 <powerbell>APK 15.4.6 release Publish. 2025-06-23 20:04:59 +08:00
ZhanGSKen
a0575a5e8b 调整像素拾取窗口、背景图片设置窗口与主窗口的切换逻辑。 2025-06-23 20:03:32 +08:00
ZhanGSKen
0e57ce679e <powerbell>APK 15.4.5 release Publish. 2025-06-23 14:49:46 +08:00
ZhanGSKen
f9211a8eb4 优化背景像素拾取UI 2025-06-23 14:48:24 +08:00
ZhanGSKen
4c31ff9b54 调整窗口切换模式 2025-06-23 14:29:59 +08:00
ZhanGSKen
8cf610962e <powerbell>APK 15.4.4 release Publish. 2025-06-22 16:21:12 +08:00
ZhanGSKen
3071d186ec 添加图片像素拾取并可以设置像素为图片背景 2025-06-22 16:19:24 +08:00
ZhanGSKen
df10306059 <powerbell>APK 15.4.3 release Publish. 2025-06-19 21:16:18 +08:00
ZhanGSKen
ccdb9c5abd UI美化,应用视图布局调整。 2025-06-19 21:14:22 +08:00
ZhanGSKen
f27209ab87 Merge remote-tracking branch 'origin/appbase' into powerbell 2025-06-19 20:58:26 +08:00
ZhanGSKen
2a819e94e4 <powerbell>APK 15.4.2 release Publish. 2025-06-19 10:22:12 +08:00
ZhanGSKen
6635358ec5 设置图片临时剪裁路径保存在Pictures。 2025-06-19 10:19:10 +08:00
69 changed files with 2731 additions and 1164 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jun 28 12:59:51 HKT 2025
stageCount=3
#Sun Aug 31 23:40:17 HKT 2025
stageCount=4
libraryProject=libaes
baseVersion=15.9
publishVersion=15.9.2
publishVersion=15.9.3
buildCount=0
baseBetaVersion=15.9.3
baseBetaVersion=15.9.4

View File

@@ -0,0 +1 @@

View File

@@ -67,6 +67,6 @@ dependencies {
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
api 'com.android.support:recyclerview-v7:28.0.0'
api 'cc.winboll.studio:libapputils:15.8.2'
api 'cc.winboll.studio:libappbase:15.8.2'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Jun 01 08:02:46 GMT 2025
stageCount=0
#Sun Aug 31 05:11:26 CST 2025
stageCount=1
libraryProject=
baseVersion=15.0
publishVersion=15.0.0
buildCount=27
buildCount=0
baseBetaVersion=15.0.1

View File

@@ -0,0 +1 @@

View File

@@ -29,7 +29,7 @@ android {
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.0"
versionName "15.1"
if(true) {
versionName = genVersionName("${versionName}")
}
@@ -67,7 +67,7 @@ dependencies {
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
api 'cc.winboll.studio:libaes:15.9.2'
api 'cc.winboll.studio:libapputils:15.8.4'
api 'cc.winboll.studio:libappbase:15.8.4'
api 'cc.winboll.studio:libaes:15.9.3'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jun 28 05:02:54 GMT 2025
stageCount=0
#Sun Aug 31 05:42:50 CST 2025
stageCount=2
libraryProject=
baseVersion=15.0
publishVersion=15.0.0
buildCount=27
baseBetaVersion=15.0.1
baseVersion=15.1
publishVersion=15.1.1
buildCount=0
baseBetaVersion=15.1.2

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Jun 03 15:05:48 HKT 2025
stageCount=5
#Sun Aug 31 23:39:16 HKT 2025
stageCount=6
libraryProject=libapputils
baseVersion=15.8
publishVersion=15.8.4
publishVersion=15.8.5
buildCount=0
baseBetaVersion=15.8.5
baseBetaVersion=15.8.6

35
autoinstaller/README.md Normal file
View File

@@ -0,0 +1,35 @@
# AutoInstaller
#### 介绍
APK 文件监控,监控指定文件路径的 APK 文件。
在 APK 文件改变后自动调用系统安装命令安装,或者调用[应用信息查看器]打开。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh autoinstaller
#### 使用说明
#### 参与贡献
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

@@ -45,9 +45,9 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libaes:15.9.2'
api 'cc.winboll.studio:libapputils:15.8.4'
api 'cc.winboll.studio:libappbase:15.8.4'
api 'cc.winboll.studio:libaes:15.9.3'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
// 权限请求框架https://github.com/getActivity/XXPermissions
api 'com.github.getActivity:XXPermissions:18.63'

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Jul 17 09:57:24 HKT 2025
stageCount=12
#Sun Aug 31 06:05:42 CST 2025
stageCount=17
libraryProject=
baseVersion=15.3
publishVersion=15.3.11
publishVersion=15.3.16
buildCount=0
baseBetaVersion=15.3.12
baseBetaVersion=15.3.17

View File

@@ -1,5 +1,10 @@
package cc.winboll.studio.contacts;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/30 14:32
* @Describe 主窗口
*/
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
@@ -8,6 +13,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
@@ -41,7 +47,7 @@ import java.util.List;
final public class MainActivity extends AppCompatActivity implements IWinBoLLActivity, ViewPager.OnPageChangeListener, View.OnClickListener {
public static final String TAG = "MainActivity";
public static final String TAG = "MainActivity";
public static final int REQUEST_HOME_ACTIVITY = 0;
public static final int REQUEST_ABOUT_ACTIVITY = 1;
@@ -55,13 +61,10 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
MainServiceBean mMainServiceBean;
private TabLayout tabLayout;
private ViewPager viewPager;
private List<View> views; //用来存放放进ViewPager里面的布局
//实例化存储imageView导航原点的集合
private List<View> views;
ImageView[] imageViews;
//MyPagerAdapter adapter;//适配器
MyPagerAdapter pagerAdapter;
LinearLayout linearLayout;//下标所在在LinearLayout布局里
int currentPoint = 0;//当前被选中中页面的下标
LinearLayout linearLayout;
int currentPoint = 0;
private TelephonyManager telephonyManager;
private MyPhoneStateListener phoneStateListener;
@@ -70,30 +73,6 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
private static final int DIALER_REQUEST_CODE = 1;
// @Override
// public Activity getActivity() {
// return this;
// }
// @Override
// public APPInfo getAppInfo() {
// String szBranchName = "contacts";
//
// APPInfo appInfo = AboutActivityFactory.buildDefaultAPPInfo();
// appInfo.setAppName("Contacts");
// appInfo.setAppIcon(cc.winboll.studio.libapputils.R.drawable.ic_winboll);
// appInfo.setAppDescription("Contacts Description");
// appInfo.setAppGitName("APP");
// appInfo.setAppGitOwner("Studio");
// appInfo.setAppGitAPPBranch(szBranchName);
// appInfo.setAppGitAPPSubProjectFolder(szBranchName);
// appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts");
// appInfo.setAppAPKName("Contacts");
// appInfo.setAppAPKFolderName("Contacts");
// return appInfo;
// return null;
// }
@Override
public Activity getActivity() {
@@ -107,89 +86,62 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
@Override
protected void onCreate(Bundle savedInstanceState) {
// 接收并处理 Intent 数据,函数 Intent 处理接收就直接返回
//if (prosessIntents(getIntent())) return;
// 以下正常创建主窗口
super.onCreate(savedInstanceState);
_MainActivity = this;
setContentView(R.layout.activity_main);
// 初始化工具栏
mToolbar = findViewById(R.id.activitymainToolbar1);
// 初始化工具栏仅加载基础UI
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
setSupportActionBar(mToolbar);
// if (isEnableDisplayHomeAsUp()) {
// // 显示后退按钮
// getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// }
getSupportActionBar().setSubtitle(TAG);
tabLayout = findViewById(R.id.tabLayout);
viewPager = findViewById(R.id.viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
// 创建Fragment列表和标题列表
fragmentList = new ArrayList<>();
tabTitleList = new ArrayList<>();
// 创建Fragment列表(仅实例化,不加载数据)
fragmentList = new ArrayList<Fragment>();
tabTitleList = new ArrayList<String>();
fragmentList.add(CallLogFragment.newInstance(0));
fragmentList.add(ContactsFragment.newInstance(1));
fragmentList.add(ContactsFragment.newInstance(1)); // 延迟加载联系人数据
fragmentList.add(LogFragment.newInstance(2));
tabTitleList.add("通话记录");
tabTitleList.add("联系人");
tabTitleList.add("应用日志");
// 设置ViewPager适配器
// 设置ViewPager适配器
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
viewPager.setAdapter(adapter);
// 关键:关闭预加载,仅当前页初始化
viewPager.setOffscreenPageLimit(0);
// 关联TabLayout和ViewPager
tabLayout.setupWithViewPager(viewPager);
// initData();
// initView();
// //initPoint();//调用初始化导航原点的方法
// viewPager.addOnPageChangeListener(this);//滑动事件
//ViewPager viewPager = findViewById(R.id.activitymainViewPager1);
//MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
//viewPager.setAdapter(pagerAdapter);
//TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1);
//tabLayout.setupWithViewPager(viewPager);
// mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
// if (mMainServiceBean == null) {
// mMainServiceBean = new MainServiceBean();
// }
// cbMainService = findViewById(R.id.activitymainCheckBox1);
// cbMainService.setChecked(mMainServiceBean.isEnable());
// cbMainService.setOnClickListener(new View.OnClickListener(){
// @Override
// public void onClick(View view) {
// if (cbMainService.isChecked()) {
// MainService.startMainService(MainActivity.this);
// } else {
// MainService.stopMainService(MainActivity.this);
// }
// }
// });
// 初始化服务状态(延迟启动非核心服务)
MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean == null) {
mMainServiceBean = new MainServiceBean();
MainServiceBean.saveBean(this, mMainServiceBean);
}
if (mMainServiceBean.isEnable()) {
MainService.startMainService(this);
// 延迟1秒启动服务避免阻塞启动
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
MainService.startMainService(MainActivity.this);
}
}, 1000);
}
// 初始化TelephonyManager和PhoneStateListener
// 初始化电话状态监听(基础功能保留)
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
phoneStateListener = new MyPhoneStateListener();
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
// ViewPager适配器
// ViewPager适配器Java 7语法
private class MyPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList;
@@ -226,91 +178,22 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
_MainActivity.startActivity(intent);
}
//初始化view即显示的图片
// void initView() {
// viewPager = findViewById(R.id.activitymainViewPager1);
// pagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
// viewPager.setAdapter(pagerAdapter);
// //adapter = new MyPagerAdapter(views);
// //viewPager = findViewById(R.id.activitymainViewPager1);
// //viewPager.setAdapter(adapter);
// //linearLayout = findViewById(R.id.activitymainLinearLayout1);
// //initPoint();//初始化页面下方的点
// viewPager.setOnPageChangeListener(this);
//
// }
//初始化所要显示的布局
// void initData() {
// LayoutInflater inflater = LayoutInflater.from(getActivity());
// View view1 = inflater.inflate(R.layout.fragment_call_log, viewPager, false);
// View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false);
// View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false);
//
// views = new ArrayList<>();
// views.add(view1);
// views.add(view2);
// views.add(view3);
// }
// void initPoint() {
// imageViews = new ImageView[5];//实例化5个图片
// for (int i = 0; i < linearLayout.getChildCount(); i++) {
// imageViews[i] = (ImageView) linearLayout.getChildAt(i);
// imageViews[i].setImageResource(R.drawable.ic_launcher);
// imageViews[i].setOnClickListener(this);//点击导航点,即可跳转
// imageViews[i].setTag(i);//重复利用实例化的对象
// }
// currentPoint = 0;//默认第一个坐标
// imageViews[currentPoint].setImageResource(R.drawable.ic_launcher);
// }
//OnPageChangeListener接口要实现的三个方法
/* onPageScrollStateChanged(int state)
此方法是在状态改变的时候调用其中state这个参数有三种状态
SCROLL_STATE_DRAGGING1表示用户手指“按在屏幕上并且开始拖动”的状态
手指按下但是还没有拖动的时候还不是这个状态只有按下并且手指开始拖动后log才打出。
SCROLL_STATE_IDLE0滑动动画做完的状态。
SCROLL_STATE_SETTLING2在“手指离开屏幕”的状态。*/
// OnPageChangeListener接口实现
@Override
public void onPageScrollStateChanged(int state) {
public void onPageScrollStateChanged(int state) {}
}
/* onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为:
position :当前页面即你点击滑动的页面从A滑B则是A页面的position。
positionOffset:当前页面偏移的百分比
positionOffsetPixels:当前页面偏移的像素位置*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
}
/* onPageSelected(int position)
此方法是页面滑动完后得到调用position是你当前选中的页面的Position位置编号
(从A滑动到B就是B的position)*/
public void onPageSelected(int position) {
// ImageView preView = imageViews[currentPoint];
// preView.setImageResource(R.drawable.ic_launcher);
// ImageView currView = imageViews[position];
// currView.setImageResource(R.drawable.ic_launcher);
// currentPoint = position;
}
//小圆点点击事件
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//通过getTag(),可以判断是哪个控件
// int i = (Integer) v.getTag();
// viewPager.setCurrentItem(i);//直接跳转到某一个页面的情况
}
public void onPageSelected(int position) {}
@Override
public void onClick(View v) {}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
//setSubTitle("");
}
private class MyPhoneStateListener extends PhoneStateListener {
@@ -336,109 +219,31 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
LogUtils.d(TAG, "onDestroy() SOS");
}
//
// 处理传入的 Intent 数据
//
// boolean prosessIntents(Intent intent) {
// if (intent == null
// || intent.getAction() == null
// || intent.getAction().equals(""))
// return false;
//
// if (intent.getAction().equals(StringToQrCodeView.ACTION_UNITTEST_QRCODE)) {
// try {
// WinBoLLActivity clazzActivity = UnitTestActivity.class.newInstance();
// String tag = clazzActivity.getTag();
// LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag);
// Intent subIntent = new Intent(this, UnitTestActivity.class);
// subIntent.setAction(intent.getAction());
// File file = new File(getCacheDir(), UUID.randomUUID().toString());
// //取出文件uri
// Uri uri = intent.getData();
// if (uri == null) {
// uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
// }
// //获取文件真实地址
// String szSrcPath = UriUtils.getFileFromUri(getApplication(), uri);
// if (TextUtils.isEmpty(szSrcPath)) {
// return false;
// }
//
// Files.copy(Paths.get(szSrcPath), Paths.get(file.getPath()));
// //startWinBoLLActivity(subIntent, tag);
// WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, subIntent, UnitTestActivity.class);
// } catch (IllegalAccessException | InstantiationException | IOException e) {
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
// // 函数处理异常返回失败
// return false;
// }
// } else {
// LogUtils.d(TAG, "prosessIntents|" + intent.getAction() + "|yet");
// return false;
// }
// return true;
// }
// @Override
// public String getTag() {
// return TAG;
// }
// @Override
// public void onBackPressed() {
// exit();
// }
//
// void exit() {
// YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){
//
// @Override
// public void onYes() {
// WinBoLLActivityManager.getInstance(getApplicationContext()).finishAll();
// }
//
// @Override
// public void onNo() {
// }
// };
// YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener);
// }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.item_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
//WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, CallActivity.class);
}
// } else
// if (item.getItemId() == R.id.item_exit) {
// exit();
// return true;
// }
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
}
/**
* Android M 及以上检查是否是系统默认电话应用
* 检查是否是系统默认电话应用
*/
public boolean isDefaultPhoneCallApp() {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE);
if (manger != null && manger.getDefaultDialerPackage() != null) {
return manger.getDefaultDialerPackage().equals(getPackageName());
@@ -452,35 +257,22 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
if (manager == null) return false;
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
Integer.MAX_VALUE)) {
Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// switch (resultCode) {
// case REQUEST_HOME_ACTIVITY : {
// LogUtils.d(TAG, "REQUEST_HOME_ACTIVITY");
// break;
// }
// case REQUEST_ABOUT_ACTIVITY : {
// LogUtils.d(TAG, "REQUEST_ABOUT_ACTIVITY");
// break;
// }
// default : {
// super.onActivityResult(requestCode, resultCode, data);
// }
// }
if (requestCode == DIALER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用",
Toast.LENGTH_SHORT).show();
Toast.LENGTH_SHORT).show();
}
}
}
}

View File

@@ -40,6 +40,10 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV
this.mContactUtils = ContactUtils.getInstance(mContext);
this.callLogList = callLogList;
}
public void relaodContacts() {
this.mContactUtils.relaodContacts();
}
@NonNull
@Override

View File

@@ -1,9 +1,9 @@
package cc.winboll.studio.contacts.beans;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/02/26 13:37:00
* @Describe ContactModel
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/30 14:32
* @Describe 联系人信息数据模型
*/
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
@@ -18,13 +18,18 @@ public class ContactModel {
private String name;
private String number;
private String pinyin;
// 新增:存储姓名的拼音首字母(如"啊牛"→"an"
private String pinyinFirstLetter;
public ContactModel(String name, String number) {
this.name = name;
this.number = number.replaceAll("\\s", "");
this.pinyin = convertToPinyin(name);
// 初始化时生成拼音首字母
this.pinyinFirstLetter = convertToPinyinFirstLetter(name);
}
// 原方法:转换为全拼(如"啊牛"→"aniu"
private String convertToPinyin(String chinese) {
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
@@ -33,22 +38,55 @@ public class ContactModel {
StringBuilder pinyin = new StringBuilder();
for (int i = 0; i < chinese.length(); i++) {
char ch = chinese.charAt(i);
if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) {
if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { // 仅处理汉字
try {
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
if (pinyinArray != null) {
pinyin.append(pinyinArray[0]);
if (pinyinArray != null && pinyinArray.length > 0) {
pinyin.append(pinyinArray[0]); // 取第一个拼音(多音字默认首选项)
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pinyin.append(ch);
pinyin.append(ch); // 非汉字直接拼接(如字母、数字、符号)
}
}
return pinyin.toString();
}
// 新增:转换为拼音首字母(如"啊牛"→"an"
private String convertToPinyinFirstLetter(String chinese) {
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
StringBuilder firstLetters = new StringBuilder();
for (int i = 0; i < chinese.length(); i++) {
char ch = chinese.charAt(i);
if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { // 仅处理汉字
try {
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
if (pinyinArray != null && pinyinArray.length > 0) {
// 取拼音的第一个字母(如"a"、"niu"→"a"、"n"
firstLetters.append(pinyinArray[0].charAt(0));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
// 非汉字可根据需求处理:此处保留原字符(如"李3"→"l3""张A"→"za"
firstLetters.append(ch);
}
}
return firstLetters.toString();
}
// 新增:获取拼音首字母
public String getPinyinFirstLetter() {
return pinyinFirstLetter;
}
// 原有getter方法
public String getName() {
return name;
}

View File

@@ -11,6 +11,7 @@ import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.libappbase.LogUtils;
import com.hjq.toast.ToastUtils;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -154,6 +155,62 @@ public class TomCat {
File getWorkingFolder() {
return mContext.getExternalFilesDir(TAG);
}
public File getBoBullToonDataFolder() {
File fCheckRoot = getWorkingFolder();
if (fCheckRoot == null || !fCheckRoot.exists()) {
return fCheckRoot;
}
// 递归查找符合条件的文件夹
File targetFolder = findTargetFolder(fCheckRoot);
return targetFolder != null ? targetFolder : fCheckRoot;
}
/**
* 递归查找同时包含LICENSE和README.md文件的文件夹
*/
private File findTargetFolder(File currentFolder) {
// 检查当前文件夹是否符合条件
if (hasRequiredFiles(currentFolder)) {
return currentFolder;
}
// 查找子文件夹Java 7不支持方法引用用匿名内部类过滤
File[] subFolders = currentFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory(); // 仅保留子文件夹
}
});
if (subFolders != null) {
for (File subFolder : subFolders) {
File result = findTargetFolder(subFolder);
if (result != null) {
return result;
}
}
}
return null;
}
/**
* 检查文件夹中是否同时存在LICENSE和README.md文件
*/
private boolean hasRequiredFiles(File folder) {
if (folder == null || !folder.isDirectory()) {
return false;
}
// 检查两个文件是否同时存在且均为文件(非文件夹)
File licenseFile = new File(folder, "LICENSE");
File readmeFile = new File(folder, "README.md");
return licenseFile.exists() && licenseFile.isFile()
&& readmeFile.exists() && readmeFile.isFile();
}
public void cleanBoBullToon() {
String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径
@@ -170,9 +227,9 @@ public class TomCat {
public boolean loadPhoneBoBullToon() {
listPhoneBoBullToon.clear();
File fBoBullToon = new File(getWorkingFolder(), "bobulltoon");
File fBoBullToon = getBoBullToonDataFolder();
if (fBoBullToon.exists()) {
LogUtils.d(TAG, String.format("getWorkingFolder() %s", getWorkingFolder()));
LogUtils.d(TAG, String.format("getBoBullToonDataFolder() %s", getWorkingFolder()));
for (File userFolder : fBoBullToon.listFiles()) {
if (userFolder.isDirectory()) {
for (File recordFile : userFolder.listFiles()) {

View File

@@ -161,4 +161,12 @@ public class CallLogFragment extends Fragment {
_CallLogFragment.triggerUpdate();
}
}
@Override
public void onResume() {
super.onResume();
//ToastUtils.show("onResume");
callLogAdapter.relaodContacts();
readCallLog(); // 窗口回显时更新通话记录
}
}

View File

@@ -1,15 +1,18 @@
package cc.winboll.studio.contacts.fragments;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/02/20 12:57:50
* @Describe 联系人
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/30 14:32
* @Describe 联系人视图
*/
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.ContactsContract;
import android.text.Editable;
import android.text.TextWatcher;
@@ -27,24 +30,39 @@ import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.ContactAdapter;
import cc.winboll.studio.contacts.beans.ContactModel;
import cc.winboll.studio.libappbase.LogUtils;
import com.hjq.toast.ToastUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ContactsFragment extends Fragment {
public static final String TAG = "ContactsFragment";
private static final String ARG_PAGE = "ARG_PAGE";
private int mPage;
private static final int REQUEST_READ_CONTACTS = 1;
private int mPage;
private RecyclerView recyclerView;
private ContactAdapter contactAdapter;
private List<ContactModel> contactList = new ArrayList<>();
private List<ContactModel> originalContactList = new ArrayList<>();
private EditText searchEditText;
private Button btnDial;
private boolean isViewInitialized = false; // 标记视图是否已初始化
// 静态缓存:全局复用联系人数据
private static List<ContactModel> sCachedOriginalList = new ArrayList<ContactModel>();
private static List<ContactModel> sCachedFilteredList = new ArrayList<ContactModel>();
// 当前页面数据容器
private List<ContactModel> contactList = new ArrayList<ContactModel>();
private List<ContactModel> originalContactList = new ArrayList<ContactModel>();
// 异步工具
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private boolean isDataLoaded = false;
public static ContactsFragment newInstance(int page) {
Bundle args = new Bundle();
@@ -65,103 +83,272 @@ public class ContactsFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_contacts, container, false);
// 加载布局(已移除进度条相关代码)
View view = inflater.inflate(R.layout.fragment_contacts, container, false);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.contacts_recycler_view);
// 初始化RecyclerView
recyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
contactList = new ArrayList<ContactModel>();
contactAdapter = new ContactAdapter(getContext(), contactList);
recyclerView.setAdapter(contactAdapter);
// 初始隐藏列表,数据加载后显示
recyclerView.setVisibility(View.GONE);
searchEditText = view.findViewById(R.id.search_edit_text);
searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
// 绑定搜索框和拨号按钮
searchEditText = (EditText) view.findViewById(R.id.search_edit_text);
btnDial = (Button) view.findViewById(R.id.btn_dial);
// 初始隐藏搜索相关控件,延迟到首次可见时显示
searchEditText.setVisibility(View.GONE);
btnDial.setVisibility(View.GONE);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
filterContacts(s.toString());
}
// 首次可见时初始化资源
@Override
public void onResume() {
super.onResume();
if (!isViewInitialized) {
initSearchAndDial(); // 初始化搜索和拨号功能
checkContactPermission(); // 检查权限并加载数据
isViewInitialized = true;
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
// 初始化搜索框和拨号按钮
private void initSearchAndDial() {
// 显示搜索相关控件
searchEditText.setVisibility(View.VISIBLE);
btnDial.setVisibility(View.VISIBLE);
// 搜索框防抖监听
searchEditText.addTextChangedListener(new DebounceTextWatcher(300) {
@Override
public void onDebounceTextChanged(String query) {
filterContacts(query);
}
});
// 拨号按钮点击事件
btnDial.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", "");
if (phoneNumber.isEmpty()) {
ToastUtils.show("请输入号码");
return;
}
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
}
// 权限检查
private void checkContactPermission() {
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS);
} else {
readContacts();
loadContacts();
}
Button btnDial = view.findViewById(R.id.btn_dial);
btnDial.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View p1) {
String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", "");
//phoneNumber = "+8616769764848";
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
// 添加 FLAG_ACTIVITY_NEW_TASK 标志
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
}
// 加载联系人(延迟到首次可见时)
private void loadContacts() {
// 若有缓存,直接复用
if (!sCachedOriginalList.isEmpty() && !sCachedFilteredList.isEmpty()) {
originalContactList.clear();
originalContactList.addAll(sCachedOriginalList);
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE); // 显示列表
isDataLoaded = true;
return;
}
// 无缓存时异步加载
if (!isDataLoaded) {
recyclerView.setVisibility(View.GONE); // 加载中隐藏列表
executor.execute(new Runnable() {
@Override
public void run() {
// 子线程读取联系人
final List<ContactModel> tempList = readContactsInBackground();
// 主线程更新UI
mainHandler.post(new Runnable() {
@Override
public void run() {
// 更新缓存
sCachedOriginalList.clear();
sCachedOriginalList.addAll(tempList);
sCachedFilteredList.clear();
sCachedFilteredList.addAll(tempList);
// 更新当前列表
originalContactList.clear();
originalContactList.addAll(sCachedOriginalList);
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
LogUtils.d(TAG, String.format("联系人加载完成,共%d条数据", contactList.size()));
// 数据加载后显示列表
recyclerView.setVisibility(View.VISIBLE);
isDataLoaded = true;
}
});
}
});
}
}
// 子线程读取联系人
private List<ContactModel> readContactsInBackground() {
List<ContactModel> tempList = new ArrayList<ContactModel>();
Cursor cursor = null;
try {
// 查询联系人姓名和号码
cursor = requireContext().getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
},
null,
null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
);
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
do {
String name = cursor.getString(nameIndex);
String number = cursor.getString(numberIndex).replaceAll("\\s", ""); // 去除空格
tempList.add(new ContactModel(name, number));
} while (cursor.moveToNext());
}
} catch (Exception e) {
LogUtils.d(TAG, "读取联系人失败:" + e);
} finally {
if (cursor != null) {
cursor.close(); // 关闭游标,避免内存泄漏
}
}
return tempList;
}
// 过滤联系人
private void filterContacts(String query) {
contactList.clear();
if (query.isEmpty()) {
contactList.addAll(originalContactList);
sCachedFilteredList.clear();
sCachedFilteredList.addAll(originalContactList);
} else {
String lowerQuery = query.toLowerCase();
for (ContactModel contact : originalContactList) {
// 匹配姓名、全拼、简拼、号码
boolean matchName = contact.getName().toLowerCase().contains(lowerQuery);
boolean matchPinyin = contact.getPinyin().toLowerCase().contains(lowerQuery);
boolean matchFirstLetter = contact.getPinyinFirstLetter().toLowerCase().contains(lowerQuery);
boolean matchNumber = contact.getNumber().contains(lowerQuery);
if (matchName || matchPinyin || matchFirstLetter || matchNumber) {
contactList.add(contact);
}
}
sCachedFilteredList.clear();
sCachedFilteredList.addAll(contactList);
}
contactAdapter.notifyDataSetChanged();
// 过滤后确保列表可见
recyclerView.setVisibility(View.VISIBLE);
}
// 权限回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
loadContacts(); // 授权后加载联系人
} else {
ToastUtils.show("请授予联系人权限以查看联系人列表");
recyclerView.setVisibility(View.VISIBLE); // 显示空列表
}
}
}
private void readContacts() {
contactList.clear();
originalContactList.clear();
Cursor cursor = requireContext().getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");
// 防抖TextWatcherJava 7实现
public abstract static class DebounceTextWatcher implements TextWatcher {
private final long debounceDelay;
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable pendingRunnable;
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
ContactModel contact = new ContactModel(name, number);
contactList.add(contact);
originalContactList.add(contact);
}
cursor.close();
contactAdapter.notifyDataSetChanged();
public DebounceTextWatcher(long debounceDelay) {
this.debounceDelay = debounceDelay;
}
}
private void filterContacts(String query) {
contactList.clear();
if (query.isEmpty()) {
contactList.addAll(originalContactList);
} else {
for (ContactModel contact : originalContactList) {
if (contact.getName().toLowerCase().contains(query.toLowerCase()) ||
contact.getPinyin().toLowerCase().contains(query.toLowerCase()) ||
contact.getNumber().toLowerCase().contains(query.toLowerCase())) {
contactList.add(contact);
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 无需处理
}
@Override
public void onTextChanged(final CharSequence s, int start, int before, int count) {
// 移除之前的延迟任务
if (pendingRunnable != null) {
handler.removeCallbacks(pendingRunnable);
}
// 延迟执行过滤
pendingRunnable = new Runnable() {
@Override
public void run() {
onDebounceTextChanged(s.toString());
}
}
};
handler.postDelayed(pendingRunnable, debounceDelay);
}
@Override
public void afterTextChanged(Editable s) {
// 无需处理
}
// 抽象方法:防抖后的回调
public abstract void onDebounceTextChanged(String query);
}
// 资源释放
@Override
public void onDestroy() {
super.onDestroy();
executor.shutdown(); // 关闭线程池
mainHandler.removeCallbacksAndMessages(null); // 清除未执行任务
}
// Fragment隐藏/显示时的处理
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden && isDataLoaded) {
// 复用缓存数据并显示列表
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE);
}
contactAdapter.notifyDataSetChanged();
}
}

View File

@@ -1,9 +1,9 @@
package cc.winboll.studio.contacts.utils;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/03/06 21:08:16
* @Describe ContactUtils
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/30 14:32
* @Describe 联系人工具集
*/
import android.content.ContentResolver;
import android.content.Context;

View File

@@ -2,6 +2,6 @@
<resources>
<string name="app_name">Contacts</string>
<string name="default_bobulltoon_url">https://gitea.winboll.cc/Studio/BoBullToon/archive/main.zip</string>
<string name="default_bobulltoon_url">https://gitee.com/zhangsken/bobulltoon/repository/archive/main.zip</string>
</resources>

View File

@@ -21,8 +21,8 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libapputils:15.8.4'
api 'cc.winboll.studio:libappbase:15.8.4'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
// 吐司类库
api 'com.github.getActivity:ToastUtils:10.5'

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jun 28 12:59:30 HKT 2025
stageCount=3
#Sun Aug 31 05:00:43 CST 2025
stageCount=4
libraryProject=libaes
baseVersion=15.9
publishVersion=15.9.2
publishVersion=15.9.3
buildCount=0
baseBetaVersion=15.9.3
baseBetaVersion=15.9.4

View File

@@ -21,7 +21,7 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libappbase:15.8.2'
api 'cc.winboll.studio:libappbase:15.9.5'
// 二维码类库
api 'com.google.zxing:core:3.4.1'

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Jun 03 15:05:42 HKT 2025
stageCount=5
#Sun Aug 31 04:53:04 CST 2025
stageCount=6
libraryProject=libapputils
baseVersion=15.8
publishVersion=15.8.4
publishVersion=15.8.5
buildCount=0
baseBetaVersion=15.8.5
baseBetaVersion=15.8.6

View File

@@ -45,15 +45,17 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libaes:15.9.2'
api 'cc.winboll.studio:libapputils:15.8.4'
api 'cc.winboll.studio:libappbase:15.8.4'
api 'cc.winboll.studio:libaes:15.9.3'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
api 'io.github.medyo:android-about-page:2.0.0'
api 'com.github.getActivity:ToastUtils:10.5'
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'

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Jul 03 13:50:15 HKT 2025
stageCount=2
#Sun Aug 31 06:13:45 CST 2025
stageCount=7
libraryProject=
baseVersion=15.3
publishVersion=15.3.1
publishVersion=15.3.6
buildCount=0
baseBetaVersion=15.3.2
baseBetaVersion=15.3.7

View File

@@ -1,5 +1,10 @@
package cc.winboll.studio.mymessagemanager.activitys;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/30 14:32
* @Describe 联系人查询与短信发送窗口
*/
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -11,13 +16,17 @@ import android.widget.RelativeLayout;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toolbar;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.mymessagemanager.R;
import cc.winboll.studio.mymessagemanager.activitys.ComposeSMSActivity;
import cc.winboll.studio.mymessagemanager.beans.PhoneBean;
import cc.winboll.studio.mymessagemanager.utils.PhoneUtil;
import cc.winboll.studio.mymessagemanager.utils.SMSUtil;
import com.hjq.toast.ToastUtils;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -26,166 +35,282 @@ import java.util.Map;
public class ComposeSMSActivity extends BaseActivity {
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";
static String MAP_NAME = "NAME";
static String MAP_PHONE = "PHONE";
String mszSMSBody;
String mszScheme;
String mszPhoneTo;
EditText metTO;
EditText metSMSBody;
SimpleAdapter mSimpleAdapter;
List<Map<String,Object>> mAdapterData = new ArrayList<>();
ListView mlvContracts;
List<PhoneBean> mListPhoneBeanContracts;
Toolbar mToolbar;
AOHPCTCSeekBar mAOHPCTCSeekBar;
RelativeLayout mrlContracts;
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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_composesms);
mszSMSBody = getIntent().getStringExtra(EXTRA_SMSBODY);
mszScheme = getIntent().getData().getScheme();
mszPhoneTo = getIntent().getData().getSchemeSpecificPart();
if (!mszScheme.equals("smsto")) {
// 其他方式未支持就退出
finish();
// 初始化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();
// 设置搜索到的匹配位置
setListViewPrePosition();
initAdapter(null); // 初始加载所有联系人
setListViewPrePositionByPhone();
}
//
// 初始化视图
//
void initView() {
//Drawable drawableFrame = AppCompatResources.getDrawable(this, R.drawable.bg_frame);
private void initView() {
// 初始化标题栏
mToolbar = findViewById(R.id.activitycomposesmsASupportToolbar1);
mToolbar = (Toolbar) findViewById(R.id.activitycomposesmsASupportToolbar1);
mToolbar.setSubtitle(getString(R.string.activity_name_composesms));
setActionBar(mToolbar);
// 初始化联系人栏目框
mrlContracts = findViewById(R.id.activitycomposesmsRelativeLayout1);
//mrlContracts.setBackground(drawableFrame);
// 初始化联系人姓名显示和搜索
mtvTOName = (TextView) findViewById(R.id.activitycomposesmsTextView2);
mrlContracts = (RelativeLayout) findViewById(R.id.activitycomposesmsRelativeLayout1);
metTONameSearch = (EditText) findViewById(R.id.activitycomposesmsEditText2);
// 初始化联系人列表
mlvContracts = findViewById(R.id.activitycomposesmsListView1);
// 姓名搜索框文本变化监听
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(); // 按姓名搜索
}
}
// 初始化联系人输入框
metTO = findViewById(R.id.activitycomposesmsEditText1);
metTO.setText(mszPhoneTo);
metTO.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
setListViewPrePosition();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 无操作
}
@Override
public void afterTextChanged(Editable s) {
}
});
// 初始化发送拉动控件
mAOHPCTCSeekBar = findViewById(R.id.viewsmssendpart1AOHPCTCSeekBar1);
mAOHPCTCSeekBar.setThumb(getDrawable(R.drawable.ic_message));
mAOHPCTCSeekBar.setThumbOffset(20);
mAOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
@Override
public void onOHPCommit() {
// 空号码不发送
mszPhoneTo = metTO.getText().toString();
if (mszPhoneTo.trim().equals("")) {
ToastUtils.show("没有设置接收号码。");
return;
}
// 空消息不发送
mszSMSBody = metSMSBody.getText().toString();
if (mszSMSBody.equals("")) {
ToastUtils.show("没有消息内容可发送。");
return;
}
// 发送消息
if (SMSUtil.sendMessageByInterface2(ComposeSMSActivity.this, mszPhoneTo, mszSMSBody)) {
ComposeSMSActivity.this.finish();
}
}
@Override
public void afterTextChanged(Editable s) {
// 无操作
}
});
// 初始化提示框
TextView tvAOHPCTCSeekBarMSG = findViewById(R.id.viewsmssendpart1TextView1);
// 初始化联系人列表
mlvContracts = (ListView) findViewById(R.id.activitycomposesmsListView1);
// 初始化号码输入框(核心:优化文本变化监听逻辑)
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 = findViewById(R.id.viewsmssendpart1EditText1);
//metSMSBody.setBackground(drawableFrame);
metSMSBody.setText(mszSMSBody);
metSMSBody = (EditText) findViewById(R.id.viewsmssendpart1EditText1);
if (mszSMSBody != null) {
metSMSBody.setText(mszSMSBody);
}
}
//
// 设置搜索到的匹配位置
//
void setListViewPrePosition() {
int nPrePosition = getContractsDataPrePosition(metTO.getText().toString());
mlvContracts.setSelected(false);
mlvContracts.setSelection(nPrePosition);
// 核心优化:根据输入号码筛选列表(无结果则显示空列表)
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()) {
mlvContracts.setSelection(0);
mtvTOName.setText(matchedContacts.get(0).getName());
} else {
mtvTOName.setText(""); // 无结果时清空姓名显示
}
}
//
// 返回搜索到的匹配位置
//
int getContractsDataPrePosition(String szPhone) {
// 根据姓名搜索联系人
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);
}
}
// 初始定位号码对应的联系人
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++) {
if (mListPhoneBeanContracts.get(i).getTelPhone().compareTo(szPhone) > -1) {
PhoneBean bean = mListPhoneBeanContracts.get(i);
if (bean.getTelPhone().compareTo(szPhone) >= 0) {
return i;
}
}
return 0;
}
//
// 初始化适配器
//
void initAdapter() {
// 初始化联系人数据适配器
mAdapterData = new ArrayList<>();
// 读取联系人数据
PhoneUtil phoneUtils = new PhoneUtil(this);
mListPhoneBeanContracts = phoneUtils.getPhoneList();
// 映射联系人数据给适配器数据对象
for (int i = 0;i < mListPhoneBeanContracts.size();i++) {
Map<String,Object> map =new HashMap<>();
map.put(MAP_NAME, mListPhoneBeanContracts.get(i).getName());
map.put(MAP_PHONE, mListPhoneBeanContracts.get(i).getTelPhone());
mAdapterData.add(map);
// 获取姓名匹配的位置(兼容旧逻辑)
private int getContractsDataPrePositionByName(String szName) {
if (mListPhoneBeanContracts == null || mListPhoneBeanContracts.isEmpty()) {
return 0;
}
// 绑定适配器与数据
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 ListView.OnItemClickListener() {
for (int i = 0; i < mListPhoneBeanContracts.size(); i++) {
if (mListPhoneBeanContracts.get(i).getName().startsWith(szName)) {
return i;
}
}
return 0;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
metTO.setText(mAdapterData.get(position).get(MAP_PHONE).toString());
// 初始化或更新列表适配器
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()) {
String phone = mAdapterData.get(position).get(MAP_PHONE).toString();
metTO.setText(phone);
mtvTOName.setText(phoneUtil.getNameByPhone(phone));
}
}
});
} else {
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

@@ -4,11 +4,16 @@ 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.os.Message;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
@@ -16,19 +21,17 @@ import android.widget.Toolbar;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.mymessagemanager.R;
import cc.winboll.studio.mymessagemanager.activitys.SMSActivity;
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 java.lang.ref.WeakReference;
public class SMSActivity extends BaseActivity {
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;
@@ -36,10 +39,11 @@ public class SMSActivity extends BaseActivity {
Toolbar mToolbar;
String mszPhoneTo;
SMSArrayAdapter mSMSArrayAdapter;
ScrollView mScrollView;
BottomPositionFixedScrollView mScrollView1;
EditText metSMSBody;
SMSActivityBroadcastReceiver mSMSActivityBroadcastReceiver;
Handler mSetFocusHandler;
private boolean isImeVisible = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -47,45 +51,90 @@ public class SMSActivity extends BaseActivity {
setContentView(R.layout.activity_sms);
initView();
mSetFocusHandler = new MyHandler(SMSActivity.this);
scrollScrollView();
setupImeStatusListener();
// 每隔一定时间设置输入框获得焦点
//
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {}
Message message = mSetFocusHandler.obtainMessage(MSG_SET_FOCUS);
mSetFocusHandler.sendMessage(message);
}
}}.start();
// 新增监听窗口加载完成触发mScrollView1滚动到底部
setupScrollToBottomAfterWindowLoaded();
}
//
// 设置输入框获得焦点的类
//
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();
break;
default:
break;
}
super.handleMessage(msg);
}
}
// 新增窗口加载完成后让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() {
@@ -94,135 +143,130 @@ public class SMSActivity extends BaseActivity {
}
void initView() {
// 发送端空号码退出
mszPhoneTo = getIntent().getStringExtra(EXTRA_PHONE);
if (mszPhoneTo == null || mszPhoneTo.trim().equals("")) {
finish();
}
// 初始化标题栏
mToolbar = findViewById(R.id.activitysmsASupportToolbar1);
mToolbar = (Toolbar) findViewById(R.id.activitysmsASupportToolbar1);
mToolbar.setSubtitle(getString(R.string.activity_name_smsinphone) + " < Phone : " + AddressUtils.getFormattedAddress(mszPhoneTo) + " >");
setActionBar(mToolbar);
// 初始化滚动窗口
mScrollView = findViewById(R.id.activitysmsinphoneScrollView1);
mScrollView1 = (BottomPositionFixedScrollView) findViewById(R.id.activitysmsScrollView1);
// 初始化发送消息框
//Drawable drawableFrame = AppCompatResources.getDrawable(this, R.drawable.bg_frame);
metSMSBody = findViewById(R.id.viewsmssendpart1EditText1);
//metSMSBody.setBackground(drawableFrame);
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 = findViewById(R.id.viewsmssendpart1AOHPCTCSeekBar1);
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() {
//Toast.makeText(getApplication(), "Send", Toast.LENGTH_SHORT).show();
sendSMS();
}
});
aOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
@Override
public void onOHPCommit() {
sendSMS();
}
});
// 初始化提示框
TextView tvAOHPCTCSeekBarMSG = findViewById(R.id.viewsmssendpart1TextView1);
TextView tvAOHPCTCSeekBarMSG = (TextView) findViewById(R.id.viewsmssendpart1TextView1);
tvAOHPCTCSeekBarMSG.setText(R.string.msg_100sendmsg);
mlvSMS = (SMSListViewForScrollView) findViewById(R.id.activitysmsinphoneListView1);
// 准备数据
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 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();
}
}
});
@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();
intentFilter.addAction(ACTION_NOTIFY_SMS_CHANGED);
IntentFilter intentFilter = new IntentFilter(ACTION_NOTIFY_SMS_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mSMSActivityBroadcastReceiver, intentFilter);
/*SMSView mSMSView = findViewById(R.id.viewsmssendSMSView1);
mSMSView.setSMSType(SMSView.SMSType.SEND);*/
}
//
// 更新信息列表
//
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(mScrollView);
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("");
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
updateSMSView();
ViewUtil.scrollScrollView(mScrollView);
}
}, 1000);
metSMSBody.clearFocus();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
updateSMSView();
ViewUtil.scrollScrollView(mScrollView1);
}
}, 1000);
}
}
class SMSActivityBroadcastReceiver extends BroadcastReceiver {
public SMSActivityBroadcastReceiver() {
//LogUtils.d(TAG, "SMSActivityBroadcastReceiver()");
}
public SMSActivityBroadcastReceiver() {}
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case ACTION_NOTIFY_SMS_CHANGED :
//Toast.makeText(context, "ACTION_NOTIFY_SMS_CHANGED", Toast.LENGTH_SHORT).show();
updateSMSView();
ViewUtil.scrollScrollView(mScrollView);
//LogUtils.d(TAG, "ACTION_NOTIFY_SMS_CHANGED");
break;
default:
throw new IllegalStateException("Unexpected value: " + intent.getAction());
if (ACTION_NOTIFY_SMS_CHANGED.equals(intent.getAction())) {
updateSMSView();
ViewUtil.scrollScrollView(mScrollView1);
} else {
throw new IllegalStateException("Unexpected value: " + intent.getAction());
}
}
}
}

View File

@@ -1,8 +1,8 @@
package cc.winboll.studio.mymessagemanager.utils;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2024/07/19 14:30:57
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/30 14:32
* @Describe 手机联系人工具类
*/
import android.content.ContentResolver;
@@ -12,6 +12,12 @@ import android.net.Uri;
import android.provider.ContactsContract;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.mymessagemanager.beans.PhoneBean;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -38,28 +44,137 @@ public class PhoneUtil {
}
// 读取所有联系人
//
public List<PhoneBean> getPhoneList() {
List<PhoneBean> listPhoneBean = new ArrayList<>();
ContentResolver cr = mContext.getContentResolver();
Cursor cursor = cr.query(mUriPhoneContent, new String[]{NUMBER, DISPLAY_NAME}, null, null, null);
while (cursor.moveToNext()) {
PhoneBean phoneBean = new PhoneBean(cursor.getString(1), cursor.getString(0).replaceAll("\\s", ""));
listPhoneBean.add(phoneBean);
if (cursor != null) {
while (cursor.moveToNext()) {
// 去除号码中的空格
String phone = cursor.getString(0).replaceAll("\\s", "");
String name = cursor.getString(1);
PhoneBean phoneBean = new PhoneBean(name, phone);
listPhoneBean.add(phoneBean);
}
cursor.close();
}
cursor.close();
// 按电话号码排序
Collections.sort(listPhoneBean, new Comparator<PhoneBean>() {
@Override
public int compare(PhoneBean o1, PhoneBean o2) {
return o1.getTelPhone().compareTo(o2.getTelPhone());
}
});
@Override
public int compare(PhoneBean o1, PhoneBean o2) {
return o1.getTelPhone().compareTo(o2.getTelPhone());
}
});
return listPhoneBean;
}
/**
* 根据联系人名称查询号码(兼容拼音查询)
* @param keyword 搜索关键词(支持汉字、拼音、拼音首字母)
* @return 匹配的联系人列表(包含姓名和号码)
*/
public List<PhoneBean> getPhonesByName(String keyword) {
List<PhoneBean> result = new ArrayList<>();
if (keyword == null || keyword.trim().isEmpty()) {
return result; // 关键词为空,返回空列表
}
// 获取所有联系人
List<PhoneBean> allContacts = getPhoneList();
// 统一转为小写,忽略大小写
String keywordLower = keyword.trim().toLowerCase();
for (PhoneBean contact : allContacts) {
String name = contact.getName();
if (name == null || name.isEmpty()) {
continue;
}
// 1. 直接匹配姓名(包含关键词)
if (name.toLowerCase().contains(keywordLower)) {
result.add(contact);
continue;
}
// 2. 匹配姓名的全拼(包含关键词)
String namePinyin = getPinyin(name).toLowerCase();
if (namePinyin.contains(keywordLower)) {
result.add(contact);
continue;
}
// 3. 匹配姓名的拼音首字母(包含关键词)
String namePinyinFirstLetter = getPinyinFirstLetter(name).toLowerCase();
if (namePinyinFirstLetter.contains(keywordLower)) {
result.add(contact);
continue;
}
}
return result;
}
/**
* 将汉字转为全拼(不带声调,小写)
* 例如:"张三" → "zhangsan"
*/
private String getPinyin(String chinese) {
StringBuilder pinyin = new StringBuilder();
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE); // 小写
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); // 不带声调
char[] chars = chinese.toCharArray();
for (char c : chars) {
// 如果是汉字,转换为拼音;否则直接拼接(如字母、数字、符号)
if (Character.toString(c).matches("[\\u4e00-\\u9fa5]")) {
try {
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c, format);
if (pinyinArray != null && pinyinArray.length > 0) {
pinyin.append(pinyinArray[0]); // 取第一个拼音(多音字默认取第一个)
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
LogUtils.e(TAG, "拼音转换失败:" + e.getMessage());
}
} else {
pinyin.append(c);
}
}
return pinyin.toString();
}
/**
* 将汉字转为拼音首字母(小写)
* 例如:"张三" → "zs"
*/
private String getPinyinFirstLetter(String chinese) {
StringBuilder firstLetters = new StringBuilder();
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
char[] chars = chinese.toCharArray();
for (char c : chars) {
if (Character.toString(c).matches("[\\u4e00-\\u9fa5]")) {
try {
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c, format);
if (pinyinArray != null && pinyinArray.length > 0) {
// 取拼音首字母(如"zhang" → "z"
firstLetters.append(pinyinArray[0].charAt(0));
}
} catch (BadHanyuPinyinOutputFormatCombination e) {
LogUtils.e(TAG, "拼音首字母转换失败:" + e.getMessage());
}
} else {
// 非汉字直接拼接首字符(如"李3" → "l3"
firstLetters.append(c);
}
}
return firstLetters.toString();
}
public boolean isPhoneInContacts(String szPhone) {
List<PhoneBean> listPhoneDto = getPhoneList();
LogUtils.d(TAG, String.format("isPhoneInContacts(...) listPhoneDto.size() %d", listPhoneDto.size()));
@@ -70,49 +185,56 @@ public class PhoneUtil {
}
return false;
}
boolean isTheSamePhoneNumber(String szNum1, String szNum2) {
//LogUtils.d(TAG, String.format("szNum1 %s\nszNum2 %s", szNum1, szNum2));
if(szNum1.equals(szNum2)) {
public String getNameByPhone(String szPhone) {
if (szPhone == null || szPhone.equals("")) {
return "";
}
List<PhoneBean> listPhoneDto = getPhoneList();
LogUtils.d(TAG, String.format("getNameByPhone(...) listPhoneDto.size() %d", listPhoneDto.size()));
for (int i = 0; i < listPhoneDto.size(); i++) {
if (isTheSamePhoneNumber(listPhoneDto.get(i).getTelPhone(), szPhone)) {
return listPhoneDto.get(i).getName();
}
}
return "";
}
public boolean isTheSamePhoneNumber(String szNum1, String szNum2) {
if (szNum1.equals(szNum2)) {
LogUtils.d(TAG, "szNum1.equals(szNum2)");
return true;
}
if(UnitAreaUtils.getInstance(mContext).isCurrentUnitAreaNumber(szNum1)) {
if(szNum1.equals(UnitAreaUtils.getInstance(mContext).genCurrentUnitAreaNumber(szNum2))) {
if (UnitAreaUtils.getInstance(mContext).isCurrentUnitAreaNumber(szNum1)) {
if (szNum1.equals(UnitAreaUtils.getInstance(mContext).genCurrentUnitAreaNumber(szNum2))) {
LogUtils.d(TAG, "szNum1.equals(UnitAreaUtils.genCurrentUnitAreaNumber(szNum2))");
return true;
}
}
if(UnitAreaUtils.getInstance(mContext).isCurrentUnitAreaNumber(szNum2)) {
if(szNum2.equals(UnitAreaUtils.getInstance(mContext).genCurrentUnitAreaNumber(szNum1))) {
if (UnitAreaUtils.getInstance(mContext).isCurrentUnitAreaNumber(szNum2)) {
if (szNum2.equals(UnitAreaUtils.getInstance(mContext).genCurrentUnitAreaNumber(szNum1))) {
LogUtils.d(TAG, "szNum2.equals(UnitAreaUtils.genCurrentUnitAreaNumber(szNum1))");
return true;
}
}
LogUtils.d(TAG, "isTheSamePhoneNumber(...) return false;");
return false;
}
//
// 检验电话号码是否是数字
//
public static boolean isPhoneByDigit(String szPhone) {
if(!RegexPPiUtils.isPPiOK(szPhone)) {
if (!RegexPPiUtils.isPPiOK(szPhone)) {
return false;
}
//String text = "这里是一些任意的文本内容";
String regex = "[+]?\\d+";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(szPhone);
LogUtils.d(TAG, String.format("matcher.matches() : %s", matcher.matches()));
/*if (matcher.matches()) {
System.out.println("文本满足该正则表达式模式");
} else {
System.out.println("文本不满足该正则表达式模式");
}*/
return matcher.matches();
}
}

View File

@@ -10,9 +10,9 @@ import cc.winboll.studio.mymessagemanager.beans.AppConfigBean;
import android.content.Context;
public class UnitAreaUtils {
public static final String TAG = "UnitAreaUtils";
static UnitAreaUtils _UnitAreaUtils;
Context mContext;
@@ -26,19 +26,25 @@ public class UnitAreaUtils {
}
return _UnitAreaUtils;
}
public boolean isCurrentUnitAreaNumber(String szPhoneNumer) {
String szUnitArea = getUnitArea();
LogUtils.d(TAG, String.format("szPhoneNumer.substring(1,3) %s", szPhoneNumer.substring(1,3)));
return szPhoneNumer.substring(1,3).equals(szUnitArea);
try {
String szPhoneNumerUnitArea = szPhoneNumer.substring(1, 3);
LogUtils.d(TAG, String.format("szPhoneNumerUnitArea %s", szPhoneNumerUnitArea));
return szPhoneNumerUnitArea.equals(szUnitArea);
} catch (StringIndexOutOfBoundsException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
return false;
}
public String genCurrentUnitAreaNumber(String szPhoneNumer) {
String szUnitArea = getUnitArea();
LogUtils.d(TAG, String.format("szUnitArea %s", szUnitArea));
return "+" + szUnitArea + szPhoneNumer;
}
String getUnitArea() {
String szUnitArea = AppConfigUtil.getInstance(mContext).mAppConfigBean.getCountryCode();
LogUtils.d(TAG, String.format("szUnitArea %s", szUnitArea));

View File

@@ -0,0 +1,125 @@
package cc.winboll.studio.mymessagemanager.views;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/08/23 00:39
* @Describe 多级拉动响应自定义控件
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.widget.ScrollView;
public class BottomPositionFixedScrollView extends ScrollView {
public static final String TAG = "BottomPositionFixedScrollView";
// 记录底部对应的内容绝对位置即底部位置在内容中的y坐标该位置需始终保持在视图底部
private int mBottomContentY = 0;
// 标记是否是首次布局(避免初始加载误触发)
private boolean isFirstLayout = true;
public BottomPositionFixedScrollView(Context context) {
super(context);
init();
}
public BottomPositionFixedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BottomPositionFixedScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// 监听布局变化(高度改变时触发)
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (isFirstLayout) {
isFirstLayout = false;
return;
}
// 布局变化后,恢复底部位置
restoreBottomPosition();
}
});
}
/**
* 重写滚动事件,记录“底部对应的内容绝对位置”
* 即当前视图底部边缘对应的内容y坐标该坐标需始终保持在视图底部
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (getChildCount() == 0) {
mBottomContentY = 0;
return;
}
// 内容总高度
int contentHeight = getChildAt(0).getMeasuredHeight();
// 视图可视高度(自身高度)
int scrollViewHeight = getMeasuredHeight();
// 当前视图底部边缘对应的内容y坐标 = 顶部滚动距离(t) + 可视高度
// (该坐标就是“底部内容的绝对位置”,需始终保持在视图底部)
mBottomContentY = t + scrollViewHeight;
// 避免超过内容总高度(比如内容不足一屏时,底部最多到内容底部)
if (mBottomContentY > contentHeight) {
mBottomContentY = contentHeight;
}
}
/**
* 恢复底部位置:让原记录的“底部内容绝对位置”仍保持在视图底部
*/
private void restoreBottomPosition() {
if (getChildCount() == 0) {
return;
}
// 新的内容总高度
int newContentHeight = getChildAt(0).getMeasuredHeight();
// 新的视图可视高度
int newScrollViewHeight = getMeasuredHeight();
// 目标让原mBottomContentY底部内容绝对位置仍位于视图底部
// 此时需要的顶部滚动距离 = mBottomContentY - 新的可视高度
int targetScrollY = mBottomContentY - newScrollViewHeight;
// 边界修正:
// 1. 不能小于0避免滚动到负数位置
// 2. 不能大于“最大可滚动距离”(内容高度 - 可视高度,避免超出内容范围)
int maxScrollY = Math.max(newContentHeight - newScrollViewHeight, 0);
targetScrollY = Math.max(targetScrollY, 0);
targetScrollY = Math.min(targetScrollY, maxScrollY);
// 滚动到目标位置,保持底部内容位置不变
smoothScrollTo(0, targetScrollY);
}
/**
* 外部手动设置底部内容绝对位置(可选)
*/
public void setBottomContentY(int bottomContentY) {
if (getChildCount() == 0) {
mBottomContentY = bottomContentY;
return;
}
// 限制不超过内容总高度
int contentHeight = getChildAt(0).getMeasuredHeight();
mBottomContentY = Math.min(bottomContentY, contentHeight);
restoreBottomPosition();
}
/**
* 获取当前底部内容绝对位置(可选)
*/
public int getBottomContentY() {
return mBottomContentY;
}
}

View File

@@ -21,25 +21,63 @@
android:layout_height="wrap_content"
android:id="@+id/activitycomposesmsRelativeLayout1">
<TextView
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SMS TO : "
android:id="@+id/activitycomposesmsTextView1"
android:layout_alignParentLeft="true"
android:layout_marginLeft="10dp"
android:layout_centerVertical="true"/>
<EditText
android:layout_toRightOf="@id/activitycomposesmsTextView1"
android:layout_width="wrap_content"
android:inputType="phone"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/activitycomposesmsEditText1"
android:id="@+id/activitycomposesmsLinearLayout1"
android:gravity="center_vertical"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_centerVertical="true"/>
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="(拼音搜索):"/>
<EditText
android:layout_width="80dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/activitycomposesmsEditText2"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/activitycomposesmsEditText2"
android:id="@+id/activitycomposesmsTextView2"
android:layout_weight="1.0"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_below="@id/activitycomposesmsLinearLayout1"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:layout_alignParentLeft="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="(SMS TO) :"
android:id="@+id/activitycomposesmsTextView1"/>
<EditText
android:layout_width="wrap_content"
android:inputType="phone"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/activitycomposesmsEditText1"/>
</LinearLayout>
</RelativeLayout>

View File

@@ -27,7 +27,7 @@
android:layout_width="match_parent"
android:layout_height="60dp"
android:padding="10dp"
android:text="@string/text_onlyreceivecontacts"
android:text="@string/text_norulesreceivecontacts"
android:id="@+id/activitymainSwitchView2"/>
<cc.winboll.studio.mymessagemanager.views.ConfirmSwitchView

View File

@@ -10,36 +10,40 @@
android:layout_height="@dimen/toolbar_height"
android:id="@+id/activitysmsASupportToolbar1"/>
<RelativeLayout
android:orientation="vertical"
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="10dp">
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/activitysmsScrollView2">
<ScrollView
android:layout_alignParentTop="true"
android:layout_above="@+id/activitysmsinclude1"
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/activitysmsinphoneScrollView1"
android:layout_weight="1.0"
android:isScrollContainer="false">
android:layout_height="match_parent"
android:id="@+id/activitysmsLinearLayout1">
<cc.winboll.studio.mymessagemanager.views.SMSListViewForScrollView
<cc.winboll.studio.mymessagemanager.views.BottomPositionFixedScrollView
android:layout_width="match_parent"
android:layout_height="520dp"
android:isScrollContainer="false"
android:id="@+id/activitysmsScrollView1">
<cc.winboll.studio.mymessagemanager.views.SMSListViewForScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activitysmsSMSListViewForScrollView1"/>
</cc.winboll.studio.mymessagemanager.views.BottomPositionFixedScrollView>
<include
layout="@layout/view_smssend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activitysmsinphoneListView1"/>
android:id="@+id/activitysmsinclude1"/>
</LinearLayout>
</ScrollView>
<include
android:layout_alignParentBottom="true"
layout="@layout/view_smssend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activitysmsinclude1"/>
</RelativeLayout>
</ScrollView>
</LinearLayout>

View File

@@ -7,6 +7,18 @@
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:id="@+id/viewsmssendpart1TextView1"/>
<cc.winboll.studio.libaes.views.AOHPCTCSeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/viewsmssendpart1AOHPCTCSeekBar1"/>
<EditText
android:scrollbars="vertical"
android:maxHeight="150dp"
@@ -17,17 +29,6 @@
android:id="@+id/viewsmssendpart1EditText1"
android:background="@drawable/bg_frame"/>
<cc.winboll.studio.libaes.views.AOHPCTCSeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/viewsmssendpart1AOHPCTCSeekBar1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:id="@+id/viewsmssendpart1TextView1"/>
</LinearLayout>

View File

@@ -32,7 +32,7 @@
<string name="text_item_rule_clean">清理设置</string>
<string name="text_sendsms">发送短信</string>
<string name="text_mainservice">短信服务管理总开关</string>
<string name="text_onlyreceivecontacts">接收联系人短信</string>
<string name="text_norulesreceivecontacts">无限制接收联系人短信</string>
<string name="text_usingtts">使用TTS语音播报</string>
<string name="text_usingttsrule">使用TTS语音自定义规则</string>
<string name="text_iamhere">短信管理服务已启动。</string>
@@ -41,6 +41,6 @@
<string name="text_appsettings">应用设置</string>
<string name="text_ttsplaydelaytimes">TTS播放延迟时间</string>
<string name="msg_newsms">接收到新的消息。</string>
<string name="msg_100sendmsg">&gt;&gt;&gt;图标动到 100% 以发送信息&gt;&gt;&gt;</string>
<string name="msg_100applysettings">&gt;&gt;&gt;图标动到 100% 应用设置&gt;&gt;&gt;</string>
<string name="msg_100sendmsg">&gt;&gt;&gt;拉动到100%可发信息&gt;&gt;&gt;</string>
<string name="msg_100applysettings">&gt;&gt;&gt;拉动到100%应用设置&gt;&gt;&gt;</string>
</resources>

View File

@@ -34,7 +34,7 @@
<string name="text_item_rule_clean">Clean Setting</string>
<string name="text_sendsms">Send SMS</string>
<string name="text_mainservice">Main Service</string>
<string name="text_onlyreceivecontacts">Only Receive Contacts</string>
<string name="text_norulesreceivecontacts">No rules Receive Contacts</string>
<string name="text_usingtts">Using TTS</string>
<string name="text_usingttsrule">Using TTS Rule</string>
<string name="text_iamhere">The main service is start.</string>

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Jun 08 21:21:11 HKT 2025
stageCount=1
#Thu Jul 24 10:16:27 HKT 2025
stageCount=2
libraryProject=
baseVersion=15.1
publishVersion=15.1.0
publishVersion=15.1.1
buildCount=0
baseBetaVersion=15.1.1
baseBetaVersion=15.1.2

View File

@@ -3,6 +3,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.numtable">
<!-- Android 13及以下需要的权限 -->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- 可选:查询快捷方式是否存在(部分机型需要) -->
<uses-permission android:name="com.android.launcher.permission.QUERY_ALL_PACKAGES" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
@@ -29,9 +34,7 @@
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name=".GlobalApplication$CrashActivity"/>
</application>
</manifest>

View File

@@ -1,10 +1,20 @@
package cc.winboll.studio.numtable;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.RadioButton;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.numtable.R;
import com.hjq.toast.ToastUtils;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
@@ -19,10 +29,99 @@ public class MainActivity extends AppCompatActivity {
setSupportActionBar(toolbar);
mLogView = findViewById(R.id.logview);
ToastUtils.show("onCreate");
// 初始化创建快捷方式按钮
Button btnCreateShortcut = findViewById(R.id.btn_create_shortcut);
btnCreateShortcut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createDesktopShortcut();
}
});
((RadioButton)findViewById(R.id.rb_right)).setChecked(true);
((RadioButton)findViewById(R.id.rb_left)).setChecked(false);
}
public void onDirectionClick(View view) {
switch (view.getId()) {
case R.id.rb_right:
{
((RadioButton)findViewById(R.id.rb_left)).setChecked(false);
break;
}
case R.id.rb_left:
{
((RadioButton)findViewById(R.id.rb_right)).setChecked(false);
break;
}
}
}
/**
* 创建桌面快捷方式
*/
private void createDesktopShortcut() {
// 1. 创建启动目标Activity的Intent通常是你的主Activity
Intent targetIntent = new Intent(this, MainActivity.class); // 替换为你的主Activity
targetIntent.setAction(Intent.ACTION_MAIN);
targetIntent.addCategory(Intent.CATEGORY_LAUNCHER);
targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
// 2. 创建快捷方式的Intent
Intent shortcutIntent = new Intent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 生成随机UUID
UUID uuid = UUID.randomUUID();
// 转换为字符串
String uuidStr = uuid.toString();
// Android 8.0及以上使用ShortcutManager
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
if (shortcutManager.isRequestPinShortcutSupported()) {
if (((RadioButton)findViewById(R.id.rb_right)).isChecked()) {
// 创建快捷方式信息
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, uuidStr) // 唯一ID
.setShortLabel(">>") // 短标签(显示在桌面)
.setLongLabel("在右边 >>") // 长标签(长按显示)
.setIcon(Icon.createWithResource(this, R.drawable.ic_point_right)) // 图标
.setIntent(targetIntent)
.build();
// 发送创建请求
shortcutManager.requestPinShortcut(shortcutInfo, null);
} else {
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(this, uuidStr) // 唯一ID
.setShortLabel("<<") // 短标签(显示在桌面)
.setLongLabel("在左边 <<") // 长标签(长按显示)
.setIcon(Icon.createWithResource(this, R.drawable.ic_point_left)) // 图标
.setIntent(targetIntent)
.build();
// 发送创建请求
shortcutManager.requestPinShortcut(shortcutInfo, null);
}
ToastUtils.show("已请求创建快捷方式");
} else {
ToastUtils.show("当前设备不支持创建快捷方式");
}
} else {
// Android 7.1及以下:使用旧版广播方式
shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, targetIntent);
shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "应用快捷方式"); // 快捷方式名称
// 设置图标(使用应用图标)
shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher));
// 标记为创建快捷方式
shortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
// 防止重复创建
shortcutIntent.putExtra("duplicate", false);
// 发送广播创建快捷方式
sendBroadcast(shortcutIntent);
ToastUtils.show("快捷方式已创建");
}
}
@Override
protected void onResume() {
super.onResume();

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF3D8A1C"
android:strokeColor="#FFF5DD00"
android:strokeWidth="5.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M401.84 141.95C361.52 236.12 354.86 278.98 399.2 382.12 359.53 383.01 245.24 289.83 245.38 265.73 247.41 236.75 361.69 143.4 401.84 141.95Z"/>
<path
android:fillColor="#FF3D8A1C"
android:strokeColor="#FFF5DD00"
android:strokeWidth="5.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M276.81 141.95C236.49 236.12 229.83 278.98 274.17 382.12 234.5 383.01 120.2 289.83 120.35 265.73 122.37 236.75 236.66 143.4 276.81 141.95Z"/>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF3D8A1C"
android:strokeColor="#FFF5DD00"
android:strokeWidth="5.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M103.57 145.68C143.89 239.85 150.55 282.71 106.21 385.85 145.88 386.74 260.17 293.56 260.03 269.46 258 240.48 143.72 147.13 103.57 145.68Z"/>
<path
android:fillColor="#FF3D8A1C"
android:strokeColor="#FFF5DD00"
android:strokeWidth="5.0"
android:strokeLineCap="round"
android:strokeMiterLimit="10"
android:pathData="M228.6 145.68C268.92 239.85 275.58 282.71 231.24 385.85 270.91 386.74 385.21 293.56 385.06 269.46 383.04 240.48 268.75 147.13 228.6 145.68Z"/>
</vector>

View File

@@ -23,14 +23,44 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:gravity="center_vertical|center_horizontal">
android:layout_weight="1.0">
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="NumTable"
android:textAppearance="?android:attr/textAppearanceLarge"/>
android:gravity="right|center_vertical"
android:layout_gravity="center_vertical">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/ic_point_right"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/rb_right"
android:onClick="onDirectionClick"/>
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/ic_point_left"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/rb_left"
android:onClick="onDirectionClick"/>
<Button
android:id="@+id/btn_create_shortcut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="创建桌面快捷方式"/>
</LinearLayout>
</LinearLayout>

View File

@@ -45,9 +45,9 @@ android {
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
api 'cc.winboll.studio:libaes:15.6.0'
api 'cc.winboll.studio:libapputils:15.3.4'
api 'cc.winboll.studio:libappbase:15.7.6'
api 'cc.winboll.studio:libaes:15.9.3'
api 'cc.winboll.studio:libapputils:15.8.5'
api 'cc.winboll.studio:libappbase:15.9.5'
// 吐司提示库
api 'com.github.getActivity:ToastUtils:10.5'

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu May 29 09:43:37 HKT 2025
stageCount=2
#Sun Aug 31 06:21:48 CST 2025
stageCount=12
libraryProject=
baseVersion=15.4
publishVersion=15.4.1
publishVersion=15.4.11
buildCount=0
baseBetaVersion=15.4.2
baseBetaVersion=15.4.12

View File

@@ -121,6 +121,8 @@
<activity android:name="cc.winboll.studio.powerbell.activities.AboutActivity"/>
<activity android:name="cc.winboll.studio.powerbell.activities.PixelPickerActivity"/>
</application>
</manifest>
</manifest>

View File

@@ -1,6 +1,7 @@
package cc.winboll.studio.powerbell;
import android.content.Context;
import android.os.Environment;
import android.view.Gravity;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.powerbell.receivers.GlobalApplicationReceiver;
@@ -18,7 +19,7 @@ public class App extends GlobalApplication {
static AppCacheUtils _mAppCacheUtils;
GlobalApplicationReceiver mReceiver;
static String szTempDir = "";
public static String getTempDirPath() {
return szTempDir;
}
@@ -26,15 +27,24 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
// 临时文件夹方案1
// 获取Pictures文件夹路径Android 10及以上推荐使用MediaStore此处为传统方式
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
// 定义目标文件路径在Pictures目录下创建"PowerBell"子文件夹及文件)
File powerBellDir = new File(picturesDir, "PowerBell");
// 初始化临时文件夹目录
File fTempDir = new File(getExternalCacheDir(), "TempDir");
if(!fTempDir.exists()) {
fTempDir.mkdirs();
// 临时文件夹方案2 <图片保存失败>
// 获取Pictures文件夹路径Android 10及以上推荐使用MediaStore此处为传统方式
//File powerBellDir = getExternalFilesDir("TempDir");
// 先创建文件夹(如果不存在)
if (!powerBellDir.exists()) {
powerBellDir.mkdirs();
}
szTempDir = fTempDir.getAbsolutePath();
szTempDir = powerBellDir.getAbsolutePath();
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
@@ -45,7 +55,7 @@ public class App extends GlobalApplication {
// 设置数据配置存储工具
_mAppConfigUtils = getAppConfigUtils(this);
_mAppCacheUtils = getAppCacheUtils(this);
mReceiver = new GlobalApplicationReceiver(this);
mReceiver.registerAction();
}

View File

@@ -10,23 +10,27 @@ import android.os.Bundle;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;
import android.widget.Toast;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.activities.AboutActivity;
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
import cc.winboll.studio.powerbell.activities.BatteryReporterActivity;
import cc.winboll.studio.powerbell.activities.ClearRecordActivity;
import cc.winboll.studio.powerbell.activities.WinBoLLActivity;
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
import cc.winboll.studio.powerbell.fragments.MainViewFragment;
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
public class MainActivity extends WinBoLLActivity {
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
public static final int BACKGROUND_PICTURE_REQUEST_CODE = 0;
public static MainActivity _mMainActivity;
LogView mLogView;
//LogView mLogView;
//ArrayList<Fragment> mlistFragment;
App mApplication;
//AppConfigUtils mAppConfigUtils;
@@ -35,6 +39,16 @@ public class MainActivity extends Activity {
MainViewFragment mMainViewFragment;
AToolbar mAToolbar;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
//LogUtils.d(TAG, "onCreate(...)");
@@ -42,10 +56,10 @@ public class MainActivity extends Activity {
setContentView(R.layout.activity_main);
// 设置调试日志
mLogView = findViewById(R.id.logview);
mLogView.start();
//LogUtils.d(TAG, "LogView Start.");
mLogView.updateLogView();
// mLogView = findViewById(R.id.logview);
// mLogView.start();
// //LogUtils.d(TAG, "LogView Start.");
// mLogView.updateLogView();
_mMainActivity = MainActivity.this;
mApplication = (App) getApplication();
@@ -117,8 +131,8 @@ public class MainActivity extends Activity {
super.onResume();
// 回到窗口自动取消提醒消息
//NotificationHelper.cancelRemindNotification(this);
reloadBackground();
setBackgroundColor();
}
// Menu icons are inflated just as they were with actionbar
@@ -157,6 +171,8 @@ public class MainActivity extends Activity {
Intent intent = new Intent();
intent.setClass(this, BackgroundPictureActivity.class);
startActivity(intent);
} else if (menuItemId == R.id.action_log) {
App.getWinBoLLActivityManager().startLogActivity(this);
}
return true;
@@ -193,4 +209,12 @@ public class MainActivity extends Activity {
moveTaskToBack(true);
}
}
void setBackgroundColor() {
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(MainActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitymainRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor);
}
}

View File

@@ -53,11 +53,11 @@ public class AboutActivity extends Activity {
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_launcher);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("APP");
appInfo.setAppGitName("APPBase");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(szBranchName);
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=PowerBell");
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=1");
appInfo.setAppAPKName("PowerBell");
appInfo.setAppAPKFolderName("PowerBell");
return new AboutView(mContext, appInfo);

View File

@@ -1,22 +1,27 @@
package cc.winboll.studio.powerbell.activities;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.RelativeLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.utils.ToastUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.activities.BackgroundPictureActivity;
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
@@ -24,42 +29,45 @@ import cc.winboll.studio.powerbell.utils.FileUtils;
import cc.winboll.studio.powerbell.utils.UriUtil;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class BackgroundPictureActivity extends Activity
implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
public class BackgroundPictureActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
public static final String TAG = "BackgroundPictureActivity";
public BackgroundPictureUtils mBackgroundPictureUtils;
// 图片选择请求
// 图片选择请求
public static final int REQUEST_SELECT_PICTURE = 0;
// 照相选择请求
public static final int REQUEST_TAKE_PHOTO = 1;
// 图片裁剪选择请求
public static final int REQUEST_CROP_IMAGE = 2;
private static final int STORAGE_PERMISSION_REQUEST = 100;
AToolbar mAToolbar;
// 所有图片存储文件夹
File mfBackgroundDir;
// 拍照与剪裁的文件
File mfPictureDir;
// 拍照文件
File mfTakePhoto;
// 接收到的图片文件类
public File mfRecivedPicture;
// 剪裁文件类
File mfTempCropPicture;
// 剪裁接收后的文件的文件名
private AToolbar mAToolbar;
private File mfBackgroundDir; // 背景图片存储文件夹
private File mfPictureDir; // 拍照与剪裁临时文件夹
private File mfTakePhoto; // 拍照文件
private File mfRecivedPicture; // 接收的图片文件
private File mfTempCropPicture; // 剪裁临时文件
private File mfRecivedCropPicture; // 剪裁后的目标文件
// 静态变量
public static String _mszRecivedCropPicture = "RecivedCrop.jpg";
File mfRecivedCropPicture;
static String _mszCommonFileType = "jpeg";
// 背景图片的压缩比
int mnPictureCompress = 100;
static String _RecivedPictureFileName;
private static String _mszCommonFileType = "jpeg";
private int mnPictureCompress = 100;
private static String _RecivedPictureFileName;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -67,30 +75,29 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
setContentView(R.layout.activity_backgroundpicture);
initEnv();
// 初始化工具类和文件夹
mBackgroundPictureUtils = BackgroundPictureUtils.getInstance(this);
mfBackgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
if (!mfBackgroundDir.exists()) {
mfBackgroundDir.mkdirs();
}
//mfPictureDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getString(R.string.app_projectname));
mfPictureDir = new File(App.getTempDirPath());
if (!mfPictureDir.exists()) {
mfPictureDir.mkdirs();
}
// 初始化文件对象
mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg");
mfTempCropPicture = new File(mfPictureDir, "TempCrop.jpg");
mfRecivedPicture = getRecivedPictureFile(this);
mfRecivedCropPicture = new File(mfBackgroundDir, _mszRecivedCropPicture);
// 初始化工具栏
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
setActionBar(mAToolbar);
//mAToolbar.setTitle(getTitle() + "-" + getString(R.string.subtitle_activity_backgroundpicture));
mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture);
//mAToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
//mAToolbar.setSubtitleTextAppearance(this, R.style.Toolbar_SubTitleText);
//mAToolbar.setBackgroundColor(getColor(R.color.colorPrimary));
setActionBar(mAToolbar);
getActionBar().setDisplayHomeAsUpEnabled(true);
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
@@ -99,41 +106,30 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
}
});
//给按钮设置点击事件
// 设置按钮点击事件
findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener);
findViewById(R.id.activitybackgroundpictureAButton4).setOnClickListener(onReceivedPictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton1).setOnClickListener(onTakePhotoClickListener);
findViewById(R.id.activitybackgroundpictureAButton2).setOnClickListener(onSelectPictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener);
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
updatePreviewBackground();
// 判断并且处理应用分享到的文件
//
//ToastUtils.show("Activity Opened.");
// 预备接收参数
// 处理分享的图片
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
//LogUtils.d(TAG, "action : " + action);
//LogUtils.d(TAG, "type : " + type);
// 判断是否进入图片分享状态
if (Intent.ACTION_SEND.equals(action)
&& type != null
&& ("image/*".equals(type) || "image/jpeg".equals(type) || "image/jpg".equals(type) || "image/png".equals(type) || "image/webp".equals(type))) {
// 预览图片
BackgroundPicturePreviewDialog dlg= new BackgroundPicturePreviewDialog(this);
if (Intent.ACTION_SEND.equals(action) && type != null && isImageType(type)) {
BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this);
dlg.show();
}
}
void initEnv() {
private void initEnv() {
LogUtils.d(TAG, "initEnv()");
_RecivedPictureFileName = "Recived.data";
}
@@ -144,47 +140,55 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
@Override
public void onAcceptRecivedPicture(String szPreRecivedPictureName) {
//ToastUtils.show("onAcceptRecivedPicture");
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
utils.saveData();
File fPreRecivedPictureName = new File(utils.getBackgroundDir(), szPreRecivedPictureName);
FileUtils.copyFile(fPreRecivedPictureName, mfRecivedPicture);
// 加载背景
startCropImageActivity(false);
}
//
// 更新预览背景
//
public void updatePreviewBackground() {
LogUtils.d(TAG, "updatePreviewBackground");
ImageView ivPreviewBackground = findViewById(R.id.activitybackgroundpictureImageView1);
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
utils.loadBackgroundPictureBean();
boolean isUseBackgroundFile = utils.getBackgroundPictureBean().isUseBackgroundFile();
if (isUseBackgroundFile && mfRecivedCropPicture.exists()) {
try {
String szBackgroundFilePath = utils.getBackgroundDir() + getBackgroundFileName();
Drawable drawableBackground = FileUtils.getImageDrawable(szBackgroundFilePath);
drawableBackground.setAlpha(120);
ivPreviewBackground.setImageDrawable(drawableBackground);
ToastUtils.show("Use acceptRecived background.");
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
File sourceFile = new File(utils.getBackgroundDir(), szPreRecivedPictureName);
if (FileUtils.copyFile(sourceFile, mfRecivedPicture)) {
startCropImageActivity(false);
} else {
ToastUtils.show(" No background.");
Drawable drawableBackground = getDrawable(R.drawable.blank10x10);
drawableBackground.setAlpha(120);
ivPreviewBackground.setImageDrawable(drawableBackground);
ToastUtils.show("图片复制失败,请重试");
}
}
/**
* 更新背景图片预览
*/
public void updatePreviewBackground() {
LogUtils.d(TAG, "updatePreviewBackground");
ImageView ivPreviewBackground = (ImageView) findViewById(R.id.activitybackgroundpictureImageView1);
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(this);
utils.loadBackgroundPictureBean();
boolean isUseBackgroundFile = utils.getBackgroundPictureBean().isUseBackgroundFile();
if (isUseBackgroundFile && mfRecivedCropPicture.exists()) {
try {
String filePath = utils.getBackgroundDir() + getBackgroundFileName();
Drawable drawable = FileUtils.getImageDrawable(filePath);
if (drawable != null) {
//drawable.setAlpha(120);
ivPreviewBackground.setImageDrawable(drawable);
}
//ToastUtils.show("背景图片已更新");
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("背景图片加载失败");
}
} else {
ToastUtils.show("未使用背景图片");
Drawable drawable = getResources().getDrawable(R.drawable.blank10x10);
if (drawable != null) {
drawable.setAlpha(120);
ivPreviewBackground.setImageDrawable(drawable);
}
}
}
// 点击事件监听器
private View.OnClickListener onOriginNullClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 选择原始空白背景
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
bean.setIsUseBackgroundFile(false);
@@ -196,11 +200,10 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 导入外部图片
Intent intent = new Intent(
Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_SELECT_PICTURE);
if (checkAndRequestStoragePermission()) {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, REQUEST_SELECT_PICTURE);
}
}
};
@@ -211,7 +214,7 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
if (fCheck.exists()) {
startCropImageActivity(false);
} else {
ToastUtils.show("There is not any picture to crop.");
ToastUtils.show("没有可剪裁的图片");
}
}
};
@@ -223,7 +226,7 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
if (fCheck.exists()) {
startCropImageActivity(true);
} else {
ToastUtils.show("There is not any picture to crop.");
ToastUtils.show("没有可剪裁的图片");
}
}
};
@@ -233,6 +236,7 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
public void onClick(View v) {
LogUtils.d(TAG, "onTakePhotoClickListener");
LogUtils.d(TAG, "mfTakePhoto : " + mfTakePhoto.getPath());
if (mfTakePhoto.exists()) {
mfTakePhoto.delete();
}
@@ -240,56 +244,94 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
mfTakePhoto.createNewFile();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("拍照文件创建失败");
return;
}
if (checkAndRequestStoragePermission()) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
};
private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 选择接收到的背景图片
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
utils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
utils.saveData();
updatePreviewBackground();
}
};
private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 从文件路径启动像素拾取活动
//String imagePath = "/storage/emulated/0/DCIM/Camera/sample.jpg";
String imagePath = mfRecivedCropPicture.toString();
Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
intent.putExtra("imagePath", imagePath);
startActivity(intent);
//App.getWinBoLLActivityManager().startWinBoLLActivity(getActivity(), intent, PixelPickerActivity.class);
}
};
private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
bean.setPixelColor(0);
utils.saveData();
setBackgroundColor();
}
};
/**
* 压缩图片并保存到接收文件
*/
void compressQualityToRecivedPicture(Bitmap bitmap) {
// 设置输出流
OutputStream outStream = null;
try {
// 创建输出流对象,准备写入压缩后的图片文件
mfRecivedPicture = getRecivedPictureFile(this);
// 创建新的接收文件
if (!mfRecivedPicture.exists()) {
mfRecivedPicture.createNewFile();
}
FileOutputStream fos = new FileOutputStream(mfRecivedPicture);
// 获取输出流对象
outStream = new BufferedOutputStream(fos);
// 使用默认的质量参数压缩图片
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); // 70% 的质量
// 关闭输出流以完成文件操作
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("图片压缩失败");
} finally {
if (outStream != null) {
try {
outStream.close();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
/**
* 启动图片裁剪活动
* @param isCropFree 是否自由裁剪
*/
public void startCropImageActivity(boolean isCropFree) {
LogUtils.d(TAG, "startCropImageActivity");
BackgroundPictureBean bean = mBackgroundPictureUtils.loadBackgroundPictureBean();
mfRecivedPicture = getRecivedPictureFile(this);
Uri uri = UriUtil.getUriForFile(this, mfRecivedPicture);
LogUtils.d(TAG, "uri : " + uri.toString());
if (mfTempCropPicture.exists()) {
mfTempCropPicture.delete();
}
@@ -297,27 +339,24 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
mfTempCropPicture.createNewFile();
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
ToastUtils.show("剪裁临时文件创建失败");
return;
}
// 使用正确的文件路径构建 Uri
Uri cropOutPutUri = Uri.fromFile(mfTempCropPicture);
LogUtils.d(TAG, "mfTempCropPicture : " + mfTempCropPicture.getPath());
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/" + _mszCommonFileType);
// 下面这个crop=true是设置在开启的Intent中设置显示的VIEW可裁剪
intent.putExtra("crop", "true");
intent.putExtra("noFaceDetection", true);
if (!isCropFree) {
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", bean.getBackgroundWidth());
intent.putExtra("aspectY", bean.getBackgroundHeight());
}
// outputX outputY 是裁剪图片宽高
//intent.putExtra("outputX", 100);
//intent.putExtra("outputY", 100);
//return-data =false 意味着裁剪成功后不能在onActivityResult 的intent 中获得图片
//intent.putExtra("return-data", false);
intent.putExtra("return-data", true);
//裁剪后的图片输出至 cropOutPutUri
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri);
intent.putExtra("scale", true);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
@@ -325,13 +364,102 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
startActivityForResult(intent, REQUEST_CROP_IMAGE);
}
// 启动裁剪窗口,裁剪操作文件为 uirImage
//
/**
* 保存剪裁后的Bitmap优化版
*/
private void saveCropBitmap(Bitmap bitmap) {
if (bitmap == null) {
ToastUtils.show("剪裁图片为空");
return;
}
// 内存优化:大图片自动缩放
Bitmap scaledBitmap = bitmap;
if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB
float scale = 1.0f;
while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) {
scale -= 0.2f; // 每次缩小20%
if (scale < 0.2f) break; // 最小缩放到20%
scaledBitmap = scaleBitmap(scaledBitmap, scale);
}
if (scaledBitmap != bitmap) {
bitmap.recycle(); // 回收原Bitmap
}
}
// 优化:创建保存目录
File backgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
if (!backgroundDir.exists()) {
if (!backgroundDir.mkdirs()) {
ToastUtils.show("无法创建保存目录");
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
File saveFile = new File(backgroundDir, getBackgroundFileName());
// 优化:检查文件是否可写
if (saveFile.exists() && !saveFile.canWrite()) {
if (!saveFile.delete()) {
ToastUtils.show("无法删除旧文件");
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(saveFile);
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
fos.flush();
if (success) {
ToastUtils.show("保存成功");
// 更新数据
mBackgroundPictureUtils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
updatePreviewBackground();
} else {
ToastUtils.show("图片压缩保存失败");
}
} catch (FileNotFoundException e) {
LogUtils.e(TAG, "文件未找到" + e);
ToastUtils.show("保存失败:文件路径错误");
} catch (IOException e) {
LogUtils.e(TAG, "写入异常" + e);
ToastUtils.show("保存失败:磁盘可能已满或路径错误");
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
LogUtils.e(TAG, "流关闭异常" + e);
}
}
if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle();
}
}
}
/**
* 缩放Bitmap
*/
private Bitmap scaleBitmap(Bitmap original, float scale) {
if (original == null) {
return null;
}
int width = (int) (original.getWidth() * scale);
int height = (int) (original.getHeight() * scale);
return Bitmap.createScaledBitmap(original, width, height, true);
}
/**
* 分享图片
*/
void sharePicture() {
Uri uri = UriUtil.getUriForFile(this, mfRecivedPicture);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("image/" + _mszCommonFileType);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share Image"));
@@ -345,45 +473,121 @@ implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_SELECT_PICTURE) {
// 处理选择后图片
if (resultCode == RESULT_OK) {
try {
Uri selectedImage = data.getData();
LogUtils.d(TAG, "Uri is : " + selectedImage.toString());
File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage));
mfRecivedPicture = getRecivedPictureFile(this);
FileUtils.copyFile(fSrcImage, mfRecivedPicture);
// 启动剪裁文件窗口
if (requestCode == REQUEST_SELECT_PICTURE && resultCode == RESULT_OK) {
try {
Uri selectedImage = data.getData();
LogUtils.d(TAG, "Uri is : " + selectedImage.toString());
File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage));
mfRecivedPicture = getRecivedPictureFile(this);
if (FileUtils.copyFile(fSrcImage, mfRecivedPicture)) {
startCropImageActivity(false);
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
} else {
ToastUtils.show("图片复制失败,请重试");
}
} catch (Exception e) {
LogUtils.e(TAG, "选择图片异常" + e);
ToastUtils.show("选择图片失败:" + e.getMessage());
}
} else if (requestCode == REQUEST_TAKE_PHOTO) {
if (resultCode == RESULT_OK) {
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
Bundle extras = data.getExtras();
} else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
Bundle extras = data.getExtras();
if (extras != null) {
Bitmap imageBitmap = (Bitmap) extras.get("data");
compressQualityToRecivedPicture(imageBitmap);
startCropImageActivity(false);
}
} else if (requestCode == REQUEST_CROP_IMAGE) {
if (resultCode == RESULT_OK) {
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
FileUtils.copyFile(mfTempCropPicture, mfRecivedCropPicture);
mfTempCropPicture.delete();
mBackgroundPictureUtils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
updatePreviewBackground();
if (imageBitmap != null) {
compressQualityToRecivedPicture(imageBitmap);
startCropImageActivity(false);
} else {
ToastUtils.show("拍照图片为空");
}
} else {
ToastUtils.show("拍照数据获取失败");
}
} else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) {
LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE");
try {
Bitmap cropBitmap = null;
// 方案1通过Intent获取剪裁后的Bitmap
if (data != null && data.hasExtra("data")) {
cropBitmap = data.getParcelableExtra("data");
} else if (mfTempCropPicture.exists()) {
cropBitmap = BitmapFactory.decodeFile(mfTempCropPicture.getPath());
} else {
ToastUtils.show("剪裁文件不存在");
return;
}
} else {
String sz = "Unsolved requestCode = " + Integer.toString(requestCode);
Toast.makeText(getApplication(), sz, Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, sz);
if (cropBitmap != null) {
saveCropBitmap(cropBitmap);
} else {
ToastUtils.show("获取剪裁图片失败");
}
} catch (OutOfMemoryError e) {
LogUtils.e(TAG, "内存溢出" + e);
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片");
} catch (Exception e) {
LogUtils.e(TAG, "剪裁保存异常" + e);
ToastUtils.show("保存失败:" + e.getMessage());
}/* finally {
// 安全删除临时文件
if (mfTempCropPicture.exists()) {
mfTempCropPicture.delete();
}
}*/
} else if (resultCode != RESULT_OK) {
LogUtils.d(TAG, "操作取消或失败requestCode: " + requestCode);
ToastUtils.show("操作已取消");
}
}
/**
* 检查类型是否为图片
*/
private boolean isImageType(String type) {
return type.startsWith("image/") || "image/jpeg".equals(type) ||
"image/jpg".equals(type) || "image/png".equals(type) ||
"image/webp".equals(type);
}
/**
* 检查并申请存储权限
*/
private boolean checkAndRequestStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
STORAGE_PERMISSION_REQUEST);
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_REQUEST) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
ToastUtils.show("存储权限已获取");
} else {
ToastUtils.show("需要存储权限才能保存图片");
}
}
}
void setBackgroundColor() {
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(BackgroundPictureActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor);
}
@Override
protected void onResume() {
super.onResume();
setBackgroundColor();
}
}

View File

@@ -0,0 +1,251 @@
package cc.winboll.studio.powerbell.activities;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/06/22 14:15
*/
import android.app.Activity;
import android.app.Dialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
import cc.winboll.studio.powerbell.beans.BackgroundPictureBean;
import cc.winboll.studio.powerbell.utils.BackgroundPictureUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "PixelPickerActivity";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
private AToolbar mAToolbar;
private ImageView imageView;
private Bitmap originalBitmap;
private TextView infoText;
private ViewGroup imageContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pixelpicker);
// 初始化工具栏
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
setActionBar(mAToolbar);
mAToolbar.setSubtitle(R.string.subtitle_activity_pixelpicker);
getActionBar().setDisplayHomeAsUpEnabled(true);
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
imageView = findViewById(R.id.imageView);
infoText = findViewById(R.id.infoText);
imageContainer = findViewById(R.id.imageContainer);
// 从Intent获取图片路径并加载
String imagePath = getIntent().getStringExtra("imagePath");
if (imagePath != null) {
loadImage(imagePath);
} else {
infoText.setText("未找到图片路径");
}
// 设置图片点击事件
imageContainer.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && originalBitmap != null) {
// 计算点击位置在图片上的实际坐标
float touchX = event.getX();
float touchY = event.getY();
int pixelX = -1, pixelY = -1;
try {
// 获取图片在容器中的实际位置和尺寸
int[] imageLocation = new int[2];
imageView.getLocationInWindow(imageLocation);
int imageWidth = imageView.getWidth();
int imageHeight = imageView.getHeight();
// 计算缩放比例
float scaleX = (float) originalBitmap.getWidth() / imageWidth;
float scaleY = (float) originalBitmap.getHeight() / imageHeight;
// 调整触摸坐标到图片坐标系
float adjustedX = touchX - imageLocation[0];
float adjustedY = touchY - imageLocation[1];
// 检查是否在图片范围内
if (adjustedX >= 0 && adjustedX <= imageWidth && adjustedY >= 0 && adjustedY <= imageHeight) {
// 计算实际像素坐标
pixelX = (int) (adjustedX * scaleX);
pixelY = (int) (adjustedY * scaleY);
// 再次检查像素坐标是否在有效范围内
if (pixelX >= 0 && pixelX < originalBitmap.getWidth() &&
pixelY >= 0 && pixelY < originalBitmap.getHeight()) {
int pixelColor = originalBitmap.getPixel(pixelX, pixelY);
showPixelDialog(pixelColor, pixelX, pixelY);
} else {
Toast.makeText(PixelPickerActivity.this, "像素坐标超出范围", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(PixelPickerActivity.this, "点击位置超出图片显示范围", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(PixelPickerActivity.this, "计算像素位置失败", Toast.LENGTH_SHORT).show();
}
}
return true;
}
});
}
/**
* 加载图片
*/
private void loadImage(String imagePath) {
try {
File file = new File(imagePath);
if (file.exists()) {
// 解码图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1; // 加载原图
originalBitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
if (originalBitmap != null) {
imageView.setImageBitmap(originalBitmap);
infoText.setText("图片已加载,点击获取像素值");
} else {
infoText.setText("图片加载失败");
}
} else {
infoText.setText("图片文件不存在");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
infoText.setText("图片文件未找到");
}
}
/**
* 显示像素对话框
*/
private void showPixelDialog(final int pixelColor, int x, int y) {
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_pixel);
dialog.setCancelable(true);
// 设置像素颜色视图背景
TextView colorView = dialog.findViewById(R.id.pixelColorView);
colorView.setBackgroundColor(pixelColor);
// 显示颜色信息
TextView infoText = dialog.findViewById(R.id.colorInfoText);
String colorInfo = String.format(
"RGB: (%d, %d, %d)\n" +
"ARGB: #%08X\n" +
"实际像素位置: (%d, %d)",
Color.red(pixelColor),
Color.green(pixelColor),
Color.blue(pixelColor),
pixelColor,
x, y);
infoText.setText(colorInfo);
// 设置确定按钮点击事件
Button confirmButton = dialog.findViewById(R.id.confirmButton);
confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
// 可以在这里添加确定后的回调逻辑
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(PixelPickerActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
bean.setPixelColor(pixelColor);
utils.saveData();
Toast.makeText(PixelPickerActivity.this, "已记录像素值", Toast.LENGTH_SHORT).show();
setBackgroundColor();
}
});
dialog.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 回收Bitmap资源
if (originalBitmap != null && !originalBitmap.isRecycled()) {
originalBitmap.recycle();
originalBitmap = null;
}
}
void setBackgroundColor() {
BackgroundPictureUtils utils = BackgroundPictureUtils.getInstance(PixelPickerActivity.this);
BackgroundPictureBean bean = utils.getBackgroundPictureBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitypixelpickerRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor);
}
@Override
protected void onResume() {
super.onResume();
setBackgroundColor();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
super.onBackPressed();
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
}
}

View File

@@ -0,0 +1,53 @@
package cc.winboll.studio.powerbell.activities;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/06/19 20:35
* @Describe 应用窗口基类
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
public abstract class WinBoLLActivity extends Activity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
GlobalApplication.getWinBoLLActivityManager().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
super.onBackPressed();
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
}
}

View File

@@ -17,6 +17,8 @@ public class BackgroundPictureBean extends BaseBean {
int backgroundWidth = 100;
int backgroundHeight = 100;
boolean isUseBackgroundFile = false;
// 图片拾取像素颜色
int pixelColor = 0;
public BackgroundPictureBean() {
}
@@ -25,6 +27,14 @@ public class BackgroundPictureBean extends BaseBean {
this.isUseBackgroundFile = isUseBackgroundFile;
}
public void setPixelColor(int pixelColor) {
this.pixelColor = pixelColor;
}
public int getPixelColor() {
return pixelColor;
}
public void setBackgroundWidth(int backgroundWidth) {
this.backgroundWidth = backgroundWidth;
}
@@ -61,6 +71,7 @@ public class BackgroundPictureBean extends BaseBean {
jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
jsonWriter.name("pixelColor").value(bean.getPixelColor());
}
@Override
@@ -75,6 +86,8 @@ public class BackgroundPictureBean extends BaseBean {
bean.setBackgroundHeight(jsonReader.nextInt());
} else if (name.equals("isUseBackgroundFile")) {
bean.setIsUseBackgroundFile(jsonReader.nextBoolean());
} else if (name.equals("pixelColor")) {
bean.setPixelColor(jsonReader.nextInt());
} else {
jsonReader.skipValue();
}

View File

@@ -311,11 +311,11 @@ public class MainViewFragment extends Fragment {
LogUtils.d(TAG, String.format("fBackgroundFilePath.exists() %s", fBackgroundFilePath.exists()));
if (bean.isUseBackgroundFile() && fBackgroundFilePath.exists()) {
Drawable drawableBackground = Drawable.createFromPath(szBackgroundFilePath);
drawableBackground.setAlpha(120);
//drawableBackground.setAlpha(120);
imageView.setImageDrawable(drawableBackground);
} else {
Drawable drawableBackground = getActivity().getDrawable(R.drawable.blank10x10);
drawableBackground.setAlpha(120);
//drawableBackground.setAlpha(120);
imageView.setImageDrawable(drawableBackground);
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
@@ -12,83 +12,110 @@
android:id="@+id/toolbar"
style="@style/DefaultAToolbar"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activitybackgroundpictureImageView1"
android:layout_below="@id/toolbar">
</ImageView>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/toolbar">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activitybackgroundpictureRelativeLayout1"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="160dp"
android:layout_height="36dp"
android:text="Origin BG"
android:id="@+id/activitybackgroundpictureAButton5"
android:layout_alignParentLeft="true"
android:layout_margin="5dp"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="160dp"
android:layout_height="36dp"
android:text="Received BG"
android:id="@+id/activitybackgroundpictureAButton4"
android:layout_alignParentRight="true"
android:layout_margin="5dp"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activitybackgroundpictureImageView1"
android:layout_below="@id/toolbar">
</RelativeLayout>
</ImageView>
<LinearLayout
android:orientation="horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
android:layout_below="@id/toolbar">
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="◎"
android:layout_gravity="center_vertical"
android:layout_margin="10dp"
android:id="@+id/activitybackgroundpictureAButton1"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text=""
android:layout_gravity="center_vertical"
android:layout_margin="10dp"
android:id="@+id/activitybackgroundpictureAButton2"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="160dp"
android:layout_height="36dp"
android:text="Origin BG"
android:id="@+id/activitybackgroundpictureAButton5"
android:layout_alignParentLeft="true"
android:layout_margin="5dp"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[+]"
android:layout_gravity="center_vertical"
android:layout_margin="10dp"
android:id="@+id/activitybackgroundpictureAButton3"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="160dp"
android:layout_height="36dp"
android:text="Received BG"
android:id="@+id/activitybackgroundpictureAButton4"
android:layout_alignParentRight="true"
android:layout_margin="5dp"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[+~]"
android:layout_gravity="center_vertical"
android:layout_margin="10dp"
android:id="@+id/activitybackgroundpictureAButton6"/>
</RelativeLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="◎"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton1"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="☑"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton2"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[+]"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton3"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[+~]"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton6"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[◐]"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton7"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[○]"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton8"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>

View File

@@ -25,12 +25,19 @@
</LinearLayout>
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activityclearrecordTextView1"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"/>
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/activityclearrecordTextView1"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"

View File

@@ -2,42 +2,34 @@
<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_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
<cc.winboll.studio.libaes.views.AToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
android:gravity="center_vertical"
style="@style/DefaultAToolbar"/>
<cc.winboll.studio.libaes.views.AToolbar
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
android:gravity="center_vertical"
style="@style/DefaultAToolbar"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_weight="1.0">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activitymainRelativeLayout1"
android:background="#FFEE2121"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activitymainFrameLayout1"/>
</LinearLayout>
<cc.winboll.studio.libappbase.LogView
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/logview"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -0,0 +1,54 @@
<?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">
<cc.winboll.studio.libaes.views.AToolbar
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:id="@+id/toolbar"
android:gravity="center_vertical"
style="@style/DefaultAToolbar"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFEE2121"
android:id="@+id/activitypixelpickerRelativeLayout1"/>
<FrameLayout
android:id="@+id/imageContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_launcher"/>
</FrameLayout>
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:padding="10dp"
android:background="#80000000"
android:textColor="#FFFFFF"
android:text="点击图片获取像素值"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/pixelColorView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/colorInfoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:textColor="#333333"
android:textSize="14sp" />
<Button
android:id="@+id/confirmButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@android:color/holo_blue_light"
android:text="确定"
android:textColor="@android:color/white" />
</LinearLayout>

View File

@@ -43,15 +43,6 @@
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tips"
android:textSize="@dimen/text_content_size"
android:id="@+id/fragmentandroidviewTextView1"
android:background="@drawable/bg_frame"
android:padding="10dp"/>
</LinearLayout>
<LinearLayout
@@ -205,6 +196,21 @@
</LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tips"
android:textSize="@dimen/text_content_size"
android:id="@+id/fragmentandroidviewTextView1"
android:background="@drawable/bg_frame"
android:padding="10dp"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@@ -9,6 +9,9 @@
<item
android:id="@+id/action_changepicture"
android:title="@string/item_changepicture"/>
<item
android:id="@+id/action_log"
android:title="@string/item_logview"/>
<item
android:id="@+id/action_about"
android:title="@string/item_aboutview"/>

View File

@@ -15,6 +15,7 @@
<string name="texthint_CustomSlideToCleanRecord">Slide Right To Clean Up APP Record.</string>
<string name="subtitle_activity_clearrecord">清理记录</string>
<string name="subtitle_activity_backgroundpicture">更换背景图片</string>
<string name="subtitle_activity_pixelpicker">背景像素拾取</string>
<string name="subtitle_activity_about">关于应用</string>
<string name="msg_AOHPCTCSeekBar_ClearRecord">&gt;&gt;&gt;Seek 100% To Clear Battery Record.&gt;&gt;&gt;</string>
<string name="msg_AOHPCTCSeekBar_ClearRecord">&gt;&gt;&gt;向右滑动100%可以清理电量记录。&gt;&gt;&gt;</string>
</resources>

View File

@@ -18,6 +18,7 @@
<string name="texthint_CustomSlideToCleanRecord">Slide Right To Clean Up APP Record.</string>
<string name="subtitle_activity_clearrecord">Clean Record</string>
<string name="subtitle_activity_backgroundpicture">Background Picture</string>
<string name="subtitle_activity_pixelpicker">Pixel Picker</string>
<string name="subtitle_activity_about">About The APP</string>
<string name="msg_AOHPCTCSeekBar_ClearRecord">&gt;&gt;&gt;Seek 100% To Clear Battery Record.&gt;&gt;&gt;</string>
<string name="msg_AOHPCTCSeekBar_ClearRecord">&gt;&gt;&gt;Seek 100% Right Is Clean Record.&gt;&gt;&gt;</string>
</resources>

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed May 07 15:07:02 HKT 2025
stageCount=2
#Mon Jul 28 11:36:51 HKT 2025
stageCount=4
libraryProject=
baseVersion=15.1
publishVersion=15.1.1
publishVersion=15.1.3
buildCount=0
baseBetaVersion=15.1.2
baseBetaVersion=15.1.4

View File

@@ -3,6 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.timestamp">
<!-- 开机启动 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- 运行前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
@@ -45,6 +48,20 @@
<service android:name=".AssistantService"/>
<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>
</application>
</manifest>

View File

@@ -5,6 +5,7 @@ import android.view.View;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
@@ -12,6 +13,8 @@ import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.timestamp.MainService;
import cc.winboll.studio.timestamp.R;
import cc.winboll.studio.timestamp.utils.AppConfigsUtil;
import cc.winboll.studio.timestamp.utils.ClipboardUtil;
import cc.winboll.studio.timestamp.utils.TimeStampUtil;
import com.hjq.toast.ToastUtils;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -97,4 +100,22 @@ public class MainActivity extends AppCompatActivity {
}
return false;
}
public void onTimeStamp(View view) {
// 在这里编写按钮点击后要执行的代码
TimeStampUtil.getInstance(this).genTimeStamp();
String formattedDateTime = TimeStampUtil.getInstance(this).getTimeStampCopyString();
ClipboardUtil.copyTextToClipboard(this, formattedDateTime);
// 更新时间戳文本框显示内容
EditText etTimeStamp = findViewById(R.id.et_timestamp);
etTimeStamp.setText(formattedDateTime);
// 输出一个提示音
TimeStampUtil.getInstance(this).playNotifyMusic(this);
// 比如显示一个 Toast
Toast.makeText(this, "时间戳:\n" + TimeStampUtil.getInstance(this).getTimeStampCopyString() + "\n已拷贝到剪贴板。", Toast.LENGTH_SHORT).show();
MainService.updateCopiedTimeStamp();
}
}

View File

@@ -0,0 +1,40 @@
package cc.winboll.studio.timestamp.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.timestamp.MainService;
import cc.winboll.studio.timestamp.models.AppConfigsModel;
import cc.winboll.studio.timestamp.utils.AppConfigsUtil;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
* @Date 2025/07/28 11:15
* @Describe MainReceiver
*/
public class MainReceiver extends BroadcastReceiver {
public static final String TAG = "MainReceiver";
static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
AppConfigsUtil mAppConfigsUtil;
@Override
public void onReceive(Context context, Intent intent) {
String szAction = intent.getAction();
if (szAction.equals(ACTION_BOOT_COMPLETED)) {
AppConfigsModel appConfigs = AppConfigsUtil.getInstance(context).loadAppConfigs();
boolean isEnable = appConfigs.isEnableService();
AppConfigsUtil.getInstance(context).saveAppConfigs();
Intent intentMainService = new Intent(context, MainService.class);
if (isEnable) {
context.startService(intentMainService);
}
LogUtils.i(TAG, "System Boot And Start ManagerService Completed!");
}
}
}

View File

@@ -130,16 +130,10 @@ public class NotificationHelper {
mForegroundNotification.bigContentView = remoteViews;
service.startForeground(ID_MSG_SERVICE, mForegroundNotification);
// 播放默认短信铃声
Uri defaultSmsRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
AudioPlayerUriUtil.playAudio(service, defaultSmsRingtoneUri);
// 播放应用铃声
// 获取MP3文件的Uri
Uri soundUri = Uri.parse("android.resource://" + service.getPackageName() + "/" + R.raw.diweiyi);
AudioPlayerUriUtil.playAudio(service, soundUri);
TimeStampUtil.getInstance(service).playNotifyMusic(service);
}

View File

@@ -6,6 +6,8 @@ package cc.winboll.studio.timestamp.utils;
* @Describe TimeStampUtil
*/
import android.content.Context;
import android.net.Uri;
import cc.winboll.studio.timestamp.R;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -55,4 +57,19 @@ public class TimeStampUtil {
String formattedDateTime = ldt.format(formatter);
return formattedDateTime;
}
//
// 播放时间戳确定时的提示音乐
//
public static void playNotifyMusic(Context context) {
// 播放默认短信铃声
//Uri defaultSmsRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
//AudioPlayerUriUtil.playAudio(context, defaultSmsRingtoneUri);
// 播放应用铃声
// 获取MP3文件的Uri
Uri soundUri = Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.diweiyi);
AudioPlayerUriUtil.playAudio(context, soundUri);
}
}

View File

@@ -1,144 +1,180 @@
<?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">
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<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>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用时间戳常驻通知栏"
android:id="@+id/activitymainSwitch1"
android:onClick="onSetMainServiceStatus"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show Format Preview:"
android:paddingRight="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_timestampformatstring"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestampformatstring"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ApplyShow"
android:id="@+id/btn_saveformatstring"
android:textAllCaps="false"
android:onClick="onSaveFormatString"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Copy Format Preview:"
android:paddingRight="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_timestampcopyformatstring"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestampcopyformatstring"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ApplyCopy"
android:id="@+id/btn_savecopyformatstring"
android:textAllCaps="false"
android:onClick="onSaveCopyFormatString"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:gravity="center_vertical|center_horizontal">
</LinearLayout>
<LinearLayout
android:orientation="vertical"
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<cc.winboll.studio.libappbase.LogView
<LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/logview"/>
android:orientation="vertical">
</LinearLayout>
<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>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启用时间戳常驻通知栏"
android:id="@+id/activitymainSwitch1"
android:onClick="onSetMainServiceStatus"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show Format Preview:"
android:paddingRight="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_timestampformatstring"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestampformatstring"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ApplyShow"
android:id="@+id/btn_saveformatstring"
android:textAllCaps="false"
android:onClick="onSaveFormatString"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Copy Format Preview:"
android:paddingRight="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_timestampcopyformatstring"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<EditText
android:layout_width="0dp"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestampcopyformatstring"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ApplyCopy"
android:id="@+id/btn_savecopyformatstring"
android:textAllCaps="false"
android:onClick="onSaveCopyFormatString"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前截取的时间戳为:"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal">
<ImageButton
android:layout_width="100dp"
android:layout_height="100dp"
android:onClick="onTimeStamp"
android:background="@drawable/ic_timestamp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:ems="10"
android:layout_height="wrap_content"
android:id="@+id/et_timestamp"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
<cc.winboll.studio.libappbase.LogView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/logview"/>
</LinearLayout>