Compare commits
90 Commits
powerbell-
...
appbase
Author | SHA1 | Date | |
---|---|---|---|
c4e88e9593 | |||
08d9d92ae4 | |||
74841c08dc | |||
945bacb825 | |||
0e464495fd | |||
e8682ce410 | |||
2e4003dae0 | |||
198b0975ce | |||
24a578a9d2 | |||
46de24447f | |||
1320984829 | |||
abf1e5ba42 | |||
1cd2f88038 | |||
3f6e583d68 | |||
271456bfcd | |||
ee5458d82c | |||
3a83367f71 | |||
74b9350a6a | |||
d2858f23f7 | |||
40a5b9c339 | |||
fd79113572 | |||
9b911b583c | |||
37817c3e8c | |||
0b5402f5f3 | |||
bea22e3853 | |||
7e2ad0c01d | |||
476ce02fc8 | |||
bc697279ad | |||
dee01f1179 | |||
a500decc7a | |||
5099d00050 | |||
515d14e896 | |||
f630e27ed8 | |||
cd7ed01216 | |||
bb24bbfbd1 | |||
2ba2f88510 | |||
db3a3644a8 | |||
556bfa7024 | |||
4842a1ec30 | |||
b0388a2972 | |||
bd5a1f18ce | |||
99798b4816 | |||
f93b6047a8 | |||
daa3f858a0 | |||
3fded32426 | |||
8f85006040 | |||
e28b0bd75e | |||
af1d6d3439 | |||
470d1ffa1f | |||
49ae869df1 | |||
77e98bafe4 | |||
ff14d0c0c3 | |||
950be3a182 | |||
1f20fca9be | |||
8d29d11078 | |||
7534881f50 | |||
ef992dcd7c | |||
71c1baa4ba | |||
9e149037db | |||
89df24f736 | |||
2118495bc8 | |||
1dd614bd68 | |||
b793c74e81 | |||
8d1872a893 | |||
5c58ee34e7 | |||
e530403af7 | |||
264ab802c5 | |||
7c1832dc05 | |||
9a0ee889ba | |||
c40066ca4d | |||
5348d1ef6d | |||
063c997bbb | |||
1376ca7ebb | |||
92e271b569 | |||
a5083cc52f | |||
0692d4efb2 | |||
f10438d3d3 | |||
1e697bc12d | |||
d5a3c626b3 | |||
56692db142 | |||
934d54963a | |||
c105123e7b | |||
d8c534bbc8 | |||
6cce9c4d3f | |||
df18c34976 | |||
![]() |
b61c63c426 | ||
![]() |
f02dc215ca | ||
![]() |
1c27d0ccdc | ||
![]() |
803745d12e | ||
![]() |
a66be9cd37 |
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
|||||||
[submodule "libjc/jcc/libs"]
|
[submodule "libjc/jcc/libs"]
|
||||||
path = libjc/jcc/libs
|
path = libjc/jcc/libs
|
||||||
url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git
|
url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git
|
||||||
|
[submodule "keystore"]
|
||||||
|
path = keystore
|
||||||
|
url = https://gitea.winboll.cc/Studio/keystore.git
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/usr/bash
|
|
||||||
## Change Back To Beta KeyStore in keystore module.
|
|
||||||
cd keystore;git reset --hard f5bc75ff45fcb8894b5bd3f49b91bdd8fe3c317e;cd ..
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/usr/bash
|
|
||||||
## Change Back To StageMG KeyStore in keystore module.
|
|
||||||
cd keystore;git reset --hard d22519b11253f85f495400b01b6373e9657defb4;cd ..
|
|
@ -113,10 +113,10 @@ if [[ $? -eq 0 ]]; then
|
|||||||
# 如果Git已经提交了所有代码就执行标签和应用发布操作
|
# 如果Git已经提交了所有代码就执行标签和应用发布操作
|
||||||
|
|
||||||
# 预先询问是否添加工作流标签
|
# 预先询问是否添加工作流标签
|
||||||
echo "Add Github Workflows Tag? (yes/No)"
|
#echo "Add Github Workflows Tag? (yes/No)"
|
||||||
result=$(askAddWorkflowsTag)
|
#result=$(askAddWorkflowsTag)
|
||||||
nAskAddWorkflowsTag=$?
|
#nAskAddWorkflowsTag=$?
|
||||||
echo $result
|
#echo $result
|
||||||
|
|
||||||
# 发布应用
|
# 发布应用
|
||||||
echo "Publishing WinBoLL APK ..."
|
echo "Publishing WinBoLL APK ..."
|
||||||
@ -138,17 +138,17 @@ if [[ $? -eq 0 ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 添加 GitHub 工作流标签
|
# 添加 GitHub 工作流标签
|
||||||
if [[ $nAskAddWorkflowsTag -eq 1 ]]; then
|
#if [[ $nAskAddWorkflowsTag -eq 1 ]]; then
|
||||||
# 如果用户选择添加工作流标签
|
# 如果用户选择添加工作流标签
|
||||||
result=$(addWorkflowsTag $1)
|
#result=$(addWorkflowsTag $1)
|
||||||
if [[ $? -eq 0 ]]; then
|
#if [[ $? -eq 0 ]]; then
|
||||||
echo $result
|
# echo $result
|
||||||
# 工作流标签添加成功
|
# 工作流标签添加成功
|
||||||
else
|
#else
|
||||||
echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel."
|
#echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel."
|
||||||
exit 1 # addWorkflowsTag 异常
|
#exit 1 # addWorkflowsTag 异常
|
||||||
fi
|
#fi
|
||||||
fi
|
#fi
|
||||||
|
|
||||||
## 清理更新描述文件内容
|
## 清理更新描述文件内容
|
||||||
echo "" > $1/app_update_description.txt
|
echo "" > $1/app_update_description.txt
|
||||||
|
49
GenKeyStore/gen_debug_keystore.sh
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 应用秘钥创建脚本
|
||||||
|
# Linux 命令行创建JKS秘钥,alias和keyAlias可配置,文件名含时间戳
|
||||||
|
|
||||||
|
# 可配置参数(按需修改)
|
||||||
|
ALIAS="WinBoLL.CC_Debug" # 别名(与keyAlias一致)
|
||||||
|
STORE_PASS="androiddebugkey"
|
||||||
|
KEY_PASS="androiddebugkey"
|
||||||
|
COUNTRY="CN" # 国家代码
|
||||||
|
|
||||||
|
# 获取当前时间戳
|
||||||
|
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||||
|
FILENAME="${ALIAS}_${TIMESTAMP}.jks"
|
||||||
|
STORENAME="${ALIAS}_${TIMESTAMP}.keystore"
|
||||||
|
|
||||||
|
# 生成JKS文件(alias与keyAlias同步)
|
||||||
|
keytool -genkeypair \
|
||||||
|
-alias "${ALIAS}" \
|
||||||
|
-keyalg RSA \
|
||||||
|
-keysize 2048 \
|
||||||
|
-validity 1 \
|
||||||
|
-keystore "${FILENAME}" \
|
||||||
|
-dname "CN=WBFans, OU=Studio, O=WinBoLL, L=Shanwei, ST=Guangdong, C=${COUNTRY}" \
|
||||||
|
-storepass "${STORE_PASS}" \
|
||||||
|
-keypass "${KEY_PASS}"
|
||||||
|
|
||||||
|
# 写入配置文件
|
||||||
|
cat <<EOF > ${STORENAME}
|
||||||
|
keyAlias=${ALIAS}
|
||||||
|
keyPassword=${KEY_PASS}
|
||||||
|
storeFile=../appkey.jks
|
||||||
|
storePassword=${STORE_PASS}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "已生成秘钥:${FILENAME}"
|
||||||
|
echo "配置已写入 ${STORENAME}(keyAlias=${ALIAS})"
|
||||||
|
|
||||||
|
# 询问是否复制文件
|
||||||
|
read -p "是否需要将文件复制为 appkey.jks 和 appkey.keystore?(y/n): " CONFIRM
|
||||||
|
|
||||||
|
if [[ $CONFIRM =~ ^[Yy]$ ]]; then
|
||||||
|
# 复制 jks 文件为 appkey.jks
|
||||||
|
cp -v ${FILENAME} ../appkey.jks
|
||||||
|
# 复制 keystore 文件为 appkey.keystore
|
||||||
|
cp -v ${STORENAME} ../appkey.keystore
|
||||||
|
echo "文件复制完成"
|
||||||
|
else
|
||||||
|
echo "已取消文件复制"
|
||||||
|
fi
|
@ -114,9 +114,11 @@
|
|||||||
|
|
||||||
# 本项目要实际运用需要注意以下几个步骤:
|
# 本项目要实际运用需要注意以下几个步骤:
|
||||||
# 在项目根目录下:
|
# 在项目根目录下:
|
||||||
## 1. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。
|
## ★. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。
|
||||||
## 2. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。
|
## ★. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。
|
||||||
## 3. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。
|
## ★. 应用签名密钥 keystore 设置问题。一般调试编译只需用【Termux】cd 进 GenKeyStore 目录执行 $ bash gen_debug_keystore.sh 命令即可完成设置。
|
||||||
|
## ☆. 应用 WiBoLL 签名密钥配置问题<非必须考虑>。设置时需要 clone 【keystore】模块源码并拷贝模块目录的 appkey.jks 与 appkey.keystore 到项目根目录即可。
|
||||||
|
## ☆. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。
|
||||||
|
|
||||||
|
|
||||||
# ☆类库型项目编译方法
|
# ☆类库型项目编译方法
|
||||||
|
35
aes/README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# AES
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
安卓视图元素类库
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||||
|
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||||
|
|
||||||
|
|
||||||
|
#### Gradle 编译说明
|
||||||
|
调试版编译命令 :gradle assembleBetaDebug
|
||||||
|
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh aes
|
||||||
|
阶段版类库发布命令 :git pull &&bash .winboll/bashPublishLIBAddTag.sh libaes
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
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/)
|
||||||
|
|
||||||
|
#### 参考文档
|
@ -29,7 +29,7 @@ android {
|
|||||||
// versionName 更新后需要手动设置
|
// versionName 更新后需要手动设置
|
||||||
// 项目模块目录的 build.gradle 文件的 stageCount=0
|
// 项目模块目录的 build.gradle 文件的 stageCount=0
|
||||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
versionName "15.8"
|
versionName "15.9"
|
||||||
if(true) {
|
if(true) {
|
||||||
versionName = genVersionName("${versionName}")
|
versionName = genVersionName("${versionName}")
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Tue May 13 11:22:39 HKT 2025
|
#Thu Jun 19 20:42:40 HKT 2025
|
||||||
stageCount=1
|
stageCount=2
|
||||||
libraryProject=libaes
|
libraryProject=libaes
|
||||||
baseVersion=15.8
|
baseVersion=15.9
|
||||||
publishVersion=15.8.0
|
publishVersion=15.9.1
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.8.1
|
baseBetaVersion=15.9.2
|
||||||
|
@ -79,11 +79,11 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity
|
|||||||
appInfo.setAppName("AES");
|
appInfo.setAppName("AES");
|
||||||
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
|
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
|
||||||
appInfo.setAppDescription("AES Description");
|
appInfo.setAppDescription("AES Description");
|
||||||
appInfo.setAppGitName("APP");
|
appInfo.setAppGitName("APPBase");
|
||||||
appInfo.setAppGitOwner("Studio");
|
appInfo.setAppGitOwner("Studio");
|
||||||
appInfo.setAppGitAPPBranch(szBranchName);
|
appInfo.setAppGitAPPBranch(szBranchName);
|
||||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||||
appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=AES");
|
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=2&fromuid=1");
|
||||||
appInfo.setAppAPKName("AES");
|
appInfo.setAppAPKName("AES");
|
||||||
appInfo.setAppAPKFolderName("AES");
|
appInfo.setAppAPKFolderName("AES");
|
||||||
//appInfo.setIsAddDebugTools(false);
|
//appInfo.setIsAddDebugTools(false);
|
||||||
|
@ -67,6 +67,6 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
|
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
|
||||||
api 'com.android.support:recyclerview-v7:28.0.0'
|
api 'com.android.support:recyclerview-v7:28.0.0'
|
||||||
|
|
||||||
api 'cc.winboll.studio:libapputils:15.8.1'
|
api 'cc.winboll.studio:libapputils:15.8.2'
|
||||||
api 'cc.winboll.studio:libappbase:15.8.1'
|
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Mon May 19 21:45:28 GMT 2025
|
#Sun Jun 01 08:02:46 GMT 2025
|
||||||
stageCount=0
|
stageCount=0
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.0
|
baseVersion=15.0
|
||||||
publishVersion=15.0.0
|
publishVersion=15.0.0
|
||||||
buildCount=25
|
buildCount=27
|
||||||
baseBetaVersion=15.0.1
|
baseBetaVersion=15.0.1
|
||||||
|
@ -68,6 +68,6 @@ dependencies {
|
|||||||
//api 'androidx.fragment:fragment:1.1.0'
|
//api 'androidx.fragment:fragment:1.1.0'
|
||||||
|
|
||||||
api 'cc.winboll.studio:libaes:15.8.0'
|
api 'cc.winboll.studio:libaes:15.8.0'
|
||||||
api 'cc.winboll.studio:libapputils:15.8.1'
|
api 'cc.winboll.studio:libapputils:15.8.2'
|
||||||
api 'cc.winboll.studio:libappbase:15.8.1'
|
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Mon May 19 21:43:40 GMT 2025
|
#Thu Jun 19 12:49:47 GMT 2025
|
||||||
stageCount=0
|
stageCount=0
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.0
|
baseVersion=15.0
|
||||||
publishVersion=15.0.0
|
publishVersion=15.0.0
|
||||||
buildCount=22
|
buildCount=26
|
||||||
baseBetaVersion=15.0.1
|
baseBetaVersion=15.0.1
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Tue May 20 03:28:00 GMT 2025
|
#Mon Jun 09 09:38:19 HKT 2025
|
||||||
stageCount=2
|
stageCount=9
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.8
|
baseVersion=15.8
|
||||||
publishVersion=15.8.1
|
publishVersion=15.8.8
|
||||||
buildCount=1
|
buildCount=0
|
||||||
baseBetaVersion=15.8.2
|
baseBetaVersion=15.8.9
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/MyAPPBaseTheme"
|
android:theme="@style/MyAPPBaseTheme"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:process=":App">
|
android:process=":App"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@ -39,7 +40,8 @@
|
|||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||||
|
|
||||||
<activity android:name=".activities.New2Activity"
|
<activity
|
||||||
|
android:name=".activities.New2Activity"
|
||||||
android:label="New2Activity"
|
android:label="New2Activity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
@ -74,7 +76,8 @@
|
|||||||
|
|
||||||
<service android:name=".services.AssistantService"/>
|
<service android:name=".services.AssistantService"/>
|
||||||
|
|
||||||
<receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
|
<receiver
|
||||||
|
android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -105,7 +108,8 @@
|
|||||||
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".receivers.APPNewsWidgetClickListener"
|
<receiver
|
||||||
|
android:name=".receivers.APPNewsWidgetClickListener"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -122,7 +126,6 @@
|
|||||||
android:name="android.max_aspect"
|
android:name="android.max_aspect"
|
||||||
android:value="4.0"/>
|
android:value="4.0"/>
|
||||||
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -40,7 +40,6 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Toolbar mToolbar;
|
Toolbar mToolbar;
|
||||||
LogView mLogView;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -53,19 +52,21 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
|||||||
|
|
||||||
CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1);
|
CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1);
|
||||||
cbIsDebugMode.setChecked(GlobalApplication.isDebuging());
|
cbIsDebugMode.setChecked(GlobalApplication.isDebuging());
|
||||||
mLogView = findViewById(R.id.logview);
|
|
||||||
mLogView.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
getMenuInflater().inflate(R.menu.toolbar_appbase, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if(item.getItemId() == R.id.item_yun) {
|
||||||
|
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class);
|
||||||
|
} else if(item.getItemId() == R.id.item_logon) {
|
||||||
|
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.LogonActivity.class);
|
||||||
|
}
|
||||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
@ -176,8 +177,7 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
mLogView.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -66,16 +66,11 @@ public class New2Activity extends WinBoLLActivity implements IWinBoLLActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
getMenuInflater().inflate(R.menu.toolbar_appbase, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
|
|
||||||
//GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -64,16 +64,11 @@ public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
getMenuInflater().inflate(R.menu.toolbar_appbase, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() == cc.winboll.studio.appbase.R.id.item_log) {
|
|
||||||
//GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,12 @@ package cc.winboll.studio.appbase.activities;
|
|||||||
* @Describe WinBoLL 窗口基础类
|
* @Describe WinBoLL 窗口基础类
|
||||||
*/
|
*/
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import cc.winboll.studio.appbase.MainActivity;
|
||||||
|
import cc.winboll.studio.appbase.R;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||||
|
|
||||||
public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
||||||
@ -21,4 +27,35 @@ public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
|||||||
public String getTag() {
|
public String getTag() {
|
||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
LogUtils.d(TAG, String.format("onResume %s", getTag()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.item_log) {
|
||||||
|
GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.item_home) {
|
||||||
|
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
GlobalApplication.getWinBoLLActivityManager().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,19 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Hello, WinBoLL!"/>
|
android:text="安卓R对应的是Android 11,其API级别是30。以下是Android 11中一些重要的API相关特性:
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Android版本10的代号是“Q”,API级别是29。 Android 10开始谷歌不再公开使用甜品作为版本代号,但内部仍保留了大量与“Q”相关的元素。Android 10本身并没有严格对应某个特定的Java版本,但在开发Android 10应用时,通常可以使用Java 8或更高版本。 Java 8为Android开发带来了诸如Lambda表达式、方法引用等新特性,能提高开发效率和代码可读性,与Android 10开发适配良好。Java 9及更高版本也可用于Android 10开发,能使用一些新的语言特性和API,但可能需要注意兼容性和配置问题。"/>
|
|
||||||
|
|
||||||
|
\n- 隐私保护方面:引入单次授权,让用户可以选择授予应用对位置信息、麦克风和摄像头的临时访问权限。还增加了数据访问审核功能,能让开发者深入了解应用在何处访问私密数据。
|
||||||
|
|
||||||
|
\n- 系统功能方面:提供了ControlsProviderService API,用于向连接的外部设备提供控件,这些控件显示于Android电源菜单中的设备控件下。媒体控件也得到更新,显示于快捷设置旁,来自多个应用的会话排列在一个可滑动的轮播界面中。
|
||||||
|
|
||||||
|
\n- 硬件支持方面:提供了一些API以支持瀑布屏,通过将窗口布局属性 layoutInDisplayCutoutMode 设为 LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS ,可允许窗口延伸到屏幕各个边缘上的刘海和瀑布区域。对于合页式屏幕配置的设备,提供了具有 TYPE_HINGE_ANGLE 的新传感器以及新的 SensorEvent ,用于监控合页角度。
|
||||||
|
|
||||||
|
\n- 安全方面:对生物识别身份验证机制进行了更新,引入了 BiometricManager.Authenticators 接口,定义了 BIOMETRIC_STRONG 、 BIOMETRIC_WEAK 、 DEVICE_CREDENTIAL 等身份验证类型。还在 BiometricPrompt 类中提供了对“每次使用时进行身份验证”密钥的更多支持。
|
||||||
|
|
||||||
|
\n- 性能和质量方面:支持无线调试,通过Android调试桥(adb)从工作站以无线方式部署和调试应用,避免了常见的USB连接问题。"/>
|
||||||
|
|
||||||
|
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
@ -211,11 +217,5 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
<cc.winboll.studio.libappbase.LogView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="300dp"
|
|
||||||
android:id="@+id/logview"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_home"
|
||||||
|
android:title="HOME"
|
||||||
|
android:icon="@drawable/ic_winboll"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_yun"
|
||||||
|
android:title="YUN"
|
||||||
|
android:icon="@drawable/ic_winboll"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_logon"
|
||||||
|
android:title="Logon"
|
||||||
|
android:icon="@drawable/ic_winboll"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_log"
|
||||||
|
android:title="LOG"
|
||||||
|
android:icon="@drawable/ic_winboll_log"/>
|
||||||
</menu>
|
</menu>
|
||||||
|
12
appbase/src/main/res/xml/network_security_config.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<!-- 允许访问 winboll.cc 及其子域名(原配置) -->
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">winboll.cc</domain>
|
||||||
|
</domain-config>
|
||||||
|
|
||||||
|
<!-- **新增:允许访问 IP 地址 10.8.0.250** -->
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="false">10.8.0.250</domain> <!-- 不包含子域名 -->
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Tue May 13 11:18:09 HKT 2025
|
#Tue Jun 03 15:05:48 HKT 2025
|
||||||
stageCount=2
|
stageCount=5
|
||||||
libraryProject=libapputils
|
libraryProject=libapputils
|
||||||
baseVersion=15.8
|
baseVersion=15.8
|
||||||
publishVersion=15.8.1
|
publishVersion=15.8.4
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.8.2
|
baseBetaVersion=15.8.5
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sun May 04 05:32:00 GMT 2025
|
#Tue Jun 24 09:54:47 HKT 2025
|
||||||
stageCount=1
|
stageCount=3
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.2
|
baseVersion=15.2
|
||||||
publishVersion=15.2.0
|
publishVersion=15.2.2
|
||||||
buildCount=74
|
buildCount=0
|
||||||
baseBetaVersion=15.2.1
|
baseBetaVersion=15.2.3
|
||||||
|
@ -72,7 +72,7 @@ allprojects {
|
|||||||
bashCommitAppPublishBuildFlagInfoFilePath = ".winboll/bashCommitAppPublishBuildFlagInfo.sh"
|
bashCommitAppPublishBuildFlagInfoFilePath = ".winboll/bashCommitAppPublishBuildFlagInfo.sh"
|
||||||
|
|
||||||
winbollFilePath = "winboll.properties"
|
winbollFilePath = "winboll.properties"
|
||||||
keyPropsFilePath = "current.keystore"
|
keyPropsFilePath = "appkey.keystore"
|
||||||
// 定义 lint 输出文件
|
// 定义 lint 输出文件
|
||||||
lintXmlReportFilePath = "build/reports/lint-results.xml"
|
lintXmlReportFilePath = "build/reports/lint-results.xml"
|
||||||
lintHTMLReportFilePath = "build/reports/lint-results.html"
|
lintHTMLReportFilePath = "build/reports/lint-results.html"
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
keyAlias=WinBoLL.CC
|
|
||||||
keyPassword=androiddebugkey
|
|
||||||
storeFile=../WinBoLL.CC.jks
|
|
||||||
storePassword=androiddebugkey
|
|
@ -42,23 +42,24 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
/*compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
api project(':libjc')
|
||||||
|
api 'cc.winboll.studio:libaes:15.9.1'
|
||||||
|
api 'cc.winboll.studio:libapputils:15.8.4'
|
||||||
|
api 'cc.winboll.studio:libappbase:15.8.4'
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
|
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on
|
||||||
//implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
//implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15to18:1.69'
|
implementation 'org.bouncycastle:bcprov-jdk15to18:1.69'
|
||||||
implementation 'org.bouncycastle:bcpkix-jdk15to18:1.69'
|
implementation 'org.bouncycastle:bcpkix-jdk15to18:1.69'
|
||||||
|
|
||||||
api project(':libjc')
|
|
||||||
api 'androidx.appcompat:appcompat:1.0.0'
|
api 'androidx.appcompat:appcompat:1.0.0'
|
||||||
api 'com.google.android.material:material:1.0.0'
|
api 'com.google.android.material:material:1.0.0'
|
||||||
|
|
||||||
api 'cc.winboll.studio:libapputils:9.1.0'
|
|
||||||
api 'cc.winboll.studio:libappbase:1.0.3'
|
|
||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Fri Jan 10 22:03:57 GMT 2025
|
#Tue Jun 24 11:17:30 GMT 2025
|
||||||
stageCount=0
|
stageCount=0
|
||||||
libraryProject=libjc
|
libraryProject=libjc
|
||||||
baseVersion=1.0
|
baseVersion=1.0
|
||||||
publishVersion=1.0.0
|
publishVersion=1.0.0
|
||||||
buildCount=133
|
buildCount=135
|
||||||
baseBetaVersion=1.0.1
|
baseBetaVersion=1.0.1
|
||||||
|
@ -15,10 +15,10 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import cc.winboll.studio.jc.R;
|
import cc.winboll.studio.jc.R;
|
||||||
import cc.winboll.studio.libapputils.log.LogUtils;
|
|
||||||
import cc.winboll.studio.libjc.JAR_RUNNING_MODE;
|
|
||||||
import cc.winboll.studio.libjc.JCMainThread;
|
import cc.winboll.studio.libjc.JCMainThread;
|
||||||
import cc.winboll.studio.libjc.net.JCSocketClient;
|
import cc.winboll.studio.libjc.net.JCSocketClient;
|
||||||
|
import cc.winboll.studio.libjc.util.LogUtils;
|
||||||
|
import cc.winboll.studio.libjc.Main;
|
||||||
|
|
||||||
final public class MainActivity extends Activity implements JCMainThread.OnMessageListener {
|
final public class MainActivity extends Activity implements JCMainThread.OnMessageListener {
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ final public class MainActivity extends Activity implements JCMainThread.OnMessa
|
|||||||
// 启动主线程
|
// 启动主线程
|
||||||
_JCMainThread = JCMainThread.getInstance(getPackageName());
|
_JCMainThread = JCMainThread.getInstance(getPackageName());
|
||||||
_JCMainThread.setOnLogListener(this);
|
_JCMainThread.setOnLogListener(this);
|
||||||
_JCMainThread.setRunningMode(JAR_RUNNING_MODE.JC);
|
//_JCMainThread.setRunningMode(Main.JAR_RUNNING_MODE.JC);
|
||||||
_JCMainThread.start();
|
_JCMainThread.start();
|
||||||
|
|
||||||
// 设置 WinBoll 应用 UI 类型
|
// 设置 WinBoll 应用 UI 类型
|
||||||
|
1
keystore
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e7f70226c1471f77e89079b308bf3bf431587996
|
@ -21,8 +21,8 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
api 'cc.winboll.studio:libapputils:15.8.1'
|
api 'cc.winboll.studio:libapputils:15.8.2'
|
||||||
api 'cc.winboll.studio:libappbase:15.8.0'
|
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||||
|
|
||||||
// 吐司类库
|
// 吐司类库
|
||||||
api 'com.github.getActivity:ToastUtils:10.5'
|
api 'com.github.getActivity:ToastUtils:10.5'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Tue May 13 11:22:23 HKT 2025
|
#Thu Jun 19 20:42:26 HKT 2025
|
||||||
stageCount=1
|
stageCount=2
|
||||||
libraryProject=libaes
|
libraryProject=libaes
|
||||||
baseVersion=15.8
|
baseVersion=15.9
|
||||||
publishVersion=15.8.0
|
publishVersion=15.9.1
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.8.1
|
baseBetaVersion=15.9.2
|
||||||
|
@ -115,7 +115,8 @@ public class AboutView extends LinearLayout {
|
|||||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
}
|
}
|
||||||
mszCurrentAppPackageName = mszAppAPKName + "_" + mszAppVersionName + ".apk";
|
mszCurrentAppPackageName = mszAppAPKName + "_" + mszAppVersionName + ".apk";
|
||||||
mszHomePage = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
|
mszHomePage = mAPPInfo.getAppHomePage();
|
||||||
|
//mszHomePage = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
|
||||||
if (mAPPInfo.getAppGitAPPBranch().equals("")) {
|
if (mAPPInfo.getAppGitAPPBranch().equals("")) {
|
||||||
mszGitea = "https://gitea.winboll.cc/" + mAPPInfo.getAppGitOwner() + "/" + mszAppGitName;
|
mszGitea = "https://gitea.winboll.cc/" + mAPPInfo.getAppGitOwner() + "/" + mszAppGitName;
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,4 +22,9 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
// 网络连接类库
|
||||||
|
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||||
|
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
|
||||||
|
|
||||||
|
api 'com.google.code.gson:gson:2.10.1'
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Tue May 20 03:28:00 GMT 2025
|
#Mon Jun 09 09:38:19 HKT 2025
|
||||||
stageCount=2
|
stageCount=9
|
||||||
libraryProject=libappbase
|
libraryProject=libappbase
|
||||||
baseVersion=15.8
|
baseVersion=15.8
|
||||||
publishVersion=15.8.1
|
publishVersion=15.8.8
|
||||||
buildCount=1
|
buildCount=0
|
||||||
baseBetaVersion=15.8.2
|
baseBetaVersion=15.8.9
|
||||||
|
@ -103,6 +103,10 @@
|
|||||||
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -47,7 +47,7 @@ public class GlobalApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static WinBoLLActivityManager getWinBoLLActivityManager() {
|
public static WinBoLLActivityManager getWinBoLLActivityManager() {
|
||||||
return WinBoLLActivityManager.getInstance(_GlobalApplication);
|
return WinBoLLActivityManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,150 @@
|
|||||||
|
package cc.winboll.studio.libappbase.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import cc.winboll.studio.libappbase.BuildConfig;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.LogView;
|
||||||
|
import cc.winboll.studio.libappbase.R;
|
||||||
|
import cc.winboll.studio.libappbase.models.UserInfoModel;
|
||||||
|
import cc.winboll.studio.libappbase.utils.RSAUtils;
|
||||||
|
import cc.winboll.studio.libappbase.utils.YunUtils;
|
||||||
|
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/04 13:29
|
||||||
|
* @Describe 用户登录框
|
||||||
|
*/
|
||||||
|
public class LogonActivity extends Activity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "LogonActivity";
|
||||||
|
|
||||||
|
public static final String DEBUG_HOST = "http://10.8.0.250:456";
|
||||||
|
public static final String YUN_HOST = "https://yun.winboll.cc";
|
||||||
|
|
||||||
|
|
||||||
|
String mHost = "";
|
||||||
|
RadioButton mrbYunHost;
|
||||||
|
RadioButton mrbDebugHost;
|
||||||
|
LogView mLogView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_logon);
|
||||||
|
mLogView = findViewById(R.id.logview);
|
||||||
|
mLogView.start();
|
||||||
|
|
||||||
|
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
mrbYunHost = findViewById(R.id.rb_yunhost);
|
||||||
|
mrbDebugHost = findViewById(R.id.rb_debughost);
|
||||||
|
mrbYunHost.setChecked(!BuildConfig.DEBUG);
|
||||||
|
mrbDebugHost.setChecked(BuildConfig.DEBUG);
|
||||||
|
} else {
|
||||||
|
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSwitchHost(View view) {
|
||||||
|
if (view.getId() == R.id.rb_yunhost) {
|
||||||
|
mrbDebugHost.setChecked(false);
|
||||||
|
mHost = YUN_HOST;
|
||||||
|
} else if (view.getId() == R.id.rb_debughost) {
|
||||||
|
mrbYunHost.setChecked(false);
|
||||||
|
mHost = DEBUG_HOST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
mLogView.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestLogin(View view) {
|
||||||
|
LogUtils.d(TAG, "onTestLogin");
|
||||||
|
final YunUtils yunUtils = YunUtils.getInstance(this);
|
||||||
|
|
||||||
|
UserInfoModel userInfoModel = new UserInfoModel();
|
||||||
|
userInfoModel.setUsername("jian");
|
||||||
|
userInfoModel.setPassword("kkiio");
|
||||||
|
userInfoModel.setToken("aaa111");
|
||||||
|
yunUtils.login(mHost, userInfoModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestRSA(View view) {
|
||||||
|
LogUtils.d(TAG, "onTestRSA");
|
||||||
|
RSAUtils utils = RSAUtils.getInstance(this);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 测试 1:首次生成密钥对
|
||||||
|
LogUtils.d(TAG, "==== 首次生成密钥对 ====");
|
||||||
|
if (utils.keysExist()) {
|
||||||
|
LogUtils.d(TAG, "密钥对已生成");
|
||||||
|
} else {
|
||||||
|
utils.generateAndSaveKeys();
|
||||||
|
LogUtils.d(TAG, "密钥对生成成功。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试 2:获取密钥对(自动读取已生成的文件)
|
||||||
|
KeyPair keyPair = utils.getOrGenerateKeys();
|
||||||
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
|
||||||
|
// 打印密钥信息
|
||||||
|
LogUtils.d(TAG, "\n==== 密钥信息 ====");
|
||||||
|
LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm());
|
||||||
|
LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节");
|
||||||
|
LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm());
|
||||||
|
LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节");
|
||||||
|
|
||||||
|
// 测试 3:重复调用时检查是否复用文件
|
||||||
|
LogUtils.d(TAG, "\n==== 二次调用 ====");
|
||||||
|
KeyPair reusedPair = utils.getOrGenerateKeys();
|
||||||
|
LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用)
|
||||||
|
LogUtils.d(TAG, "操作完成");
|
||||||
|
|
||||||
|
String testMessage = "Hello, RSA Encryption!";
|
||||||
|
|
||||||
|
// 1. 获取或生成密钥对
|
||||||
|
PublicKey publicKeyReused = reusedPair.getPublic();
|
||||||
|
PrivateKey privateKeyReused = reusedPair.getPrivate();
|
||||||
|
|
||||||
|
// 2. 公钥加密
|
||||||
|
byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused);
|
||||||
|
LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length);
|
||||||
|
|
||||||
|
// 3. 私钥解密
|
||||||
|
String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused);
|
||||||
|
LogUtils.d(TAG, "解密结果: " + decryptedMessage);
|
||||||
|
|
||||||
|
// 4. 验证解密是否成功
|
||||||
|
if (testMessage.equals(decryptedMessage)) {
|
||||||
|
LogUtils.d(TAG, "加密解密测试通过!");
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "测试失败:内容不一致");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package cc.winboll.studio.libappbase.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import cc.winboll.studio.libappbase.BuildConfig;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.R;
|
||||||
|
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||||
|
import java.io.IOException;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import cc.winboll.studio.libappbase.LogView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/04 11:06
|
||||||
|
* @Describe 云宝云
|
||||||
|
*/
|
||||||
|
public class YunActivity extends Activity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "YunActivity";
|
||||||
|
|
||||||
|
public static final String DEBUG_HOST = "http://10.8.0.250:456";
|
||||||
|
public static final String YUN_HOST = "https://yun.winboll.cc";
|
||||||
|
|
||||||
|
String mHost = "";
|
||||||
|
RadioButton mrbYunHost;
|
||||||
|
RadioButton mrbDebugHost;
|
||||||
|
LogView mLogView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_yun);
|
||||||
|
mLogView = findViewById(R.id.logview);
|
||||||
|
mLogView.start();
|
||||||
|
|
||||||
|
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
mrbYunHost = findViewById(R.id.rb_yunhost);
|
||||||
|
mrbDebugHost = findViewById(R.id.rb_debughost);
|
||||||
|
mrbYunHost.setChecked(!BuildConfig.DEBUG);
|
||||||
|
mrbDebugHost.setChecked(BuildConfig.DEBUG);
|
||||||
|
} else {
|
||||||
|
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSwitchHost(View view) {
|
||||||
|
if (view.getId() == R.id.rb_yunhost) {
|
||||||
|
mrbDebugHost.setChecked(false);
|
||||||
|
mHost = YUN_HOST;
|
||||||
|
} else if (view.getId() == R.id.rb_debughost) {
|
||||||
|
mrbYunHost.setChecked(false);
|
||||||
|
mHost = DEBUG_HOST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
mLogView.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestYun(View view) {
|
||||||
|
LogUtils.d(TAG, "onTestYun");
|
||||||
|
(new Thread(new Runnable(){
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
testYun();
|
||||||
|
}
|
||||||
|
})).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void testYun() {
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(mHost + "/backups/")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Response response = null;
|
||||||
|
try {
|
||||||
|
response = client.newCall(request).execute();
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
String responseBody = "";
|
||||||
|
if (response.body() != null) {
|
||||||
|
responseBody = response.body().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正则匹配:任意主机名 -> Test OK(主机名部分匹配非空字符)
|
||||||
|
boolean isMatch = responseBody.matches(".+? -> Test OK");
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
LogUtils.d(TAG, responseBody);
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "响应内容不匹配,内容:" + responseBody);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "请求失败,状态码:" + response.code());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.d(TAG, "读取响应体失败:" + e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "异常:" + e.getMessage());
|
||||||
|
e.printStackTrace(); // Java 7 需显式打印堆栈
|
||||||
|
} finally {
|
||||||
|
// 手动关闭 Response(Java 7 不支持 try-with-resources)
|
||||||
|
if (response != null && response.body() != null) {
|
||||||
|
response.body().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package cc.winboll.studio.libappbase.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/05 11:26
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ResponseData {
|
||||||
|
|
||||||
|
public static final String STATUS_SUCCESS = "success";
|
||||||
|
public static final String STATUS_ERROR = "error";
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
private String message;
|
||||||
|
private UserInfoModel data;
|
||||||
|
|
||||||
|
public ResponseData() {
|
||||||
|
this.status = "";
|
||||||
|
this.message = "";
|
||||||
|
this.data = new UserInfoModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseData(String status, String message, UserInfoModel data) {
|
||||||
|
this.status = status;
|
||||||
|
this.message = message;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(UserInfoModel data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserInfoModel getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
|||||||
|
package cc.winboll.studio.libappbase.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/04 19:14
|
||||||
|
*/
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class UserInfoModel extends BaseBean {
|
||||||
|
|
||||||
|
public static final String TAG = "UserInfoModel";
|
||||||
|
|
||||||
|
String username;
|
||||||
|
String password;
|
||||||
|
String token;
|
||||||
|
|
||||||
|
public UserInfoModel() {
|
||||||
|
this.username = "";
|
||||||
|
this.password = "";
|
||||||
|
this.token = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return UserInfoModel.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name("username").value(getUsername());
|
||||||
|
jsonWriter.name("password").value(getPassword());
|
||||||
|
jsonWriter.name("token").value(getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||||
|
if (name.equals("username")) {
|
||||||
|
setUsername(jsonReader.nextString());
|
||||||
|
} else if (name.equals("password")) {
|
||||||
|
setPassword(jsonReader.nextString());
|
||||||
|
} else if (name.equals("token")) {
|
||||||
|
setToken(jsonReader.nextString());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||||
|
jsonReader.beginObject();
|
||||||
|
while (jsonReader.hasNext()) {
|
||||||
|
String name = jsonReader.nextName();
|
||||||
|
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||||
|
jsonReader.skipValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 结束 JSON 对象
|
||||||
|
jsonReader.endObject();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package cc.winboll.studio.libappbase.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/04 20:15
|
||||||
|
* @Describe 文件操作类
|
||||||
|
*/
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FileUtils {
|
||||||
|
public static final String TAG = "FileUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取文件为字节数组(Java 7 语法)
|
||||||
|
*/
|
||||||
|
public static byte[] readFileToByteArray(String filePath) {
|
||||||
|
FileInputStream fis = null;
|
||||||
|
ByteArrayOutputStream bos = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(filePath);
|
||||||
|
bos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||||
|
bos.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return bos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
// 手动关闭流(Java 7 不支持 try-with-resources)
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bos != null) {
|
||||||
|
try {
|
||||||
|
bos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入字节数组到文件(Java 7 语法)
|
||||||
|
*/
|
||||||
|
public static boolean writeByteArrayToFile(byte[] data, String filePath) {
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(filePath);
|
||||||
|
fos.write(data);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (fos != null) {
|
||||||
|
try {
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原字符串读写方法(适配 Java 7)
|
||||||
|
public static String readFileToString(String filePath) {
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new FileReader(filePath));
|
||||||
|
StringBuilder content = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
content.append(line).append(System.getProperty("line.separator"));
|
||||||
|
}
|
||||||
|
// 去除最后一个换行符(可选)
|
||||||
|
if (content.length() > 0) {
|
||||||
|
content.deleteCharAt(content.length() - 1);
|
||||||
|
}
|
||||||
|
return content.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean writeStringToFile(String content, String filePath, boolean append) {
|
||||||
|
BufferedWriter writer = null;
|
||||||
|
try {
|
||||||
|
writer = new BufferedWriter(new FileWriter(filePath, append));
|
||||||
|
writer.write(content);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (writer != null) {
|
||||||
|
try {
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,222 @@
|
|||||||
|
package cc.winboll.studio.libappbase.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/04 13:36
|
||||||
|
* @Describe RSA加密工具
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Base64;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
public class RSAUtils {
|
||||||
|
private static final String TAG = "RSAUtils";
|
||||||
|
private static final int KEY_SIZE = 2048;
|
||||||
|
private static final String KEY_ALGORITHM = "RSA";
|
||||||
|
private static final String PUBLIC_KEY_FILE = "public.key";
|
||||||
|
private static final String PRIVATE_KEY_FILE = "private.key";
|
||||||
|
private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式
|
||||||
|
|
||||||
|
private final String keyPath;
|
||||||
|
private static volatile RSAUtils INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法:初始化密钥存储路径(内部存储)
|
||||||
|
*/
|
||||||
|
private RSAUtils(Context context) {
|
||||||
|
keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例
|
||||||
|
*/
|
||||||
|
public static synchronized RSAUtils getInstance(Context context) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new RSAUtils(context);
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查密钥文件是否存在
|
||||||
|
*/
|
||||||
|
public boolean keysExist() {
|
||||||
|
File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE);
|
||||||
|
File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE);
|
||||||
|
return publicKeyFile.exists() && privateKeyFile.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成密钥对并保存到文件
|
||||||
|
*/
|
||||||
|
public void generateAndSaveKeys() throws Exception {
|
||||||
|
LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)");
|
||||||
|
KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
|
||||||
|
generator.initialize(KEY_SIZE);
|
||||||
|
KeyPair keyPair = generator.generateKeyPair();
|
||||||
|
|
||||||
|
saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded());
|
||||||
|
saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
|
||||||
|
LogUtils.d(TAG, "密钥对生成并保存成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或生成密钥对(线程安全)
|
||||||
|
*/
|
||||||
|
public KeyPair getOrGenerateKeys() throws Exception {
|
||||||
|
if (!keysExist()) {
|
||||||
|
synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成
|
||||||
|
if (!keysExist()) {
|
||||||
|
generateAndSaveKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return readKeysFromFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从文件读取密钥对
|
||||||
|
*/
|
||||||
|
private KeyPair readKeysFromFile() throws Exception {
|
||||||
|
LogUtils.d(TAG, "读取密钥对文件");
|
||||||
|
try {
|
||||||
|
byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE);
|
||||||
|
byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE);
|
||||||
|
|
||||||
|
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes);
|
||||||
|
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||||
|
|
||||||
|
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||||
|
PublicKey publicKey = factory.generatePublic(publicSpec);
|
||||||
|
PrivateKey privateKey = factory.generatePrivate(privateSpec);
|
||||||
|
|
||||||
|
return new KeyPair(publicKey, privateKey);
|
||||||
|
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
|
LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage());
|
||||||
|
throw new Exception("密钥文件损坏或格式错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存密钥到文件(通用方法)
|
||||||
|
*/
|
||||||
|
private void saveKey(String fileName, byte[] keyBytes) throws IOException {
|
||||||
|
Objects.requireNonNull(keyBytes, "密钥字节数据不可为空");
|
||||||
|
File dir = new File(keyPath);
|
||||||
|
if (!dir.exists() && !dir.mkdirs()) {
|
||||||
|
throw new IOException("创建密钥目录失败:" + keyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(keyPath + fileName);
|
||||||
|
fos.write(keyBytes);
|
||||||
|
} finally {
|
||||||
|
if (fos != null) {
|
||||||
|
try {
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取文件为字节数组(Java 7 兼容)
|
||||||
|
*/
|
||||||
|
private byte[] readFileToBytes(String filePath) throws IOException {
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (!file.exists() || file.isDirectory()) {
|
||||||
|
throw new IOException("文件不存在或为目录:" + filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
byte[] data = new byte[(int) file.length()];
|
||||||
|
int bytesRead = fis.read(data);
|
||||||
|
if (bytesRead != data.length) {
|
||||||
|
throw new IOException("文件读取不完整");
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公钥加密(带参数校验)
|
||||||
|
*/
|
||||||
|
public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception {
|
||||||
|
Objects.requireNonNull(plainText, "明文不可为空");
|
||||||
|
Objects.requireNonNull(publicKey, "公钥不可为空");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||||
|
|
||||||
|
// 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding)
|
||||||
|
int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度
|
||||||
|
if (plainText.getBytes("UTF-8").length > maxPlainTextSize) {
|
||||||
|
throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节");
|
||||||
|
}
|
||||||
|
|
||||||
|
return cipher.doFinal(plainText.getBytes("UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私钥解密(带参数校验)
|
||||||
|
*/
|
||||||
|
public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception {
|
||||||
|
Objects.requireNonNull(encryptedData, "密文不可为空");
|
||||||
|
Objects.requireNonNull(privateKey, "私钥不可为空");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||||
|
byte[] decryptedBytes = cipher.doFinal(encryptedData);
|
||||||
|
return new String(decryptedBytes, "UTF-8");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将 HTTP 传输的 Base64 字符串还原为加密字节数组(Java 7 兼容)
|
||||||
|
* @param httpString Base64 字符串(非 null)
|
||||||
|
* @return 加密字节数组
|
||||||
|
* @throws IllegalArgumentException 解码失败时抛出
|
||||||
|
*/
|
||||||
|
public byte[] httpStringToEncryptBytes(String httpString) {
|
||||||
|
Objects.requireNonNull(httpString, "HTTP 字符串不可为空");
|
||||||
|
|
||||||
|
// 计算缺失的填充符数量(Java 7 不支持 repeat(),手动拼接)
|
||||||
|
int pad = httpString.length() % 4;
|
||||||
|
StringBuilder paddedString = new StringBuilder(httpString);
|
||||||
|
if (pad != 0) {
|
||||||
|
for (int i = 0; i < pad; i++) {
|
||||||
|
paddedString.append('='); // 补全 '='
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Base64 解码(Android 原生 Base64 类兼容 Java 7)
|
||||||
|
return Base64.decode(paddedString.toString(), Base64.URL_SAFE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,281 @@
|
|||||||
|
package cc.winboll.studio.libappbase.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@188.com>
|
||||||
|
* @Date 2025/06/04 17:21
|
||||||
|
* @Describe 应用登录与接口工具
|
||||||
|
*/
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.models.ResponseData;
|
||||||
|
import cc.winboll.studio.libappbase.models.UserInfoModel;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
public class YunUtils {
|
||||||
|
public static final String TAG = "YunUtils";
|
||||||
|
// 私有静态实例,类加载时创建
|
||||||
|
private static volatile YunUtils INSTANCE;
|
||||||
|
Context mContext;
|
||||||
|
UserInfoModel mUserInfoModel;
|
||||||
|
String token = "";
|
||||||
|
String mDataFolderPath = "";
|
||||||
|
String mUserInfoModelPath = "";
|
||||||
|
|
||||||
|
private static final int CONNECT_TIMEOUT = 15; // 连接超时时间(秒)
|
||||||
|
private static final int READ_TIMEOUT = 20; // 读取超时时间(秒)
|
||||||
|
private static volatile YunUtils instance;
|
||||||
|
private OkHttpClient okHttpClient;
|
||||||
|
private Handler mainHandler; // 主线程 Handler
|
||||||
|
|
||||||
|
// 私有构造方法,防止外部实例化
|
||||||
|
private YunUtils(Context context) {
|
||||||
|
LogUtils.d(TAG, "YunUtils");
|
||||||
|
mContext = context;
|
||||||
|
mDataFolderPath = mContext.getExternalFilesDir(TAG).toString();
|
||||||
|
File fTest = new File(mDataFolderPath);
|
||||||
|
if (!fTest.exists()) {
|
||||||
|
fTest.mkdirs();
|
||||||
|
}
|
||||||
|
mUserInfoModelPath = mDataFolderPath + File.separator + "UserInfoModel.rsajson";
|
||||||
|
|
||||||
|
okHttpClient = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
mainHandler = new Handler(Looper.getMainLooper()); // 获取主线程 Looper
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共静态方法,返回唯一实例
|
||||||
|
public static synchronized YunUtils getInstance(Context context) {
|
||||||
|
LogUtils.d(TAG, "getInstance");
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new YunUtils(context);
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkLoginStatus() {
|
||||||
|
String token = getLocalToken();
|
||||||
|
LogUtils.d(TAG, String.format("checkLoginStatus token is %s", token));
|
||||||
|
}
|
||||||
|
|
||||||
|
String getLocalToken() {
|
||||||
|
UserInfoModel userInfoModel = loadUserInfoModel();
|
||||||
|
return (userInfoModel == null) ?"": userInfoModel.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void login(String host, UserInfoModel userInfoModel) {
|
||||||
|
LogUtils.d(TAG, "login");
|
||||||
|
|
||||||
|
// 发送 POST 请求
|
||||||
|
String apiUrl = host + "/login/index.php";
|
||||||
|
// 序列化对象为JSON
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String jsonData = gson.toJson(userInfoModel); // 自动生成标准JSON
|
||||||
|
//String jsonData = userInfoModel.toString();
|
||||||
|
LogUtils.d(TAG, "要发送的数据 : " + jsonData);
|
||||||
|
|
||||||
|
sendPostRequest(apiUrl, jsonData, new OnResponseListener() {
|
||||||
|
// 成功回调(主线程)
|
||||||
|
@Override
|
||||||
|
public void onSuccess(String responseBody) {
|
||||||
|
LogUtils.d(TAG, "onSuccess");
|
||||||
|
LogUtils.d(TAG, String.format("responseBody %s", responseBody));
|
||||||
|
Gson gson = new Gson();
|
||||||
|
ResponseData result = gson.fromJson(responseBody, ResponseData.class); // 转为 Result 实例
|
||||||
|
if(result.getStatus().equals(ResponseData.STATUS_SUCCESS)) {
|
||||||
|
|
||||||
|
UserInfoModel userInfoModel = result.getData();
|
||||||
|
if (userInfoModel != null) {
|
||||||
|
LogUtils.d(TAG, "收到网站 UserInfoModel");
|
||||||
|
String token = userInfoModel.getToken();
|
||||||
|
saveLocalToken(token);
|
||||||
|
checkLoginStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(result.getStatus().equals(ResponseData.STATUS_ERROR)) {
|
||||||
|
try {
|
||||||
|
String decodedMessage = URLDecoder.decode(result.getMessage(), "UTF-8");
|
||||||
|
LogUtils.d(TAG, "服务器返回信息: " + decodedMessage);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 失败回调(主线程)
|
||||||
|
@Override
|
||||||
|
public void onFailure(String errorMsg) {
|
||||||
|
LogUtils.d(TAG, errorMsg);
|
||||||
|
// 处理错误
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveLocalToken(String token) {
|
||||||
|
UserInfoModel userInfoModel = new UserInfoModel();
|
||||||
|
userInfoModel.setToken(token);
|
||||||
|
saveUserInfoModel(userInfoModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfoModel loadUserInfoModel() {
|
||||||
|
LogUtils.d(TAG, "loadUserInfoModel");
|
||||||
|
if (new File(mUserInfoModelPath).exists()) {
|
||||||
|
try {
|
||||||
|
// 加载加密后的模型数据
|
||||||
|
byte[] encryptedData = FileUtils.readFileToByteArray(mUserInfoModelPath);
|
||||||
|
// 加载 RSA 工具
|
||||||
|
RSAUtils utils = RSAUtils.getInstance(mContext);
|
||||||
|
KeyPair keyPair = utils.getOrGenerateKeys();
|
||||||
|
//PublicKey publicKey = keyPair.getPublic();
|
||||||
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
|
// 私钥解密模型数据
|
||||||
|
String szInfo = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate());
|
||||||
|
LogUtils.d(TAG, String.format("szInfo %s", szInfo));
|
||||||
|
mUserInfoModel = UserInfoModel.parseStringToBean(szInfo, UserInfoModel.class);
|
||||||
|
if (mUserInfoModel == null) {
|
||||||
|
LogUtils.d(TAG, "模型数据解析为空数据。");
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "UserInfoModel 解密加载结束。");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "云服务登录信息不存在。");
|
||||||
|
mUserInfoModel = null;
|
||||||
|
}
|
||||||
|
return mUserInfoModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveUserInfoModel(UserInfoModel userInfoModel) {
|
||||||
|
LogUtils.d(TAG, "saveUserInfoModel");
|
||||||
|
try {
|
||||||
|
String szInfo = userInfoModel.toString();
|
||||||
|
LogUtils.d(TAG, "原始数据: " + szInfo);
|
||||||
|
|
||||||
|
RSAUtils utils = RSAUtils.getInstance(mContext);
|
||||||
|
KeyPair keyPair = utils.getOrGenerateKeys();
|
||||||
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
|
|
||||||
|
// 公钥加密(传入字节数组,避免中间字符串转换)
|
||||||
|
byte[] encryptedData = utils.encryptWithPublicKey(szInfo, publicKey);
|
||||||
|
|
||||||
|
// 保存加密字节数组到文件(直接操作字节,无需转字符串)
|
||||||
|
FileUtils.writeByteArrayToFile(encryptedData, mUserInfoModelPath);
|
||||||
|
LogUtils.d(TAG, "加密数据已保存");
|
||||||
|
|
||||||
|
// 测试解密(仅调试用)
|
||||||
|
String szInfo2 = utils.decryptWithPrivateKey(encryptedData, keyPair.getPrivate());
|
||||||
|
LogUtils.d(TAG, "解密结果: " + szInfo2);
|
||||||
|
|
||||||
|
mUserInfoModel = UserInfoModel.parseStringToBean(szInfo2, UserInfoModel.class);
|
||||||
|
if (mUserInfoModel == null) {
|
||||||
|
LogUtils.d(TAG, "模型解析失败");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "加密/解密失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 POST 请求(JSON 数据)
|
||||||
|
public void sendPostRequest(String url, String data, OnResponseListener listener) {
|
||||||
|
RequestBody requestBody = RequestBody.create(
|
||||||
|
MediaType.parse("application/json; charset=utf-8"), // 关键头信息
|
||||||
|
data.getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.post(requestBody)
|
||||||
|
.addHeader("Content-Type", "application/json") // 显式添加头
|
||||||
|
.build();
|
||||||
|
|
||||||
|
executeRequest(request, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 GET 请求
|
||||||
|
public void sendGetRequest(String url, OnResponseListener listener) {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
executeRequest(request, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行请求(子线程处理)
|
||||||
|
private void executeRequest(final Request request, final OnResponseListener listener) {
|
||||||
|
okHttpClient.newCall(request).enqueue(new Callback() {
|
||||||
|
// 响应成功(子线程)
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) throws IOException {
|
||||||
|
try {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
postFailure(listener, "响应码错误:" + response.code());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String responseBody = response.body().string();
|
||||||
|
postSuccess(listener, responseBody);
|
||||||
|
} catch (Exception e) {
|
||||||
|
postFailure(listener, "解析失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应失败(子线程)
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
postFailure(listener, "网络失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主线程回调(使用 Handler)
|
||||||
|
private void postSuccess(final OnResponseListener listener, final String msg) {
|
||||||
|
mainHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onSuccess(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postFailure(final OnResponseListener listener, final String msg) {
|
||||||
|
mainHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listener.onFailure(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnResponseListener {
|
||||||
|
/**
|
||||||
|
* 成功响应(主线程回调)
|
||||||
|
* @param responseBody 响应体字符串
|
||||||
|
*/
|
||||||
|
void onSuccess(String responseBody);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败回调(包含错误信息)
|
||||||
|
* @param errorMsg 错误描述
|
||||||
|
*/
|
||||||
|
void onFailure(String errorMsg);
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,9 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class WinBoLLActivityManager {
|
public class WinBoLLActivityManager {
|
||||||
|
|
||||||
public static final String TAG = "IWinBoLLActivityManager";
|
public static final String TAG = "WinBoLLActivityManager";
|
||||||
|
|
||||||
|
public static final String EXTRA_TAG = "EXTRA_TAG";
|
||||||
|
|
||||||
|
|
||||||
public enum WinBoLLUI_TYPE { Aplication, Service }
|
public enum WinBoLLUI_TYPE { Aplication, Service }
|
||||||
@ -39,14 +41,14 @@ public class WinBoLLActivityManager {
|
|||||||
public static WinBoLLUI_TYPE getWinBoLLUI_TYPE() {
|
public static WinBoLLUI_TYPE getWinBoLLUI_TYPE() {
|
||||||
return _WinBoLLUI_TYPE;
|
return _WinBoLLUI_TYPE;
|
||||||
}
|
}
|
||||||
WinBoLLActivityManager(Context context) {
|
WinBoLLActivityManager() {
|
||||||
mContext = context;
|
mContext = GlobalApplication.getInstance();
|
||||||
mActivityListMap = new HashMap<String, IWinBoLLActivity>();
|
mActivityListMap = new HashMap<String, IWinBoLLActivity>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized WinBoLLActivityManager getInstance(Context context) {
|
public static synchronized WinBoLLActivityManager getInstance() {
|
||||||
if (_mIWinBoLLActivityManager == null) {
|
if (_mIWinBoLLActivityManager == null) {
|
||||||
_mIWinBoLLActivityManager = new WinBoLLActivityManager(context);
|
_mIWinBoLLActivityManager = new WinBoLLActivityManager();
|
||||||
}
|
}
|
||||||
return _mIWinBoLLActivityManager;
|
return _mIWinBoLLActivityManager;
|
||||||
}
|
}
|
||||||
@ -55,7 +57,7 @@ public class WinBoLLActivityManager {
|
|||||||
* 把Activity添加到管理中
|
* 把Activity添加到管理中
|
||||||
*/
|
*/
|
||||||
public <T extends IWinBoLLActivity> void add(T activity) {
|
public <T extends IWinBoLLActivity> void add(T activity) {
|
||||||
if (isActive(activity.getTag())) {
|
if (isActivityActive(activity.getTag())) {
|
||||||
LogUtils.d(TAG, String.format("add(...) %s is active.", activity.getTag()));
|
LogUtils.d(TAG, String.format("add(...) %s is active.", activity.getTag()));
|
||||||
} else {
|
} else {
|
||||||
mActivityListMap.put(activity.getTag(), activity);
|
mActivityListMap.put(activity.getTag(), activity);
|
||||||
@ -70,106 +72,107 @@ public class WinBoLLActivityManager {
|
|||||||
// intent.putExtra 函数 "tag" 参数为 activity.getTag()
|
// intent.putExtra 函数 "tag" 参数为 activity.getTag()
|
||||||
//
|
//
|
||||||
public <T extends IWinBoLLActivity> void startWinBoLLActivity(Context context, Class<T> clazz) {
|
public <T extends IWinBoLLActivity> void startWinBoLLActivity(Context context, Class<T> clazz) {
|
||||||
try {
|
// 如果窗口已存在就重启窗口
|
||||||
// 如果窗口已存在就重启窗口
|
if (!resumeActivity(clazz)) {
|
||||||
String tag = clazz.newInstance().getTag();
|
|
||||||
if (isActive(tag)) {
|
|
||||||
resumeActivity(context, tag);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新建一个任务窗口
|
// 新建一个任务窗口
|
||||||
Intent intent = new Intent(context, clazz);
|
Intent intent = new Intent(context, clazz);
|
||||||
//打开多任务窗口 flags
|
//打开多任务窗口 flags
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||||
intent.putExtra("tag", tag);
|
//intent.putExtra("tag", tag);
|
||||||
mContext.startActivity(intent);
|
context.startActivity(intent);
|
||||||
} catch (InstantiationException | IllegalAccessException e) {
|
|
||||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends IWinBoLLActivity> void startWinBoLLActivity(Context context, Intent intent, Class<T> clazz) {
|
public <T extends IWinBoLLActivity> void startWinBoLLActivity(Context context, Intent intent, Class<T> clazz) {
|
||||||
try {
|
// 如果窗口已存在就重启窗口
|
||||||
// 如果窗口已存在就重启窗口
|
if (!resumeActivity(clazz)) {
|
||||||
String tag = clazz.newInstance().getTag();
|
|
||||||
if (isActive(tag)) {
|
|
||||||
resumeActivity(context, tag);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新建一个任务窗口
|
// 新建一个任务窗口
|
||||||
//Intent intent = new Intent(context, clazz);
|
//Intent intent = new Intent(context, clazz);
|
||||||
//打开多任务窗口 flags
|
//打开多任务窗口 flags
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||||
intent.putExtra("tag", tag);
|
//intent.putExtra("tag", tag);
|
||||||
mContext.startActivity(intent);
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends IWinBoLLActivity> void startLogActivity(Context context) {
|
||||||
|
// 如果窗口已存在就重启窗口
|
||||||
|
//if (!resumeActivity(LogActivity.class)) {
|
||||||
|
// 新建一个任务窗口
|
||||||
|
Intent intent = new Intent(context, LogActivity.class);
|
||||||
|
//打开多任务窗口 flags
|
||||||
|
// Define the bounds.
|
||||||
|
// Rect bounds = new Rect(0, 0, 800, 200);
|
||||||
|
// // Set the bounds as an activity option.
|
||||||
|
// ActivityOptions options = ActivityOptions.makeBasic();
|
||||||
|
// options.setLaunchBounds(bounds);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||||
|
|
||||||
|
//intent.putExtra(EXTRA_TAG, tag);
|
||||||
|
|
||||||
|
//context.startActivity(intent, options.toBundle());
|
||||||
|
context.startActivity(intent);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 判断 tag 绑定的 Activity 是否已经创建
|
||||||
|
//
|
||||||
|
public boolean isActivityActive(String tag) {
|
||||||
|
return mActivityListMap.get(tag) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity getActivityByTag(String tag) {
|
||||||
|
return (mActivityListMap.get(tag) == null) ?null: mActivityListMap.get(tag).getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台
|
||||||
|
//
|
||||||
|
public <T extends IWinBoLLActivity> boolean resumeActivity(Class<T> clazz) {
|
||||||
|
try {
|
||||||
|
Activity activity = getActivityByTag(clazz.newInstance().getTag());
|
||||||
|
if (activity != null) {
|
||||||
|
return resumeActivity(activity);
|
||||||
|
}
|
||||||
} catch (InstantiationException | IllegalAccessException e) {
|
} catch (InstantiationException | IllegalAccessException e) {
|
||||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
//
|
||||||
* 判断 tag绑定的 MyActivity是否存在
|
// 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台
|
||||||
*/
|
//
|
||||||
public boolean isActive(String tag) {
|
public <T extends IWinBoLLActivity> boolean resumeActivity(String tag) {
|
||||||
//printAvtivityListInfo();
|
Activity activity = getActivityByTag(tag);
|
||||||
IWinBoLLActivity iWinBoLLActivity = getIWinBoLLActivity(tag);
|
if (activity != null) {
|
||||||
if (iWinBoLLActivity != null) {
|
return resumeActivity(activity);
|
||||||
Activity activity = iWinBoLLActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
LogUtils.d(TAG, "isActive(...) activity != null tag " + tag);
|
|
||||||
//ToastUtils.show("activity != null tag " + tag);
|
|
||||||
//判断是否为 BaseActivity,如果已经销毁,则移除
|
|
||||||
if (activity.isFinishing() || activity.isDestroyed()) {
|
|
||||||
mActivityListMap.remove(iWinBoLLActivity.getTag());
|
|
||||||
//_mIWinBoLLActivityList.remove(activity);
|
|
||||||
LogUtils.d(TAG, String.format("isActive(...) remove activity.\ntag : %s", tag));
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
LogUtils.d(TAG, String.format("isActive(...) activity is exist.\ntag : %s", tag));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IWinBoLLActivity getIWinBoLLActivity(String tag) {
|
//
|
||||||
return mActivityListMap.get(tag);
|
// 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台
|
||||||
}
|
//
|
||||||
|
public <T extends IWinBoLLActivity> boolean resumeActivity(Activity activity) {
|
||||||
/**
|
ActivityManager am = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
* 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台
|
|
||||||
*/
|
|
||||||
public <T extends IWinBoLLActivity> void resumeActivity(Context context, String tag) {
|
|
||||||
LogUtils.d(TAG, "resumeActivty");
|
|
||||||
T iWinBoLLActivity = (T)getIWinBoLLActivity(tag);
|
|
||||||
LogUtils.d(TAG, "activity " + iWinBoLLActivity.getTag());
|
|
||||||
if (iWinBoLLActivity != null && iWinBoLLActivity.getActivity() != null && !iWinBoLLActivity.getActivity().isFinishing() && !iWinBoLLActivity.getActivity().isDestroyed()) {
|
|
||||||
resumeActivity(context, iWinBoLLActivity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台
|
|
||||||
*/
|
|
||||||
public <T extends IWinBoLLActivity> void resumeActivity(Context context, T activity) {
|
|
||||||
ActivityManager am = (ActivityManager) activity.getActivity().getSystemService(Context.ACTIVITY_SERVICE);
|
|
||||||
//返回启动它的根任务(home 或者 MainActivity)
|
//返回启动它的根任务(home 或者 MainActivity)
|
||||||
Intent intent = new Intent(context, activity.getClass());
|
//Intent intent = new Intent(mContext, activity.getClass());
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
//TaskStackBuilder stackBuilder = TaskStackBuilder.create(mContext);
|
||||||
stackBuilder.addNextIntentWithParentStack(intent);
|
//stackBuilder.addNextIntentWithParentStack(intent);
|
||||||
stackBuilder.startActivities();
|
//stackBuilder.startActivities();
|
||||||
//moveTaskToFront(YourTaskId, 0);
|
am.moveTaskToFront(activity.getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION);
|
||||||
LogUtils.d(TAG, "am.moveTaskToFront");
|
//ToastUtils.show("resumeActivity");
|
||||||
//ToastUtils.show("resumeActivity am.moveTaskToFront");
|
return true;
|
||||||
am.moveTaskToFront(activity.getActivity().getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -221,10 +224,10 @@ public class WinBoLLActivityManager {
|
|||||||
// ★:0 1 2 [3] 4 >> 2
|
// ★:0 1 2 [3] 4 >> 2
|
||||||
// ★:0 1 2 3 [4] >> 3
|
// ★:0 1 2 3 [4] >> 3
|
||||||
// ★:[0] >> 直接关闭当前窗口
|
// ★:[0] >> 直接关闭当前窗口
|
||||||
IWinBoLLActivity preActivity = getPreActivity(iWinBoLLActivity);
|
Activity preActivity = getPreActivity(iWinBoLLActivity);
|
||||||
iWinBoLLActivity.getActivity().finish();
|
iWinBoLLActivity.getActivity().finish();
|
||||||
if (preActivity != null) {
|
if (preActivity != null) {
|
||||||
resumeActivity(mContext, preActivity);
|
resumeActivity(preActivity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +236,7 @@ public class WinBoLLActivityManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IWinBoLLActivity getPreActivity(IWinBoLLActivity iWinBoLLActivity) {
|
Activity getPreActivity(IWinBoLLActivity iWinBoLLActivity) {
|
||||||
try {
|
try {
|
||||||
boolean bingo = false;
|
boolean bingo = false;
|
||||||
IWinBoLLActivity preIWinBoLLActivity = null;
|
IWinBoLLActivity preIWinBoLLActivity = null;
|
||||||
@ -247,7 +250,7 @@ public class WinBoLLActivityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (bingo) {
|
if (bingo) {
|
||||||
return preIWinBoLLActivity;
|
return preIWinBoLLActivity.getActivity();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
68
libappbase/src/main/res/layout/activity_logon.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?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:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="right"
|
||||||
|
android:gravity="right"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:id="@+id/ll_hostbar">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="10.8.0.250:456"
|
||||||
|
android:id="@+id/rb_debughost"
|
||||||
|
android:onClick="onSwitchHost"/>
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="yun.winboll.cc"
|
||||||
|
android:id="@+id/rb_yunhost"
|
||||||
|
android:onClick="onSwitchHost"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="right">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Test RSA"
|
||||||
|
android:onClick="onTestRSA"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Test Login"
|
||||||
|
android:onClick="onTestLogin"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.LogView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/logview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
63
libappbase/src/main/res/layout/activity_yun.xml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?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:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="right"
|
||||||
|
android:gravity="right"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:id="@+id/ll_hostbar">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="10.8.0.250:456"
|
||||||
|
android:id="@+id/rb_debughost"
|
||||||
|
android:onClick="onSwitchHost"/>
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="yun.winboll.cc"
|
||||||
|
android:id="@+id/rb_yunhost"
|
||||||
|
android:onClick="onSwitchHost"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="right"
|
||||||
|
android:gravity="right">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="TestYun"
|
||||||
|
android:onClick="onTestYun"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.LogView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/logview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:id="@+id/item_minimal"
|
|
||||||
android:title="MINIMAL"
|
|
||||||
android:icon="@drawable/ic_winboll_point"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/item_about"
|
|
||||||
android:title="ABOUT"
|
|
||||||
android:icon="@drawable/ic_winboll_logo"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/item_help"
|
|
||||||
android:title="HELP"
|
|
||||||
android:icon="@drawable/ic_winboll_help"/>
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/item_log"
|
|
||||||
android:title="LOG"
|
|
||||||
android:icon="@drawable/ic_winboll_log"/>
|
|
||||||
</menu>
|
|
@ -21,7 +21,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
api 'cc.winboll.studio:libappbase:15.8.0'
|
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||||
|
|
||||||
// 二维码类库
|
// 二维码类库
|
||||||
api 'com.google.zxing:core:3.4.1'
|
api 'com.google.zxing:core:3.4.1'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Tue May 13 11:17:53 HKT 2025
|
#Tue Jun 03 15:05:42 HKT 2025
|
||||||
stageCount=2
|
stageCount=5
|
||||||
libraryProject=libapputils
|
libraryProject=libapputils
|
||||||
baseVersion=15.8
|
baseVersion=15.8
|
||||||
publishVersion=15.8.1
|
publishVersion=15.8.4
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.8.2
|
baseBetaVersion=15.8.5
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Fri Jan 10 22:03:57 GMT 2025
|
#Tue Jun 24 11:17:30 GMT 2025
|
||||||
stageCount=0
|
stageCount=0
|
||||||
libraryProject=libjc
|
libraryProject=libjc
|
||||||
baseVersion=1.0
|
baseVersion=1.0
|
||||||
publishVersion=1.0.0
|
publishVersion=1.0.0
|
||||||
buildCount=133
|
buildCount=135
|
||||||
baseBetaVersion=1.0.1
|
baseBetaVersion=1.0.1
|
||||||
|
@ -21,7 +21,7 @@ public class Main {
|
|||||||
public final static int JAR_RUNNING_MODE_JCNDK_DEBUG = 4;
|
public final static int JAR_RUNNING_MODE_JCNDK_DEBUG = 4;
|
||||||
public final static int JAR_RUNNING_MODE_JC = 5;
|
public final static int JAR_RUNNING_MODE_JC = 5;
|
||||||
public final static int JAR_RUNNING_MODE_JC_DEBUG = 6;
|
public final static int JAR_RUNNING_MODE_JC_DEBUG = 6;
|
||||||
public enum JAR_RUNNING_MODE {
|
public static enum JAR_RUNNING_MODE {
|
||||||
UNKNOWN(JAR_RUNNING_MODE_UNKNOWN),
|
UNKNOWN(JAR_RUNNING_MODE_UNKNOWN),
|
||||||
CONSOLE(JAR_RUNNING_MODE_CONSOLE),
|
CONSOLE(JAR_RUNNING_MODE_CONSOLE),
|
||||||
CONSOLE_DEBUG(JAR_RUNNING_MODE_CONSOLE_DEBUG),
|
CONSOLE_DEBUG(JAR_RUNNING_MODE_CONSOLE_DEBUG),
|
||||||
|
1
numtable/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
34
numtable/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# NumTable
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
桌面图标多元应用。提供一个数字风格化的桌面标识图标,快捷的桌面标识创建途径。主要应用于桌面繁多时的页面环境辅助识别。
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||||
|
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||||
|
|
||||||
|
|
||||||
|
#### Gradle 编译说明
|
||||||
|
调试版编译命令 :gradle assembleBetaDebug
|
||||||
|
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh numtable
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
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/)
|
||||||
|
|
||||||
|
#### 参考文档
|
1
numtable/app_update_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
73
numtable/build.gradle
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: '../.winboll/winboll_app_build.gradle'
|
||||||
|
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||||
|
|
||||||
|
def genVersionName(def versionName){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
// 保存基础版本号
|
||||||
|
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
// 返回编译版本号
|
||||||
|
return "${versionName}." + winbollBuildProps['stageCount']
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 32
|
||||||
|
buildToolsVersion "32.0.0"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "cc.winboll.studio.numtable"
|
||||||
|
minSdkVersion 24
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
// versionName 更新后需要手动设置
|
||||||
|
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||||
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
|
versionName "15.1"
|
||||||
|
if(true) {
|
||||||
|
versionName = genVersionName("${versionName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
// SSH
|
||||||
|
api 'com.jcraft:jsch:0.1.55'
|
||||||
|
// Html 解析
|
||||||
|
api 'org.jsoup:jsoup:1.13.1'
|
||||||
|
// 二维码类库
|
||||||
|
api 'com.google.zxing:core:3.4.1'
|
||||||
|
api 'com.journeyapps:zxing-android-embedded:3.6.0'
|
||||||
|
// 应用介绍页类库
|
||||||
|
api 'io.github.medyo:android-about-page:2.0.0'
|
||||||
|
// 吐司类库
|
||||||
|
api 'com.github.getActivity:ToastUtils:10.5'
|
||||||
|
// 网络连接类库
|
||||||
|
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||||
|
// AndroidX 类库
|
||||||
|
api 'androidx.appcompat:appcompat:1.1.0'
|
||||||
|
api 'com.google.android.material:material:1.4.0'
|
||||||
|
//api 'androidx.viewpager:viewpager:1.0.0'
|
||||||
|
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||||
|
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||||
|
//api 'androidx.fragment:fragment:1.1.0'
|
||||||
|
|
||||||
|
api 'cc.winboll.studio:libaes:15.8.0'
|
||||||
|
api 'cc.winboll.studio:libapputils:15.8.2'
|
||||||
|
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||||
|
}
|
8
numtable/build.properties
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
|
#Sun Jun 08 21:21:11 HKT 2025
|
||||||
|
stageCount=1
|
||||||
|
libraryProject=
|
||||||
|
baseVersion=15.1
|
||||||
|
publishVersion=15.1.0
|
||||||
|
buildCount=0
|
||||||
|
baseBetaVersion=15.1.1
|
21
numtable/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
14
numtable/src/beta/AndroidManifest.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application
|
||||||
|
tools:replace="android:icon"
|
||||||
|
android:icon="@drawable/ic_launcher_beta">
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
6
numtable/src/beta/res/values/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">NumTable +</string>
|
||||||
|
|
||||||
|
</resources>
|
37
numtable/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="cc.winboll.studio.numtable">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:roundIcon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/MyAppTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:name=".App">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.max_aspect"
|
||||||
|
android:value="4.0"/>
|
||||||
|
|
||||||
|
<activity android:name=".GlobalApplication$CrashActivity"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
345
numtable/src/main/java/cc/winboll/studio/numtable/App.java
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
package cc.winboll.studio.numtable;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import com.hjq.toast.ToastUtils;
|
||||||
|
import com.hjq.toast.style.WhiteToastStyle;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
|
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
// 初始化 Toast 框架
|
||||||
|
ToastUtils.init(this);
|
||||||
|
// 设置 Toast 布局样式
|
||||||
|
//ToastUtils.setView(R.layout.view_toast);
|
||||||
|
ToastUtils.setStyle(new WhiteToastStyle());
|
||||||
|
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||||
|
|
||||||
|
//CrashHandler.getInstance().registerGlobal(this);
|
||||||
|
//CrashHandler.getInstance().registerPart(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||||
|
byte[] buf = new byte[1024 * 8];
|
||||||
|
int len;
|
||||||
|
while ((len = input.read(buf)) != -1) {
|
||||||
|
output.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(File file, byte[] data) throws IOException {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
if (parent != null && !parent.exists()) parent.mkdirs();
|
||||||
|
|
||||||
|
ByteArrayInputStream input = new ByteArrayInputStream(data);
|
||||||
|
FileOutputStream output = new FileOutputStream(file);
|
||||||
|
try {
|
||||||
|
write(input, output);
|
||||||
|
} finally {
|
||||||
|
closeIO(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(InputStream input) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
write(input, output);
|
||||||
|
try {
|
||||||
|
return output.toString("UTF-8");
|
||||||
|
} finally {
|
||||||
|
closeIO(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeIO(Closeable... closeables) {
|
||||||
|
for (Closeable closeable : closeables) {
|
||||||
|
try {
|
||||||
|
if (closeable != null) closeable.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CrashHandler {
|
||||||
|
|
||||||
|
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
private static CrashHandler sInstance;
|
||||||
|
|
||||||
|
private PartCrashHandler mPartCrashHandler;
|
||||||
|
|
||||||
|
public static CrashHandler getInstance() {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new CrashHandler();
|
||||||
|
}
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerGlobal(Context context) {
|
||||||
|
registerGlobal(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerGlobal(Context context, String crashDir) {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPart(Context context) {
|
||||||
|
unregisterPart(context);
|
||||||
|
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
|
||||||
|
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPart(Context context) {
|
||||||
|
if (mPartCrashHandler != null) {
|
||||||
|
mPartCrashHandler.isRunning.set(false);
|
||||||
|
mPartCrashHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PartCrashHandler implements Runnable {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
public PartCrashHandler(Context context) {
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (isRunning.get()) {
|
||||||
|
try {
|
||||||
|
Looper.loop();
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
if (isRunning.get()) {
|
||||||
|
MAIN_HANDLER.post(new Runnable(){
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (e instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException)e;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
private final File mCrashDir;
|
||||||
|
|
||||||
|
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
|
||||||
|
this.mContext = context;
|
||||||
|
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
String log = buildLog(throwable);
|
||||||
|
writeLog(log);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(mContext, CrashActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
writeLog(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
throwable.printStackTrace();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildLog(Throwable throwable) {
|
||||||
|
String time = DATE_FORMAT.format(new Date());
|
||||||
|
|
||||||
|
String versionName = "unknown";
|
||||||
|
long versionCode = 0;
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
|
||||||
|
versionName = packageInfo.versionName;
|
||||||
|
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
|
||||||
|
head.put("Time Of Crash", time);
|
||||||
|
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
|
||||||
|
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
|
||||||
|
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
|
||||||
|
head.put("Kernel", getKernel());
|
||||||
|
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
|
||||||
|
head.put("Fingerprint", Build.FINGERPRINT);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (String key : head.keySet()) {
|
||||||
|
if (builder.length() != 0) builder.append("\n");
|
||||||
|
builder.append(key);
|
||||||
|
builder.append(" : ");
|
||||||
|
builder.append(head.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("\n\n");
|
||||||
|
builder.append(Log.getStackTraceString(throwable));
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLog(String log) {
|
||||||
|
String time = DATE_FORMAT.format(new Date());
|
||||||
|
File file = new File(mCrashDir, "crash_" + time + ".txt");
|
||||||
|
try {
|
||||||
|
write(file, log.getBytes("UTF-8"));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getKernel() {
|
||||||
|
try {
|
||||||
|
return App.toString(new FileInputStream("/proc/version")).trim();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class CrashActivity extends Activity {
|
||||||
|
|
||||||
|
private String mLog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setTheme(android.R.style.Theme_DeviceDefault);
|
||||||
|
setTitle("App Crash");
|
||||||
|
|
||||||
|
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
|
||||||
|
ScrollView contentView = new ScrollView(this);
|
||||||
|
contentView.setFillViewport(true);
|
||||||
|
|
||||||
|
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
|
||||||
|
|
||||||
|
TextView textView = new TextView(this);
|
||||||
|
int padding = dp2px(16);
|
||||||
|
textView.setPadding(padding, padding, padding, padding);
|
||||||
|
textView.setText(mLog);
|
||||||
|
textView.setTextIsSelectable(true);
|
||||||
|
textView.setTypeface(Typeface.DEFAULT);
|
||||||
|
textView.setLinksClickable(true);
|
||||||
|
|
||||||
|
horizontalScrollView.addView(textView);
|
||||||
|
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
setContentView(contentView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restart() {
|
||||||
|
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
||||||
|
if (intent != null) {
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int dp2px(float dpValue) {
|
||||||
|
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||||
|
return (int) (dpValue * scale + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
|
||||||
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.copy:
|
||||||
|
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package cc.winboll.studio.numtable;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.libappbase.LogView;
|
||||||
|
import com.hjq.toast.ToastUtils;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
LogView mLogView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
mLogView = findViewById(R.id.logview);
|
||||||
|
|
||||||
|
ToastUtils.show("onCreate");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
mLogView.start();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</vector>
|
13
numtable/src/main/res/drawable/ic_launcher.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<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="20.0"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeMiterLimit="10"
|
||||||
|
android:pathData="M47.53 417.75C38.23 258.17 97.09 94.95 156.27 94.16 211.66 98.38 260.9 237.99 329.08 242.47 351.54 241.69 397.94 228.17 449.46 88.59 447.52 262.11 394.97 417.45 338.71 418.74 279.06 412.93 220.17 297.22 157.81 295.26 132.98 294.7 116.37 308.69 47.53 417.75Z"/>
|
||||||
|
</vector>
|
170
numtable/src/main/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFBC41E"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
</vector>
|
11
numtable/src/main/res/drawable/ic_launcher_beta.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:clickable="true">
|
||||||
|
<item android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<item
|
||||||
|
android:left="15dp"
|
||||||
|
android:top="15dp"
|
||||||
|
android:right="15dp"
|
||||||
|
android:bottom="15dp"
|
||||||
|
android:drawable="@drawable/ic_launcher"/>
|
||||||
|
</layer-list>
|
51
numtable/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:gravity="center_vertical|center_horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="NumTable"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.LogView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/logview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
5
numtable/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
BIN
numtable/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
numtable/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
numtable/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
numtable/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
numtable/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
numtable/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
numtable/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
numtable/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
numtable/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
numtable/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 15 KiB |
6
numtable/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#009688</color>
|
||||||
|
<color name="colorPrimaryDark">#00796B</color>
|
||||||
|
<color name="colorAccent">#FF9800</color>
|
||||||
|
</resources>
|
4
numtable/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">NumTable</string>
|
||||||
|
|
||||||
|
</resources>
|
11
numtable/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
12
numtable/src/stage/AndroidManifest.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
6
numtable/src/stage/res/values/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Put flavor specific strings here -->
|
||||||
|
|
||||||
|
</resources>
|
1
ollama/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
1
ollama/app_update_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
73
ollama/build.gradle
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: '../.winboll/winboll_app_build.gradle'
|
||||||
|
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||||
|
|
||||||
|
def genVersionName(def versionName){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
// 保存基础版本号
|
||||||
|
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
// 返回编译版本号
|
||||||
|
return "${versionName}." + winbollBuildProps['stageCount']
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 32
|
||||||
|
buildToolsVersion "32.0.0"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "cc.winboll.studio.ollama"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 29
|
||||||
|
versionCode 1
|
||||||
|
// versionName 更新后需要手动设置
|
||||||
|
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||||
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
|
versionName "15.0"
|
||||||
|
if(true) {
|
||||||
|
versionName = genVersionName("${versionName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||||
|
|
||||||
|
// 吐司类库
|
||||||
|
api 'com.github.getActivity:ToastUtils:10.5'
|
||||||
|
|
||||||
|
// Android 类库
|
||||||
|
api 'com.android.support:appcompat-v7:28.0.0' // 包含 AppCompatActivity
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/support-compat
|
||||||
|
api 'com.android.support:support-compat:28.0.0' // 保留原有依赖(可选)
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/support-v4
|
||||||
|
api 'com.android.support:support-v4:28.0.0'
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/support-media-compat
|
||||||
|
api 'com.android.support:support-media-compat:28.0.0'
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/support-core-utils
|
||||||
|
api 'com.android.support:support-core-utils:28.0.0'
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/support-core-ui
|
||||||
|
api 'com.android.support:support-core-ui:28.0.0'
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/support-fragment
|
||||||
|
api 'com.android.support:support-fragment:28.0.0'
|
||||||
|
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
|
||||||
|
api 'com.android.support:recyclerview-v7:28.0.0'
|
||||||
|
|
||||||
|
api 'cc.winboll.studio:libappbase:15.0.9'
|
||||||
|
api 'cc.winboll.studio:libapputils:15.0.11'
|
||||||
|
}
|
8
ollama/build.properties
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
|
#Fri Mar 28 05:09:21 HKT 2025
|
||||||
|
stageCount=1
|
||||||
|
libraryProject=
|
||||||
|
baseVersion=15.0
|
||||||
|
publishVersion=15.0.0
|
||||||
|
buildCount=0
|
||||||
|
baseBetaVersion=15.0.1
|
21
ollama/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
12
ollama/src/beta/AndroidManifest.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
7
ollama/src/beta/res/values/strings.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="app_name">Ollama +</string>
|
||||||
|
|
||||||
|
</resources>
|
41
ollama/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="cc.winboll.studio.ollama">
|
||||||
|
|
||||||
|
<!-- 拥有完全的网络访问权限 -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/MyAppTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:name=".App"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.max_aspect"
|
||||||
|
android:value="4.0"/>
|
||||||
|
|
||||||
|
<activity android:name=".GlobalApplication$CrashActivity"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
334
ollama/src/main/java/cc/winboll/studio/ollama/App.java
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
package cc.winboll.studio.ollama;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
|
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
//CrashHandler.getInstance().registerGlobal(this);
|
||||||
|
//CrashHandler.getInstance().registerPart(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||||
|
byte[] buf = new byte[1024 * 8];
|
||||||
|
int len;
|
||||||
|
while ((len = input.read(buf)) != -1) {
|
||||||
|
output.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(File file, byte[] data) throws IOException {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
if (parent != null && !parent.exists()) parent.mkdirs();
|
||||||
|
|
||||||
|
ByteArrayInputStream input = new ByteArrayInputStream(data);
|
||||||
|
FileOutputStream output = new FileOutputStream(file);
|
||||||
|
try {
|
||||||
|
write(input, output);
|
||||||
|
} finally {
|
||||||
|
closeIO(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(InputStream input) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
write(input, output);
|
||||||
|
try {
|
||||||
|
return output.toString("UTF-8");
|
||||||
|
} finally {
|
||||||
|
closeIO(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeIO(Closeable... closeables) {
|
||||||
|
for (Closeable closeable : closeables) {
|
||||||
|
try {
|
||||||
|
if (closeable != null) closeable.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CrashHandler {
|
||||||
|
|
||||||
|
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
private static CrashHandler sInstance;
|
||||||
|
|
||||||
|
private PartCrashHandler mPartCrashHandler;
|
||||||
|
|
||||||
|
public static CrashHandler getInstance() {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new CrashHandler();
|
||||||
|
}
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerGlobal(Context context) {
|
||||||
|
registerGlobal(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerGlobal(Context context, String crashDir) {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPart(Context context) {
|
||||||
|
unregisterPart(context);
|
||||||
|
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
|
||||||
|
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPart(Context context) {
|
||||||
|
if (mPartCrashHandler != null) {
|
||||||
|
mPartCrashHandler.isRunning.set(false);
|
||||||
|
mPartCrashHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PartCrashHandler implements Runnable {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
public PartCrashHandler(Context context) {
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (isRunning.get()) {
|
||||||
|
try {
|
||||||
|
Looper.loop();
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
if (isRunning.get()) {
|
||||||
|
MAIN_HANDLER.post(new Runnable(){
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (e instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException)e;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
private final File mCrashDir;
|
||||||
|
|
||||||
|
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
|
||||||
|
this.mContext = context;
|
||||||
|
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
String log = buildLog(throwable);
|
||||||
|
writeLog(log);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(mContext, CrashActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
writeLog(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
throwable.printStackTrace();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildLog(Throwable throwable) {
|
||||||
|
String time = DATE_FORMAT.format(new Date());
|
||||||
|
|
||||||
|
String versionName = "unknown";
|
||||||
|
long versionCode = 0;
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
|
||||||
|
versionName = packageInfo.versionName;
|
||||||
|
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
|
||||||
|
head.put("Time Of Crash", time);
|
||||||
|
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
|
||||||
|
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
|
||||||
|
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
|
||||||
|
head.put("Kernel", getKernel());
|
||||||
|
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
|
||||||
|
head.put("Fingerprint", Build.FINGERPRINT);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (String key : head.keySet()) {
|
||||||
|
if (builder.length() != 0) builder.append("\n");
|
||||||
|
builder.append(key);
|
||||||
|
builder.append(" : ");
|
||||||
|
builder.append(head.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("\n\n");
|
||||||
|
builder.append(Log.getStackTraceString(throwable));
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLog(String log) {
|
||||||
|
String time = DATE_FORMAT.format(new Date());
|
||||||
|
File file = new File(mCrashDir, "crash_" + time + ".txt");
|
||||||
|
try {
|
||||||
|
write(file, log.getBytes("UTF-8"));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getKernel() {
|
||||||
|
try {
|
||||||
|
return App.toString(new FileInputStream("/proc/version")).trim();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class CrashActivity extends Activity {
|
||||||
|
|
||||||
|
private String mLog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setTheme(android.R.style.Theme_DeviceDefault);
|
||||||
|
setTitle("App Crash");
|
||||||
|
|
||||||
|
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
|
||||||
|
ScrollView contentView = new ScrollView(this);
|
||||||
|
contentView.setFillViewport(true);
|
||||||
|
|
||||||
|
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
|
||||||
|
|
||||||
|
TextView textView = new TextView(this);
|
||||||
|
int padding = dp2px(16);
|
||||||
|
textView.setPadding(padding, padding, padding, padding);
|
||||||
|
textView.setText(mLog);
|
||||||
|
textView.setTextIsSelectable(true);
|
||||||
|
textView.setTypeface(Typeface.DEFAULT);
|
||||||
|
textView.setLinksClickable(true);
|
||||||
|
|
||||||
|
horizontalScrollView.addView(textView);
|
||||||
|
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
setContentView(contentView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restart() {
|
||||||
|
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
||||||
|
if (intent != null) {
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int dp2px(float dpValue) {
|
||||||
|
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||||
|
return (int) (dpValue * scale + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
|
||||||
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.copy:
|
||||||
|
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package cc.winboll.studio.ollama;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public final static int MSG_APPEND = 0;
|
||||||
|
|
||||||
|
private Handler _Handler = new Handler(Looper.getMainLooper());
|
||||||
|
private TextView mtvMessage;
|
||||||
|
private EditText metAsk;
|
||||||
|
private Button mbtSend;
|
||||||
|
private ScrollView msvMessage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
mtvMessage = (TextView) findViewById(R.id.message_tv);
|
||||||
|
metAsk = (EditText) findViewById(R.id.ask_et);
|
||||||
|
mbtSend = (Button) findViewById(R.id.send_bt);
|
||||||
|
msvMessage = findViewById(R.id.message_sv);
|
||||||
|
|
||||||
|
mbtSend.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
sendQuestion();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// 设置输入框获得焦点的类
|
||||||
|
//
|
||||||
|
// static class MyHandler extends Handler {
|
||||||
|
// WeakReference<MainActivity> mActivity;
|
||||||
|
// MyHandler(MainActivity activity) {
|
||||||
|
// mActivity = new WeakReference<MainActivity>(activity);
|
||||||
|
// }
|
||||||
|
// public void handleMessage(Message msg) {
|
||||||
|
// MainActivity theActivity = mActivity.get();
|
||||||
|
// switch (msg.what) {
|
||||||
|
// case MSG_APPEND:
|
||||||
|
// theActivity.mtvMessage.append((String)msg.obj);
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// super.handleMessage(msg);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
private void sendQuestion() {
|
||||||
|
final String question = metAsk.getText().toString().trim();
|
||||||
|
if (!question.equals("")) {
|
||||||
|
mtvMessage.append("\n\nI :" + metAsk.getText().toString() + "\nOllama : ");
|
||||||
|
metAsk.setText("");
|
||||||
|
new OllamaClient.SyncAskThread(question, new OllamaClient.OnAnswerCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAnswer(final String answer) {
|
||||||
|
_Handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mtvMessage.append(answer);
|
||||||
|
msvMessage.post(new Runnable(){
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
msvMessage.fullScroll(View.FOCUS_DOWN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
223
ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package cc.winboll.studio.ollama;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen@AliYun.Com
|
||||||
|
* @Date 2025/03/27 19:55:28
|
||||||
|
* @Describe 简单Http协议访问客户端
|
||||||
|
*/
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import okio.Buffer;
|
||||||
|
import okio.Source;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class OllamaClient {
|
||||||
|
|
||||||
|
public static final String TAG = "OllamaClient";
|
||||||
|
|
||||||
|
private static final String API_BASE_URL = "https://ollama-api.winboll.cc";
|
||||||
|
//private static final String API_BASE_URL = "http://10.8.0.10:11434";
|
||||||
|
//private static final OkHttpClient client = new OkHttpClient();
|
||||||
|
private static final OkHttpClient client = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 1. 生成文本示例
|
||||||
|
// static void generateText(String prompt, String model) {
|
||||||
|
// String url = API_BASE_URL + "/api/generate";
|
||||||
|
// try {
|
||||||
|
// JSONObject payload = new JSONObject()
|
||||||
|
// .put("model", model)
|
||||||
|
// .put("prompt", prompt)
|
||||||
|
// .put("temperature", 0.7)
|
||||||
|
// .put("max_tokens", 200);
|
||||||
|
//
|
||||||
|
// Request request = new Request.Builder()
|
||||||
|
// .url(url)
|
||||||
|
// .post(RequestBody.create(payload.toString(), MediaType.get("application/json")))
|
||||||
|
// .build();
|
||||||
|
//
|
||||||
|
// Response response = client.newCall(request).execute();
|
||||||
|
// if (response.isSuccessful()) {
|
||||||
|
// String result = response.body().string();
|
||||||
|
// String formattedStream = OllamaResponseFormatter.formatStreamingResponse(result);
|
||||||
|
//
|
||||||
|
// // 输出示例:
|
||||||
|
//// [2025-03-27T19:34:29.274955439Z] [llama3.1:8b] It looks like you might have miss
|
||||||
|
//// [2025-03-27T19:34:30.482553089Z] [llama3.1:8b] pelled the word "Ollama" or perhaps said something that is not a standard word in the English language. However, I'm here to provide information and assistance on various topics, so please let me know what you meant by "Ollama." Was it related to a name, place, movie, game, or something else?
|
||||||
|
//
|
||||||
|
// LogUtils.d(TAG, formattedStream);
|
||||||
|
//// JSONObject json = new JSONObject(result);
|
||||||
|
//// LogUtils.d(TAG, "生成结果: " + json.getString("response"));
|
||||||
|
// //System.out.println("生成结果: " + json.getString("response"));
|
||||||
|
// } else {
|
||||||
|
// LogUtils.d(TAG, "请求失败: " + response.code());
|
||||||
|
// //System.out.println("请求失败: " + response.code() + " " + response.message());
|
||||||
|
// }
|
||||||
|
// } catch (JSONException|IOException e) {
|
||||||
|
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 实时输出流式响应的函数
|
||||||
|
static void generateTextStream(String prompt, String model, final OnAnswerCallback callback) {
|
||||||
|
String url = API_BASE_URL + "/api/generate";
|
||||||
|
try {
|
||||||
|
JSONObject payload = new JSONObject()
|
||||||
|
.put("model", model)
|
||||||
|
.put("prompt", prompt)
|
||||||
|
.put("temperature", 0.7)
|
||||||
|
.put("max_tokens", 200)
|
||||||
|
.put("stream", true); // 启用流式响应
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.post(RequestBody.create(payload.toString(), MediaType.get("application/json")))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "Request request");
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
LogUtils.d(TAG, "请求失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) throws IOException {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
LogUtils.d(TAG, "请求失败: " + response.code());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ResponseBody body = response.body()) {
|
||||||
|
if (body == null) return;
|
||||||
|
|
||||||
|
// 实时流式解析
|
||||||
|
LogUtils.d(TAG, "实时流式解析");
|
||||||
|
Source source = body.source();
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
StringBuilder fullResponse = new StringBuilder();
|
||||||
|
boolean isDone = false;
|
||||||
|
|
||||||
|
while (!isDone && source.read(buffer, 1024) != -1) {
|
||||||
|
//LogUtils.d(TAG, "!isDone");
|
||||||
|
String chunk = buffer.readUtf8();
|
||||||
|
String[] lines = chunk.split("\n");
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
LogUtils.d(TAG, line);
|
||||||
|
if (line.trim().startsWith("{\"model\":")) {
|
||||||
|
LogUtils.d(TAG, line);
|
||||||
|
String jsonStr = line;
|
||||||
|
if (jsonStr.equals("[DONE]")) {
|
||||||
|
isDone = true;
|
||||||
|
LogUtils.d(TAG, "流式生成完成");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//LogUtils.d(TAG, jsonStr);
|
||||||
|
JSONObject json = new JSONObject(jsonStr);
|
||||||
|
//LogUtils.d(TAG, json.toString());
|
||||||
|
String responseText = json.getString("response");
|
||||||
|
//LogUtils.d(TAG, responseText);
|
||||||
|
fullResponse.append(responseText);
|
||||||
|
|
||||||
|
// 实时输出
|
||||||
|
callback.onAnswer(responseText);
|
||||||
|
LogUtils.d(TAG, "实时响应: " + responseText);
|
||||||
|
|
||||||
|
// 处理完成状态
|
||||||
|
if (json.getBoolean("done")) {
|
||||||
|
isDone = true;
|
||||||
|
String doneReason = json.optString("done_reason", "unknown");
|
||||||
|
LogUtils.d(TAG, "生成完成 (原因: " + doneReason + ")");
|
||||||
|
LogUtils.d(TAG, "完整回答: " + fullResponse.toString());
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LogUtils.d(TAG, "JSON解析错误: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LogUtils.d(TAG, "JSON格式错误: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 2. 获取模型列表示例
|
||||||
|
static void getModelList() {
|
||||||
|
String url = API_BASE_URL + "/v1/models";
|
||||||
|
LogUtils.d(TAG, "url : " + url);
|
||||||
|
Request request = new Request.Builder().url(url).build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Response response = client.newCall(request).execute();
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
LogUtils.d(TAG, response.body().string());
|
||||||
|
// JSONArray models = new JSONArray(response.body().string());
|
||||||
|
// //System.out.println("可用模型列表:");
|
||||||
|
// LogUtils.d(TAG, "可用模型列表:");
|
||||||
|
// for (int i = 0; i < models.length(); i++) {
|
||||||
|
// JSONObject model = models.getJSONObject(i);
|
||||||
|
// LogUtils.d(TAG, "- " + model.getString("name") + " (" + model.getString("size") + ")");
|
||||||
|
// //System.out.println("- " + model.getString("name") + " (" + model.getString("size") + ")");
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "获取模型列表失败: " + response.code());
|
||||||
|
//System.out.println("获取模型列表失败: " + response.code());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unittest(String ask, OnAnswerCallback callback) {
|
||||||
|
// 获取模型列表
|
||||||
|
getModelList();
|
||||||
|
|
||||||
|
// 生成文本
|
||||||
|
generateTextStream(ask, "llama3.1:8b", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SyncAskThread extends Thread {
|
||||||
|
private String ask;
|
||||||
|
private OnAnswerCallback callback;
|
||||||
|
|
||||||
|
public SyncAskThread(String ask, OnAnswerCallback callback) {
|
||||||
|
this.ask = ask;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
super.run();
|
||||||
|
LogUtils.d(TAG, "run() start.");
|
||||||
|
unittest(ask, callback);
|
||||||
|
LogUtils.d(TAG, "run() end.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnAnswerCallback {
|
||||||
|
void onAnswer(String answer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
|||||||
|
package cc.winboll.studio.ollama;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen@AliYun.Com
|
||||||
|
* @Date 2025/03/28 03:38:50
|
||||||
|
*/
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class OllamaResponseFormatter {
|
||||||
|
public static final String TAG = "OllamaResponseFormatter";
|
||||||
|
|
||||||
|
// 处理模型列表响应
|
||||||
|
public static String formatModelList(String jsonResponse) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(jsonResponse);
|
||||||
|
JSONArray models = json.getJSONArray("data");
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("可用模型列表:\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < models.length(); i++) {
|
||||||
|
JSONObject model = models.getJSONObject(i);
|
||||||
|
String modelId = model.getString("id");
|
||||||
|
sb.append(String.format("-%d. %s\n", i + 1, modelId));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "格式解析错误: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理流式生成文本响应
|
||||||
|
public static String formatStreamingResponse(String jsonResponse) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(jsonResponse);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String responseText = json.getString("response");
|
||||||
|
boolean isDone = json.getBoolean("done");
|
||||||
|
|
||||||
|
// 添加时间戳和模型标识
|
||||||
|
String timestamp = json.getString("created_at");
|
||||||
|
String modelName = json.getString("model");
|
||||||
|
sb.append(String.format("[%s] [%s] ", timestamp, modelName));
|
||||||
|
|
||||||
|
// 处理响应内容
|
||||||
|
if (responseText.isEmpty() && isDone) {
|
||||||
|
sb.append("生成完成\n");
|
||||||
|
} else {
|
||||||
|
sb.append(responseText);
|
||||||
|
if (isDone) {
|
||||||
|
String doneReason = json.optString("done_reason", "unknown");
|
||||||
|
sb.append(String.format(" (完成原因: %s)\n", doneReason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "格式解析错误: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 模型列表测试
|
||||||
|
String modelListJson = "{\"object\":\"list\",\"data\":[{...}]}";
|
||||||
|
System.out.println(formatModelList(modelListJson));
|
||||||
|
|
||||||
|
// 流式响应测试
|
||||||
|
String streamingJson = "{\"model\":\"llama3.1:8b\",\"created_at\":\"2025-03-27T19:34:29.274955439Z\",\"response\":\"It\",\"done\":false}";
|
||||||
|
System.out.println(formatStreamingResponse(streamingJson));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
ollama/src/main/res/drawable/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
69
ollama/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:id="@+id/message_sv">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="Asking Ollama ..."
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:id="@+id/message_tv"/>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/ask_et"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:text="Hello, World!"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Send"
|
||||||
|
android:id="@+id/send_bt"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.LogView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Text"
|
||||||
|
android:id="@+id/logview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|